blob: 55d43c548f9feed49ecc0db67e54b368cb36310b [file] [log] [blame]
Andrew Grieve9c2b31d2019-03-26 15:08:10 +00001#!/usr/bin/env vpython
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
Matt Menke3a293bd2021-08-13 20:34:43 +00006"""This is a simple HTTP/TCP/PROXY/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
initial.commit94958cf2008-07-26 22:42:52 +000022import os
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +000023import json
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000024import random
initial.commit94958cf2008-07-26 22:42:52 +000025import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000026import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000027import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000028import SocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000029import ssl
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000030import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000031import sys
32import threading
initial.commit94958cf2008-07-26 22:42:52 +000033import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000034import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000035import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000036import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000037
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000038BASE_DIR = os.path.dirname(os.path.abspath(__file__))
39ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(BASE_DIR)))
40
davidben@chromium.org7d53b542014-04-10 17:56:44 +000041# Insert at the beginning of the path, we want to use our copies of the library
Robert Iannucci0e7ec952018-01-18 22:44:16 +000042# unconditionally (since they contain modifications from anything that might be
43# obtained from e.g. PyPi).
Keita Suzuki83e26f92020-03-06 09:42:48 +000044sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'pywebsocket3', 'src'))
davidben@chromium.org7d53b542014-04-10 17:56:44 +000045sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'tlslite'))
46
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000047import mod_pywebsocket.standalone
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000048from mod_pywebsocket.standalone import WebSocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000049# import manually
50mod_pywebsocket.standalone.ssl = ssl
davidben@chromium.org06fcf202010-09-22 18:15:23 +000051
davidben@chromium.org7d53b542014-04-10 17:56:44 +000052import tlslite
53import tlslite.api
54
davidben@chromium.org7d53b542014-04-10 17:56:44 +000055import testserver_base
56
maruel@chromium.org756cf982009-03-05 12:46:38 +000057SERVER_HTTP = 0
Matt Menke3a293bd2021-08-13 20:34:43 +000058SERVER_BASIC_AUTH_PROXY = 1
59SERVER_WEBSOCKET = 2
60SERVER_PROXY = 3
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000061
62# Default request queue size for WebSocketServer.
63_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000064
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000065class WebSocketOptions:
66 """Holds options for WebSocketServer."""
67
68 def __init__(self, host, port, data_dir):
69 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
70 self.server_host = host
71 self.port = port
72 self.websock_handlers = data_dir
73 self.scan_dir = None
74 self.allow_handlers_outside_root_dir = False
75 self.websock_handlers_map_file = None
76 self.cgi_directories = []
77 self.is_executable_method = None
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000078
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000079 self.use_tls = False
80 self.private_key = None
81 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000082 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000083 self.tls_client_ca = None
84 self.use_basic_auth = False
Keita Suzuki83e26f92020-03-06 09:42:48 +000085 self.basic_auth_credential = 'Basic ' + base64.b64encode(
86 'test:test').decode()
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000087
mattm@chromium.org830a3712012-11-07 23:00:07 +000088
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000089class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
90 testserver_base.BrokenPipeHandlerMixIn,
91 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +000092 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +000093 verification."""
94
95 pass
96
Adam Rice34b2e312018-04-06 16:48:30 +000097class ThreadingHTTPServer(SocketServer.ThreadingMixIn,
98 HTTPServer):
99 """This variant of HTTPServer creates a new thread for every connection. It
100 should only be used with handlers that are known to be threadsafe."""
101
102 pass
103
mattm@chromium.org830a3712012-11-07 23:00:07 +0000104
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000105class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000106 testserver_base.ClientRestrictingServerMixIn,
107 testserver_base.BrokenPipeHandlerMixIn,
108 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000109 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000110 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000111
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000112 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000113 ssl_client_auth, ssl_client_cas, ssl_client_cert_types,
bnc5fb33bd2016-08-05 12:09:21 -0700114 ssl_bulk_ciphers, ssl_key_exchanges, alpn_protocols,
David Benjamin9aadbed2021-09-15 03:29:09 +0000115 npn_protocols, tls_intolerant, tls_intolerance_type,
116 signed_cert_timestamps, fallback_scsv_enabled, ocsp_response,
David Benjaminf839f1c2018-10-16 06:01:29 +0000117 alert_after_handshake, disable_channel_id, disable_ems,
118 simulate_tls13_downgrade, simulate_tls12_downgrade,
119 tls_max_version):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000120 self.cert_chain = tlslite.api.X509CertChain()
121 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000122 # Force using only python implementation - otherwise behavior is different
123 # depending on whether m2crypto Python module is present (error is thrown
124 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
125 # the hood.
126 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
127 private=True,
128 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000129 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000130 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000131 self.ssl_client_cert_types = []
bnc609ad4c2015-10-02 05:11:24 -0700132 self.npn_protocols = npn_protocols
ekasper@google.com24aa8222013-11-28 13:43:26 +0000133 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000134 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000135 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000136
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000137 if ssl_client_auth:
138 for ca_file in ssl_client_cas:
139 s = open(ca_file).read()
140 x509 = tlslite.api.X509()
141 x509.parse(s)
142 self.ssl_client_cas.append(x509.subject)
143
144 for cert_type in ssl_client_cert_types:
145 self.ssl_client_cert_types.append({
146 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000147 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
148 }[cert_type])
149
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000150 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800151 # Enable SSLv3 for testing purposes.
152 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000153 if ssl_bulk_ciphers is not None:
154 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000155 if ssl_key_exchanges is not None:
156 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000157 if tls_intolerant != 0:
158 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
159 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
davidben21cda342015-03-17 18:04:28 -0700160 if alert_after_handshake:
161 self.ssl_handshake_settings.alertAfterHandshake = True
nharper1e8bf4b2015-09-18 12:23:02 -0700162 if disable_channel_id:
163 self.ssl_handshake_settings.enableChannelID = False
164 if disable_ems:
165 self.ssl_handshake_settings.enableExtendedMasterSecret = False
David Benjaminf839f1c2018-10-16 06:01:29 +0000166 if simulate_tls13_downgrade:
167 self.ssl_handshake_settings.simulateTLS13Downgrade = True
168 if simulate_tls12_downgrade:
169 self.ssl_handshake_settings.simulateTLS12Downgrade = True
170 if tls_max_version != 0:
171 self.ssl_handshake_settings.maxVersion = (3, tls_max_version)
bnc5fb33bd2016-08-05 12:09:21 -0700172 self.ssl_handshake_settings.alpnProtos=alpn_protocols;
initial.commit94958cf2008-07-26 22:42:52 +0000173
David Benjamin9aadbed2021-09-15 03:29:09 +0000174 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000175 testserver_base.StoppableHTTPServer.__init__(self,
176 server_address,
177 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000178
179 def handshake(self, tlsConnection):
180 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000181
initial.commit94958cf2008-07-26 22:42:52 +0000182 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000183 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000184 tlsConnection.handshakeServer(certChain=self.cert_chain,
185 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000186 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000187 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000188 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000189 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000190 reqCertTypes=self.ssl_client_cert_types,
bnc609ad4c2015-10-02 05:11:24 -0700191 nextProtos=self.npn_protocols,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000192 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000193 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000194 fallbackSCSV=self.fallback_scsv_enabled,
195 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000196 tlsConnection.ignoreAbruptClose = True
197 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000198 except tlslite.api.TLSAbruptCloseError:
199 # Ignore abrupt close.
200 return True
initial.commit94958cf2008-07-26 22:42:52 +0000201 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000202 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000203 return False
204
akalin@chromium.org154bb132010-11-12 02:20:27 +0000205
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000206class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000207 # Class variables to allow for persistence state between page handler
208 # invocations
209 rst_limits = {}
210 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000211
212 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000213 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000214 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000215 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000216 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000217 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000218 self.NoCacheMaxAgeTimeHandler,
219 self.NoCacheTimeHandler,
220 self.CacheTimeHandler,
221 self.CacheExpiresHandler,
222 self.CacheProxyRevalidateHandler,
223 self.CachePrivateHandler,
224 self.CachePublicHandler,
225 self.CacheSMaxAgeHandler,
226 self.CacheMustRevalidateHandler,
227 self.CacheMustRevalidateMaxAgeHandler,
228 self.CacheNoStoreHandler,
229 self.CacheNoStoreMaxAgeHandler,
230 self.CacheNoTransformHandler,
231 self.DownloadHandler,
232 self.DownloadFinishHandler,
233 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000234 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000235 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000236 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000237 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000238 self.SetCookieHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000239 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000240 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000241 self.AuthBasicHandler,
242 self.AuthDigestHandler,
243 self.SlowServerHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000244 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000245 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700246 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000247 self.ClientRedirectHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000248 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000249 self.GetChannelID,
pneubeckfd4f0442015-08-07 04:55:10 -0700250 self.GetClientCert,
davidben599e7e72014-09-03 16:19:09 -0700251 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000252 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000253 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000254 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000255 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000256 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000257 self.PostOnlyFileHandler,
258 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000259 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000260 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000261 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000262 head_handlers = [
263 self.FileHandler,
264 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000265
maruel@google.come250a9b2009-03-10 17:39:46 +0000266 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000267 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000268 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000269 'gif': 'image/gif',
270 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000271 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700272 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000273 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000274 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000275 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000276 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000277 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000278 }
initial.commit94958cf2008-07-26 22:42:52 +0000279 self._default_mime_type = 'text/html'
280
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000281 testserver_base.BasePageHandler.__init__(self, request, client_address,
282 socket_server, connect_handlers,
283 get_handlers, head_handlers,
284 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000285
initial.commit94958cf2008-07-26 22:42:52 +0000286 def GetMIMETypeFromName(self, file_name):
287 """Returns the mime type for the specified file_name. So far it only looks
288 at the file extension."""
289
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000290 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000291 if len(extension) == 0:
292 # no extension.
293 return self._default_mime_type
294
ericroman@google.comc17ca532009-05-07 03:51:05 +0000295 # extension starts with a dot, so we need to remove it
296 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000297
initial.commit94958cf2008-07-26 22:42:52 +0000298 def NoCacheMaxAgeTimeHandler(self):
299 """This request handler yields a page with the title set to the current
300 system time, and no caching requested."""
301
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000302 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000303 return False
304
305 self.send_response(200)
306 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000307 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000308 self.end_headers()
309
maruel@google.come250a9b2009-03-10 17:39:46 +0000310 self.wfile.write('<html><head><title>%s</title></head></html>' %
311 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000312
313 return True
314
315 def NoCacheTimeHandler(self):
316 """This request handler yields a page with the title set to the current
317 system time, and no caching requested."""
318
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000319 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000320 return False
321
322 self.send_response(200)
323 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000324 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000325 self.end_headers()
326
maruel@google.come250a9b2009-03-10 17:39:46 +0000327 self.wfile.write('<html><head><title>%s</title></head></html>' %
328 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000329
330 return True
331
332 def CacheTimeHandler(self):
333 """This request handler yields a page with the title set to the current
334 system time, and allows caching for one minute."""
335
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000336 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000337 return False
338
339 self.send_response(200)
340 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000341 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000342 self.end_headers()
343
maruel@google.come250a9b2009-03-10 17:39:46 +0000344 self.wfile.write('<html><head><title>%s</title></head></html>' %
345 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000346
347 return True
348
349 def CacheExpiresHandler(self):
350 """This request handler yields a page with the title set to the current
351 system time, and set the page to expire on 1 Jan 2099."""
352
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000353 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000354 return False
355
356 self.send_response(200)
357 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000358 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000359 self.end_headers()
360
maruel@google.come250a9b2009-03-10 17:39:46 +0000361 self.wfile.write('<html><head><title>%s</title></head></html>' %
362 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000363
364 return True
365
366 def CacheProxyRevalidateHandler(self):
367 """This request handler yields a page with the title set to the current
368 system time, and allows caching for 60 seconds"""
369
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000370 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000371 return False
372
373 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000374 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000375 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
376 self.end_headers()
377
maruel@google.come250a9b2009-03-10 17:39:46 +0000378 self.wfile.write('<html><head><title>%s</title></head></html>' %
379 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000380
381 return True
382
383 def CachePrivateHandler(self):
384 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700385 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000386
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000387 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000388 return False
389
390 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000391 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000392 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000393 self.end_headers()
394
maruel@google.come250a9b2009-03-10 17:39:46 +0000395 self.wfile.write('<html><head><title>%s</title></head></html>' %
396 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000397
398 return True
399
400 def CachePublicHandler(self):
401 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700402 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000403
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000404 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000405 return False
406
407 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000408 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000409 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000410 self.end_headers()
411
maruel@google.come250a9b2009-03-10 17:39:46 +0000412 self.wfile.write('<html><head><title>%s</title></head></html>' %
413 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000414
415 return True
416
417 def CacheSMaxAgeHandler(self):
418 """This request handler yields a page with the title set to the current
419 system time, and does not allow for caching."""
420
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000421 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000422 return False
423
424 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000425 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000426 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
427 self.end_headers()
428
maruel@google.come250a9b2009-03-10 17:39:46 +0000429 self.wfile.write('<html><head><title>%s</title></head></html>' %
430 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000431
432 return True
433
434 def CacheMustRevalidateHandler(self):
435 """This request handler yields a page with the title set to the current
436 system time, and does not allow caching."""
437
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000438 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000439 return False
440
441 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000442 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000443 self.send_header('Cache-Control', 'must-revalidate')
444 self.end_headers()
445
maruel@google.come250a9b2009-03-10 17:39:46 +0000446 self.wfile.write('<html><head><title>%s</title></head></html>' %
447 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000448
449 return True
450
451 def CacheMustRevalidateMaxAgeHandler(self):
452 """This request handler yields a page with the title set to the current
453 system time, and does not allow caching event though max-age of 60
454 seconds is specified."""
455
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000456 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000457 return False
458
459 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000460 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000461 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
462 self.end_headers()
463
maruel@google.come250a9b2009-03-10 17:39:46 +0000464 self.wfile.write('<html><head><title>%s</title></head></html>' %
465 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000466
467 return True
468
initial.commit94958cf2008-07-26 22:42:52 +0000469 def CacheNoStoreHandler(self):
470 """This request handler yields a page with the title set to the current
471 system time, and does not allow the page to be stored."""
472
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000473 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000474 return False
475
476 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000477 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000478 self.send_header('Cache-Control', 'no-store')
479 self.end_headers()
480
maruel@google.come250a9b2009-03-10 17:39:46 +0000481 self.wfile.write('<html><head><title>%s</title></head></html>' %
482 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000483
484 return True
485
486 def CacheNoStoreMaxAgeHandler(self):
487 """This request handler yields a page with the title set to the current
488 system time, and does not allow the page to be stored even though max-age
489 of 60 seconds is specified."""
490
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000491 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000492 return False
493
494 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000495 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000496 self.send_header('Cache-Control', 'max-age=60, no-store')
497 self.end_headers()
498
maruel@google.come250a9b2009-03-10 17:39:46 +0000499 self.wfile.write('<html><head><title>%s</title></head></html>' %
500 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000501
502 return True
503
504
505 def CacheNoTransformHandler(self):
506 """This request handler yields a page with the title set to the current
507 system time, and does not allow the content to transformed during
508 user-agent caching"""
509
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000510 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000511 return False
512
513 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000514 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000515 self.send_header('Cache-Control', 'no-transform')
516 self.end_headers()
517
maruel@google.come250a9b2009-03-10 17:39:46 +0000518 self.wfile.write('<html><head><title>%s</title></head></html>' %
519 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000520
521 return True
522
523 def EchoHeader(self):
524 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000525
ananta@chromium.org219b2062009-10-23 16:09:41 +0000526 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000527
ananta@chromium.org56812d02011-04-07 17:52:05 +0000528 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000529 """This function echoes back the value of a specific request header while
Eric Romanf6a38402018-02-14 20:19:53 +0000530 allowing caching for 10 hours."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000531
ananta@chromium.org56812d02011-04-07 17:52:05 +0000532 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000533
534 def EchoHeaderHelper(self, echo_header):
535 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000536
ananta@chromium.org219b2062009-10-23 16:09:41 +0000537 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000538 return False
539
540 query_char = self.path.find('?')
541 if query_char != -1:
542 header_name = self.path[query_char+1:]
543
544 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000545 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000546 if echo_header == '/echoheadercache':
547 self.send_header('Cache-control', 'max-age=60000')
548 else:
549 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000550 # insert a vary header to properly indicate that the cachability of this
551 # request is subject to value of the request header being echoed.
552 if len(header_name) > 0:
553 self.send_header('Vary', header_name)
554 self.end_headers()
555
556 if len(header_name) > 0:
557 self.wfile.write(self.headers.getheader(header_name))
558
559 return True
560
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000561 def ReadRequestBody(self):
562 """This function reads the body of the current HTTP request, handling
563 both plain and chunked transfer encoded requests."""
564
565 if self.headers.getheader('transfer-encoding') != 'chunked':
566 length = int(self.headers.getheader('content-length'))
567 return self.rfile.read(length)
568
569 # Read the request body as chunks.
570 body = ""
571 while True:
572 line = self.rfile.readline()
573 length = int(line, 16)
574 if length == 0:
575 self.rfile.readline()
576 break
577 body += self.rfile.read(length)
578 self.rfile.read(2)
579 return body
580
initial.commit94958cf2008-07-26 22:42:52 +0000581 def EchoHandler(self):
582 """This handler just echoes back the payload of the request, for testing
583 form submission."""
584
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000585 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000586 return False
587
hirono2838c572015-01-21 12:18:11 -0800588 _, _, _, _, query, _ = urlparse.urlparse(self.path)
589 query_params = cgi.parse_qs(query, True)
590 if 'status' in query_params:
591 self.send_response(int(query_params['status'][0]))
592 else:
593 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000594 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000595 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000596 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000597 return True
598
599 def EchoTitleHandler(self):
600 """This handler is like Echo, but sets the page title to the request."""
601
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000602 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000603 return False
604
605 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000606 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000607 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000608 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000609 self.wfile.write('<html><head><title>')
610 self.wfile.write(request)
611 self.wfile.write('</title></head></html>')
612 return True
613
614 def EchoAllHandler(self):
615 """This handler yields a (more) human-readable page listing information
616 about the request header & contents."""
617
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000618 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000619 return False
620
621 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000622 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000623 self.end_headers()
624 self.wfile.write('<html><head><style>'
625 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
626 '</style></head><body>'
627 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000628 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000629 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000630
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000631 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000632 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000633 params = cgi.parse_qs(qs, keep_blank_values=1)
634
635 for param in params:
636 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000637
638 self.wfile.write('</pre>')
639
640 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
641
642 self.wfile.write('</body></html>')
643 return True
644
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000645 def EchoMultipartPostHandler(self):
646 """This handler echoes received multipart post data as json format."""
647
648 if not (self._ShouldHandleRequest("/echomultipartpost") or
649 self._ShouldHandleRequest("/searchbyimage")):
650 return False
651
652 content_type, parameters = cgi.parse_header(
653 self.headers.getheader('content-type'))
654 if content_type == 'multipart/form-data':
655 post_multipart = cgi.parse_multipart(self.rfile, parameters)
656 elif content_type == 'application/x-www-form-urlencoded':
657 raise Exception('POST by application/x-www-form-urlencoded is '
658 'not implemented.')
659 else:
660 post_multipart = {}
661
662 # Since the data can be binary, we encode them by base64.
663 post_multipart_base64_encoded = {}
664 for field, values in post_multipart.items():
665 post_multipart_base64_encoded[field] = [base64.b64encode(value)
666 for value in values]
667
668 result = {'POST_multipart' : post_multipart_base64_encoded}
669
670 self.send_response(200)
671 self.send_header("Content-type", "text/plain")
672 self.end_headers()
673 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
674 return True
675
initial.commit94958cf2008-07-26 22:42:52 +0000676 def DownloadHandler(self):
677 """This handler sends a downloadable file with or without reporting
678 the size (6K)."""
679
680 if self.path.startswith("/download-unknown-size"):
681 send_length = False
682 elif self.path.startswith("/download-known-size"):
683 send_length = True
684 else:
685 return False
686
687 #
688 # The test which uses this functionality is attempting to send
689 # small chunks of data to the client. Use a fairly large buffer
690 # so that we'll fill chrome's IO buffer enough to force it to
691 # actually write the data.
692 # See also the comments in the client-side of this test in
693 # download_uitest.cc
694 #
695 size_chunk1 = 35*1024
696 size_chunk2 = 10*1024
697
698 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000699 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000700 self.send_header('Cache-Control', 'max-age=0')
701 if send_length:
702 self.send_header('Content-Length', size_chunk1 + size_chunk2)
703 self.end_headers()
704
705 # First chunk of data:
706 self.wfile.write("*" * size_chunk1)
707 self.wfile.flush()
708
709 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000710 self.server.wait_for_download = True
711 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000712 self.server.handle_request()
713
714 # Second chunk of data:
715 self.wfile.write("*" * size_chunk2)
716 return True
717
718 def DownloadFinishHandler(self):
719 """This handler just tells the server to finish the current download."""
720
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000721 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000722 return False
723
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000724 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000725 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000726 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000727 self.send_header('Cache-Control', 'max-age=0')
728 self.end_headers()
729 return True
730
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000731 def _ReplaceFileData(self, data, query_parameters):
732 """Replaces matching substrings in a file.
733
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000734 If the 'replace_text' URL query parameter is present, it is expected to be
735 of the form old_text:new_text, which indicates that any old_text strings in
736 the file are replaced with new_text. Multiple 'replace_text' parameters may
737 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000738
739 If the parameters are not present, |data| is returned.
740 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000741
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000742 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000743 replace_text_values = query_dict.get('replace_text', [])
744 for replace_text_value in replace_text_values:
745 replace_text_args = replace_text_value.split(':')
746 if len(replace_text_args) != 2:
747 raise ValueError(
748 'replace_text must be of form old_text:new_text. Actual value: %s' %
749 replace_text_value)
750 old_text_b64, new_text_b64 = replace_text_args
751 old_text = base64.urlsafe_b64decode(old_text_b64)
752 new_text = base64.urlsafe_b64decode(new_text_b64)
753 data = data.replace(old_text, new_text)
754 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000755
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000756 def ZipFileHandler(self):
757 """This handler sends the contents of the requested file in compressed form.
758 Can pass in a parameter that specifies that the content length be
759 C - the compressed size (OK),
760 U - the uncompressed size (Non-standard, but handled),
761 S - less than compressed (OK because we keep going),
762 M - larger than compressed but less than uncompressed (an error),
763 L - larger than uncompressed (an error)
764 Example: compressedfiles/Picture_1.doc?C
765 """
766
767 prefix = "/compressedfiles/"
768 if not self.path.startswith(prefix):
769 return False
770
771 # Consume a request body if present.
772 if self.command == 'POST' or self.command == 'PUT' :
773 self.ReadRequestBody()
774
775 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
776
777 if not query in ('C', 'U', 'S', 'M', 'L'):
778 return False
779
780 sub_path = url_path[len(prefix):]
781 entries = sub_path.split('/')
782 file_path = os.path.join(self.server.data_dir, *entries)
783 if os.path.isdir(file_path):
784 file_path = os.path.join(file_path, 'index.html')
785
786 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000787 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000788 self.send_error(404)
789 return True
790
791 f = open(file_path, "rb")
792 data = f.read()
793 uncompressed_len = len(data)
794 f.close()
795
796 # Compress the data.
797 data = zlib.compress(data)
798 compressed_len = len(data)
799
800 content_length = compressed_len
801 if query == 'U':
802 content_length = uncompressed_len
803 elif query == 'S':
804 content_length = compressed_len / 2
805 elif query == 'M':
806 content_length = (compressed_len + uncompressed_len) / 2
807 elif query == 'L':
808 content_length = compressed_len + uncompressed_len
809
810 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000811 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000812 self.send_header('Content-encoding', 'deflate')
813 self.send_header('Connection', 'close')
814 self.send_header('Content-Length', content_length)
815 self.send_header('ETag', '\'' + file_path + '\'')
816 self.end_headers()
817
818 self.wfile.write(data)
819
820 return True
821
initial.commit94958cf2008-07-26 22:42:52 +0000822 def FileHandler(self):
823 """This handler sends the contents of the requested file. Wow, it's like
824 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000825
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000826 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000827 if not self.path.startswith(prefix):
828 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000829 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000830
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000831 def PostOnlyFileHandler(self):
832 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000833
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000834 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000835 if not self.path.startswith(prefix):
836 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000837 return self._FileHandlerHelper(prefix)
838
839 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000840 request_body = ''
841 if self.command == 'POST' or self.command == 'PUT':
842 # Consume a request body if present.
843 request_body = self.ReadRequestBody()
844
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000845 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000846 query_dict = cgi.parse_qs(query)
847
848 expected_body = query_dict.get('expected_body', [])
849 if expected_body and request_body not in expected_body:
850 self.send_response(404)
851 self.end_headers()
852 self.wfile.write('')
853 return True
854
855 expected_headers = query_dict.get('expected_headers', [])
856 for expected_header in expected_headers:
857 header_name, expected_value = expected_header.split(':')
858 if self.headers.getheader(header_name) != expected_value:
859 self.send_response(404)
860 self.end_headers()
861 self.wfile.write('')
862 return True
863
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000864 sub_path = url_path[len(prefix):]
865 entries = sub_path.split('/')
866 file_path = os.path.join(self.server.data_dir, *entries)
867 if os.path.isdir(file_path):
868 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000869
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000870 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000871 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000872 self.send_error(404)
873 return True
874
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000875 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000876 data = f.read()
877 f.close()
878
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000879 data = self._ReplaceFileData(data, query)
880
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000881 old_protocol_version = self.protocol_version
882
initial.commit94958cf2008-07-26 22:42:52 +0000883 # If file.mock-http-headers exists, it contains the headers we
884 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000885 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000886 if os.path.isfile(headers_path):
887 f = open(headers_path, "r")
888
889 # "HTTP/1.1 200 OK"
890 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000891 http_major, http_minor, status_code = re.findall(
892 'HTTP/(\d+).(\d+) (\d+)', response)[0]
893 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000894 self.send_response(int(status_code))
895
896 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000897 header_values = re.findall('(\S+):\s*(.*)', line)
898 if len(header_values) > 0:
899 # "name: value"
900 name, value = header_values[0]
901 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000902 f.close()
903 else:
904 # Could be more generic once we support mime-type sniffing, but for
905 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000906
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000907 range_header = self.headers.get('Range')
908 if range_header and range_header.startswith('bytes='):
909 # Note this doesn't handle all valid byte range_header values (i.e.
910 # left open ended ones), just enough for what we needed so far.
911 range_header = range_header[6:].split('-')
912 start = int(range_header[0])
913 if range_header[1]:
914 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000915 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000916 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000917
918 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000919 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
920 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000921 self.send_header('Content-Range', content_range)
922 data = data[start: end + 1]
923 else:
924 self.send_response(200)
925
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000926 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000927 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000928 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000929 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000930 self.end_headers()
931
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000932 if (self.command != 'HEAD'):
933 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000934
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000935 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000936 return True
937
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000938 def SetCookieHandler(self):
939 """This handler just sets a cookie, for testing cookie handling."""
940
941 if not self._ShouldHandleRequest("/set-cookie"):
942 return False
943
944 query_char = self.path.find('?')
945 if query_char != -1:
946 cookie_values = self.path[query_char + 1:].split('&')
947 else:
948 cookie_values = ("",)
949 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000950 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000951 for cookie_value in cookie_values:
952 self.send_header('Set-Cookie', '%s' % cookie_value)
953 self.end_headers()
954 for cookie_value in cookie_values:
955 self.wfile.write('%s' % cookie_value)
956 return True
957
mattm@chromium.org983fc462012-06-30 00:52:08 +0000958 def ExpectAndSetCookieHandler(self):
959 """Expects some cookies to be sent, and if they are, sets more cookies.
960
961 The expect parameter specifies a required cookie. May be specified multiple
962 times.
963 The set parameter specifies a cookie to set if all required cookies are
964 preset. May be specified multiple times.
965 The data parameter specifies the response body data to be returned."""
966
967 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
968 return False
969
970 _, _, _, _, query, _ = urlparse.urlparse(self.path)
971 query_dict = cgi.parse_qs(query)
972 cookies = set()
973 if 'Cookie' in self.headers:
974 cookie_header = self.headers.getheader('Cookie')
975 cookies.update([s.strip() for s in cookie_header.split(';')])
976 got_all_expected_cookies = True
977 for expected_cookie in query_dict.get('expect', []):
978 if expected_cookie not in cookies:
979 got_all_expected_cookies = False
980 self.send_response(200)
981 self.send_header('Content-Type', 'text/html')
982 if got_all_expected_cookies:
983 for cookie_value in query_dict.get('set', []):
984 self.send_header('Set-Cookie', '%s' % cookie_value)
985 self.end_headers()
986 for data_value in query_dict.get('data', []):
987 self.wfile.write(data_value)
988 return True
989
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000990 def SetHeaderHandler(self):
991 """This handler sets a response header. Parameters are in the
992 key%3A%20value&key2%3A%20value2 format."""
993
994 if not self._ShouldHandleRequest("/set-header"):
995 return False
996
997 query_char = self.path.find('?')
998 if query_char != -1:
999 headers_values = self.path[query_char + 1:].split('&')
1000 else:
1001 headers_values = ("",)
1002 self.send_response(200)
1003 self.send_header('Content-Type', 'text/html')
1004 for header_value in headers_values:
1005 header_value = urllib.unquote(header_value)
1006 (key, value) = header_value.split(': ', 1)
1007 self.send_header(key, value)
1008 self.end_headers()
1009 for header_value in headers_values:
1010 self.wfile.write('%s' % header_value)
1011 return True
1012
initial.commit94958cf2008-07-26 22:42:52 +00001013 def AuthBasicHandler(self):
1014 """This handler tests 'Basic' authentication. It just sends a page with
1015 title 'user/pass' if you succeed."""
1016
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001017 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001018 return False
1019
1020 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001021 expected_password = 'secret'
1022 realm = 'testrealm'
1023 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001024
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001025 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1026 query_params = cgi.parse_qs(query, True)
1027 if 'set-cookie-if-challenged' in query_params:
1028 set_cookie_if_challenged = True
1029 if 'password' in query_params:
1030 expected_password = query_params['password'][0]
1031 if 'realm' in query_params:
1032 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001033
initial.commit94958cf2008-07-26 22:42:52 +00001034 auth = self.headers.getheader('authorization')
1035 try:
1036 if not auth:
1037 raise Exception('no auth')
1038 b64str = re.findall(r'Basic (\S+)', auth)[0]
1039 userpass = base64.b64decode(b64str)
1040 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001041 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001042 raise Exception('wrong password')
1043 except Exception, e:
1044 # Authentication failed.
1045 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001046 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001047 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001048 if set_cookie_if_challenged:
1049 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001050 self.end_headers()
1051 self.wfile.write('<html><head>')
1052 self.wfile.write('<title>Denied: %s</title>' % e)
1053 self.wfile.write('</head><body>')
1054 self.wfile.write('auth=%s<p>' % auth)
1055 self.wfile.write('b64str=%s<p>' % b64str)
1056 self.wfile.write('username: %s<p>' % username)
1057 self.wfile.write('userpass: %s<p>' % userpass)
1058 self.wfile.write('password: %s<p>' % password)
1059 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1060 self.wfile.write('</body></html>')
1061 return True
1062
1063 # Authentication successful. (Return a cachable response to allow for
1064 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001065 old_protocol_version = self.protocol_version
1066 self.protocol_version = "HTTP/1.1"
1067
initial.commit94958cf2008-07-26 22:42:52 +00001068 if_none_match = self.headers.getheader('if-none-match')
1069 if if_none_match == "abc":
1070 self.send_response(304)
1071 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001072 elif url_path.endswith(".gif"):
1073 # Using chrome/test/data/google/logo.gif as the test image
1074 test_image_path = ['google', 'logo.gif']
1075 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1076 if not os.path.isfile(gif_path):
1077 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001078 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001079 return True
1080
1081 f = open(gif_path, "rb")
1082 data = f.read()
1083 f.close()
1084
1085 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001086 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001087 self.send_header('Cache-control', 'max-age=60000')
1088 self.send_header('Etag', 'abc')
1089 self.end_headers()
1090 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001091 else:
1092 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001093 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001094 self.send_header('Cache-control', 'max-age=60000')
1095 self.send_header('Etag', 'abc')
1096 self.end_headers()
1097 self.wfile.write('<html><head>')
1098 self.wfile.write('<title>%s/%s</title>' % (username, password))
1099 self.wfile.write('</head><body>')
1100 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001101 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001102 self.wfile.write('</body></html>')
1103
rvargas@google.com54453b72011-05-19 01:11:11 +00001104 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001105 return True
1106
tonyg@chromium.org75054202010-03-31 22:06:10 +00001107 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001108 """Returns a nonce that's stable per request path for the server's lifetime.
1109 This is a fake implementation. A real implementation would only use a given
1110 nonce a single time (hence the name n-once). However, for the purposes of
1111 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001112
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001113 Args:
1114 force_reset: Iff set, the nonce will be changed. Useful for testing the
1115 "stale" response.
1116 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001117
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001118 if force_reset or not self.server.nonce_time:
1119 self.server.nonce_time = time.time()
1120 return hashlib.md5('privatekey%s%d' %
1121 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001122
1123 def AuthDigestHandler(self):
1124 """This handler tests 'Digest' authentication.
1125
1126 It just sends a page with title 'user/pass' if you succeed.
1127
1128 A stale response is sent iff "stale" is present in the request path.
1129 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001130
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001131 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001132 return False
1133
tonyg@chromium.org75054202010-03-31 22:06:10 +00001134 stale = 'stale' in self.path
1135 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001136 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001137 password = 'secret'
1138 realm = 'testrealm'
1139
1140 auth = self.headers.getheader('authorization')
1141 pairs = {}
1142 try:
1143 if not auth:
1144 raise Exception('no auth')
1145 if not auth.startswith('Digest'):
1146 raise Exception('not digest')
1147 # Pull out all the name="value" pairs as a dictionary.
1148 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1149
1150 # Make sure it's all valid.
1151 if pairs['nonce'] != nonce:
1152 raise Exception('wrong nonce')
1153 if pairs['opaque'] != opaque:
1154 raise Exception('wrong opaque')
1155
1156 # Check the 'response' value and make sure it matches our magic hash.
1157 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001158 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001159 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001160 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001161 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001162 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001163 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1164 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001165 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001166
1167 if pairs['response'] != response:
1168 raise Exception('wrong password')
1169 except Exception, e:
1170 # Authentication failed.
1171 self.send_response(401)
1172 hdr = ('Digest '
1173 'realm="%s", '
1174 'domain="/", '
1175 'qop="auth", '
1176 'algorithm=MD5, '
1177 'nonce="%s", '
1178 'opaque="%s"') % (realm, nonce, opaque)
1179 if stale:
1180 hdr += ', stale="TRUE"'
1181 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001182 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001183 self.end_headers()
1184 self.wfile.write('<html><head>')
1185 self.wfile.write('<title>Denied: %s</title>' % e)
1186 self.wfile.write('</head><body>')
1187 self.wfile.write('auth=%s<p>' % auth)
1188 self.wfile.write('pairs=%s<p>' % pairs)
1189 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1190 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1191 self.wfile.write('</body></html>')
1192 return True
1193
1194 # Authentication successful.
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.end_headers()
1198 self.wfile.write('<html><head>')
1199 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1200 self.wfile.write('</head><body>')
1201 self.wfile.write('auth=%s<p>' % auth)
1202 self.wfile.write('pairs=%s<p>' % pairs)
1203 self.wfile.write('</body></html>')
1204
1205 return True
1206
1207 def SlowServerHandler(self):
1208 """Wait for the user suggested time before responding. The syntax is
1209 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001210
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001211 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001212 return False
1213 query_char = self.path.find('?')
1214 wait_sec = 1.0
1215 if query_char >= 0:
1216 try:
davidben05f82202015-03-31 13:48:07 -07001217 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001218 except ValueError:
1219 pass
1220 time.sleep(wait_sec)
1221 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001222 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001223 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001224 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001225 return True
1226
creis@google.com2f4f6a42011-03-25 19:44:19 +00001227 def NoContentHandler(self):
1228 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001229
creis@google.com2f4f6a42011-03-25 19:44:19 +00001230 if not self._ShouldHandleRequest("/nocontent"):
1231 return False
1232 self.send_response(204)
1233 self.end_headers()
1234 return True
1235
initial.commit94958cf2008-07-26 22:42:52 +00001236 def ServerRedirectHandler(self):
1237 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001238 '/server-redirect?http://foo.bar/asdf' to redirect to
1239 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001240
1241 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001242 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001243 return False
1244
1245 query_char = self.path.find('?')
1246 if query_char < 0 or len(self.path) <= query_char + 1:
1247 self.sendRedirectHelp(test_name)
1248 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001249 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001250
1251 self.send_response(301) # moved permanently
1252 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001253 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001254 self.end_headers()
1255 self.wfile.write('<html><head>')
1256 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1257
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001258 return True
initial.commit94958cf2008-07-26 22:42:52 +00001259
naskoe7a0d0d2014-09-29 08:53:05 -07001260 def CrossSiteRedirectHandler(self):
1261 """Sends a server redirect to the given site. The syntax is
1262 '/cross-site/hostname/...' to redirect to //hostname/...
1263 It is used to navigate between different Sites, causing
1264 cross-site/cross-process navigations in the browser."""
1265
1266 test_name = "/cross-site"
1267 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001268 return False
1269
1270 params = urllib.unquote(self.path[(len(test_name) + 1):])
1271 slash = params.find('/')
1272 if slash < 0:
1273 self.sendRedirectHelp(test_name)
1274 return True
1275
1276 host = params[:slash]
1277 path = params[(slash+1):]
1278 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1279
1280 self.send_response(301) # moved permanently
1281 self.send_header('Location', dest)
1282 self.send_header('Content-Type', 'text/html')
1283 self.end_headers()
1284 self.wfile.write('<html><head>')
1285 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1286
1287 return True
1288
initial.commit94958cf2008-07-26 22:42:52 +00001289 def ClientRedirectHandler(self):
1290 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001291 '/client-redirect?http://foo.bar/asdf' to redirect to
1292 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001293
1294 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001295 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001296 return False
1297
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001298 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001299 if query_char < 0 or len(self.path) <= query_char + 1:
1300 self.sendRedirectHelp(test_name)
1301 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001302 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001303
1304 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001305 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001306 self.end_headers()
1307 self.wfile.write('<html><head>')
1308 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1309 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1310
1311 return True
1312
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001313 def SSLManySmallRecords(self):
1314 """Sends a reply consisting of a variety of small writes. These will be
1315 translated into a series of small SSL records when used over an HTTPS
1316 server."""
1317
1318 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1319 return False
1320
1321 self.send_response(200)
1322 self.send_header('Content-Type', 'text/plain')
1323 self.end_headers()
1324
1325 # Write ~26K of data, in 1350 byte chunks
1326 for i in xrange(20):
1327 self.wfile.write('*' * 1350)
1328 self.wfile.flush()
1329 return True
1330
agl@chromium.org04700be2013-03-02 18:40:41 +00001331 def GetChannelID(self):
1332 """Send a reply containing the hashed ChannelID that the client provided."""
1333
1334 if not self._ShouldHandleRequest('/channel-id'):
1335 return False
1336
1337 self.send_response(200)
1338 self.send_header('Content-Type', 'text/plain')
1339 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001340 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001341 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1342 return True
1343
pneubeckfd4f0442015-08-07 04:55:10 -07001344 def GetClientCert(self):
1345 """Send a reply whether a client certificate was provided."""
1346
1347 if not self._ShouldHandleRequest('/client-cert'):
1348 return False
1349
1350 self.send_response(200)
1351 self.send_header('Content-Type', 'text/plain')
1352 self.end_headers()
1353
1354 cert_chain = self.server.tlsConnection.session.clientCertChain
1355 if cert_chain != None:
1356 self.wfile.write('got client cert with fingerprint: ' +
1357 cert_chain.getFingerprint())
1358 else:
1359 self.wfile.write('got no client cert')
1360 return True
1361
davidben599e7e72014-09-03 16:19:09 -07001362 def ClientCipherListHandler(self):
1363 """Send a reply containing the cipher suite list that the client
1364 provided. Each cipher suite value is serialized in decimal, followed by a
1365 newline."""
1366
1367 if not self._ShouldHandleRequest('/client-cipher-list'):
1368 return False
1369
1370 self.send_response(200)
1371 self.send_header('Content-Type', 'text/plain')
1372 self.end_headers()
1373
davidben11682512014-10-06 21:09:11 -07001374 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1375 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001376 return True
1377
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001378 def CloseSocketHandler(self):
1379 """Closes the socket without sending anything."""
1380
1381 if not self._ShouldHandleRequest('/close-socket'):
1382 return False
1383
1384 self.wfile.close()
1385 return True
1386
initial.commit94958cf2008-07-26 22:42:52 +00001387 def DefaultResponseHandler(self):
1388 """This is the catch-all response handler for requests that aren't handled
1389 by one of the special handlers above.
1390 Note that we specify the content-length as without it the https connection
1391 is not closed properly (and the browser keeps expecting data)."""
1392
1393 contents = "Default response given for path: " + self.path
1394 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001395 self.send_header('Content-Type', 'text/html')
1396 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001397 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001398 if (self.command != 'HEAD'):
1399 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001400 return True
1401
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001402 def RedirectConnectHandler(self):
1403 """Sends a redirect to the CONNECT request for www.redirect.com. This
1404 response is not specified by the RFC, so the browser should not follow
1405 the redirect."""
1406
1407 if (self.path.find("www.redirect.com") < 0):
1408 return False
1409
1410 dest = "http://www.destination.com/foo.js"
1411
1412 self.send_response(302) # moved temporarily
1413 self.send_header('Location', dest)
1414 self.send_header('Connection', 'close')
1415 self.end_headers()
1416 return True
1417
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001418 def ServerAuthConnectHandler(self):
1419 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1420 response doesn't make sense because the proxy server cannot request
1421 server authentication."""
1422
1423 if (self.path.find("www.server-auth.com") < 0):
1424 return False
1425
1426 challenge = 'Basic realm="WallyWorld"'
1427
1428 self.send_response(401) # unauthorized
1429 self.send_header('WWW-Authenticate', challenge)
1430 self.send_header('Connection', 'close')
1431 self.end_headers()
1432 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001433
1434 def DefaultConnectResponseHandler(self):
1435 """This is the catch-all response handler for CONNECT requests that aren't
1436 handled by one of the special handlers above. Real Web servers respond
1437 with 400 to CONNECT requests."""
1438
1439 contents = "Your client has issued a malformed or illegal request."
1440 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001441 self.send_header('Content-Type', 'text/html')
1442 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001443 self.end_headers()
1444 self.wfile.write(contents)
1445 return True
1446
initial.commit94958cf2008-07-26 22:42:52 +00001447 # called by the redirect handling function when there is no parameter
1448 def sendRedirectHelp(self, redirect_name):
1449 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001450 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001451 self.end_headers()
1452 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1453 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1454 self.wfile.write('</body></html>')
1455
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001456 # called by chunked handling function
1457 def sendChunkHelp(self, chunk):
1458 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1459 self.wfile.write('%X\r\n' % len(chunk))
1460 self.wfile.write(chunk)
1461 self.wfile.write('\r\n')
1462
akalin@chromium.org154bb132010-11-12 02:20:27 +00001463
Adam Rice9476b8c2018-08-02 15:28:43 +00001464class ProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1465 """A request handler that behaves as a proxy server. Only CONNECT, GET and
1466 HEAD methods are supported.
bashi@chromium.org33233532012-09-08 17:37:24 +00001467 """
1468
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001469 redirect_connect_to_localhost = False;
bashi@chromium.org33233532012-09-08 17:37:24 +00001470
bashi@chromium.org33233532012-09-08 17:37:24 +00001471 def _start_read_write(self, sock):
1472 sock.setblocking(0)
1473 self.request.setblocking(0)
1474 rlist = [self.request, sock]
1475 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001476 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001477 if errors:
1478 self.send_response(500)
1479 self.end_headers()
1480 return
1481 for s in ready_sockets:
1482 received = s.recv(1024)
1483 if len(received) == 0:
1484 return
1485 if s == self.request:
1486 other = sock
1487 else:
1488 other = self.request
Adam Rice54443aa2018-06-06 00:11:54 +00001489 # This will lose data if the kernel write buffer fills up.
1490 # TODO(ricea): Correctly use the return value to track how much was
1491 # written and buffer the rest. Use select to determine when the socket
1492 # becomes writable again.
bashi@chromium.org33233532012-09-08 17:37:24 +00001493 other.send(received)
1494
1495 def _do_common_method(self):
1496 url = urlparse.urlparse(self.path)
1497 port = url.port
1498 if not port:
1499 if url.scheme == 'http':
1500 port = 80
1501 elif url.scheme == 'https':
1502 port = 443
1503 if not url.hostname or not port:
1504 self.send_response(400)
1505 self.end_headers()
1506 return
1507
1508 if len(url.path) == 0:
1509 path = '/'
1510 else:
1511 path = url.path
1512 if len(url.query) > 0:
1513 path = '%s?%s' % (url.path, url.query)
1514
1515 sock = None
1516 try:
1517 sock = socket.create_connection((url.hostname, port))
1518 sock.send('%s %s %s\r\n' % (
1519 self.command, path, self.protocol_version))
1520 for header in self.headers.headers:
1521 header = header.strip()
1522 if (header.lower().startswith('connection') or
1523 header.lower().startswith('proxy')):
1524 continue
1525 sock.send('%s\r\n' % header)
1526 sock.send('\r\n')
Adam Rice54443aa2018-06-06 00:11:54 +00001527 # This is wrong: it will pass through connection-level headers and
1528 # misbehave on connection reuse. The only reason it works at all is that
1529 # our test servers have never supported connection reuse.
1530 # TODO(ricea): Use a proper HTTP client library instead.
bashi@chromium.org33233532012-09-08 17:37:24 +00001531 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001532 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001533 logging.exception('failure in common method: %s %s', self.command, path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001534 self.send_response(500)
1535 self.end_headers()
1536 finally:
1537 if sock is not None:
1538 sock.close()
1539
1540 def do_CONNECT(self):
1541 try:
1542 pos = self.path.rfind(':')
1543 host = self.path[:pos]
1544 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001545 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001546 self.send_response(400)
1547 self.end_headers()
1548
Adam Rice9476b8c2018-08-02 15:28:43 +00001549 if ProxyRequestHandler.redirect_connect_to_localhost:
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001550 host = "127.0.0.1"
1551
Adam Rice54443aa2018-06-06 00:11:54 +00001552 sock = None
bashi@chromium.org33233532012-09-08 17:37:24 +00001553 try:
1554 sock = socket.create_connection((host, port))
1555 self.send_response(200, 'Connection established')
1556 self.end_headers()
1557 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001558 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001559 logging.exception('failure in CONNECT: %s', path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001560 self.send_response(500)
1561 self.end_headers()
1562 finally:
Adam Rice54443aa2018-06-06 00:11:54 +00001563 if sock is not None:
1564 sock.close()
bashi@chromium.org33233532012-09-08 17:37:24 +00001565
1566 def do_GET(self):
1567 self._do_common_method()
1568
1569 def do_HEAD(self):
1570 self._do_common_method()
1571
Adam Rice9476b8c2018-08-02 15:28:43 +00001572class BasicAuthProxyRequestHandler(ProxyRequestHandler):
1573 """A request handler that behaves as a proxy server which requires
1574 basic authentication.
1575 """
1576
1577 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1578
1579 def parse_request(self):
1580 """Overrides parse_request to check credential."""
1581
1582 if not ProxyRequestHandler.parse_request(self):
1583 return False
1584
1585 auth = self.headers.getheader('Proxy-Authorization')
1586 if auth != self._AUTH_CREDENTIAL:
1587 self.send_response(407)
1588 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1589 self.end_headers()
1590 return False
1591
1592 return True
1593
bashi@chromium.org33233532012-09-08 17:37:24 +00001594
mattm@chromium.org830a3712012-11-07 23:00:07 +00001595class ServerRunner(testserver_base.TestServerRunner):
1596 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001597
mattm@chromium.org830a3712012-11-07 23:00:07 +00001598 def __init__(self):
1599 super(ServerRunner, self).__init__()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001600
mattm@chromium.org830a3712012-11-07 23:00:07 +00001601 def __make_data_dir(self):
1602 if self.options.data_dir:
1603 if not os.path.isdir(self.options.data_dir):
1604 raise testserver_base.OptionError('specified data dir not found: ' +
1605 self.options.data_dir + ' exiting...')
1606 my_data_dir = self.options.data_dir
1607 else:
1608 # Create the default path to our data dir, relative to the exe dir.
Asanka Herath0ec37152019-08-02 15:23:57 +00001609 my_data_dir = os.path.join(BASE_DIR, "..", "..", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001610
mattm@chromium.org830a3712012-11-07 23:00:07 +00001611 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001612
mattm@chromium.org830a3712012-11-07 23:00:07 +00001613 def create_server(self, server_data):
1614 port = self.options.port
1615 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001616
Adam Rice54443aa2018-06-06 00:11:54 +00001617 logging.basicConfig()
1618
estark21667d62015-04-08 21:00:16 -07001619 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1620 # will result in a call to |getaddrinfo|, which fails with "nodename
1621 # nor servname provided" for localhost:0 on 10.6.
Adam Rice54443aa2018-06-06 00:11:54 +00001622 # TODO(ricea): Remove this if no longer needed.
estark21667d62015-04-08 21:00:16 -07001623 if self.options.server_type == SERVER_WEBSOCKET and \
1624 host == "localhost" and \
1625 port == 0:
1626 host = "127.0.0.1"
1627
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001628 # Construct the subjectAltNames for any ad-hoc generated certificates.
1629 # As host can be either a DNS name or IP address, attempt to determine
1630 # which it is, so it can be placed in the appropriate SAN.
1631 dns_sans = None
1632 ip_sans = None
1633 ip = None
1634 try:
1635 ip = socket.inet_aton(host)
1636 ip_sans = [ip]
1637 except socket.error:
1638 pass
1639 if ip is None:
1640 dns_sans = [host]
1641
mattm@chromium.org830a3712012-11-07 23:00:07 +00001642 if self.options.server_type == SERVER_HTTP:
1643 if self.options.https:
David Benjamin8ed48d12021-09-14 21:28:30 +00001644 if not self.options.cert_and_key_file:
1645 raise testserver_base.OptionError('server cert file not specified')
1646 if not os.path.isfile(self.options.cert_and_key_file):
1647 raise testserver_base.OptionError(
1648 'specified server cert file not found: ' +
1649 self.options.cert_and_key_file + ' exiting...')
1650 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
mattm@chromium.org830a3712012-11-07 23:00:07 +00001651
1652 for ca_cert in self.options.ssl_client_ca:
1653 if not os.path.isfile(ca_cert):
1654 raise testserver_base.OptionError(
1655 'specified trusted client CA file not found: ' + ca_cert +
1656 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001657
1658 stapled_ocsp_response = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001659 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1660 self.options.ssl_client_auth,
1661 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00001662 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00001663 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00001664 self.options.ssl_key_exchange,
bnc5fb33bd2016-08-05 12:09:21 -07001665 self.options.alpn_protocols,
bnc609ad4c2015-10-02 05:11:24 -07001666 self.options.npn_protocols,
ekasper@google.com24aa8222013-11-28 13:43:26 +00001667 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00001668 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001669 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00001670 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001671 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07001672 stapled_ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -07001673 self.options.alert_after_handshake,
1674 self.options.disable_channel_id,
David Benjaminf839f1c2018-10-16 06:01:29 +00001675 self.options.disable_extended_master_secret,
1676 self.options.simulate_tls13_downgrade,
1677 self.options.simulate_tls12_downgrade,
1678 self.options.tls_max_version)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001679 print 'HTTPS server started on https://%s:%d...' % \
1680 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001681 else:
1682 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001683 print 'HTTP server started on http://%s:%d...' % \
1684 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001685
1686 server.data_dir = self.__make_data_dir()
1687 server.file_root_url = self.options.file_root_url
1688 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001689 elif self.options.server_type == SERVER_WEBSOCKET:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001690 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1691 # is required to work correctly. It should be fixed from pywebsocket side.
1692 os.chdir(self.__make_data_dir())
1693 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00001694 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00001695 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00001696 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00001697 websocket_options.use_tls = True
Sergey Ulanovdd8b8ea2017-09-08 22:53:25 +00001698 key_path = os.path.join(ROOT_DIR, self.options.cert_and_key_file)
1699 if not os.path.isfile(key_path):
1700 raise testserver_base.OptionError(
1701 'specified server cert file not found: ' +
1702 self.options.cert_and_key_file + ' exiting...')
1703 websocket_options.private_key = key_path
1704 websocket_options.certificate = key_path
1705
mattm@chromium.org830a3712012-11-07 23:00:07 +00001706 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00001707 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00001708 websocket_options.tls_client_auth = True
1709 if len(self.options.ssl_client_ca) != 1:
1710 raise testserver_base.OptionError(
1711 'one trusted client CA file should be specified')
1712 if not os.path.isfile(self.options.ssl_client_ca[0]):
1713 raise testserver_base.OptionError(
1714 'specified trusted client CA file not found: ' +
1715 self.options.ssl_client_ca[0] + ' exiting...')
1716 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07001717 print 'Trying to start websocket server on %s://%s:%d...' % \
1718 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001719 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001720 print 'WebSocket server started on %s://%s:%d...' % \
1721 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001722 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00001723 websocket_options.use_basic_auth = self.options.ws_basic_auth
Adam Rice9476b8c2018-08-02 15:28:43 +00001724 elif self.options.server_type == SERVER_PROXY:
1725 ProxyRequestHandler.redirect_connect_to_localhost = \
1726 self.options.redirect_connect_to_localhost
1727 server = ThreadingHTTPServer((host, port), ProxyRequestHandler)
1728 print 'Proxy server started on port %d...' % server.server_port
1729 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001730 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
Adam Rice9476b8c2018-08-02 15:28:43 +00001731 ProxyRequestHandler.redirect_connect_to_localhost = \
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001732 self.options.redirect_connect_to_localhost
Adam Rice34b2e312018-04-06 16:48:30 +00001733 server = ThreadingHTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001734 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001735 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001736 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001737 raise testserver_base.OptionError('unknown server type' +
1738 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00001739
mattm@chromium.org830a3712012-11-07 23:00:07 +00001740 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001741
mattm@chromium.org830a3712012-11-07 23:00:07 +00001742 def add_options(self):
1743 testserver_base.TestServerRunner.add_options(self)
Adam Rice9476b8c2018-08-02 15:28:43 +00001744 self.option_parser.add_option('--proxy', action='store_const',
1745 const=SERVER_PROXY,
1746 default=SERVER_HTTP, dest='server_type',
1747 help='start up a proxy server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001748 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
1749 const=SERVER_BASIC_AUTH_PROXY,
1750 default=SERVER_HTTP, dest='server_type',
1751 help='start up a proxy server which requires '
1752 'basic authentication.')
1753 self.option_parser.add_option('--websocket', action='store_const',
1754 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
1755 dest='server_type',
1756 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001757 self.option_parser.add_option('--https', action='store_true',
1758 dest='https', help='Specify that https '
1759 'should be used.')
1760 self.option_parser.add_option('--cert-and-key-file',
1761 dest='cert_and_key_file', help='specify the '
1762 'path to the file containing the certificate '
1763 'and private key for the server in PEM '
1764 'format')
agl@chromium.orgdf778142013-07-31 21:57:28 +00001765 self.option_parser.add_option('--cert-serial', dest='cert_serial',
1766 default=0, type=int,
1767 help='If non-zero then the generated '
1768 'certificate will have this serial number')
Adam Langley1b622652017-12-05 23:57:33 +00001769 self.option_parser.add_option('--cert-common-name', dest='cert_common_name',
1770 default="127.0.0.1",
1771 help='The generated certificate will have '
1772 'this common name')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001773 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
1774 default='0', type='int',
1775 help='If nonzero, certain TLS connections '
1776 'will be aborted in order to test version '
1777 'fallback. 1 means all TLS versions will be '
1778 'aborted. 2 means TLS 1.1 or higher will be '
1779 'aborted. 3 means TLS 1.2 or higher will be '
davidbendf2e3862017-04-12 15:23:34 -07001780 'aborted. 4 means TLS 1.3 or higher will be '
mattm@chromium.org830a3712012-11-07 23:00:07 +00001781 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00001782 self.option_parser.add_option('--tls-intolerance-type',
1783 dest='tls_intolerance_type',
1784 default="alert",
1785 help='Controls how the server reacts to a '
1786 'TLS version it is intolerant to. Valid '
1787 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001788 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
1789 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00001790 default='',
1791 help='Base64 encoded SCT list. If set, '
1792 'server will respond with a '
1793 'signed_certificate_timestamp TLS extension '
1794 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00001795 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
1796 default=False, const=True,
1797 action='store_const',
1798 help='If given, TLS_FALLBACK_SCSV support '
1799 'will be enabled. This causes the server to '
1800 'reject fallback connections from compatible '
1801 'clients (e.g. Chrome).')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001802 self.option_parser.add_option('--ssl-client-auth', action='store_true',
1803 help='Require SSL client auth on every '
1804 'connection.')
1805 self.option_parser.add_option('--ssl-client-ca', action='append',
1806 default=[], help='Specify that the client '
1807 'certificate request should include the CA '
1808 'named in the subject of the DER-encoded '
1809 'certificate contained in the specified '
1810 'file. This option may appear multiple '
1811 'times, indicating multiple CA names should '
1812 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00001813 self.option_parser.add_option('--ssl-client-cert-type', action='append',
1814 default=[], help='Specify that the client '
1815 'certificate request should include the '
1816 'specified certificate_type value. This '
1817 'option may appear multiple times, '
1818 'indicating multiple values should be send '
1819 'in the request. Valid values are '
1820 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
1821 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001822 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
1823 help='Specify the bulk encryption '
1824 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08001825 'SSL server. Valid values are "aes128gcm", '
1826 '"aes256", "aes128", "3des", "rc4". If '
1827 'omitted, all algorithms will be used. This '
1828 'option may appear multiple times, '
1829 'indicating multiple algorithms should be '
1830 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00001831 self.option_parser.add_option('--ssl-key-exchange', action='append',
1832 help='Specify the key exchange algorithm(s)'
1833 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07001834 'Valid values are "rsa", "dhe_rsa", '
1835 '"ecdhe_rsa". If omitted, all algorithms '
1836 'will be used. This option may appear '
1837 'multiple times, indicating multiple '
1838 'algorithms should be enabled.');
bnc5fb33bd2016-08-05 12:09:21 -07001839 self.option_parser.add_option('--alpn-protocols', action='append',
1840 help='Specify the list of ALPN protocols. '
1841 'The server will not send an ALPN response '
1842 'if this list does not overlap with the '
1843 'list of protocols the client advertises.')
bnc609ad4c2015-10-02 05:11:24 -07001844 self.option_parser.add_option('--npn-protocols', action='append',
bnc5fb33bd2016-08-05 12:09:21 -07001845 help='Specify the list of protocols sent in '
bnc609ad4c2015-10-02 05:11:24 -07001846 'an NPN response. The server will not'
1847 'support NPN if the list is empty.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001848 self.option_parser.add_option('--file-root-url', default='/files/',
1849 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00001850 # TODO(ricea): Generalize this to support basic auth for HTTP too.
1851 self.option_parser.add_option('--ws-basic-auth', action='store_true',
1852 dest='ws_basic_auth',
1853 help='Enable basic-auth for WebSocket')
davidben21cda342015-03-17 18:04:28 -07001854 self.option_parser.add_option('--alert-after-handshake',
1855 dest='alert_after_handshake',
1856 default=False, action='store_true',
1857 help='If set, the server will send a fatal '
1858 'alert immediately after the handshake.')
nharper1e8bf4b2015-09-18 12:23:02 -07001859 self.option_parser.add_option('--disable-channel-id', action='store_true')
1860 self.option_parser.add_option('--disable-extended-master-secret',
1861 action='store_true')
David Benjaminf839f1c2018-10-16 06:01:29 +00001862 self.option_parser.add_option('--simulate-tls13-downgrade',
1863 action='store_true')
1864 self.option_parser.add_option('--simulate-tls12-downgrade',
1865 action='store_true')
1866 self.option_parser.add_option('--tls-max-version', default='0', type='int',
1867 help='If non-zero, the maximum TLS version '
1868 'to support. 1 means TLS 1.0, 2 means '
1869 'TLS 1.1, and 3 means TLS 1.2.')
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001870 self.option_parser.add_option('--redirect-connect-to-localhost',
1871 dest='redirect_connect_to_localhost',
1872 default=False, action='store_true',
1873 help='If set, the Proxy server will connect '
1874 'to localhost instead of the requested URL '
1875 'on CONNECT requests')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001876
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00001877
initial.commit94958cf2008-07-26 22:42:52 +00001878if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00001879 sys.exit(ServerRunner().main())