blob: ba1fd662abf95c98bfa10f838068484b87a1d858 [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,
davidben599e7e72014-09-03 16:19:09 -0700339 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000340 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000341 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000342 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000343 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000344 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000345 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000346 self.PostOnlyFileHandler,
347 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000348 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000349 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000350 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000351 head_handlers = [
352 self.FileHandler,
353 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000354
maruel@google.come250a9b2009-03-10 17:39:46 +0000355 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000356 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000357 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000358 'gif': 'image/gif',
359 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000360 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700361 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000362 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000363 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000364 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000365 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000366 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000367 }
initial.commit94958cf2008-07-26 22:42:52 +0000368 self._default_mime_type = 'text/html'
369
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000370 testserver_base.BasePageHandler.__init__(self, request, client_address,
371 socket_server, connect_handlers,
372 get_handlers, head_handlers,
373 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000374
initial.commit94958cf2008-07-26 22:42:52 +0000375 def GetMIMETypeFromName(self, file_name):
376 """Returns the mime type for the specified file_name. So far it only looks
377 at the file extension."""
378
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000379 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000380 if len(extension) == 0:
381 # no extension.
382 return self._default_mime_type
383
ericroman@google.comc17ca532009-05-07 03:51:05 +0000384 # extension starts with a dot, so we need to remove it
385 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000386
initial.commit94958cf2008-07-26 22:42:52 +0000387 def NoCacheMaxAgeTimeHandler(self):
388 """This request handler yields a page with the title set to the current
389 system time, and no caching requested."""
390
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000391 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000392 return False
393
394 self.send_response(200)
395 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000396 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000397 self.end_headers()
398
maruel@google.come250a9b2009-03-10 17:39:46 +0000399 self.wfile.write('<html><head><title>%s</title></head></html>' %
400 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000401
402 return True
403
404 def NoCacheTimeHandler(self):
405 """This request handler yields a page with the title set to the current
406 system time, and no caching requested."""
407
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000408 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000409 return False
410
411 self.send_response(200)
412 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000413 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000414 self.end_headers()
415
maruel@google.come250a9b2009-03-10 17:39:46 +0000416 self.wfile.write('<html><head><title>%s</title></head></html>' %
417 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000418
419 return True
420
421 def CacheTimeHandler(self):
422 """This request handler yields a page with the title set to the current
423 system time, and allows caching for one minute."""
424
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000425 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000426 return False
427
428 self.send_response(200)
429 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000430 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000431 self.end_headers()
432
maruel@google.come250a9b2009-03-10 17:39:46 +0000433 self.wfile.write('<html><head><title>%s</title></head></html>' %
434 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000435
436 return True
437
438 def CacheExpiresHandler(self):
439 """This request handler yields a page with the title set to the current
440 system time, and set the page to expire on 1 Jan 2099."""
441
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000442 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000443 return False
444
445 self.send_response(200)
446 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000447 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000448 self.end_headers()
449
maruel@google.come250a9b2009-03-10 17:39:46 +0000450 self.wfile.write('<html><head><title>%s</title></head></html>' %
451 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000452
453 return True
454
455 def CacheProxyRevalidateHandler(self):
456 """This request handler yields a page with the title set to the current
457 system time, and allows caching for 60 seconds"""
458
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000459 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000460 return False
461
462 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000463 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000464 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
465 self.end_headers()
466
maruel@google.come250a9b2009-03-10 17:39:46 +0000467 self.wfile.write('<html><head><title>%s</title></head></html>' %
468 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000469
470 return True
471
472 def CachePrivateHandler(self):
473 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700474 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000475
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000476 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000477 return False
478
479 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000480 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000481 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000482 self.end_headers()
483
maruel@google.come250a9b2009-03-10 17:39:46 +0000484 self.wfile.write('<html><head><title>%s</title></head></html>' %
485 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000486
487 return True
488
489 def CachePublicHandler(self):
490 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700491 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000492
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000493 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000494 return False
495
496 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000497 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000498 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000499 self.end_headers()
500
maruel@google.come250a9b2009-03-10 17:39:46 +0000501 self.wfile.write('<html><head><title>%s</title></head></html>' %
502 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000503
504 return True
505
506 def CacheSMaxAgeHandler(self):
507 """This request handler yields a page with the title set to the current
508 system time, and does not allow for caching."""
509
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000510 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000511 return False
512
513 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000514 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000515 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
516 self.end_headers()
517
maruel@google.come250a9b2009-03-10 17:39:46 +0000518 self.wfile.write('<html><head><title>%s</title></head></html>' %
519 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000520
521 return True
522
523 def CacheMustRevalidateHandler(self):
524 """This request handler yields a page with the title set to the current
525 system time, and does not allow caching."""
526
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000527 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000528 return False
529
530 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000531 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000532 self.send_header('Cache-Control', 'must-revalidate')
533 self.end_headers()
534
maruel@google.come250a9b2009-03-10 17:39:46 +0000535 self.wfile.write('<html><head><title>%s</title></head></html>' %
536 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000537
538 return True
539
540 def CacheMustRevalidateMaxAgeHandler(self):
541 """This request handler yields a page with the title set to the current
542 system time, and does not allow caching event though max-age of 60
543 seconds is specified."""
544
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000545 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000546 return False
547
548 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000549 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000550 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
551 self.end_headers()
552
maruel@google.come250a9b2009-03-10 17:39:46 +0000553 self.wfile.write('<html><head><title>%s</title></head></html>' %
554 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000555
556 return True
557
initial.commit94958cf2008-07-26 22:42:52 +0000558 def CacheNoStoreHandler(self):
559 """This request handler yields a page with the title set to the current
560 system time, and does not allow the page to be stored."""
561
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000562 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000563 return False
564
565 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000566 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000567 self.send_header('Cache-Control', 'no-store')
568 self.end_headers()
569
maruel@google.come250a9b2009-03-10 17:39:46 +0000570 self.wfile.write('<html><head><title>%s</title></head></html>' %
571 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000572
573 return True
574
575 def CacheNoStoreMaxAgeHandler(self):
576 """This request handler yields a page with the title set to the current
577 system time, and does not allow the page to be stored even though max-age
578 of 60 seconds is specified."""
579
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000580 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000581 return False
582
583 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000584 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000585 self.send_header('Cache-Control', 'max-age=60, no-store')
586 self.end_headers()
587
maruel@google.come250a9b2009-03-10 17:39:46 +0000588 self.wfile.write('<html><head><title>%s</title></head></html>' %
589 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000590
591 return True
592
593
594 def CacheNoTransformHandler(self):
595 """This request handler yields a page with the title set to the current
596 system time, and does not allow the content to transformed during
597 user-agent caching"""
598
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000599 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000600 return False
601
602 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000603 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000604 self.send_header('Cache-Control', 'no-transform')
605 self.end_headers()
606
maruel@google.come250a9b2009-03-10 17:39:46 +0000607 self.wfile.write('<html><head><title>%s</title></head></html>' %
608 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000609
610 return True
611
612 def EchoHeader(self):
613 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000614
ananta@chromium.org219b2062009-10-23 16:09:41 +0000615 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000616
ananta@chromium.org56812d02011-04-07 17:52:05 +0000617 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000618 """This function echoes back the value of a specific request header while
619 allowing caching for 16 hours."""
620
ananta@chromium.org56812d02011-04-07 17:52:05 +0000621 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000622
623 def EchoHeaderHelper(self, echo_header):
624 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000625
ananta@chromium.org219b2062009-10-23 16:09:41 +0000626 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000627 return False
628
629 query_char = self.path.find('?')
630 if query_char != -1:
631 header_name = self.path[query_char+1:]
632
633 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000634 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000635 if echo_header == '/echoheadercache':
636 self.send_header('Cache-control', 'max-age=60000')
637 else:
638 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000639 # insert a vary header to properly indicate that the cachability of this
640 # request is subject to value of the request header being echoed.
641 if len(header_name) > 0:
642 self.send_header('Vary', header_name)
643 self.end_headers()
644
645 if len(header_name) > 0:
646 self.wfile.write(self.headers.getheader(header_name))
647
648 return True
649
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000650 def ReadRequestBody(self):
651 """This function reads the body of the current HTTP request, handling
652 both plain and chunked transfer encoded requests."""
653
654 if self.headers.getheader('transfer-encoding') != 'chunked':
655 length = int(self.headers.getheader('content-length'))
656 return self.rfile.read(length)
657
658 # Read the request body as chunks.
659 body = ""
660 while True:
661 line = self.rfile.readline()
662 length = int(line, 16)
663 if length == 0:
664 self.rfile.readline()
665 break
666 body += self.rfile.read(length)
667 self.rfile.read(2)
668 return body
669
initial.commit94958cf2008-07-26 22:42:52 +0000670 def EchoHandler(self):
671 """This handler just echoes back the payload of the request, for testing
672 form submission."""
673
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000674 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000675 return False
676
hirono2838c572015-01-21 12:18:11 -0800677 _, _, _, _, query, _ = urlparse.urlparse(self.path)
678 query_params = cgi.parse_qs(query, True)
679 if 'status' in query_params:
680 self.send_response(int(query_params['status'][0]))
681 else:
682 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000683 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000684 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000685 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000686 return True
687
688 def EchoTitleHandler(self):
689 """This handler is like Echo, but sets the page title to the request."""
690
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000691 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000692 return False
693
694 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000695 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000696 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000697 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000698 self.wfile.write('<html><head><title>')
699 self.wfile.write(request)
700 self.wfile.write('</title></head></html>')
701 return True
702
703 def EchoAllHandler(self):
704 """This handler yields a (more) human-readable page listing information
705 about the request header & contents."""
706
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000707 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000708 return False
709
710 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000711 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000712 self.end_headers()
713 self.wfile.write('<html><head><style>'
714 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
715 '</style></head><body>'
716 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000717 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000718 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000719
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000720 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000721 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000722 params = cgi.parse_qs(qs, keep_blank_values=1)
723
724 for param in params:
725 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000726
727 self.wfile.write('</pre>')
728
729 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
730
731 self.wfile.write('</body></html>')
732 return True
733
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000734 def EchoMultipartPostHandler(self):
735 """This handler echoes received multipart post data as json format."""
736
737 if not (self._ShouldHandleRequest("/echomultipartpost") or
738 self._ShouldHandleRequest("/searchbyimage")):
739 return False
740
741 content_type, parameters = cgi.parse_header(
742 self.headers.getheader('content-type'))
743 if content_type == 'multipart/form-data':
744 post_multipart = cgi.parse_multipart(self.rfile, parameters)
745 elif content_type == 'application/x-www-form-urlencoded':
746 raise Exception('POST by application/x-www-form-urlencoded is '
747 'not implemented.')
748 else:
749 post_multipart = {}
750
751 # Since the data can be binary, we encode them by base64.
752 post_multipart_base64_encoded = {}
753 for field, values in post_multipart.items():
754 post_multipart_base64_encoded[field] = [base64.b64encode(value)
755 for value in values]
756
757 result = {'POST_multipart' : post_multipart_base64_encoded}
758
759 self.send_response(200)
760 self.send_header("Content-type", "text/plain")
761 self.end_headers()
762 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
763 return True
764
initial.commit94958cf2008-07-26 22:42:52 +0000765 def DownloadHandler(self):
766 """This handler sends a downloadable file with or without reporting
767 the size (6K)."""
768
769 if self.path.startswith("/download-unknown-size"):
770 send_length = False
771 elif self.path.startswith("/download-known-size"):
772 send_length = True
773 else:
774 return False
775
776 #
777 # The test which uses this functionality is attempting to send
778 # small chunks of data to the client. Use a fairly large buffer
779 # so that we'll fill chrome's IO buffer enough to force it to
780 # actually write the data.
781 # See also the comments in the client-side of this test in
782 # download_uitest.cc
783 #
784 size_chunk1 = 35*1024
785 size_chunk2 = 10*1024
786
787 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000788 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000789 self.send_header('Cache-Control', 'max-age=0')
790 if send_length:
791 self.send_header('Content-Length', size_chunk1 + size_chunk2)
792 self.end_headers()
793
794 # First chunk of data:
795 self.wfile.write("*" * size_chunk1)
796 self.wfile.flush()
797
798 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000799 self.server.wait_for_download = True
800 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000801 self.server.handle_request()
802
803 # Second chunk of data:
804 self.wfile.write("*" * size_chunk2)
805 return True
806
807 def DownloadFinishHandler(self):
808 """This handler just tells the server to finish the current download."""
809
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000810 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000811 return False
812
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000813 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000814 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000815 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000816 self.send_header('Cache-Control', 'max-age=0')
817 self.end_headers()
818 return True
819
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000820 def _ReplaceFileData(self, data, query_parameters):
821 """Replaces matching substrings in a file.
822
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000823 If the 'replace_text' URL query parameter is present, it is expected to be
824 of the form old_text:new_text, which indicates that any old_text strings in
825 the file are replaced with new_text. Multiple 'replace_text' parameters may
826 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000827
828 If the parameters are not present, |data| is returned.
829 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000830
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000831 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000832 replace_text_values = query_dict.get('replace_text', [])
833 for replace_text_value in replace_text_values:
834 replace_text_args = replace_text_value.split(':')
835 if len(replace_text_args) != 2:
836 raise ValueError(
837 'replace_text must be of form old_text:new_text. Actual value: %s' %
838 replace_text_value)
839 old_text_b64, new_text_b64 = replace_text_args
840 old_text = base64.urlsafe_b64decode(old_text_b64)
841 new_text = base64.urlsafe_b64decode(new_text_b64)
842 data = data.replace(old_text, new_text)
843 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000844
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000845 def ZipFileHandler(self):
846 """This handler sends the contents of the requested file in compressed form.
847 Can pass in a parameter that specifies that the content length be
848 C - the compressed size (OK),
849 U - the uncompressed size (Non-standard, but handled),
850 S - less than compressed (OK because we keep going),
851 M - larger than compressed but less than uncompressed (an error),
852 L - larger than uncompressed (an error)
853 Example: compressedfiles/Picture_1.doc?C
854 """
855
856 prefix = "/compressedfiles/"
857 if not self.path.startswith(prefix):
858 return False
859
860 # Consume a request body if present.
861 if self.command == 'POST' or self.command == 'PUT' :
862 self.ReadRequestBody()
863
864 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
865
866 if not query in ('C', 'U', 'S', 'M', 'L'):
867 return False
868
869 sub_path = url_path[len(prefix):]
870 entries = sub_path.split('/')
871 file_path = os.path.join(self.server.data_dir, *entries)
872 if os.path.isdir(file_path):
873 file_path = os.path.join(file_path, 'index.html')
874
875 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000876 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000877 self.send_error(404)
878 return True
879
880 f = open(file_path, "rb")
881 data = f.read()
882 uncompressed_len = len(data)
883 f.close()
884
885 # Compress the data.
886 data = zlib.compress(data)
887 compressed_len = len(data)
888
889 content_length = compressed_len
890 if query == 'U':
891 content_length = uncompressed_len
892 elif query == 'S':
893 content_length = compressed_len / 2
894 elif query == 'M':
895 content_length = (compressed_len + uncompressed_len) / 2
896 elif query == 'L':
897 content_length = compressed_len + uncompressed_len
898
899 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000900 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000901 self.send_header('Content-encoding', 'deflate')
902 self.send_header('Connection', 'close')
903 self.send_header('Content-Length', content_length)
904 self.send_header('ETag', '\'' + file_path + '\'')
905 self.end_headers()
906
907 self.wfile.write(data)
908
909 return True
910
initial.commit94958cf2008-07-26 22:42:52 +0000911 def FileHandler(self):
912 """This handler sends the contents of the requested file. Wow, it's like
913 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000914
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000915 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000916 if not self.path.startswith(prefix):
917 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000918 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000919
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000920 def PostOnlyFileHandler(self):
921 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000922
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000923 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000924 if not self.path.startswith(prefix):
925 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000926 return self._FileHandlerHelper(prefix)
927
928 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000929 request_body = ''
930 if self.command == 'POST' or self.command == 'PUT':
931 # Consume a request body if present.
932 request_body = self.ReadRequestBody()
933
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000934 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000935 query_dict = cgi.parse_qs(query)
936
937 expected_body = query_dict.get('expected_body', [])
938 if expected_body and request_body not in expected_body:
939 self.send_response(404)
940 self.end_headers()
941 self.wfile.write('')
942 return True
943
944 expected_headers = query_dict.get('expected_headers', [])
945 for expected_header in expected_headers:
946 header_name, expected_value = expected_header.split(':')
947 if self.headers.getheader(header_name) != expected_value:
948 self.send_response(404)
949 self.end_headers()
950 self.wfile.write('')
951 return True
952
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000953 sub_path = url_path[len(prefix):]
954 entries = sub_path.split('/')
955 file_path = os.path.join(self.server.data_dir, *entries)
956 if os.path.isdir(file_path):
957 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000958
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000959 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000960 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000961 self.send_error(404)
962 return True
963
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000964 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000965 data = f.read()
966 f.close()
967
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000968 data = self._ReplaceFileData(data, query)
969
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000970 old_protocol_version = self.protocol_version
971
initial.commit94958cf2008-07-26 22:42:52 +0000972 # If file.mock-http-headers exists, it contains the headers we
973 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000974 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000975 if os.path.isfile(headers_path):
976 f = open(headers_path, "r")
977
978 # "HTTP/1.1 200 OK"
979 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000980 http_major, http_minor, status_code = re.findall(
981 'HTTP/(\d+).(\d+) (\d+)', response)[0]
982 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000983 self.send_response(int(status_code))
984
985 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000986 header_values = re.findall('(\S+):\s*(.*)', line)
987 if len(header_values) > 0:
988 # "name: value"
989 name, value = header_values[0]
990 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000991 f.close()
992 else:
993 # Could be more generic once we support mime-type sniffing, but for
994 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000995
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000996 range_header = self.headers.get('Range')
997 if range_header and range_header.startswith('bytes='):
998 # Note this doesn't handle all valid byte range_header values (i.e.
999 # left open ended ones), just enough for what we needed so far.
1000 range_header = range_header[6:].split('-')
1001 start = int(range_header[0])
1002 if range_header[1]:
1003 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001004 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001005 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001006
1007 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001008 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1009 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001010 self.send_header('Content-Range', content_range)
1011 data = data[start: end + 1]
1012 else:
1013 self.send_response(200)
1014
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001015 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001016 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001017 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001018 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001019 self.end_headers()
1020
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001021 if (self.command != 'HEAD'):
1022 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001023
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001024 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001025 return True
1026
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001027 def SetCookieHandler(self):
1028 """This handler just sets a cookie, for testing cookie handling."""
1029
1030 if not self._ShouldHandleRequest("/set-cookie"):
1031 return False
1032
1033 query_char = self.path.find('?')
1034 if query_char != -1:
1035 cookie_values = self.path[query_char + 1:].split('&')
1036 else:
1037 cookie_values = ("",)
1038 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001039 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001040 for cookie_value in cookie_values:
1041 self.send_header('Set-Cookie', '%s' % cookie_value)
1042 self.end_headers()
1043 for cookie_value in cookie_values:
1044 self.wfile.write('%s' % cookie_value)
1045 return True
1046
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001047 def SetManyCookiesHandler(self):
1048 """This handler just sets a given number of cookies, for testing handling
1049 of large numbers of cookies."""
1050
1051 if not self._ShouldHandleRequest("/set-many-cookies"):
1052 return False
1053
1054 query_char = self.path.find('?')
1055 if query_char != -1:
1056 num_cookies = int(self.path[query_char + 1:])
1057 else:
1058 num_cookies = 0
1059 self.send_response(200)
1060 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001061 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001062 self.send_header('Set-Cookie', 'a=')
1063 self.end_headers()
1064 self.wfile.write('%d cookies were sent' % num_cookies)
1065 return True
1066
mattm@chromium.org983fc462012-06-30 00:52:08 +00001067 def ExpectAndSetCookieHandler(self):
1068 """Expects some cookies to be sent, and if they are, sets more cookies.
1069
1070 The expect parameter specifies a required cookie. May be specified multiple
1071 times.
1072 The set parameter specifies a cookie to set if all required cookies are
1073 preset. May be specified multiple times.
1074 The data parameter specifies the response body data to be returned."""
1075
1076 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1077 return False
1078
1079 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1080 query_dict = cgi.parse_qs(query)
1081 cookies = set()
1082 if 'Cookie' in self.headers:
1083 cookie_header = self.headers.getheader('Cookie')
1084 cookies.update([s.strip() for s in cookie_header.split(';')])
1085 got_all_expected_cookies = True
1086 for expected_cookie in query_dict.get('expect', []):
1087 if expected_cookie not in cookies:
1088 got_all_expected_cookies = False
1089 self.send_response(200)
1090 self.send_header('Content-Type', 'text/html')
1091 if got_all_expected_cookies:
1092 for cookie_value in query_dict.get('set', []):
1093 self.send_header('Set-Cookie', '%s' % cookie_value)
1094 self.end_headers()
1095 for data_value in query_dict.get('data', []):
1096 self.wfile.write(data_value)
1097 return True
1098
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001099 def SetHeaderHandler(self):
1100 """This handler sets a response header. Parameters are in the
1101 key%3A%20value&key2%3A%20value2 format."""
1102
1103 if not self._ShouldHandleRequest("/set-header"):
1104 return False
1105
1106 query_char = self.path.find('?')
1107 if query_char != -1:
1108 headers_values = self.path[query_char + 1:].split('&')
1109 else:
1110 headers_values = ("",)
1111 self.send_response(200)
1112 self.send_header('Content-Type', 'text/html')
1113 for header_value in headers_values:
1114 header_value = urllib.unquote(header_value)
1115 (key, value) = header_value.split(': ', 1)
1116 self.send_header(key, value)
1117 self.end_headers()
1118 for header_value in headers_values:
1119 self.wfile.write('%s' % header_value)
1120 return True
1121
initial.commit94958cf2008-07-26 22:42:52 +00001122 def AuthBasicHandler(self):
1123 """This handler tests 'Basic' authentication. It just sends a page with
1124 title 'user/pass' if you succeed."""
1125
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001126 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001127 return False
1128
1129 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001130 expected_password = 'secret'
1131 realm = 'testrealm'
1132 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001133
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001134 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1135 query_params = cgi.parse_qs(query, True)
1136 if 'set-cookie-if-challenged' in query_params:
1137 set_cookie_if_challenged = True
1138 if 'password' in query_params:
1139 expected_password = query_params['password'][0]
1140 if 'realm' in query_params:
1141 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001142
initial.commit94958cf2008-07-26 22:42:52 +00001143 auth = self.headers.getheader('authorization')
1144 try:
1145 if not auth:
1146 raise Exception('no auth')
1147 b64str = re.findall(r'Basic (\S+)', auth)[0]
1148 userpass = base64.b64decode(b64str)
1149 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001150 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001151 raise Exception('wrong password')
1152 except Exception, e:
1153 # Authentication failed.
1154 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001155 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001156 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001157 if set_cookie_if_challenged:
1158 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001159 self.end_headers()
1160 self.wfile.write('<html><head>')
1161 self.wfile.write('<title>Denied: %s</title>' % e)
1162 self.wfile.write('</head><body>')
1163 self.wfile.write('auth=%s<p>' % auth)
1164 self.wfile.write('b64str=%s<p>' % b64str)
1165 self.wfile.write('username: %s<p>' % username)
1166 self.wfile.write('userpass: %s<p>' % userpass)
1167 self.wfile.write('password: %s<p>' % password)
1168 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1169 self.wfile.write('</body></html>')
1170 return True
1171
1172 # Authentication successful. (Return a cachable response to allow for
1173 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001174 old_protocol_version = self.protocol_version
1175 self.protocol_version = "HTTP/1.1"
1176
initial.commit94958cf2008-07-26 22:42:52 +00001177 if_none_match = self.headers.getheader('if-none-match')
1178 if if_none_match == "abc":
1179 self.send_response(304)
1180 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001181 elif url_path.endswith(".gif"):
1182 # Using chrome/test/data/google/logo.gif as the test image
1183 test_image_path = ['google', 'logo.gif']
1184 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1185 if not os.path.isfile(gif_path):
1186 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001187 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001188 return True
1189
1190 f = open(gif_path, "rb")
1191 data = f.read()
1192 f.close()
1193
1194 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001195 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001196 self.send_header('Cache-control', 'max-age=60000')
1197 self.send_header('Etag', 'abc')
1198 self.end_headers()
1199 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001200 else:
1201 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001202 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001203 self.send_header('Cache-control', 'max-age=60000')
1204 self.send_header('Etag', 'abc')
1205 self.end_headers()
1206 self.wfile.write('<html><head>')
1207 self.wfile.write('<title>%s/%s</title>' % (username, password))
1208 self.wfile.write('</head><body>')
1209 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001210 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001211 self.wfile.write('</body></html>')
1212
rvargas@google.com54453b72011-05-19 01:11:11 +00001213 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001214 return True
1215
tonyg@chromium.org75054202010-03-31 22:06:10 +00001216 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001217 """Returns a nonce that's stable per request path for the server's lifetime.
1218 This is a fake implementation. A real implementation would only use a given
1219 nonce a single time (hence the name n-once). However, for the purposes of
1220 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001221
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001222 Args:
1223 force_reset: Iff set, the nonce will be changed. Useful for testing the
1224 "stale" response.
1225 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001226
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001227 if force_reset or not self.server.nonce_time:
1228 self.server.nonce_time = time.time()
1229 return hashlib.md5('privatekey%s%d' %
1230 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001231
1232 def AuthDigestHandler(self):
1233 """This handler tests 'Digest' authentication.
1234
1235 It just sends a page with title 'user/pass' if you succeed.
1236
1237 A stale response is sent iff "stale" is present in the request path.
1238 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001239
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001240 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001241 return False
1242
tonyg@chromium.org75054202010-03-31 22:06:10 +00001243 stale = 'stale' in self.path
1244 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001245 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001246 password = 'secret'
1247 realm = 'testrealm'
1248
1249 auth = self.headers.getheader('authorization')
1250 pairs = {}
1251 try:
1252 if not auth:
1253 raise Exception('no auth')
1254 if not auth.startswith('Digest'):
1255 raise Exception('not digest')
1256 # Pull out all the name="value" pairs as a dictionary.
1257 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1258
1259 # Make sure it's all valid.
1260 if pairs['nonce'] != nonce:
1261 raise Exception('wrong nonce')
1262 if pairs['opaque'] != opaque:
1263 raise Exception('wrong opaque')
1264
1265 # Check the 'response' value and make sure it matches our magic hash.
1266 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001267 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001268 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001269 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001270 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001271 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001272 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1273 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001274 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001275
1276 if pairs['response'] != response:
1277 raise Exception('wrong password')
1278 except Exception, e:
1279 # Authentication failed.
1280 self.send_response(401)
1281 hdr = ('Digest '
1282 'realm="%s", '
1283 'domain="/", '
1284 'qop="auth", '
1285 'algorithm=MD5, '
1286 'nonce="%s", '
1287 'opaque="%s"') % (realm, nonce, opaque)
1288 if stale:
1289 hdr += ', stale="TRUE"'
1290 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001291 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001292 self.end_headers()
1293 self.wfile.write('<html><head>')
1294 self.wfile.write('<title>Denied: %s</title>' % e)
1295 self.wfile.write('</head><body>')
1296 self.wfile.write('auth=%s<p>' % auth)
1297 self.wfile.write('pairs=%s<p>' % pairs)
1298 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1299 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1300 self.wfile.write('</body></html>')
1301 return True
1302
1303 # Authentication successful.
1304 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001305 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001306 self.end_headers()
1307 self.wfile.write('<html><head>')
1308 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1309 self.wfile.write('</head><body>')
1310 self.wfile.write('auth=%s<p>' % auth)
1311 self.wfile.write('pairs=%s<p>' % pairs)
1312 self.wfile.write('</body></html>')
1313
1314 return True
1315
1316 def SlowServerHandler(self):
1317 """Wait for the user suggested time before responding. The syntax is
1318 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001319
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001320 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001321 return False
1322 query_char = self.path.find('?')
1323 wait_sec = 1.0
1324 if query_char >= 0:
1325 try:
davidben05f82202015-03-31 13:48:07 -07001326 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001327 except ValueError:
1328 pass
1329 time.sleep(wait_sec)
1330 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001331 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001332 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001333 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001334 return True
1335
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001336 def ChunkedServerHandler(self):
1337 """Send chunked response. Allows to specify chunks parameters:
1338 - waitBeforeHeaders - ms to wait before sending headers
1339 - waitBetweenChunks - ms to wait between chunks
1340 - chunkSize - size of each chunk in bytes
1341 - chunksNumber - number of chunks
1342 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1343 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001344
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001345 if not self._ShouldHandleRequest("/chunked"):
1346 return False
1347 query_char = self.path.find('?')
1348 chunkedSettings = {'waitBeforeHeaders' : 0,
1349 'waitBetweenChunks' : 0,
1350 'chunkSize' : 5,
1351 'chunksNumber' : 5}
1352 if query_char >= 0:
1353 params = self.path[query_char + 1:].split('&')
1354 for param in params:
1355 keyValue = param.split('=')
1356 if len(keyValue) == 2:
1357 try:
1358 chunkedSettings[keyValue[0]] = int(keyValue[1])
1359 except ValueError:
1360 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001361 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001362 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1363 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001364 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001365 self.send_header('Connection', 'close')
1366 self.send_header('Transfer-Encoding', 'chunked')
1367 self.end_headers()
1368 # Chunked encoding: sending all chunks, then final zero-length chunk and
1369 # then final CRLF.
1370 for i in range(0, chunkedSettings['chunksNumber']):
1371 if i > 0:
1372 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1373 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001374 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001375 self.sendChunkHelp('')
1376 return True
1377
creis@google.com2f4f6a42011-03-25 19:44:19 +00001378 def NoContentHandler(self):
1379 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001380
creis@google.com2f4f6a42011-03-25 19:44:19 +00001381 if not self._ShouldHandleRequest("/nocontent"):
1382 return False
1383 self.send_response(204)
1384 self.end_headers()
1385 return True
1386
initial.commit94958cf2008-07-26 22:42:52 +00001387 def ServerRedirectHandler(self):
1388 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001389 '/server-redirect?http://foo.bar/asdf' to redirect to
1390 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001391
1392 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001393 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001394 return False
1395
1396 query_char = self.path.find('?')
1397 if query_char < 0 or len(self.path) <= query_char + 1:
1398 self.sendRedirectHelp(test_name)
1399 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001400 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001401
1402 self.send_response(301) # moved permanently
1403 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001404 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001405 self.end_headers()
1406 self.wfile.write('<html><head>')
1407 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1408
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001409 return True
initial.commit94958cf2008-07-26 22:42:52 +00001410
naskoe7a0d0d2014-09-29 08:53:05 -07001411 def CrossSiteRedirectHandler(self):
1412 """Sends a server redirect to the given site. The syntax is
1413 '/cross-site/hostname/...' to redirect to //hostname/...
1414 It is used to navigate between different Sites, causing
1415 cross-site/cross-process navigations in the browser."""
1416
1417 test_name = "/cross-site"
1418 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001419 return False
1420
1421 params = urllib.unquote(self.path[(len(test_name) + 1):])
1422 slash = params.find('/')
1423 if slash < 0:
1424 self.sendRedirectHelp(test_name)
1425 return True
1426
1427 host = params[:slash]
1428 path = params[(slash+1):]
1429 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1430
1431 self.send_response(301) # moved permanently
1432 self.send_header('Location', dest)
1433 self.send_header('Content-Type', 'text/html')
1434 self.end_headers()
1435 self.wfile.write('<html><head>')
1436 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1437
1438 return True
1439
initial.commit94958cf2008-07-26 22:42:52 +00001440 def ClientRedirectHandler(self):
1441 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001442 '/client-redirect?http://foo.bar/asdf' to redirect to
1443 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001444
1445 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001446 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001447 return False
1448
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001449 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001450 if query_char < 0 or len(self.path) <= query_char + 1:
1451 self.sendRedirectHelp(test_name)
1452 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001453 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001454
1455 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001456 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001457 self.end_headers()
1458 self.wfile.write('<html><head>')
1459 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1460 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1461
1462 return True
1463
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001464 def GetSSLSessionCacheHandler(self):
1465 """Send a reply containing a log of the session cache operations."""
1466
1467 if not self._ShouldHandleRequest('/ssl-session-cache'):
1468 return False
1469
1470 self.send_response(200)
1471 self.send_header('Content-Type', 'text/plain')
1472 self.end_headers()
1473 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001474 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001475 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001476 self.wfile.write('Pass --https-record-resume in order to use' +
1477 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001478 return True
1479
1480 for (action, sessionID) in log:
1481 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001482 return True
1483
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001484 def SSLManySmallRecords(self):
1485 """Sends a reply consisting of a variety of small writes. These will be
1486 translated into a series of small SSL records when used over an HTTPS
1487 server."""
1488
1489 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1490 return False
1491
1492 self.send_response(200)
1493 self.send_header('Content-Type', 'text/plain')
1494 self.end_headers()
1495
1496 # Write ~26K of data, in 1350 byte chunks
1497 for i in xrange(20):
1498 self.wfile.write('*' * 1350)
1499 self.wfile.flush()
1500 return True
1501
agl@chromium.org04700be2013-03-02 18:40:41 +00001502 def GetChannelID(self):
1503 """Send a reply containing the hashed ChannelID that the client provided."""
1504
1505 if not self._ShouldHandleRequest('/channel-id'):
1506 return False
1507
1508 self.send_response(200)
1509 self.send_header('Content-Type', 'text/plain')
1510 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001511 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001512 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1513 return True
1514
davidben599e7e72014-09-03 16:19:09 -07001515 def ClientCipherListHandler(self):
1516 """Send a reply containing the cipher suite list that the client
1517 provided. Each cipher suite value is serialized in decimal, followed by a
1518 newline."""
1519
1520 if not self._ShouldHandleRequest('/client-cipher-list'):
1521 return False
1522
1523 self.send_response(200)
1524 self.send_header('Content-Type', 'text/plain')
1525 self.end_headers()
1526
davidben11682512014-10-06 21:09:11 -07001527 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1528 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001529 return True
1530
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001531 def CloseSocketHandler(self):
1532 """Closes the socket without sending anything."""
1533
1534 if not self._ShouldHandleRequest('/close-socket'):
1535 return False
1536
1537 self.wfile.close()
1538 return True
1539
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001540 def RangeResetHandler(self):
1541 """Send data broken up by connection resets every N (default 4K) bytes.
1542 Support range requests. If the data requested doesn't straddle a reset
1543 boundary, it will all be sent. Used for testing resuming downloads."""
1544
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001545 def DataForRange(start, end):
1546 """Data to be provided for a particular range of bytes."""
1547 # Offset and scale to avoid too obvious (and hence potentially
1548 # collidable) data.
1549 return ''.join([chr(y % 256)
1550 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1551
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001552 if not self._ShouldHandleRequest('/rangereset'):
1553 return False
1554
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001555 # HTTP/1.1 is required for ETag and range support.
1556 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001557 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1558
1559 # Defaults
1560 size = 8000
1561 # Note that the rst is sent just before sending the rst_boundary byte.
1562 rst_boundary = 4000
1563 respond_to_range = True
1564 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001565 rst_limit = -1
1566 token = 'DEFAULT'
1567 fail_precondition = 0
1568 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001569
1570 # Parse the query
1571 qdict = urlparse.parse_qs(query, True)
1572 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001573 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001574 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001575 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001576 if 'token' in qdict:
1577 # Identifying token for stateful tests.
1578 token = qdict['token'][0]
1579 if 'rst_limit' in qdict:
1580 # Max number of rsts for a given token.
1581 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001582 if 'bounce_range' in qdict:
1583 respond_to_range = False
1584 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001585 # Note that hold_for_signal will not work with null range requests;
1586 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001587 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001588 if 'no_verifiers' in qdict:
1589 send_verifiers = False
1590 if 'fail_precondition' in qdict:
1591 fail_precondition = int(qdict['fail_precondition'][0])
1592
1593 # Record already set information, or set it.
1594 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1595 if rst_limit != 0:
1596 TestPageHandler.rst_limits[token] -= 1
1597 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1598 token, fail_precondition)
1599 if fail_precondition != 0:
1600 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001601
1602 first_byte = 0
1603 last_byte = size - 1
1604
1605 # Does that define what we want to return, or do we need to apply
1606 # a range?
1607 range_response = False
1608 range_header = self.headers.getheader('range')
1609 if range_header and respond_to_range:
1610 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1611 if mo.group(1):
1612 first_byte = int(mo.group(1))
1613 if mo.group(2):
1614 last_byte = int(mo.group(2))
1615 if last_byte > size - 1:
1616 last_byte = size - 1
1617 range_response = True
1618 if last_byte < first_byte:
1619 return False
1620
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001621 if (fail_precondition and
1622 (self.headers.getheader('If-Modified-Since') or
1623 self.headers.getheader('If-Match'))):
1624 self.send_response(412)
1625 self.end_headers()
1626 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001627
1628 if range_response:
1629 self.send_response(206)
1630 self.send_header('Content-Range',
1631 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1632 else:
1633 self.send_response(200)
1634 self.send_header('Content-Type', 'application/octet-stream')
1635 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001636 if send_verifiers:
asanka@chromium.org3ffced92013-12-13 22:20:27 +00001637 # If fail_precondition is non-zero, then the ETag for each request will be
1638 # different.
1639 etag = "%s%d" % (token, fail_precondition)
1640 self.send_header('ETag', etag)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001641 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001642 self.end_headers()
1643
1644 if hold_for_signal:
1645 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1646 # a single byte, the self.server.handle_request() below hangs
1647 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001648 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001649 first_byte = first_byte + 1
1650 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001651 self.server.wait_for_download = True
1652 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001653 self.server.handle_request()
1654
1655 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001656 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001657 # No RST has been requested in this range, so we don't need to
1658 # do anything fancy; just write the data and let the python
1659 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001660 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001661 self.wfile.flush()
1662 return True
1663
1664 # We're resetting the connection part way in; go to the RST
1665 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001666 # Because socket semantics do not guarantee that all the data will be
1667 # sent when using the linger semantics to hard close a socket,
1668 # we send the data and then wait for our peer to release us
1669 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001670 data = DataForRange(first_byte, possible_rst)
1671 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001672 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001673 self.server.wait_for_download = True
1674 while self.server.wait_for_download:
1675 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001676 l_onoff = 1 # Linger is active.
1677 l_linger = 0 # Seconds to linger for.
1678 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1679 struct.pack('ii', l_onoff, l_linger))
1680
1681 # Close all duplicates of the underlying socket to force the RST.
1682 self.wfile.close()
1683 self.rfile.close()
1684 self.connection.close()
1685
1686 return True
1687
initial.commit94958cf2008-07-26 22:42:52 +00001688 def DefaultResponseHandler(self):
1689 """This is the catch-all response handler for requests that aren't handled
1690 by one of the special handlers above.
1691 Note that we specify the content-length as without it the https connection
1692 is not closed properly (and the browser keeps expecting data)."""
1693
1694 contents = "Default response given for path: " + self.path
1695 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001696 self.send_header('Content-Type', 'text/html')
1697 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001698 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001699 if (self.command != 'HEAD'):
1700 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001701 return True
1702
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001703 def RedirectConnectHandler(self):
1704 """Sends a redirect to the CONNECT request for www.redirect.com. This
1705 response is not specified by the RFC, so the browser should not follow
1706 the redirect."""
1707
1708 if (self.path.find("www.redirect.com") < 0):
1709 return False
1710
1711 dest = "http://www.destination.com/foo.js"
1712
1713 self.send_response(302) # moved temporarily
1714 self.send_header('Location', dest)
1715 self.send_header('Connection', 'close')
1716 self.end_headers()
1717 return True
1718
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001719 def ServerAuthConnectHandler(self):
1720 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1721 response doesn't make sense because the proxy server cannot request
1722 server authentication."""
1723
1724 if (self.path.find("www.server-auth.com") < 0):
1725 return False
1726
1727 challenge = 'Basic realm="WallyWorld"'
1728
1729 self.send_response(401) # unauthorized
1730 self.send_header('WWW-Authenticate', challenge)
1731 self.send_header('Connection', 'close')
1732 self.end_headers()
1733 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001734
1735 def DefaultConnectResponseHandler(self):
1736 """This is the catch-all response handler for CONNECT requests that aren't
1737 handled by one of the special handlers above. Real Web servers respond
1738 with 400 to CONNECT requests."""
1739
1740 contents = "Your client has issued a malformed or illegal request."
1741 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001742 self.send_header('Content-Type', 'text/html')
1743 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001744 self.end_headers()
1745 self.wfile.write(contents)
1746 return True
1747
initial.commit94958cf2008-07-26 22:42:52 +00001748 # called by the redirect handling function when there is no parameter
1749 def sendRedirectHelp(self, redirect_name):
1750 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001751 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001752 self.end_headers()
1753 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1754 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1755 self.wfile.write('</body></html>')
1756
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001757 # called by chunked handling function
1758 def sendChunkHelp(self, chunk):
1759 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1760 self.wfile.write('%X\r\n' % len(chunk))
1761 self.wfile.write(chunk)
1762 self.wfile.write('\r\n')
1763
akalin@chromium.org154bb132010-11-12 02:20:27 +00001764
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001765class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001766 def __init__(self, request, client_address, socket_server):
1767 handlers = [self.OCSPResponse]
1768 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001769 testserver_base.BasePageHandler.__init__(self, request, client_address,
1770 socket_server, [], handlers, [],
1771 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001772
1773 def OCSPResponse(self):
1774 self.send_response(200)
1775 self.send_header('Content-Type', 'application/ocsp-response')
1776 self.send_header('Content-Length', str(len(self.ocsp_response)))
1777 self.end_headers()
1778
1779 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001780
mattm@chromium.org830a3712012-11-07 23:00:07 +00001781
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001782class TCPEchoHandler(SocketServer.BaseRequestHandler):
1783 """The RequestHandler class for TCP echo server.
1784
1785 It is instantiated once per connection to the server, and overrides the
1786 handle() method to implement communication to the client.
1787 """
1788
1789 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001790 """Handles the request from the client and constructs a response."""
1791
1792 data = self.request.recv(65536).strip()
1793 # Verify the "echo request" message received from the client. Send back
1794 # "echo response" message if "echo request" message is valid.
1795 try:
1796 return_data = echo_message.GetEchoResponseData(data)
1797 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001798 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001799 except ValueError:
1800 return
1801
1802 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001803
1804
1805class UDPEchoHandler(SocketServer.BaseRequestHandler):
1806 """The RequestHandler class for UDP echo server.
1807
1808 It is instantiated once per connection to the server, and overrides the
1809 handle() method to implement communication to the client.
1810 """
1811
1812 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001813 """Handles the request from the client and constructs a response."""
1814
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001815 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001816 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001817 # Verify the "echo request" message received from the client. Send back
1818 # "echo response" message if "echo request" message is valid.
1819 try:
1820 return_data = echo_message.GetEchoResponseData(data)
1821 if not return_data:
1822 return
1823 except ValueError:
1824 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001825 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001826
1827
bashi@chromium.org33233532012-09-08 17:37:24 +00001828class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1829 """A request handler that behaves as a proxy server which requires
1830 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1831 """
1832
1833 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1834
1835 def parse_request(self):
1836 """Overrides parse_request to check credential."""
1837
1838 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1839 return False
1840
1841 auth = self.headers.getheader('Proxy-Authorization')
1842 if auth != self._AUTH_CREDENTIAL:
1843 self.send_response(407)
1844 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1845 self.end_headers()
1846 return False
1847
1848 return True
1849
1850 def _start_read_write(self, sock):
1851 sock.setblocking(0)
1852 self.request.setblocking(0)
1853 rlist = [self.request, sock]
1854 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001855 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001856 if errors:
1857 self.send_response(500)
1858 self.end_headers()
1859 return
1860 for s in ready_sockets:
1861 received = s.recv(1024)
1862 if len(received) == 0:
1863 return
1864 if s == self.request:
1865 other = sock
1866 else:
1867 other = self.request
1868 other.send(received)
1869
1870 def _do_common_method(self):
1871 url = urlparse.urlparse(self.path)
1872 port = url.port
1873 if not port:
1874 if url.scheme == 'http':
1875 port = 80
1876 elif url.scheme == 'https':
1877 port = 443
1878 if not url.hostname or not port:
1879 self.send_response(400)
1880 self.end_headers()
1881 return
1882
1883 if len(url.path) == 0:
1884 path = '/'
1885 else:
1886 path = url.path
1887 if len(url.query) > 0:
1888 path = '%s?%s' % (url.path, url.query)
1889
1890 sock = None
1891 try:
1892 sock = socket.create_connection((url.hostname, port))
1893 sock.send('%s %s %s\r\n' % (
1894 self.command, path, self.protocol_version))
1895 for header in self.headers.headers:
1896 header = header.strip()
1897 if (header.lower().startswith('connection') or
1898 header.lower().startswith('proxy')):
1899 continue
1900 sock.send('%s\r\n' % header)
1901 sock.send('\r\n')
1902 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001903 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001904 self.send_response(500)
1905 self.end_headers()
1906 finally:
1907 if sock is not None:
1908 sock.close()
1909
1910 def do_CONNECT(self):
1911 try:
1912 pos = self.path.rfind(':')
1913 host = self.path[:pos]
1914 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001915 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001916 self.send_response(400)
1917 self.end_headers()
1918
1919 try:
1920 sock = socket.create_connection((host, port))
1921 self.send_response(200, 'Connection established')
1922 self.end_headers()
1923 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001924 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001925 self.send_response(500)
1926 self.end_headers()
1927 finally:
1928 sock.close()
1929
1930 def do_GET(self):
1931 self._do_common_method()
1932
1933 def do_HEAD(self):
1934 self._do_common_method()
1935
1936
mattm@chromium.org830a3712012-11-07 23:00:07 +00001937class ServerRunner(testserver_base.TestServerRunner):
1938 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001939
mattm@chromium.org830a3712012-11-07 23:00:07 +00001940 def __init__(self):
1941 super(ServerRunner, self).__init__()
1942 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001943
mattm@chromium.org830a3712012-11-07 23:00:07 +00001944 def __make_data_dir(self):
1945 if self.options.data_dir:
1946 if not os.path.isdir(self.options.data_dir):
1947 raise testserver_base.OptionError('specified data dir not found: ' +
1948 self.options.data_dir + ' exiting...')
1949 my_data_dir = self.options.data_dir
1950 else:
1951 # Create the default path to our data dir, relative to the exe dir.
1952 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1953 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001954
mattm@chromium.org830a3712012-11-07 23:00:07 +00001955 #TODO(ibrar): Must use Find* funtion defined in google\tools
1956 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001957
mattm@chromium.org830a3712012-11-07 23:00:07 +00001958 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001959
mattm@chromium.org830a3712012-11-07 23:00:07 +00001960 def create_server(self, server_data):
1961 port = self.options.port
1962 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001963
estark21667d62015-04-08 21:00:16 -07001964 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1965 # will result in a call to |getaddrinfo|, which fails with "nodename
1966 # nor servname provided" for localhost:0 on 10.6.
1967 if self.options.server_type == SERVER_WEBSOCKET and \
1968 host == "localhost" and \
1969 port == 0:
1970 host = "127.0.0.1"
1971
mattm@chromium.org830a3712012-11-07 23:00:07 +00001972 if self.options.server_type == SERVER_HTTP:
1973 if self.options.https:
1974 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08001975 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001976 if self.options.cert_and_key_file:
1977 if not os.path.isfile(self.options.cert_and_key_file):
1978 raise testserver_base.OptionError(
1979 'specified server cert file not found: ' +
1980 self.options.cert_and_key_file + ' exiting...')
1981 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001982 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001983 # generate a new certificate and run an OCSP server for it.
1984 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001985 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001986 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001987
mattm@chromium.org830a3712012-11-07 23:00:07 +00001988 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001989
mattm@chromium.org830a3712012-11-07 23:00:07 +00001990 if self.options.ocsp == 'ok':
1991 ocsp_state = minica.OCSP_STATE_GOOD
1992 elif self.options.ocsp == 'revoked':
1993 ocsp_state = minica.OCSP_STATE_REVOKED
1994 elif self.options.ocsp == 'invalid':
1995 ocsp_state = minica.OCSP_STATE_INVALID
1996 elif self.options.ocsp == 'unauthorized':
1997 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1998 elif self.options.ocsp == 'unknown':
1999 ocsp_state = minica.OCSP_STATE_UNKNOWN
2000 else:
2001 raise testserver_base.OptionError('unknown OCSP status: ' +
2002 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002003
mattm@chromium.org830a3712012-11-07 23:00:07 +00002004 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2005 subject = "127.0.0.1",
2006 ocsp_url = ("http://%s:%d/ocsp" %
2007 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00002008 ocsp_state = ocsp_state,
2009 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002010
davidben3e2564a2014-11-07 18:51:00 -08002011 if self.options.ocsp_server_unavailable:
2012 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
2013 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
2014 else:
2015 self.__ocsp_server.ocsp_response = ocsp_der
mattm@chromium.org830a3712012-11-07 23:00:07 +00002016
2017 for ca_cert in self.options.ssl_client_ca:
2018 if not os.path.isfile(ca_cert):
2019 raise testserver_base.OptionError(
2020 'specified trusted client CA file not found: ' + ca_cert +
2021 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002022
2023 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08002024 if self.options.staple_ocsp_response:
2025 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002026
mattm@chromium.org830a3712012-11-07 23:00:07 +00002027 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2028 self.options.ssl_client_auth,
2029 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002030 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002031 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002032 self.options.ssl_key_exchange,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002033 self.options.enable_npn,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002034 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002035 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002036 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002037 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002038 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002039 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07002040 stapled_ocsp_response,
2041 self.options.alert_after_handshake)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002042 print 'HTTPS server started on https://%s:%d...' % \
2043 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002044 else:
2045 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002046 print 'HTTP server started on http://%s:%d...' % \
2047 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002048
2049 server.data_dir = self.__make_data_dir()
2050 server.file_root_url = self.options.file_root_url
2051 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002052 elif self.options.server_type == SERVER_WEBSOCKET:
2053 # Launch pywebsocket via WebSocketServer.
2054 logger = logging.getLogger()
2055 logger.addHandler(logging.StreamHandler())
2056 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2057 # is required to work correctly. It should be fixed from pywebsocket side.
2058 os.chdir(self.__make_data_dir())
2059 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002060 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002061 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002062 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002063 websocket_options.use_tls = True
2064 websocket_options.private_key = self.options.cert_and_key_file
2065 websocket_options.certificate = self.options.cert_and_key_file
2066 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002067 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002068 websocket_options.tls_client_auth = True
2069 if len(self.options.ssl_client_ca) != 1:
2070 raise testserver_base.OptionError(
2071 'one trusted client CA file should be specified')
2072 if not os.path.isfile(self.options.ssl_client_ca[0]):
2073 raise testserver_base.OptionError(
2074 'specified trusted client CA file not found: ' +
2075 self.options.ssl_client_ca[0] + ' exiting...')
2076 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07002077 print 'Trying to start websocket server on %s://%s:%d...' % \
2078 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002079 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002080 print 'WebSocket server started on %s://%s:%d...' % \
2081 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002082 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002083 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002084 elif self.options.server_type == SERVER_TCP_ECHO:
2085 # Used for generating the key (randomly) that encodes the "echo request"
2086 # message.
2087 random.seed()
2088 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002089 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002090 server_data['port'] = server.server_port
2091 elif self.options.server_type == SERVER_UDP_ECHO:
2092 # Used for generating the key (randomly) that encodes the "echo request"
2093 # message.
2094 random.seed()
2095 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002096 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002097 server_data['port'] = server.server_port
2098 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2099 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002100 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002101 server_data['port'] = server.server_port
2102 elif self.options.server_type == SERVER_FTP:
2103 my_data_dir = self.__make_data_dir()
2104
2105 # Instantiate a dummy authorizer for managing 'virtual' users
2106 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2107
xleng9d4c45f2015-05-04 16:26:12 -07002108 # Define a new user having full r/w permissions
mattm@chromium.org830a3712012-11-07 23:00:07 +00002109 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2110
xleng9d4c45f2015-05-04 16:26:12 -07002111 # Define a read-only anonymous user unless disabled
2112 if not self.options.no_anonymous_ftp_user:
2113 authorizer.add_anonymous(my_data_dir)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002114
2115 # Instantiate FTP handler class
2116 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2117 ftp_handler.authorizer = authorizer
2118
2119 # Define a customized banner (string returned when client connects)
2120 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2121 pyftpdlib.ftpserver.__ver__)
2122
2123 # Instantiate FTP server class and listen to address:port
2124 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2125 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002126 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002127 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002128 raise testserver_base.OptionError('unknown server type' +
2129 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002130
mattm@chromium.org830a3712012-11-07 23:00:07 +00002131 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002132
mattm@chromium.org830a3712012-11-07 23:00:07 +00002133 def run_server(self):
2134 if self.__ocsp_server:
2135 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002136
mattm@chromium.org830a3712012-11-07 23:00:07 +00002137 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002138
mattm@chromium.org830a3712012-11-07 23:00:07 +00002139 if self.__ocsp_server:
2140 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002141
mattm@chromium.org830a3712012-11-07 23:00:07 +00002142 def add_options(self):
2143 testserver_base.TestServerRunner.add_options(self)
2144 self.option_parser.add_option('-f', '--ftp', action='store_const',
2145 const=SERVER_FTP, default=SERVER_HTTP,
2146 dest='server_type',
2147 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002148 self.option_parser.add_option('--tcp-echo', action='store_const',
2149 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2150 dest='server_type',
2151 help='start up a tcp echo server.')
2152 self.option_parser.add_option('--udp-echo', action='store_const',
2153 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2154 dest='server_type',
2155 help='start up a udp echo server.')
2156 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2157 const=SERVER_BASIC_AUTH_PROXY,
2158 default=SERVER_HTTP, dest='server_type',
2159 help='start up a proxy server which requires '
2160 'basic authentication.')
2161 self.option_parser.add_option('--websocket', action='store_const',
2162 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2163 dest='server_type',
2164 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002165 self.option_parser.add_option('--https', action='store_true',
2166 dest='https', help='Specify that https '
2167 'should be used.')
2168 self.option_parser.add_option('--cert-and-key-file',
2169 dest='cert_and_key_file', help='specify the '
2170 'path to the file containing the certificate '
2171 'and private key for the server in PEM '
2172 'format')
2173 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2174 help='The type of OCSP response generated '
2175 'for the automatically generated '
2176 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002177 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2178 default=0, type=int,
2179 help='If non-zero then the generated '
2180 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002181 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2182 default='0', type='int',
2183 help='If nonzero, certain TLS connections '
2184 'will be aborted in order to test version '
2185 'fallback. 1 means all TLS versions will be '
2186 'aborted. 2 means TLS 1.1 or higher will be '
2187 'aborted. 3 means TLS 1.2 or higher will be '
2188 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002189 self.option_parser.add_option('--tls-intolerance-type',
2190 dest='tls_intolerance_type',
2191 default="alert",
2192 help='Controls how the server reacts to a '
2193 'TLS version it is intolerant to. Valid '
2194 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002195 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2196 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002197 default='',
2198 help='Base64 encoded SCT list. If set, '
2199 'server will respond with a '
2200 'signed_certificate_timestamp TLS extension '
2201 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002202 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2203 default=False, const=True,
2204 action='store_const',
2205 help='If given, TLS_FALLBACK_SCSV support '
2206 'will be enabled. This causes the server to '
2207 'reject fallback connections from compatible '
2208 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002209 self.option_parser.add_option('--staple-ocsp-response',
2210 dest='staple_ocsp_response',
2211 default=False, action='store_true',
2212 help='If set, server will staple the OCSP '
2213 'response whenever OCSP is on and the client '
2214 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002215 self.option_parser.add_option('--https-record-resume',
2216 dest='record_resume', const=True,
2217 default=False, action='store_const',
2218 help='Record resumption cache events rather '
2219 'than resuming as normal. Allows the use of '
2220 'the /ssl-session-cache request')
2221 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2222 help='Require SSL client auth on every '
2223 'connection.')
2224 self.option_parser.add_option('--ssl-client-ca', action='append',
2225 default=[], help='Specify that the client '
2226 'certificate request should include the CA '
2227 'named in the subject of the DER-encoded '
2228 'certificate contained in the specified '
2229 'file. This option may appear multiple '
2230 'times, indicating multiple CA names should '
2231 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002232 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2233 default=[], help='Specify that the client '
2234 'certificate request should include the '
2235 'specified certificate_type value. This '
2236 'option may appear multiple times, '
2237 'indicating multiple values should be send '
2238 'in the request. Valid values are '
2239 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2240 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002241 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2242 help='Specify the bulk encryption '
2243 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08002244 'SSL server. Valid values are "aes128gcm", '
2245 '"aes256", "aes128", "3des", "rc4". If '
2246 'omitted, all algorithms will be used. This '
2247 'option may appear multiple times, '
2248 'indicating multiple algorithms should be '
2249 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002250 self.option_parser.add_option('--ssl-key-exchange', action='append',
2251 help='Specify the key exchange algorithm(s)'
2252 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07002253 'Valid values are "rsa", "dhe_rsa", '
2254 '"ecdhe_rsa". If omitted, all algorithms '
2255 'will be used. This option may appear '
2256 'multiple times, indicating multiple '
2257 'algorithms should be enabled.');
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002258 # TODO(davidben): Add ALPN support to tlslite.
2259 self.option_parser.add_option('--enable-npn', dest='enable_npn',
2260 default=False, const=True,
2261 action='store_const',
2262 help='Enable server support for the NPN '
2263 'extension. The server will advertise '
2264 'support for exactly one protocol, http/1.1')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002265 self.option_parser.add_option('--file-root-url', default='/files/',
2266 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002267 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2268 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2269 dest='ws_basic_auth',
2270 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002271 self.option_parser.add_option('--ocsp-server-unavailable',
2272 dest='ocsp_server_unavailable',
2273 default=False, action='store_true',
2274 help='If set, the OCSP server will return '
2275 'a tryLater status rather than the actual '
2276 'OCSP response.')
davidben21cda342015-03-17 18:04:28 -07002277 self.option_parser.add_option('--alert-after-handshake',
2278 dest='alert_after_handshake',
2279 default=False, action='store_true',
2280 help='If set, the server will send a fatal '
2281 'alert immediately after the handshake.')
xleng9d4c45f2015-05-04 16:26:12 -07002282 self.option_parser.add_option('--no-anonymous-ftp-user',
2283 dest='no_anonymous_ftp_user',
2284 default=False, action='store_true',
2285 help='If set, the FTP server will not create '
2286 'an anonymous user.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002287
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002288
initial.commit94958cf2008-07-26 22:42:52 +00002289if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002290 sys.exit(ServerRunner().main())