blob: 42944147bd1c13ffd24af9aee67cf70cc5367384 [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00002# Copyright 2013 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00006"""This is a simple HTTP/FTP/TCP/UDP/BASIC_AUTH_PROXY/WEBSOCKET server used for
7testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00008
9It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000010By default, it listens on an ephemeral port and sends the port number back to
11the originating process over a pipe. The originating process can specify an
12explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000013It can use https if you specify the flag --https=CERT where CERT is the path
14to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000015"""
16
17import base64
18import BaseHTTPServer
19import cgi
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000020import hashlib
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000021import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000022import minica
initial.commit94958cf2008-07-26 22:42:52 +000023import os
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +000024import json
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000025import random
initial.commit94958cf2008-07-26 22:42:52 +000026import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000028import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000029import SocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000030import ssl
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000031import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000032import sys
33import threading
initial.commit94958cf2008-07-26 22:42:52 +000034import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000035import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000036import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000037import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000038
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000039BASE_DIR = os.path.dirname(os.path.abspath(__file__))
40ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(BASE_DIR)))
41
davidben@chromium.org7d53b542014-04-10 17:56:44 +000042# Temporary hack to deal with tlslite 0.3.8 -> 0.4.6 upgrade.
43#
44# TODO(davidben): Remove this when it has cycled through all the bots and
45# developer checkouts or when http://crbug.com/356276 is resolved.
46try:
47 os.remove(os.path.join(ROOT_DIR, 'third_party', 'tlslite',
48 'tlslite', 'utils', 'hmac.pyc'))
49except Exception:
50 pass
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000051
52# Append at the end of sys.path, it's fine to use the system library.
53sys.path.append(os.path.join(ROOT_DIR, 'third_party', 'pyftpdlib', 'src'))
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000054
davidben@chromium.org7d53b542014-04-10 17:56:44 +000055# Insert at the beginning of the path, we want to use our copies of the library
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000056# unconditionally.
57sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'pywebsocket', 'src'))
davidben@chromium.org7d53b542014-04-10 17:56:44 +000058sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'tlslite'))
59
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000060import mod_pywebsocket.standalone
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000061from mod_pywebsocket.standalone import WebSocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000062# import manually
63mod_pywebsocket.standalone.ssl = ssl
davidben@chromium.org06fcf202010-09-22 18:15:23 +000064
davidben@chromium.org7d53b542014-04-10 17:56:44 +000065import pyftpdlib.ftpserver
66
67import tlslite
68import tlslite.api
69
70import echo_message
71import testserver_base
72
maruel@chromium.org756cf982009-03-05 12:46:38 +000073SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000074SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000075SERVER_TCP_ECHO = 2
76SERVER_UDP_ECHO = 3
77SERVER_BASIC_AUTH_PROXY = 4
78SERVER_WEBSOCKET = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000079
80# Default request queue size for WebSocketServer.
81_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000082
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000083class WebSocketOptions:
84 """Holds options for WebSocketServer."""
85
86 def __init__(self, host, port, data_dir):
87 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
88 self.server_host = host
89 self.port = port
90 self.websock_handlers = data_dir
91 self.scan_dir = None
92 self.allow_handlers_outside_root_dir = False
93 self.websock_handlers_map_file = None
94 self.cgi_directories = []
95 self.is_executable_method = None
96 self.allow_draft75 = False
97 self.strict = True
98
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000099 self.use_tls = False
100 self.private_key = None
101 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +0000102 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000103 self.tls_client_ca = None
yhirano@chromium.org51f90d92014-03-24 04:49:23 +0000104 self.tls_module = 'ssl'
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000105 self.use_basic_auth = False
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +0000106 self.basic_auth_credential = 'Basic ' + base64.b64encode('test:test')
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000107
mattm@chromium.org830a3712012-11-07 23:00:07 +0000108
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000109class RecordingSSLSessionCache(object):
110 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
111 lookups and inserts in order to test session cache behaviours."""
112
113 def __init__(self):
114 self.log = []
115
116 def __getitem__(self, sessionID):
117 self.log.append(('lookup', sessionID))
118 raise KeyError()
119
120 def __setitem__(self, sessionID, session):
121 self.log.append(('insert', sessionID))
122
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000123
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000124class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
125 testserver_base.BrokenPipeHandlerMixIn,
126 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000127 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000128 verification."""
129
130 pass
131
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000132class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
133 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000134 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000135 """This is a specialization of HTTPServer that serves an
136 OCSP response"""
137
138 def serve_forever_on_thread(self):
139 self.thread = threading.Thread(target = self.serve_forever,
140 name = "OCSPServerThread")
141 self.thread.start()
142
143 def stop_serving(self):
144 self.shutdown()
145 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000146
mattm@chromium.org830a3712012-11-07 23:00:07 +0000147
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000148class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000149 testserver_base.ClientRestrictingServerMixIn,
150 testserver_base.BrokenPipeHandlerMixIn,
151 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000152 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000153 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000154
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000155 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000156 ssl_client_auth, ssl_client_cas, ssl_client_cert_types,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000157 ssl_bulk_ciphers, ssl_key_exchanges, enable_npn,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000158 record_resume_info, tls_intolerant,
159 tls_intolerance_type, signed_cert_timestamps,
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +0000160 fallback_scsv_enabled, ocsp_response, disable_session_cache):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000161 self.cert_chain = tlslite.api.X509CertChain()
162 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000163 # Force using only python implementation - otherwise behavior is different
164 # depending on whether m2crypto Python module is present (error is thrown
165 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
166 # the hood.
167 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
168 private=True,
169 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000170 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000171 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000172 self.ssl_client_cert_types = []
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000173 if enable_npn:
174 self.next_protos = ['http/1.1']
175 else:
176 self.next_protos = None
ekasper@google.com24aa8222013-11-28 13:43:26 +0000177 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000178 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000179 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000180
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000181 if ssl_client_auth:
182 for ca_file in ssl_client_cas:
183 s = open(ca_file).read()
184 x509 = tlslite.api.X509()
185 x509.parse(s)
186 self.ssl_client_cas.append(x509.subject)
187
188 for cert_type in ssl_client_cert_types:
189 self.ssl_client_cert_types.append({
190 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
191 "dss_sign": tlslite.api.ClientCertificateType.dss_sign,
192 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
193 }[cert_type])
194
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000195 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
196 if ssl_bulk_ciphers is not None:
197 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000198 if ssl_key_exchanges is not None:
199 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000200 if tls_intolerant != 0:
201 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
202 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
initial.commit94958cf2008-07-26 22:42:52 +0000203
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +0000204
205 if disable_session_cache:
206 self.session_cache = None
207 elif record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000208 # If record_resume_info is true then we'll replace the session cache with
209 # an object that records the lookups and inserts that it sees.
210 self.session_cache = RecordingSSLSessionCache()
211 else:
212 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000213 testserver_base.StoppableHTTPServer.__init__(self,
214 server_address,
215 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000216
217 def handshake(self, tlsConnection):
218 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000219
initial.commit94958cf2008-07-26 22:42:52 +0000220 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000221 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000222 tlsConnection.handshakeServer(certChain=self.cert_chain,
223 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000224 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000225 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000226 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000227 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000228 reqCertTypes=self.ssl_client_cert_types,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000229 nextProtos=self.next_protos,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000230 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000231 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000232 fallbackSCSV=self.fallback_scsv_enabled,
233 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000234 tlsConnection.ignoreAbruptClose = True
235 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000236 except tlslite.api.TLSAbruptCloseError:
237 # Ignore abrupt close.
238 return True
initial.commit94958cf2008-07-26 22:42:52 +0000239 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000240 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000241 return False
242
akalin@chromium.org154bb132010-11-12 02:20:27 +0000243
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000244class FTPServer(testserver_base.ClientRestrictingServerMixIn,
245 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000246 """This is a specialization of FTPServer that adds client verification."""
247
248 pass
249
250
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000251class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
252 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000253 """A TCP echo server that echoes back what it has received."""
254
255 def server_bind(self):
256 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000257
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000258 SocketServer.TCPServer.server_bind(self)
259 host, port = self.socket.getsockname()[:2]
260 self.server_name = socket.getfqdn(host)
261 self.server_port = port
262
263 def serve_forever(self):
264 self.stop = False
265 self.nonce_time = None
266 while not self.stop:
267 self.handle_request()
268 self.socket.close()
269
270
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000271class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
272 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000273 """A UDP echo server that echoes back what it has received."""
274
275 def server_bind(self):
276 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000277
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000278 SocketServer.UDPServer.server_bind(self)
279 host, port = self.socket.getsockname()[:2]
280 self.server_name = socket.getfqdn(host)
281 self.server_port = port
282
283 def serve_forever(self):
284 self.stop = False
285 self.nonce_time = None
286 while not self.stop:
287 self.handle_request()
288 self.socket.close()
289
290
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000291class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000292 # Class variables to allow for persistence state between page handler
293 # invocations
294 rst_limits = {}
295 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000296
297 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000298 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000299 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000300 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000301 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000302 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000303 self.NoCacheMaxAgeTimeHandler,
304 self.NoCacheTimeHandler,
305 self.CacheTimeHandler,
306 self.CacheExpiresHandler,
307 self.CacheProxyRevalidateHandler,
308 self.CachePrivateHandler,
309 self.CachePublicHandler,
310 self.CacheSMaxAgeHandler,
311 self.CacheMustRevalidateHandler,
312 self.CacheMustRevalidateMaxAgeHandler,
313 self.CacheNoStoreHandler,
314 self.CacheNoStoreMaxAgeHandler,
315 self.CacheNoTransformHandler,
316 self.DownloadHandler,
317 self.DownloadFinishHandler,
318 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000319 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000320 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000321 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000322 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000323 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000324 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000325 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000326 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000327 self.AuthBasicHandler,
328 self.AuthDigestHandler,
329 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000330 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000331 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000332 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000333 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700334 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000335 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000336 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000337 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000338 self.GetChannelID,
davidben599e7e72014-09-03 16:19:09 -0700339 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000340 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000341 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000342 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000343 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000344 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000345 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000346 self.PostOnlyFileHandler,
347 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000348 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000349 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000350 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000351 head_handlers = [
352 self.FileHandler,
353 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000354
maruel@google.come250a9b2009-03-10 17:39:46 +0000355 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000356 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000357 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000358 'gif': 'image/gif',
359 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000360 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000361 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000362 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000363 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000364 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000365 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000366 }
initial.commit94958cf2008-07-26 22:42:52 +0000367 self._default_mime_type = 'text/html'
368
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000369 testserver_base.BasePageHandler.__init__(self, request, client_address,
370 socket_server, connect_handlers,
371 get_handlers, head_handlers,
372 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000373
initial.commit94958cf2008-07-26 22:42:52 +0000374 def GetMIMETypeFromName(self, file_name):
375 """Returns the mime type for the specified file_name. So far it only looks
376 at the file extension."""
377
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000378 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000379 if len(extension) == 0:
380 # no extension.
381 return self._default_mime_type
382
ericroman@google.comc17ca532009-05-07 03:51:05 +0000383 # extension starts with a dot, so we need to remove it
384 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000385
initial.commit94958cf2008-07-26 22:42:52 +0000386 def NoCacheMaxAgeTimeHandler(self):
387 """This request handler yields a page with the title set to the current
388 system time, and no caching requested."""
389
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000390 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000391 return False
392
393 self.send_response(200)
394 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000395 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000396 self.end_headers()
397
maruel@google.come250a9b2009-03-10 17:39:46 +0000398 self.wfile.write('<html><head><title>%s</title></head></html>' %
399 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000400
401 return True
402
403 def NoCacheTimeHandler(self):
404 """This request handler yields a page with the title set to the current
405 system time, and no caching requested."""
406
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000407 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000408 return False
409
410 self.send_response(200)
411 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000412 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000413 self.end_headers()
414
maruel@google.come250a9b2009-03-10 17:39:46 +0000415 self.wfile.write('<html><head><title>%s</title></head></html>' %
416 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000417
418 return True
419
420 def CacheTimeHandler(self):
421 """This request handler yields a page with the title set to the current
422 system time, and allows caching for one minute."""
423
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000424 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000425 return False
426
427 self.send_response(200)
428 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000429 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000430 self.end_headers()
431
maruel@google.come250a9b2009-03-10 17:39:46 +0000432 self.wfile.write('<html><head><title>%s</title></head></html>' %
433 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000434
435 return True
436
437 def CacheExpiresHandler(self):
438 """This request handler yields a page with the title set to the current
439 system time, and set the page to expire on 1 Jan 2099."""
440
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000441 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000442 return False
443
444 self.send_response(200)
445 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000446 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000447 self.end_headers()
448
maruel@google.come250a9b2009-03-10 17:39:46 +0000449 self.wfile.write('<html><head><title>%s</title></head></html>' %
450 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000451
452 return True
453
454 def CacheProxyRevalidateHandler(self):
455 """This request handler yields a page with the title set to the current
456 system time, and allows caching for 60 seconds"""
457
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000458 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000459 return False
460
461 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000462 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000463 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
464 self.end_headers()
465
maruel@google.come250a9b2009-03-10 17:39:46 +0000466 self.wfile.write('<html><head><title>%s</title></head></html>' %
467 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000468
469 return True
470
471 def CachePrivateHandler(self):
472 """This request handler yields a page with the title set to the current
473 system time, and allows caching for 5 seconds."""
474
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000475 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000476 return False
477
478 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000479 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000480 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000481 self.end_headers()
482
maruel@google.come250a9b2009-03-10 17:39:46 +0000483 self.wfile.write('<html><head><title>%s</title></head></html>' %
484 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000485
486 return True
487
488 def CachePublicHandler(self):
489 """This request handler yields a page with the title set to the current
490 system time, and allows caching for 5 seconds."""
491
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000492 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000493 return False
494
495 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000496 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000497 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000498 self.end_headers()
499
maruel@google.come250a9b2009-03-10 17:39:46 +0000500 self.wfile.write('<html><head><title>%s</title></head></html>' %
501 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000502
503 return True
504
505 def CacheSMaxAgeHandler(self):
506 """This request handler yields a page with the title set to the current
507 system time, and does not allow for caching."""
508
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000509 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000510 return False
511
512 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000513 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000514 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
515 self.end_headers()
516
maruel@google.come250a9b2009-03-10 17:39:46 +0000517 self.wfile.write('<html><head><title>%s</title></head></html>' %
518 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000519
520 return True
521
522 def CacheMustRevalidateHandler(self):
523 """This request handler yields a page with the title set to the current
524 system time, and does not allow caching."""
525
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000526 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000527 return False
528
529 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000530 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000531 self.send_header('Cache-Control', 'must-revalidate')
532 self.end_headers()
533
maruel@google.come250a9b2009-03-10 17:39:46 +0000534 self.wfile.write('<html><head><title>%s</title></head></html>' %
535 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000536
537 return True
538
539 def CacheMustRevalidateMaxAgeHandler(self):
540 """This request handler yields a page with the title set to the current
541 system time, and does not allow caching event though max-age of 60
542 seconds is specified."""
543
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000544 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000545 return False
546
547 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000548 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000549 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
550 self.end_headers()
551
maruel@google.come250a9b2009-03-10 17:39:46 +0000552 self.wfile.write('<html><head><title>%s</title></head></html>' %
553 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000554
555 return True
556
initial.commit94958cf2008-07-26 22:42:52 +0000557 def CacheNoStoreHandler(self):
558 """This request handler yields a page with the title set to the current
559 system time, and does not allow the page to be stored."""
560
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000561 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000562 return False
563
564 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000565 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000566 self.send_header('Cache-Control', 'no-store')
567 self.end_headers()
568
maruel@google.come250a9b2009-03-10 17:39:46 +0000569 self.wfile.write('<html><head><title>%s</title></head></html>' %
570 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000571
572 return True
573
574 def CacheNoStoreMaxAgeHandler(self):
575 """This request handler yields a page with the title set to the current
576 system time, and does not allow the page to be stored even though max-age
577 of 60 seconds is specified."""
578
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000579 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000580 return False
581
582 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000583 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000584 self.send_header('Cache-Control', 'max-age=60, no-store')
585 self.end_headers()
586
maruel@google.come250a9b2009-03-10 17:39:46 +0000587 self.wfile.write('<html><head><title>%s</title></head></html>' %
588 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000589
590 return True
591
592
593 def CacheNoTransformHandler(self):
594 """This request handler yields a page with the title set to the current
595 system time, and does not allow the content to transformed during
596 user-agent caching"""
597
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000598 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000599 return False
600
601 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000602 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000603 self.send_header('Cache-Control', 'no-transform')
604 self.end_headers()
605
maruel@google.come250a9b2009-03-10 17:39:46 +0000606 self.wfile.write('<html><head><title>%s</title></head></html>' %
607 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000608
609 return True
610
611 def EchoHeader(self):
612 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000613
ananta@chromium.org219b2062009-10-23 16:09:41 +0000614 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000615
ananta@chromium.org56812d02011-04-07 17:52:05 +0000616 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000617 """This function echoes back the value of a specific request header while
618 allowing caching for 16 hours."""
619
ananta@chromium.org56812d02011-04-07 17:52:05 +0000620 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000621
622 def EchoHeaderHelper(self, echo_header):
623 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000624
ananta@chromium.org219b2062009-10-23 16:09:41 +0000625 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000626 return False
627
628 query_char = self.path.find('?')
629 if query_char != -1:
630 header_name = self.path[query_char+1:]
631
632 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000633 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000634 if echo_header == '/echoheadercache':
635 self.send_header('Cache-control', 'max-age=60000')
636 else:
637 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000638 # insert a vary header to properly indicate that the cachability of this
639 # request is subject to value of the request header being echoed.
640 if len(header_name) > 0:
641 self.send_header('Vary', header_name)
642 self.end_headers()
643
644 if len(header_name) > 0:
645 self.wfile.write(self.headers.getheader(header_name))
646
647 return True
648
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000649 def ReadRequestBody(self):
650 """This function reads the body of the current HTTP request, handling
651 both plain and chunked transfer encoded requests."""
652
653 if self.headers.getheader('transfer-encoding') != 'chunked':
654 length = int(self.headers.getheader('content-length'))
655 return self.rfile.read(length)
656
657 # Read the request body as chunks.
658 body = ""
659 while True:
660 line = self.rfile.readline()
661 length = int(line, 16)
662 if length == 0:
663 self.rfile.readline()
664 break
665 body += self.rfile.read(length)
666 self.rfile.read(2)
667 return body
668
initial.commit94958cf2008-07-26 22:42:52 +0000669 def EchoHandler(self):
670 """This handler just echoes back the payload of the request, for testing
671 form submission."""
672
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000673 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000674 return False
675
676 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000677 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000678 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000679 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000680 return True
681
682 def EchoTitleHandler(self):
683 """This handler is like Echo, but sets the page title to the request."""
684
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000685 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000686 return False
687
688 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000689 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000690 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000691 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000692 self.wfile.write('<html><head><title>')
693 self.wfile.write(request)
694 self.wfile.write('</title></head></html>')
695 return True
696
697 def EchoAllHandler(self):
698 """This handler yields a (more) human-readable page listing information
699 about the request header & contents."""
700
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000701 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000702 return False
703
704 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000705 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000706 self.end_headers()
707 self.wfile.write('<html><head><style>'
708 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
709 '</style></head><body>'
710 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000711 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000712 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000713
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000714 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000715 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000716 params = cgi.parse_qs(qs, keep_blank_values=1)
717
718 for param in params:
719 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000720
721 self.wfile.write('</pre>')
722
723 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
724
725 self.wfile.write('</body></html>')
726 return True
727
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000728 def EchoMultipartPostHandler(self):
729 """This handler echoes received multipart post data as json format."""
730
731 if not (self._ShouldHandleRequest("/echomultipartpost") or
732 self._ShouldHandleRequest("/searchbyimage")):
733 return False
734
735 content_type, parameters = cgi.parse_header(
736 self.headers.getheader('content-type'))
737 if content_type == 'multipart/form-data':
738 post_multipart = cgi.parse_multipart(self.rfile, parameters)
739 elif content_type == 'application/x-www-form-urlencoded':
740 raise Exception('POST by application/x-www-form-urlencoded is '
741 'not implemented.')
742 else:
743 post_multipart = {}
744
745 # Since the data can be binary, we encode them by base64.
746 post_multipart_base64_encoded = {}
747 for field, values in post_multipart.items():
748 post_multipart_base64_encoded[field] = [base64.b64encode(value)
749 for value in values]
750
751 result = {'POST_multipart' : post_multipart_base64_encoded}
752
753 self.send_response(200)
754 self.send_header("Content-type", "text/plain")
755 self.end_headers()
756 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
757 return True
758
initial.commit94958cf2008-07-26 22:42:52 +0000759 def DownloadHandler(self):
760 """This handler sends a downloadable file with or without reporting
761 the size (6K)."""
762
763 if self.path.startswith("/download-unknown-size"):
764 send_length = False
765 elif self.path.startswith("/download-known-size"):
766 send_length = True
767 else:
768 return False
769
770 #
771 # The test which uses this functionality is attempting to send
772 # small chunks of data to the client. Use a fairly large buffer
773 # so that we'll fill chrome's IO buffer enough to force it to
774 # actually write the data.
775 # See also the comments in the client-side of this test in
776 # download_uitest.cc
777 #
778 size_chunk1 = 35*1024
779 size_chunk2 = 10*1024
780
781 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000782 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000783 self.send_header('Cache-Control', 'max-age=0')
784 if send_length:
785 self.send_header('Content-Length', size_chunk1 + size_chunk2)
786 self.end_headers()
787
788 # First chunk of data:
789 self.wfile.write("*" * size_chunk1)
790 self.wfile.flush()
791
792 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000793 self.server.wait_for_download = True
794 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000795 self.server.handle_request()
796
797 # Second chunk of data:
798 self.wfile.write("*" * size_chunk2)
799 return True
800
801 def DownloadFinishHandler(self):
802 """This handler just tells the server to finish the current download."""
803
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000804 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000805 return False
806
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000807 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000808 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000809 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000810 self.send_header('Cache-Control', 'max-age=0')
811 self.end_headers()
812 return True
813
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000814 def _ReplaceFileData(self, data, query_parameters):
815 """Replaces matching substrings in a file.
816
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000817 If the 'replace_text' URL query parameter is present, it is expected to be
818 of the form old_text:new_text, which indicates that any old_text strings in
819 the file are replaced with new_text. Multiple 'replace_text' parameters may
820 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000821
822 If the parameters are not present, |data| is returned.
823 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000824
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000825 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000826 replace_text_values = query_dict.get('replace_text', [])
827 for replace_text_value in replace_text_values:
828 replace_text_args = replace_text_value.split(':')
829 if len(replace_text_args) != 2:
830 raise ValueError(
831 'replace_text must be of form old_text:new_text. Actual value: %s' %
832 replace_text_value)
833 old_text_b64, new_text_b64 = replace_text_args
834 old_text = base64.urlsafe_b64decode(old_text_b64)
835 new_text = base64.urlsafe_b64decode(new_text_b64)
836 data = data.replace(old_text, new_text)
837 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000838
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000839 def ZipFileHandler(self):
840 """This handler sends the contents of the requested file in compressed form.
841 Can pass in a parameter that specifies that the content length be
842 C - the compressed size (OK),
843 U - the uncompressed size (Non-standard, but handled),
844 S - less than compressed (OK because we keep going),
845 M - larger than compressed but less than uncompressed (an error),
846 L - larger than uncompressed (an error)
847 Example: compressedfiles/Picture_1.doc?C
848 """
849
850 prefix = "/compressedfiles/"
851 if not self.path.startswith(prefix):
852 return False
853
854 # Consume a request body if present.
855 if self.command == 'POST' or self.command == 'PUT' :
856 self.ReadRequestBody()
857
858 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
859
860 if not query in ('C', 'U', 'S', 'M', 'L'):
861 return False
862
863 sub_path = url_path[len(prefix):]
864 entries = sub_path.split('/')
865 file_path = os.path.join(self.server.data_dir, *entries)
866 if os.path.isdir(file_path):
867 file_path = os.path.join(file_path, 'index.html')
868
869 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000870 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000871 self.send_error(404)
872 return True
873
874 f = open(file_path, "rb")
875 data = f.read()
876 uncompressed_len = len(data)
877 f.close()
878
879 # Compress the data.
880 data = zlib.compress(data)
881 compressed_len = len(data)
882
883 content_length = compressed_len
884 if query == 'U':
885 content_length = uncompressed_len
886 elif query == 'S':
887 content_length = compressed_len / 2
888 elif query == 'M':
889 content_length = (compressed_len + uncompressed_len) / 2
890 elif query == 'L':
891 content_length = compressed_len + uncompressed_len
892
893 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000894 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000895 self.send_header('Content-encoding', 'deflate')
896 self.send_header('Connection', 'close')
897 self.send_header('Content-Length', content_length)
898 self.send_header('ETag', '\'' + file_path + '\'')
899 self.end_headers()
900
901 self.wfile.write(data)
902
903 return True
904
initial.commit94958cf2008-07-26 22:42:52 +0000905 def FileHandler(self):
906 """This handler sends the contents of the requested file. Wow, it's like
907 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000908
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000909 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000910 if not self.path.startswith(prefix):
911 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000912 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000913
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000914 def PostOnlyFileHandler(self):
915 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000916
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000917 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000918 if not self.path.startswith(prefix):
919 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000920 return self._FileHandlerHelper(prefix)
921
922 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000923 request_body = ''
924 if self.command == 'POST' or self.command == 'PUT':
925 # Consume a request body if present.
926 request_body = self.ReadRequestBody()
927
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000928 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000929 query_dict = cgi.parse_qs(query)
930
931 expected_body = query_dict.get('expected_body', [])
932 if expected_body and request_body not in expected_body:
933 self.send_response(404)
934 self.end_headers()
935 self.wfile.write('')
936 return True
937
938 expected_headers = query_dict.get('expected_headers', [])
939 for expected_header in expected_headers:
940 header_name, expected_value = expected_header.split(':')
941 if self.headers.getheader(header_name) != expected_value:
942 self.send_response(404)
943 self.end_headers()
944 self.wfile.write('')
945 return True
946
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000947 sub_path = url_path[len(prefix):]
948 entries = sub_path.split('/')
949 file_path = os.path.join(self.server.data_dir, *entries)
950 if os.path.isdir(file_path):
951 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000952
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000953 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000954 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000955 self.send_error(404)
956 return True
957
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000958 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000959 data = f.read()
960 f.close()
961
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000962 data = self._ReplaceFileData(data, query)
963
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000964 old_protocol_version = self.protocol_version
965
initial.commit94958cf2008-07-26 22:42:52 +0000966 # If file.mock-http-headers exists, it contains the headers we
967 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000968 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000969 if os.path.isfile(headers_path):
970 f = open(headers_path, "r")
971
972 # "HTTP/1.1 200 OK"
973 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000974 http_major, http_minor, status_code = re.findall(
975 'HTTP/(\d+).(\d+) (\d+)', response)[0]
976 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000977 self.send_response(int(status_code))
978
979 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000980 header_values = re.findall('(\S+):\s*(.*)', line)
981 if len(header_values) > 0:
982 # "name: value"
983 name, value = header_values[0]
984 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000985 f.close()
986 else:
987 # Could be more generic once we support mime-type sniffing, but for
988 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000989
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000990 range_header = self.headers.get('Range')
991 if range_header and range_header.startswith('bytes='):
992 # Note this doesn't handle all valid byte range_header values (i.e.
993 # left open ended ones), just enough for what we needed so far.
994 range_header = range_header[6:].split('-')
995 start = int(range_header[0])
996 if range_header[1]:
997 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000998 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000999 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001000
1001 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001002 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1003 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001004 self.send_header('Content-Range', content_range)
1005 data = data[start: end + 1]
1006 else:
1007 self.send_response(200)
1008
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001009 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001010 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001011 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001012 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001013 self.end_headers()
1014
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001015 if (self.command != 'HEAD'):
1016 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001017
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001018 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001019 return True
1020
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001021 def SetCookieHandler(self):
1022 """This handler just sets a cookie, for testing cookie handling."""
1023
1024 if not self._ShouldHandleRequest("/set-cookie"):
1025 return False
1026
1027 query_char = self.path.find('?')
1028 if query_char != -1:
1029 cookie_values = self.path[query_char + 1:].split('&')
1030 else:
1031 cookie_values = ("",)
1032 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001033 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001034 for cookie_value in cookie_values:
1035 self.send_header('Set-Cookie', '%s' % cookie_value)
1036 self.end_headers()
1037 for cookie_value in cookie_values:
1038 self.wfile.write('%s' % cookie_value)
1039 return True
1040
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001041 def SetManyCookiesHandler(self):
1042 """This handler just sets a given number of cookies, for testing handling
1043 of large numbers of cookies."""
1044
1045 if not self._ShouldHandleRequest("/set-many-cookies"):
1046 return False
1047
1048 query_char = self.path.find('?')
1049 if query_char != -1:
1050 num_cookies = int(self.path[query_char + 1:])
1051 else:
1052 num_cookies = 0
1053 self.send_response(200)
1054 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001055 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001056 self.send_header('Set-Cookie', 'a=')
1057 self.end_headers()
1058 self.wfile.write('%d cookies were sent' % num_cookies)
1059 return True
1060
mattm@chromium.org983fc462012-06-30 00:52:08 +00001061 def ExpectAndSetCookieHandler(self):
1062 """Expects some cookies to be sent, and if they are, sets more cookies.
1063
1064 The expect parameter specifies a required cookie. May be specified multiple
1065 times.
1066 The set parameter specifies a cookie to set if all required cookies are
1067 preset. May be specified multiple times.
1068 The data parameter specifies the response body data to be returned."""
1069
1070 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1071 return False
1072
1073 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1074 query_dict = cgi.parse_qs(query)
1075 cookies = set()
1076 if 'Cookie' in self.headers:
1077 cookie_header = self.headers.getheader('Cookie')
1078 cookies.update([s.strip() for s in cookie_header.split(';')])
1079 got_all_expected_cookies = True
1080 for expected_cookie in query_dict.get('expect', []):
1081 if expected_cookie not in cookies:
1082 got_all_expected_cookies = False
1083 self.send_response(200)
1084 self.send_header('Content-Type', 'text/html')
1085 if got_all_expected_cookies:
1086 for cookie_value in query_dict.get('set', []):
1087 self.send_header('Set-Cookie', '%s' % cookie_value)
1088 self.end_headers()
1089 for data_value in query_dict.get('data', []):
1090 self.wfile.write(data_value)
1091 return True
1092
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001093 def SetHeaderHandler(self):
1094 """This handler sets a response header. Parameters are in the
1095 key%3A%20value&key2%3A%20value2 format."""
1096
1097 if not self._ShouldHandleRequest("/set-header"):
1098 return False
1099
1100 query_char = self.path.find('?')
1101 if query_char != -1:
1102 headers_values = self.path[query_char + 1:].split('&')
1103 else:
1104 headers_values = ("",)
1105 self.send_response(200)
1106 self.send_header('Content-Type', 'text/html')
1107 for header_value in headers_values:
1108 header_value = urllib.unquote(header_value)
1109 (key, value) = header_value.split(': ', 1)
1110 self.send_header(key, value)
1111 self.end_headers()
1112 for header_value in headers_values:
1113 self.wfile.write('%s' % header_value)
1114 return True
1115
initial.commit94958cf2008-07-26 22:42:52 +00001116 def AuthBasicHandler(self):
1117 """This handler tests 'Basic' authentication. It just sends a page with
1118 title 'user/pass' if you succeed."""
1119
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001120 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001121 return False
1122
1123 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001124 expected_password = 'secret'
1125 realm = 'testrealm'
1126 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001127
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001128 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1129 query_params = cgi.parse_qs(query, True)
1130 if 'set-cookie-if-challenged' in query_params:
1131 set_cookie_if_challenged = True
1132 if 'password' in query_params:
1133 expected_password = query_params['password'][0]
1134 if 'realm' in query_params:
1135 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001136
initial.commit94958cf2008-07-26 22:42:52 +00001137 auth = self.headers.getheader('authorization')
1138 try:
1139 if not auth:
1140 raise Exception('no auth')
1141 b64str = re.findall(r'Basic (\S+)', auth)[0]
1142 userpass = base64.b64decode(b64str)
1143 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001144 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001145 raise Exception('wrong password')
1146 except Exception, e:
1147 # Authentication failed.
1148 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001149 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001150 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001151 if set_cookie_if_challenged:
1152 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001153 self.end_headers()
1154 self.wfile.write('<html><head>')
1155 self.wfile.write('<title>Denied: %s</title>' % e)
1156 self.wfile.write('</head><body>')
1157 self.wfile.write('auth=%s<p>' % auth)
1158 self.wfile.write('b64str=%s<p>' % b64str)
1159 self.wfile.write('username: %s<p>' % username)
1160 self.wfile.write('userpass: %s<p>' % userpass)
1161 self.wfile.write('password: %s<p>' % password)
1162 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1163 self.wfile.write('</body></html>')
1164 return True
1165
1166 # Authentication successful. (Return a cachable response to allow for
1167 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001168 old_protocol_version = self.protocol_version
1169 self.protocol_version = "HTTP/1.1"
1170
initial.commit94958cf2008-07-26 22:42:52 +00001171 if_none_match = self.headers.getheader('if-none-match')
1172 if if_none_match == "abc":
1173 self.send_response(304)
1174 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001175 elif url_path.endswith(".gif"):
1176 # Using chrome/test/data/google/logo.gif as the test image
1177 test_image_path = ['google', 'logo.gif']
1178 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1179 if not os.path.isfile(gif_path):
1180 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001181 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001182 return True
1183
1184 f = open(gif_path, "rb")
1185 data = f.read()
1186 f.close()
1187
1188 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001189 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001190 self.send_header('Cache-control', 'max-age=60000')
1191 self.send_header('Etag', 'abc')
1192 self.end_headers()
1193 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001194 else:
1195 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001196 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001197 self.send_header('Cache-control', 'max-age=60000')
1198 self.send_header('Etag', 'abc')
1199 self.end_headers()
1200 self.wfile.write('<html><head>')
1201 self.wfile.write('<title>%s/%s</title>' % (username, password))
1202 self.wfile.write('</head><body>')
1203 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001204 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001205 self.wfile.write('</body></html>')
1206
rvargas@google.com54453b72011-05-19 01:11:11 +00001207 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001208 return True
1209
tonyg@chromium.org75054202010-03-31 22:06:10 +00001210 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001211 """Returns a nonce that's stable per request path for the server's lifetime.
1212 This is a fake implementation. A real implementation would only use a given
1213 nonce a single time (hence the name n-once). However, for the purposes of
1214 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001215
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001216 Args:
1217 force_reset: Iff set, the nonce will be changed. Useful for testing the
1218 "stale" response.
1219 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001220
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001221 if force_reset or not self.server.nonce_time:
1222 self.server.nonce_time = time.time()
1223 return hashlib.md5('privatekey%s%d' %
1224 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001225
1226 def AuthDigestHandler(self):
1227 """This handler tests 'Digest' authentication.
1228
1229 It just sends a page with title 'user/pass' if you succeed.
1230
1231 A stale response is sent iff "stale" is present in the request path.
1232 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001233
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001234 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001235 return False
1236
tonyg@chromium.org75054202010-03-31 22:06:10 +00001237 stale = 'stale' in self.path
1238 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001239 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001240 password = 'secret'
1241 realm = 'testrealm'
1242
1243 auth = self.headers.getheader('authorization')
1244 pairs = {}
1245 try:
1246 if not auth:
1247 raise Exception('no auth')
1248 if not auth.startswith('Digest'):
1249 raise Exception('not digest')
1250 # Pull out all the name="value" pairs as a dictionary.
1251 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1252
1253 # Make sure it's all valid.
1254 if pairs['nonce'] != nonce:
1255 raise Exception('wrong nonce')
1256 if pairs['opaque'] != opaque:
1257 raise Exception('wrong opaque')
1258
1259 # Check the 'response' value and make sure it matches our magic hash.
1260 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001261 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001262 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001263 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001264 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001265 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001266 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1267 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001268 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001269
1270 if pairs['response'] != response:
1271 raise Exception('wrong password')
1272 except Exception, e:
1273 # Authentication failed.
1274 self.send_response(401)
1275 hdr = ('Digest '
1276 'realm="%s", '
1277 'domain="/", '
1278 'qop="auth", '
1279 'algorithm=MD5, '
1280 'nonce="%s", '
1281 'opaque="%s"') % (realm, nonce, opaque)
1282 if stale:
1283 hdr += ', stale="TRUE"'
1284 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001285 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001286 self.end_headers()
1287 self.wfile.write('<html><head>')
1288 self.wfile.write('<title>Denied: %s</title>' % e)
1289 self.wfile.write('</head><body>')
1290 self.wfile.write('auth=%s<p>' % auth)
1291 self.wfile.write('pairs=%s<p>' % pairs)
1292 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1293 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1294 self.wfile.write('</body></html>')
1295 return True
1296
1297 # Authentication successful.
1298 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001299 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001300 self.end_headers()
1301 self.wfile.write('<html><head>')
1302 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1303 self.wfile.write('</head><body>')
1304 self.wfile.write('auth=%s<p>' % auth)
1305 self.wfile.write('pairs=%s<p>' % pairs)
1306 self.wfile.write('</body></html>')
1307
1308 return True
1309
1310 def SlowServerHandler(self):
1311 """Wait for the user suggested time before responding. The syntax is
1312 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001313
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001314 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001315 return False
1316 query_char = self.path.find('?')
1317 wait_sec = 1.0
1318 if query_char >= 0:
1319 try:
1320 wait_sec = int(self.path[query_char + 1:])
1321 except ValueError:
1322 pass
1323 time.sleep(wait_sec)
1324 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001325 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001326 self.end_headers()
1327 self.wfile.write("waited %d seconds" % wait_sec)
1328 return True
1329
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001330 def ChunkedServerHandler(self):
1331 """Send chunked response. Allows to specify chunks parameters:
1332 - waitBeforeHeaders - ms to wait before sending headers
1333 - waitBetweenChunks - ms to wait between chunks
1334 - chunkSize - size of each chunk in bytes
1335 - chunksNumber - number of chunks
1336 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1337 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001338
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001339 if not self._ShouldHandleRequest("/chunked"):
1340 return False
1341 query_char = self.path.find('?')
1342 chunkedSettings = {'waitBeforeHeaders' : 0,
1343 'waitBetweenChunks' : 0,
1344 'chunkSize' : 5,
1345 'chunksNumber' : 5}
1346 if query_char >= 0:
1347 params = self.path[query_char + 1:].split('&')
1348 for param in params:
1349 keyValue = param.split('=')
1350 if len(keyValue) == 2:
1351 try:
1352 chunkedSettings[keyValue[0]] = int(keyValue[1])
1353 except ValueError:
1354 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001355 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001356 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1357 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001358 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001359 self.send_header('Connection', 'close')
1360 self.send_header('Transfer-Encoding', 'chunked')
1361 self.end_headers()
1362 # Chunked encoding: sending all chunks, then final zero-length chunk and
1363 # then final CRLF.
1364 for i in range(0, chunkedSettings['chunksNumber']):
1365 if i > 0:
1366 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1367 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001368 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001369 self.sendChunkHelp('')
1370 return True
1371
initial.commit94958cf2008-07-26 22:42:52 +00001372 def ContentTypeHandler(self):
1373 """Returns a string of html with the given content type. E.g.,
1374 /contenttype?text/css returns an html file with the Content-Type
1375 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001376
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001377 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001378 return False
1379 query_char = self.path.find('?')
1380 content_type = self.path[query_char + 1:].strip()
1381 if not content_type:
1382 content_type = 'text/html'
1383 self.send_response(200)
1384 self.send_header('Content-Type', content_type)
1385 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001386 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001387 return True
1388
creis@google.com2f4f6a42011-03-25 19:44:19 +00001389 def NoContentHandler(self):
1390 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001391
creis@google.com2f4f6a42011-03-25 19:44:19 +00001392 if not self._ShouldHandleRequest("/nocontent"):
1393 return False
1394 self.send_response(204)
1395 self.end_headers()
1396 return True
1397
initial.commit94958cf2008-07-26 22:42:52 +00001398 def ServerRedirectHandler(self):
1399 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001400 '/server-redirect?http://foo.bar/asdf' to redirect to
1401 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001402
1403 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001404 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001405 return False
1406
1407 query_char = self.path.find('?')
1408 if query_char < 0 or len(self.path) <= query_char + 1:
1409 self.sendRedirectHelp(test_name)
1410 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001411 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001412
1413 self.send_response(301) # moved permanently
1414 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001415 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001416 self.end_headers()
1417 self.wfile.write('<html><head>')
1418 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1419
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001420 return True
initial.commit94958cf2008-07-26 22:42:52 +00001421
naskoe7a0d0d2014-09-29 08:53:05 -07001422 def CrossSiteRedirectHandler(self):
1423 """Sends a server redirect to the given site. The syntax is
1424 '/cross-site/hostname/...' to redirect to //hostname/...
1425 It is used to navigate between different Sites, causing
1426 cross-site/cross-process navigations in the browser."""
1427
1428 test_name = "/cross-site"
1429 if not self._ShouldHandleRequest(test_name):
1430 print "CSRH: not handling request for " + test_name
1431 return False
1432
1433 params = urllib.unquote(self.path[(len(test_name) + 1):])
1434 slash = params.find('/')
1435 if slash < 0:
1436 self.sendRedirectHelp(test_name)
1437 return True
1438
1439 host = params[:slash]
1440 path = params[(slash+1):]
1441 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1442
1443 self.send_response(301) # moved permanently
1444 self.send_header('Location', dest)
1445 self.send_header('Content-Type', 'text/html')
1446 self.end_headers()
1447 self.wfile.write('<html><head>')
1448 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1449
1450 return True
1451
initial.commit94958cf2008-07-26 22:42:52 +00001452 def ClientRedirectHandler(self):
1453 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001454 '/client-redirect?http://foo.bar/asdf' to redirect to
1455 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001456
1457 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001458 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001459 return False
1460
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001461 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001462 if query_char < 0 or len(self.path) <= query_char + 1:
1463 self.sendRedirectHelp(test_name)
1464 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001465 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001466
1467 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001468 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001469 self.end_headers()
1470 self.wfile.write('<html><head>')
1471 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1472 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1473
1474 return True
1475
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001476 def GetSSLSessionCacheHandler(self):
1477 """Send a reply containing a log of the session cache operations."""
1478
1479 if not self._ShouldHandleRequest('/ssl-session-cache'):
1480 return False
1481
1482 self.send_response(200)
1483 self.send_header('Content-Type', 'text/plain')
1484 self.end_headers()
1485 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001486 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001487 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001488 self.wfile.write('Pass --https-record-resume in order to use' +
1489 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001490 return True
1491
1492 for (action, sessionID) in log:
1493 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001494 return True
1495
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001496 def SSLManySmallRecords(self):
1497 """Sends a reply consisting of a variety of small writes. These will be
1498 translated into a series of small SSL records when used over an HTTPS
1499 server."""
1500
1501 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1502 return False
1503
1504 self.send_response(200)
1505 self.send_header('Content-Type', 'text/plain')
1506 self.end_headers()
1507
1508 # Write ~26K of data, in 1350 byte chunks
1509 for i in xrange(20):
1510 self.wfile.write('*' * 1350)
1511 self.wfile.flush()
1512 return True
1513
agl@chromium.org04700be2013-03-02 18:40:41 +00001514 def GetChannelID(self):
1515 """Send a reply containing the hashed ChannelID that the client provided."""
1516
1517 if not self._ShouldHandleRequest('/channel-id'):
1518 return False
1519
1520 self.send_response(200)
1521 self.send_header('Content-Type', 'text/plain')
1522 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001523 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001524 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1525 return True
1526
davidben599e7e72014-09-03 16:19:09 -07001527 def ClientCipherListHandler(self):
1528 """Send a reply containing the cipher suite list that the client
1529 provided. Each cipher suite value is serialized in decimal, followed by a
1530 newline."""
1531
1532 if not self._ShouldHandleRequest('/client-cipher-list'):
1533 return False
1534
1535 self.send_response(200)
1536 self.send_header('Content-Type', 'text/plain')
1537 self.end_headers()
1538
davidben11682512014-10-06 21:09:11 -07001539 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1540 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001541 return True
1542
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001543 def CloseSocketHandler(self):
1544 """Closes the socket without sending anything."""
1545
1546 if not self._ShouldHandleRequest('/close-socket'):
1547 return False
1548
1549 self.wfile.close()
1550 return True
1551
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001552 def RangeResetHandler(self):
1553 """Send data broken up by connection resets every N (default 4K) bytes.
1554 Support range requests. If the data requested doesn't straddle a reset
1555 boundary, it will all be sent. Used for testing resuming downloads."""
1556
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001557 def DataForRange(start, end):
1558 """Data to be provided for a particular range of bytes."""
1559 # Offset and scale to avoid too obvious (and hence potentially
1560 # collidable) data.
1561 return ''.join([chr(y % 256)
1562 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1563
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001564 if not self._ShouldHandleRequest('/rangereset'):
1565 return False
1566
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001567 # HTTP/1.1 is required for ETag and range support.
1568 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001569 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1570
1571 # Defaults
1572 size = 8000
1573 # Note that the rst is sent just before sending the rst_boundary byte.
1574 rst_boundary = 4000
1575 respond_to_range = True
1576 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001577 rst_limit = -1
1578 token = 'DEFAULT'
1579 fail_precondition = 0
1580 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001581
1582 # Parse the query
1583 qdict = urlparse.parse_qs(query, True)
1584 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001585 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001586 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001587 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001588 if 'token' in qdict:
1589 # Identifying token for stateful tests.
1590 token = qdict['token'][0]
1591 if 'rst_limit' in qdict:
1592 # Max number of rsts for a given token.
1593 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001594 if 'bounce_range' in qdict:
1595 respond_to_range = False
1596 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001597 # Note that hold_for_signal will not work with null range requests;
1598 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001599 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001600 if 'no_verifiers' in qdict:
1601 send_verifiers = False
1602 if 'fail_precondition' in qdict:
1603 fail_precondition = int(qdict['fail_precondition'][0])
1604
1605 # Record already set information, or set it.
1606 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1607 if rst_limit != 0:
1608 TestPageHandler.rst_limits[token] -= 1
1609 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1610 token, fail_precondition)
1611 if fail_precondition != 0:
1612 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001613
1614 first_byte = 0
1615 last_byte = size - 1
1616
1617 # Does that define what we want to return, or do we need to apply
1618 # a range?
1619 range_response = False
1620 range_header = self.headers.getheader('range')
1621 if range_header and respond_to_range:
1622 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1623 if mo.group(1):
1624 first_byte = int(mo.group(1))
1625 if mo.group(2):
1626 last_byte = int(mo.group(2))
1627 if last_byte > size - 1:
1628 last_byte = size - 1
1629 range_response = True
1630 if last_byte < first_byte:
1631 return False
1632
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001633 if (fail_precondition and
1634 (self.headers.getheader('If-Modified-Since') or
1635 self.headers.getheader('If-Match'))):
1636 self.send_response(412)
1637 self.end_headers()
1638 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001639
1640 if range_response:
1641 self.send_response(206)
1642 self.send_header('Content-Range',
1643 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1644 else:
1645 self.send_response(200)
1646 self.send_header('Content-Type', 'application/octet-stream')
1647 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001648 if send_verifiers:
asanka@chromium.org3ffced92013-12-13 22:20:27 +00001649 # If fail_precondition is non-zero, then the ETag for each request will be
1650 # different.
1651 etag = "%s%d" % (token, fail_precondition)
1652 self.send_header('ETag', etag)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001653 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001654 self.end_headers()
1655
1656 if hold_for_signal:
1657 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1658 # a single byte, the self.server.handle_request() below hangs
1659 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001660 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001661 first_byte = first_byte + 1
1662 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001663 self.server.wait_for_download = True
1664 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001665 self.server.handle_request()
1666
1667 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001668 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001669 # No RST has been requested in this range, so we don't need to
1670 # do anything fancy; just write the data and let the python
1671 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001672 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001673 self.wfile.flush()
1674 return True
1675
1676 # We're resetting the connection part way in; go to the RST
1677 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001678 # Because socket semantics do not guarantee that all the data will be
1679 # sent when using the linger semantics to hard close a socket,
1680 # we send the data and then wait for our peer to release us
1681 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001682 data = DataForRange(first_byte, possible_rst)
1683 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001684 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001685 self.server.wait_for_download = True
1686 while self.server.wait_for_download:
1687 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001688 l_onoff = 1 # Linger is active.
1689 l_linger = 0 # Seconds to linger for.
1690 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1691 struct.pack('ii', l_onoff, l_linger))
1692
1693 # Close all duplicates of the underlying socket to force the RST.
1694 self.wfile.close()
1695 self.rfile.close()
1696 self.connection.close()
1697
1698 return True
1699
initial.commit94958cf2008-07-26 22:42:52 +00001700 def DefaultResponseHandler(self):
1701 """This is the catch-all response handler for requests that aren't handled
1702 by one of the special handlers above.
1703 Note that we specify the content-length as without it the https connection
1704 is not closed properly (and the browser keeps expecting data)."""
1705
1706 contents = "Default response given for path: " + self.path
1707 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001708 self.send_header('Content-Type', 'text/html')
1709 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001710 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001711 if (self.command != 'HEAD'):
1712 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001713 return True
1714
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001715 def RedirectConnectHandler(self):
1716 """Sends a redirect to the CONNECT request for www.redirect.com. This
1717 response is not specified by the RFC, so the browser should not follow
1718 the redirect."""
1719
1720 if (self.path.find("www.redirect.com") < 0):
1721 return False
1722
1723 dest = "http://www.destination.com/foo.js"
1724
1725 self.send_response(302) # moved temporarily
1726 self.send_header('Location', dest)
1727 self.send_header('Connection', 'close')
1728 self.end_headers()
1729 return True
1730
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001731 def ServerAuthConnectHandler(self):
1732 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1733 response doesn't make sense because the proxy server cannot request
1734 server authentication."""
1735
1736 if (self.path.find("www.server-auth.com") < 0):
1737 return False
1738
1739 challenge = 'Basic realm="WallyWorld"'
1740
1741 self.send_response(401) # unauthorized
1742 self.send_header('WWW-Authenticate', challenge)
1743 self.send_header('Connection', 'close')
1744 self.end_headers()
1745 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001746
1747 def DefaultConnectResponseHandler(self):
1748 """This is the catch-all response handler for CONNECT requests that aren't
1749 handled by one of the special handlers above. Real Web servers respond
1750 with 400 to CONNECT requests."""
1751
1752 contents = "Your client has issued a malformed or illegal request."
1753 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001754 self.send_header('Content-Type', 'text/html')
1755 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001756 self.end_headers()
1757 self.wfile.write(contents)
1758 return True
1759
initial.commit94958cf2008-07-26 22:42:52 +00001760 # called by the redirect handling function when there is no parameter
1761 def sendRedirectHelp(self, redirect_name):
1762 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001763 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001764 self.end_headers()
1765 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1766 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1767 self.wfile.write('</body></html>')
1768
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001769 # called by chunked handling function
1770 def sendChunkHelp(self, chunk):
1771 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1772 self.wfile.write('%X\r\n' % len(chunk))
1773 self.wfile.write(chunk)
1774 self.wfile.write('\r\n')
1775
akalin@chromium.org154bb132010-11-12 02:20:27 +00001776
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001777class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001778 def __init__(self, request, client_address, socket_server):
1779 handlers = [self.OCSPResponse]
1780 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001781 testserver_base.BasePageHandler.__init__(self, request, client_address,
1782 socket_server, [], handlers, [],
1783 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001784
1785 def OCSPResponse(self):
1786 self.send_response(200)
1787 self.send_header('Content-Type', 'application/ocsp-response')
1788 self.send_header('Content-Length', str(len(self.ocsp_response)))
1789 self.end_headers()
1790
1791 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001792
mattm@chromium.org830a3712012-11-07 23:00:07 +00001793
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001794class TCPEchoHandler(SocketServer.BaseRequestHandler):
1795 """The RequestHandler class for TCP echo server.
1796
1797 It is instantiated once per connection to the server, and overrides the
1798 handle() method to implement communication to the client.
1799 """
1800
1801 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001802 """Handles the request from the client and constructs a response."""
1803
1804 data = self.request.recv(65536).strip()
1805 # Verify the "echo request" message received from the client. Send back
1806 # "echo response" message if "echo request" message is valid.
1807 try:
1808 return_data = echo_message.GetEchoResponseData(data)
1809 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001810 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001811 except ValueError:
1812 return
1813
1814 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001815
1816
1817class UDPEchoHandler(SocketServer.BaseRequestHandler):
1818 """The RequestHandler class for UDP echo server.
1819
1820 It is instantiated once per connection to the server, and overrides the
1821 handle() method to implement communication to the client.
1822 """
1823
1824 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001825 """Handles the request from the client and constructs a response."""
1826
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001827 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001828 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001829 # Verify the "echo request" message received from the client. Send back
1830 # "echo response" message if "echo request" message is valid.
1831 try:
1832 return_data = echo_message.GetEchoResponseData(data)
1833 if not return_data:
1834 return
1835 except ValueError:
1836 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001837 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001838
1839
bashi@chromium.org33233532012-09-08 17:37:24 +00001840class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1841 """A request handler that behaves as a proxy server which requires
1842 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1843 """
1844
1845 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1846
1847 def parse_request(self):
1848 """Overrides parse_request to check credential."""
1849
1850 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1851 return False
1852
1853 auth = self.headers.getheader('Proxy-Authorization')
1854 if auth != self._AUTH_CREDENTIAL:
1855 self.send_response(407)
1856 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1857 self.end_headers()
1858 return False
1859
1860 return True
1861
1862 def _start_read_write(self, sock):
1863 sock.setblocking(0)
1864 self.request.setblocking(0)
1865 rlist = [self.request, sock]
1866 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001867 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001868 if errors:
1869 self.send_response(500)
1870 self.end_headers()
1871 return
1872 for s in ready_sockets:
1873 received = s.recv(1024)
1874 if len(received) == 0:
1875 return
1876 if s == self.request:
1877 other = sock
1878 else:
1879 other = self.request
1880 other.send(received)
1881
1882 def _do_common_method(self):
1883 url = urlparse.urlparse(self.path)
1884 port = url.port
1885 if not port:
1886 if url.scheme == 'http':
1887 port = 80
1888 elif url.scheme == 'https':
1889 port = 443
1890 if not url.hostname or not port:
1891 self.send_response(400)
1892 self.end_headers()
1893 return
1894
1895 if len(url.path) == 0:
1896 path = '/'
1897 else:
1898 path = url.path
1899 if len(url.query) > 0:
1900 path = '%s?%s' % (url.path, url.query)
1901
1902 sock = None
1903 try:
1904 sock = socket.create_connection((url.hostname, port))
1905 sock.send('%s %s %s\r\n' % (
1906 self.command, path, self.protocol_version))
1907 for header in self.headers.headers:
1908 header = header.strip()
1909 if (header.lower().startswith('connection') or
1910 header.lower().startswith('proxy')):
1911 continue
1912 sock.send('%s\r\n' % header)
1913 sock.send('\r\n')
1914 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001915 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001916 self.send_response(500)
1917 self.end_headers()
1918 finally:
1919 if sock is not None:
1920 sock.close()
1921
1922 def do_CONNECT(self):
1923 try:
1924 pos = self.path.rfind(':')
1925 host = self.path[:pos]
1926 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001927 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001928 self.send_response(400)
1929 self.end_headers()
1930
1931 try:
1932 sock = socket.create_connection((host, port))
1933 self.send_response(200, 'Connection established')
1934 self.end_headers()
1935 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001936 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001937 self.send_response(500)
1938 self.end_headers()
1939 finally:
1940 sock.close()
1941
1942 def do_GET(self):
1943 self._do_common_method()
1944
1945 def do_HEAD(self):
1946 self._do_common_method()
1947
1948
mattm@chromium.org830a3712012-11-07 23:00:07 +00001949class ServerRunner(testserver_base.TestServerRunner):
1950 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001951
mattm@chromium.org830a3712012-11-07 23:00:07 +00001952 def __init__(self):
1953 super(ServerRunner, self).__init__()
1954 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001955
mattm@chromium.org830a3712012-11-07 23:00:07 +00001956 def __make_data_dir(self):
1957 if self.options.data_dir:
1958 if not os.path.isdir(self.options.data_dir):
1959 raise testserver_base.OptionError('specified data dir not found: ' +
1960 self.options.data_dir + ' exiting...')
1961 my_data_dir = self.options.data_dir
1962 else:
1963 # Create the default path to our data dir, relative to the exe dir.
1964 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1965 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001966
mattm@chromium.org830a3712012-11-07 23:00:07 +00001967 #TODO(ibrar): Must use Find* funtion defined in google\tools
1968 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001969
mattm@chromium.org830a3712012-11-07 23:00:07 +00001970 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001971
mattm@chromium.org830a3712012-11-07 23:00:07 +00001972 def create_server(self, server_data):
1973 port = self.options.port
1974 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001975
mattm@chromium.org830a3712012-11-07 23:00:07 +00001976 if self.options.server_type == SERVER_HTTP:
1977 if self.options.https:
1978 pem_cert_and_key = None
1979 if self.options.cert_and_key_file:
1980 if not os.path.isfile(self.options.cert_and_key_file):
1981 raise testserver_base.OptionError(
1982 'specified server cert file not found: ' +
1983 self.options.cert_and_key_file + ' exiting...')
1984 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001985 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001986 # generate a new certificate and run an OCSP server for it.
1987 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001988 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001989 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001990
mattm@chromium.org830a3712012-11-07 23:00:07 +00001991 ocsp_der = None
1992 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001993
mattm@chromium.org830a3712012-11-07 23:00:07 +00001994 if self.options.ocsp == 'ok':
1995 ocsp_state = minica.OCSP_STATE_GOOD
1996 elif self.options.ocsp == 'revoked':
1997 ocsp_state = minica.OCSP_STATE_REVOKED
1998 elif self.options.ocsp == 'invalid':
1999 ocsp_state = minica.OCSP_STATE_INVALID
2000 elif self.options.ocsp == 'unauthorized':
2001 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2002 elif self.options.ocsp == 'unknown':
2003 ocsp_state = minica.OCSP_STATE_UNKNOWN
2004 else:
2005 raise testserver_base.OptionError('unknown OCSP status: ' +
2006 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002007
mattm@chromium.org830a3712012-11-07 23:00:07 +00002008 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2009 subject = "127.0.0.1",
2010 ocsp_url = ("http://%s:%d/ocsp" %
2011 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00002012 ocsp_state = ocsp_state,
2013 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002014
2015 self.__ocsp_server.ocsp_response = ocsp_der
2016
2017 for ca_cert in self.options.ssl_client_ca:
2018 if not os.path.isfile(ca_cert):
2019 raise testserver_base.OptionError(
2020 'specified trusted client CA file not found: ' + ca_cert +
2021 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002022
2023 stapled_ocsp_response = None
2024 if self.__ocsp_server and self.options.staple_ocsp_response:
2025 stapled_ocsp_response = self.__ocsp_server.ocsp_response
2026
mattm@chromium.org830a3712012-11-07 23:00:07 +00002027 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2028 self.options.ssl_client_auth,
2029 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002030 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002031 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002032 self.options.ssl_key_exchange,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002033 self.options.enable_npn,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002034 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002035 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002036 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002037 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002038 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002039 self.options.fallback_scsv,
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +00002040 stapled_ocsp_response,
2041 self.options.disable_session_cache)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002042 print 'HTTPS server started on https://%s:%d...' % \
2043 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002044 else:
2045 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002046 print 'HTTP server started on http://%s:%d...' % \
2047 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002048
2049 server.data_dir = self.__make_data_dir()
2050 server.file_root_url = self.options.file_root_url
2051 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002052 elif self.options.server_type == SERVER_WEBSOCKET:
2053 # Launch pywebsocket via WebSocketServer.
2054 logger = logging.getLogger()
2055 logger.addHandler(logging.StreamHandler())
2056 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2057 # is required to work correctly. It should be fixed from pywebsocket side.
2058 os.chdir(self.__make_data_dir())
2059 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002060 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002061 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002062 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002063 websocket_options.use_tls = True
2064 websocket_options.private_key = self.options.cert_and_key_file
2065 websocket_options.certificate = self.options.cert_and_key_file
2066 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002067 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002068 websocket_options.tls_client_auth = True
2069 if len(self.options.ssl_client_ca) != 1:
2070 raise testserver_base.OptionError(
2071 'one trusted client CA file should be specified')
2072 if not os.path.isfile(self.options.ssl_client_ca[0]):
2073 raise testserver_base.OptionError(
2074 'specified trusted client CA file not found: ' +
2075 self.options.ssl_client_ca[0] + ' exiting...')
2076 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2077 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002078 print 'WebSocket server started on %s://%s:%d...' % \
2079 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002080 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002081 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002082 elif self.options.server_type == SERVER_TCP_ECHO:
2083 # Used for generating the key (randomly) that encodes the "echo request"
2084 # message.
2085 random.seed()
2086 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002087 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002088 server_data['port'] = server.server_port
2089 elif self.options.server_type == SERVER_UDP_ECHO:
2090 # Used for generating the key (randomly) that encodes the "echo request"
2091 # message.
2092 random.seed()
2093 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002094 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002095 server_data['port'] = server.server_port
2096 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2097 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002098 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002099 server_data['port'] = server.server_port
2100 elif self.options.server_type == SERVER_FTP:
2101 my_data_dir = self.__make_data_dir()
2102
2103 # Instantiate a dummy authorizer for managing 'virtual' users
2104 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2105
2106 # Define a new user having full r/w permissions and a read-only
2107 # anonymous user
2108 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2109
2110 authorizer.add_anonymous(my_data_dir)
2111
2112 # Instantiate FTP handler class
2113 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2114 ftp_handler.authorizer = authorizer
2115
2116 # Define a customized banner (string returned when client connects)
2117 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2118 pyftpdlib.ftpserver.__ver__)
2119
2120 # Instantiate FTP server class and listen to address:port
2121 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2122 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002123 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002124 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002125 raise testserver_base.OptionError('unknown server type' +
2126 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002127
mattm@chromium.org830a3712012-11-07 23:00:07 +00002128 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002129
mattm@chromium.org830a3712012-11-07 23:00:07 +00002130 def run_server(self):
2131 if self.__ocsp_server:
2132 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002133
mattm@chromium.org830a3712012-11-07 23:00:07 +00002134 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002135
mattm@chromium.org830a3712012-11-07 23:00:07 +00002136 if self.__ocsp_server:
2137 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002138
mattm@chromium.org830a3712012-11-07 23:00:07 +00002139 def add_options(self):
2140 testserver_base.TestServerRunner.add_options(self)
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +00002141 self.option_parser.add_option('--disable-session-cache',
2142 action='store_true',
2143 dest='disable_session_cache',
2144 help='tells the server to disable the'
2145 'TLS session cache.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002146 self.option_parser.add_option('-f', '--ftp', action='store_const',
2147 const=SERVER_FTP, default=SERVER_HTTP,
2148 dest='server_type',
2149 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002150 self.option_parser.add_option('--tcp-echo', action='store_const',
2151 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2152 dest='server_type',
2153 help='start up a tcp echo server.')
2154 self.option_parser.add_option('--udp-echo', action='store_const',
2155 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2156 dest='server_type',
2157 help='start up a udp echo server.')
2158 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2159 const=SERVER_BASIC_AUTH_PROXY,
2160 default=SERVER_HTTP, dest='server_type',
2161 help='start up a proxy server which requires '
2162 'basic authentication.')
2163 self.option_parser.add_option('--websocket', action='store_const',
2164 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2165 dest='server_type',
2166 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002167 self.option_parser.add_option('--https', action='store_true',
2168 dest='https', help='Specify that https '
2169 'should be used.')
2170 self.option_parser.add_option('--cert-and-key-file',
2171 dest='cert_and_key_file', help='specify the '
2172 'path to the file containing the certificate '
2173 'and private key for the server in PEM '
2174 'format')
2175 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2176 help='The type of OCSP response generated '
2177 'for the automatically generated '
2178 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002179 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2180 default=0, type=int,
2181 help='If non-zero then the generated '
2182 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002183 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2184 default='0', type='int',
2185 help='If nonzero, certain TLS connections '
2186 'will be aborted in order to test version '
2187 'fallback. 1 means all TLS versions will be '
2188 'aborted. 2 means TLS 1.1 or higher will be '
2189 'aborted. 3 means TLS 1.2 or higher will be '
2190 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002191 self.option_parser.add_option('--tls-intolerance-type',
2192 dest='tls_intolerance_type',
2193 default="alert",
2194 help='Controls how the server reacts to a '
2195 'TLS version it is intolerant to. Valid '
2196 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002197 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2198 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002199 default='',
2200 help='Base64 encoded SCT list. If set, '
2201 'server will respond with a '
2202 'signed_certificate_timestamp TLS extension '
2203 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002204 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2205 default=False, const=True,
2206 action='store_const',
2207 help='If given, TLS_FALLBACK_SCSV support '
2208 'will be enabled. This causes the server to '
2209 'reject fallback connections from compatible '
2210 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002211 self.option_parser.add_option('--staple-ocsp-response',
2212 dest='staple_ocsp_response',
2213 default=False, action='store_true',
2214 help='If set, server will staple the OCSP '
2215 'response whenever OCSP is on and the client '
2216 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002217 self.option_parser.add_option('--https-record-resume',
2218 dest='record_resume', const=True,
2219 default=False, action='store_const',
2220 help='Record resumption cache events rather '
2221 'than resuming as normal. Allows the use of '
2222 'the /ssl-session-cache request')
2223 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2224 help='Require SSL client auth on every '
2225 'connection.')
2226 self.option_parser.add_option('--ssl-client-ca', action='append',
2227 default=[], help='Specify that the client '
2228 'certificate request should include the CA '
2229 'named in the subject of the DER-encoded '
2230 'certificate contained in the specified '
2231 'file. This option may appear multiple '
2232 'times, indicating multiple CA names should '
2233 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002234 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2235 default=[], help='Specify that the client '
2236 'certificate request should include the '
2237 'specified certificate_type value. This '
2238 'option may appear multiple times, '
2239 'indicating multiple values should be send '
2240 'in the request. Valid values are '
2241 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2242 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002243 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2244 help='Specify the bulk encryption '
2245 'algorithm(s) that will be accepted by the '
2246 'SSL server. Valid values are "aes256", '
2247 '"aes128", "3des", "rc4". If omitted, all '
2248 'algorithms will be used. This option may '
2249 'appear multiple times, indicating '
2250 'multiple algorithms should be enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002251 self.option_parser.add_option('--ssl-key-exchange', action='append',
2252 help='Specify the key exchange algorithm(s)'
2253 'that will be accepted by the SSL server. '
2254 'Valid values are "rsa", "dhe_rsa". If '
2255 'omitted, all algorithms will be used. This '
2256 'option may appear multiple times, '
2257 'indicating multiple algorithms should be '
2258 'enabled.');
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002259 # TODO(davidben): Add ALPN support to tlslite.
2260 self.option_parser.add_option('--enable-npn', dest='enable_npn',
2261 default=False, const=True,
2262 action='store_const',
2263 help='Enable server support for the NPN '
2264 'extension. The server will advertise '
2265 'support for exactly one protocol, http/1.1')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002266 self.option_parser.add_option('--file-root-url', default='/files/',
2267 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002268 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2269 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2270 dest='ws_basic_auth',
2271 help='Enable basic-auth for WebSocket')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002272
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002273
initial.commit94958cf2008-07-26 22:42:52 +00002274if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002275 sys.exit(ServerRunner().main())