blob: 6ff2279aa2a99ada6cf54c227b1488cb608718b3 [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
agl@chromium.orgf9e66792011-12-12 22:22:19 +000089class RecordingSSLSessionCache(object):
90 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
91 lookups and inserts in order to test session cache behaviours."""
92
93 def __init__(self):
94 self.log = []
95
96 def __getitem__(self, sessionID):
97 self.log.append(('lookup', sessionID))
98 raise KeyError()
99
100 def __setitem__(self, sessionID, session):
101 self.log.append(('insert', sessionID))
102
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000103
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000104class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
105 testserver_base.BrokenPipeHandlerMixIn,
106 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000107 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000108 verification."""
109
110 pass
111
Adam Rice34b2e312018-04-06 16:48:30 +0000112class ThreadingHTTPServer(SocketServer.ThreadingMixIn,
113 HTTPServer):
114 """This variant of HTTPServer creates a new thread for every connection. It
115 should only be used with handlers that are known to be threadsafe."""
116
117 pass
118
mattm@chromium.org830a3712012-11-07 23:00:07 +0000119
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000120class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000121 testserver_base.ClientRestrictingServerMixIn,
122 testserver_base.BrokenPipeHandlerMixIn,
123 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000124 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000125 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000126
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000127 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000128 ssl_client_auth, ssl_client_cas, ssl_client_cert_types,
bnc5fb33bd2016-08-05 12:09:21 -0700129 ssl_bulk_ciphers, ssl_key_exchanges, alpn_protocols,
130 npn_protocols, record_resume_info, tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000131 tls_intolerance_type, signed_cert_timestamps,
davidben21cda342015-03-17 18:04:28 -0700132 fallback_scsv_enabled, ocsp_response,
David Benjaminf839f1c2018-10-16 06:01:29 +0000133 alert_after_handshake, disable_channel_id, disable_ems,
134 simulate_tls13_downgrade, simulate_tls12_downgrade,
135 tls_max_version):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000136 self.cert_chain = tlslite.api.X509CertChain()
137 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000138 # Force using only python implementation - otherwise behavior is different
139 # depending on whether m2crypto Python module is present (error is thrown
140 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
141 # the hood.
142 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
143 private=True,
144 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000145 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000146 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000147 self.ssl_client_cert_types = []
bnc609ad4c2015-10-02 05:11:24 -0700148 self.npn_protocols = npn_protocols
ekasper@google.com24aa8222013-11-28 13:43:26 +0000149 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000150 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000151 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000152
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000153 if ssl_client_auth:
154 for ca_file in ssl_client_cas:
155 s = open(ca_file).read()
156 x509 = tlslite.api.X509()
157 x509.parse(s)
158 self.ssl_client_cas.append(x509.subject)
159
160 for cert_type in ssl_client_cert_types:
161 self.ssl_client_cert_types.append({
162 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000163 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
164 }[cert_type])
165
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000166 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800167 # Enable SSLv3 for testing purposes.
168 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000169 if ssl_bulk_ciphers is not None:
170 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000171 if ssl_key_exchanges is not None:
172 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000173 if tls_intolerant != 0:
174 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
175 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
davidben21cda342015-03-17 18:04:28 -0700176 if alert_after_handshake:
177 self.ssl_handshake_settings.alertAfterHandshake = True
nharper1e8bf4b2015-09-18 12:23:02 -0700178 if disable_channel_id:
179 self.ssl_handshake_settings.enableChannelID = False
180 if disable_ems:
181 self.ssl_handshake_settings.enableExtendedMasterSecret = False
David Benjaminf839f1c2018-10-16 06:01:29 +0000182 if simulate_tls13_downgrade:
183 self.ssl_handshake_settings.simulateTLS13Downgrade = True
184 if simulate_tls12_downgrade:
185 self.ssl_handshake_settings.simulateTLS12Downgrade = True
186 if tls_max_version != 0:
187 self.ssl_handshake_settings.maxVersion = (3, tls_max_version)
bnc5fb33bd2016-08-05 12:09:21 -0700188 self.ssl_handshake_settings.alpnProtos=alpn_protocols;
initial.commit94958cf2008-07-26 22:42:52 +0000189
rsleevi8146efa2015-03-16 12:31:24 -0700190 if record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000191 # If record_resume_info is true then we'll replace the session cache with
192 # an object that records the lookups and inserts that it sees.
193 self.session_cache = RecordingSSLSessionCache()
194 else:
195 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000196 testserver_base.StoppableHTTPServer.__init__(self,
197 server_address,
198 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000199
200 def handshake(self, tlsConnection):
201 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000202
initial.commit94958cf2008-07-26 22:42:52 +0000203 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000204 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000205 tlsConnection.handshakeServer(certChain=self.cert_chain,
206 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000207 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000208 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000209 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000210 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000211 reqCertTypes=self.ssl_client_cert_types,
bnc609ad4c2015-10-02 05:11:24 -0700212 nextProtos=self.npn_protocols,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000213 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000214 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000215 fallbackSCSV=self.fallback_scsv_enabled,
216 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000217 tlsConnection.ignoreAbruptClose = True
218 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000219 except tlslite.api.TLSAbruptCloseError:
220 # Ignore abrupt close.
221 return True
initial.commit94958cf2008-07-26 22:42:52 +0000222 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000223 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000224 return False
225
akalin@chromium.org154bb132010-11-12 02:20:27 +0000226
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000227class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000228 # Class variables to allow for persistence state between page handler
229 # invocations
230 rst_limits = {}
231 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000232
233 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000234 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000235 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000236 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000237 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000238 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000239 self.NoCacheMaxAgeTimeHandler,
240 self.NoCacheTimeHandler,
241 self.CacheTimeHandler,
242 self.CacheExpiresHandler,
243 self.CacheProxyRevalidateHandler,
244 self.CachePrivateHandler,
245 self.CachePublicHandler,
246 self.CacheSMaxAgeHandler,
247 self.CacheMustRevalidateHandler,
248 self.CacheMustRevalidateMaxAgeHandler,
249 self.CacheNoStoreHandler,
250 self.CacheNoStoreMaxAgeHandler,
251 self.CacheNoTransformHandler,
252 self.DownloadHandler,
253 self.DownloadFinishHandler,
254 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000255 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000256 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000257 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000258 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000259 self.SetCookieHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000260 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000261 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000262 self.AuthBasicHandler,
263 self.AuthDigestHandler,
264 self.SlowServerHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000265 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000266 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700267 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000268 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000269 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000270 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000271 self.GetChannelID,
pneubeckfd4f0442015-08-07 04:55:10 -0700272 self.GetClientCert,
davidben599e7e72014-09-03 16:19:09 -0700273 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000274 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000275 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000276 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000277 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000278 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000279 self.PostOnlyFileHandler,
280 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000281 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000282 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000283 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000284 head_handlers = [
285 self.FileHandler,
286 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000287
maruel@google.come250a9b2009-03-10 17:39:46 +0000288 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000289 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000290 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000291 'gif': 'image/gif',
292 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000293 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700294 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000295 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000296 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000297 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000298 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000299 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000300 }
initial.commit94958cf2008-07-26 22:42:52 +0000301 self._default_mime_type = 'text/html'
302
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000303 testserver_base.BasePageHandler.__init__(self, request, client_address,
304 socket_server, connect_handlers,
305 get_handlers, head_handlers,
306 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000307
initial.commit94958cf2008-07-26 22:42:52 +0000308 def GetMIMETypeFromName(self, file_name):
309 """Returns the mime type for the specified file_name. So far it only looks
310 at the file extension."""
311
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000312 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000313 if len(extension) == 0:
314 # no extension.
315 return self._default_mime_type
316
ericroman@google.comc17ca532009-05-07 03:51:05 +0000317 # extension starts with a dot, so we need to remove it
318 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000319
initial.commit94958cf2008-07-26 22:42:52 +0000320 def NoCacheMaxAgeTimeHandler(self):
321 """This request handler yields a page with the title set to the current
322 system time, and no caching requested."""
323
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000324 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000325 return False
326
327 self.send_response(200)
328 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000329 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000330 self.end_headers()
331
maruel@google.come250a9b2009-03-10 17:39:46 +0000332 self.wfile.write('<html><head><title>%s</title></head></html>' %
333 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000334
335 return True
336
337 def NoCacheTimeHandler(self):
338 """This request handler yields a page with the title set to the current
339 system time, and no caching requested."""
340
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000341 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000342 return False
343
344 self.send_response(200)
345 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000346 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000347 self.end_headers()
348
maruel@google.come250a9b2009-03-10 17:39:46 +0000349 self.wfile.write('<html><head><title>%s</title></head></html>' %
350 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000351
352 return True
353
354 def CacheTimeHandler(self):
355 """This request handler yields a page with the title set to the current
356 system time, and allows caching for one minute."""
357
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000358 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000359 return False
360
361 self.send_response(200)
362 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000363 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000364 self.end_headers()
365
maruel@google.come250a9b2009-03-10 17:39:46 +0000366 self.wfile.write('<html><head><title>%s</title></head></html>' %
367 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000368
369 return True
370
371 def CacheExpiresHandler(self):
372 """This request handler yields a page with the title set to the current
373 system time, and set the page to expire on 1 Jan 2099."""
374
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000375 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000376 return False
377
378 self.send_response(200)
379 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000380 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000381 self.end_headers()
382
maruel@google.come250a9b2009-03-10 17:39:46 +0000383 self.wfile.write('<html><head><title>%s</title></head></html>' %
384 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000385
386 return True
387
388 def CacheProxyRevalidateHandler(self):
389 """This request handler yields a page with the title set to the current
390 system time, and allows caching for 60 seconds"""
391
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000392 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000393 return False
394
395 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000396 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000397 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
398 self.end_headers()
399
maruel@google.come250a9b2009-03-10 17:39:46 +0000400 self.wfile.write('<html><head><title>%s</title></head></html>' %
401 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000402
403 return True
404
405 def CachePrivateHandler(self):
406 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700407 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000408
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000409 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000410 return False
411
412 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000413 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000414 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000415 self.end_headers()
416
maruel@google.come250a9b2009-03-10 17:39:46 +0000417 self.wfile.write('<html><head><title>%s</title></head></html>' %
418 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000419
420 return True
421
422 def CachePublicHandler(self):
423 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700424 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000425
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000426 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000427 return False
428
429 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000430 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000431 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000432 self.end_headers()
433
maruel@google.come250a9b2009-03-10 17:39:46 +0000434 self.wfile.write('<html><head><title>%s</title></head></html>' %
435 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000436
437 return True
438
439 def CacheSMaxAgeHandler(self):
440 """This request handler yields a page with the title set to the current
441 system time, and does not allow for caching."""
442
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000443 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000444 return False
445
446 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000447 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000448 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
449 self.end_headers()
450
maruel@google.come250a9b2009-03-10 17:39:46 +0000451 self.wfile.write('<html><head><title>%s</title></head></html>' %
452 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000453
454 return True
455
456 def CacheMustRevalidateHandler(self):
457 """This request handler yields a page with the title set to the current
458 system time, and does not allow caching."""
459
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000460 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000461 return False
462
463 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000464 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000465 self.send_header('Cache-Control', 'must-revalidate')
466 self.end_headers()
467
maruel@google.come250a9b2009-03-10 17:39:46 +0000468 self.wfile.write('<html><head><title>%s</title></head></html>' %
469 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000470
471 return True
472
473 def CacheMustRevalidateMaxAgeHandler(self):
474 """This request handler yields a page with the title set to the current
475 system time, and does not allow caching event though max-age of 60
476 seconds is specified."""
477
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000478 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000479 return False
480
481 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000482 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000483 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
484 self.end_headers()
485
maruel@google.come250a9b2009-03-10 17:39:46 +0000486 self.wfile.write('<html><head><title>%s</title></head></html>' %
487 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000488
489 return True
490
initial.commit94958cf2008-07-26 22:42:52 +0000491 def CacheNoStoreHandler(self):
492 """This request handler yields a page with the title set to the current
493 system time, and does not allow the page to be stored."""
494
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000495 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000496 return False
497
498 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000499 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000500 self.send_header('Cache-Control', 'no-store')
501 self.end_headers()
502
maruel@google.come250a9b2009-03-10 17:39:46 +0000503 self.wfile.write('<html><head><title>%s</title></head></html>' %
504 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000505
506 return True
507
508 def CacheNoStoreMaxAgeHandler(self):
509 """This request handler yields a page with the title set to the current
510 system time, and does not allow the page to be stored even though max-age
511 of 60 seconds is specified."""
512
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000513 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000514 return False
515
516 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000517 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000518 self.send_header('Cache-Control', 'max-age=60, no-store')
519 self.end_headers()
520
maruel@google.come250a9b2009-03-10 17:39:46 +0000521 self.wfile.write('<html><head><title>%s</title></head></html>' %
522 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000523
524 return True
525
526
527 def CacheNoTransformHandler(self):
528 """This request handler yields a page with the title set to the current
529 system time, and does not allow the content to transformed during
530 user-agent caching"""
531
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000532 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000533 return False
534
535 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000536 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000537 self.send_header('Cache-Control', 'no-transform')
538 self.end_headers()
539
maruel@google.come250a9b2009-03-10 17:39:46 +0000540 self.wfile.write('<html><head><title>%s</title></head></html>' %
541 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000542
543 return True
544
545 def EchoHeader(self):
546 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000547
ananta@chromium.org219b2062009-10-23 16:09:41 +0000548 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000549
ananta@chromium.org56812d02011-04-07 17:52:05 +0000550 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000551 """This function echoes back the value of a specific request header while
Eric Romanf6a38402018-02-14 20:19:53 +0000552 allowing caching for 10 hours."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000553
ananta@chromium.org56812d02011-04-07 17:52:05 +0000554 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000555
556 def EchoHeaderHelper(self, echo_header):
557 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000558
ananta@chromium.org219b2062009-10-23 16:09:41 +0000559 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000560 return False
561
562 query_char = self.path.find('?')
563 if query_char != -1:
564 header_name = self.path[query_char+1:]
565
566 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000567 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000568 if echo_header == '/echoheadercache':
569 self.send_header('Cache-control', 'max-age=60000')
570 else:
571 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000572 # insert a vary header to properly indicate that the cachability of this
573 # request is subject to value of the request header being echoed.
574 if len(header_name) > 0:
575 self.send_header('Vary', header_name)
576 self.end_headers()
577
578 if len(header_name) > 0:
579 self.wfile.write(self.headers.getheader(header_name))
580
581 return True
582
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000583 def ReadRequestBody(self):
584 """This function reads the body of the current HTTP request, handling
585 both plain and chunked transfer encoded requests."""
586
587 if self.headers.getheader('transfer-encoding') != 'chunked':
588 length = int(self.headers.getheader('content-length'))
589 return self.rfile.read(length)
590
591 # Read the request body as chunks.
592 body = ""
593 while True:
594 line = self.rfile.readline()
595 length = int(line, 16)
596 if length == 0:
597 self.rfile.readline()
598 break
599 body += self.rfile.read(length)
600 self.rfile.read(2)
601 return body
602
initial.commit94958cf2008-07-26 22:42:52 +0000603 def EchoHandler(self):
604 """This handler just echoes back the payload of the request, for testing
605 form submission."""
606
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000607 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000608 return False
609
hirono2838c572015-01-21 12:18:11 -0800610 _, _, _, _, query, _ = urlparse.urlparse(self.path)
611 query_params = cgi.parse_qs(query, True)
612 if 'status' in query_params:
613 self.send_response(int(query_params['status'][0]))
614 else:
615 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000616 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000617 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000618 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000619 return True
620
621 def EchoTitleHandler(self):
622 """This handler is like Echo, but sets the page title to the request."""
623
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000624 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000625 return False
626
627 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000628 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000629 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000630 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000631 self.wfile.write('<html><head><title>')
632 self.wfile.write(request)
633 self.wfile.write('</title></head></html>')
634 return True
635
636 def EchoAllHandler(self):
637 """This handler yields a (more) human-readable page listing information
638 about the request header & contents."""
639
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000640 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000641 return False
642
643 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000644 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000645 self.end_headers()
646 self.wfile.write('<html><head><style>'
647 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
648 '</style></head><body>'
649 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000650 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000651 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000652
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000653 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000654 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000655 params = cgi.parse_qs(qs, keep_blank_values=1)
656
657 for param in params:
658 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000659
660 self.wfile.write('</pre>')
661
662 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
663
664 self.wfile.write('</body></html>')
665 return True
666
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000667 def EchoMultipartPostHandler(self):
668 """This handler echoes received multipart post data as json format."""
669
670 if not (self._ShouldHandleRequest("/echomultipartpost") or
671 self._ShouldHandleRequest("/searchbyimage")):
672 return False
673
674 content_type, parameters = cgi.parse_header(
675 self.headers.getheader('content-type'))
676 if content_type == 'multipart/form-data':
677 post_multipart = cgi.parse_multipart(self.rfile, parameters)
678 elif content_type == 'application/x-www-form-urlencoded':
679 raise Exception('POST by application/x-www-form-urlencoded is '
680 'not implemented.')
681 else:
682 post_multipart = {}
683
684 # Since the data can be binary, we encode them by base64.
685 post_multipart_base64_encoded = {}
686 for field, values in post_multipart.items():
687 post_multipart_base64_encoded[field] = [base64.b64encode(value)
688 for value in values]
689
690 result = {'POST_multipart' : post_multipart_base64_encoded}
691
692 self.send_response(200)
693 self.send_header("Content-type", "text/plain")
694 self.end_headers()
695 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
696 return True
697
initial.commit94958cf2008-07-26 22:42:52 +0000698 def DownloadHandler(self):
699 """This handler sends a downloadable file with or without reporting
700 the size (6K)."""
701
702 if self.path.startswith("/download-unknown-size"):
703 send_length = False
704 elif self.path.startswith("/download-known-size"):
705 send_length = True
706 else:
707 return False
708
709 #
710 # The test which uses this functionality is attempting to send
711 # small chunks of data to the client. Use a fairly large buffer
712 # so that we'll fill chrome's IO buffer enough to force it to
713 # actually write the data.
714 # See also the comments in the client-side of this test in
715 # download_uitest.cc
716 #
717 size_chunk1 = 35*1024
718 size_chunk2 = 10*1024
719
720 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000721 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000722 self.send_header('Cache-Control', 'max-age=0')
723 if send_length:
724 self.send_header('Content-Length', size_chunk1 + size_chunk2)
725 self.end_headers()
726
727 # First chunk of data:
728 self.wfile.write("*" * size_chunk1)
729 self.wfile.flush()
730
731 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000732 self.server.wait_for_download = True
733 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000734 self.server.handle_request()
735
736 # Second chunk of data:
737 self.wfile.write("*" * size_chunk2)
738 return True
739
740 def DownloadFinishHandler(self):
741 """This handler just tells the server to finish the current download."""
742
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000743 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000744 return False
745
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000746 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000747 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000748 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000749 self.send_header('Cache-Control', 'max-age=0')
750 self.end_headers()
751 return True
752
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000753 def _ReplaceFileData(self, data, query_parameters):
754 """Replaces matching substrings in a file.
755
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000756 If the 'replace_text' URL query parameter is present, it is expected to be
757 of the form old_text:new_text, which indicates that any old_text strings in
758 the file are replaced with new_text. Multiple 'replace_text' parameters may
759 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000760
761 If the parameters are not present, |data| is returned.
762 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000763
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000764 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000765 replace_text_values = query_dict.get('replace_text', [])
766 for replace_text_value in replace_text_values:
767 replace_text_args = replace_text_value.split(':')
768 if len(replace_text_args) != 2:
769 raise ValueError(
770 'replace_text must be of form old_text:new_text. Actual value: %s' %
771 replace_text_value)
772 old_text_b64, new_text_b64 = replace_text_args
773 old_text = base64.urlsafe_b64decode(old_text_b64)
774 new_text = base64.urlsafe_b64decode(new_text_b64)
775 data = data.replace(old_text, new_text)
776 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000777
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000778 def ZipFileHandler(self):
779 """This handler sends the contents of the requested file in compressed form.
780 Can pass in a parameter that specifies that the content length be
781 C - the compressed size (OK),
782 U - the uncompressed size (Non-standard, but handled),
783 S - less than compressed (OK because we keep going),
784 M - larger than compressed but less than uncompressed (an error),
785 L - larger than uncompressed (an error)
786 Example: compressedfiles/Picture_1.doc?C
787 """
788
789 prefix = "/compressedfiles/"
790 if not self.path.startswith(prefix):
791 return False
792
793 # Consume a request body if present.
794 if self.command == 'POST' or self.command == 'PUT' :
795 self.ReadRequestBody()
796
797 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
798
799 if not query in ('C', 'U', 'S', 'M', 'L'):
800 return False
801
802 sub_path = url_path[len(prefix):]
803 entries = sub_path.split('/')
804 file_path = os.path.join(self.server.data_dir, *entries)
805 if os.path.isdir(file_path):
806 file_path = os.path.join(file_path, 'index.html')
807
808 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000809 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000810 self.send_error(404)
811 return True
812
813 f = open(file_path, "rb")
814 data = f.read()
815 uncompressed_len = len(data)
816 f.close()
817
818 # Compress the data.
819 data = zlib.compress(data)
820 compressed_len = len(data)
821
822 content_length = compressed_len
823 if query == 'U':
824 content_length = uncompressed_len
825 elif query == 'S':
826 content_length = compressed_len / 2
827 elif query == 'M':
828 content_length = (compressed_len + uncompressed_len) / 2
829 elif query == 'L':
830 content_length = compressed_len + uncompressed_len
831
832 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000833 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000834 self.send_header('Content-encoding', 'deflate')
835 self.send_header('Connection', 'close')
836 self.send_header('Content-Length', content_length)
837 self.send_header('ETag', '\'' + file_path + '\'')
838 self.end_headers()
839
840 self.wfile.write(data)
841
842 return True
843
initial.commit94958cf2008-07-26 22:42:52 +0000844 def FileHandler(self):
845 """This handler sends the contents of the requested file. Wow, it's like
846 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000847
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000848 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000849 if not self.path.startswith(prefix):
850 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000851 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000852
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000853 def PostOnlyFileHandler(self):
854 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000855
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000856 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000857 if not self.path.startswith(prefix):
858 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000859 return self._FileHandlerHelper(prefix)
860
861 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000862 request_body = ''
863 if self.command == 'POST' or self.command == 'PUT':
864 # Consume a request body if present.
865 request_body = self.ReadRequestBody()
866
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000867 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000868 query_dict = cgi.parse_qs(query)
869
870 expected_body = query_dict.get('expected_body', [])
871 if expected_body and request_body not in expected_body:
872 self.send_response(404)
873 self.end_headers()
874 self.wfile.write('')
875 return True
876
877 expected_headers = query_dict.get('expected_headers', [])
878 for expected_header in expected_headers:
879 header_name, expected_value = expected_header.split(':')
880 if self.headers.getheader(header_name) != expected_value:
881 self.send_response(404)
882 self.end_headers()
883 self.wfile.write('')
884 return True
885
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000886 sub_path = url_path[len(prefix):]
887 entries = sub_path.split('/')
888 file_path = os.path.join(self.server.data_dir, *entries)
889 if os.path.isdir(file_path):
890 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000891
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000892 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000893 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000894 self.send_error(404)
895 return True
896
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000897 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000898 data = f.read()
899 f.close()
900
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000901 data = self._ReplaceFileData(data, query)
902
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000903 old_protocol_version = self.protocol_version
904
initial.commit94958cf2008-07-26 22:42:52 +0000905 # If file.mock-http-headers exists, it contains the headers we
906 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000907 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000908 if os.path.isfile(headers_path):
909 f = open(headers_path, "r")
910
911 # "HTTP/1.1 200 OK"
912 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000913 http_major, http_minor, status_code = re.findall(
914 'HTTP/(\d+).(\d+) (\d+)', response)[0]
915 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000916 self.send_response(int(status_code))
917
918 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000919 header_values = re.findall('(\S+):\s*(.*)', line)
920 if len(header_values) > 0:
921 # "name: value"
922 name, value = header_values[0]
923 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000924 f.close()
925 else:
926 # Could be more generic once we support mime-type sniffing, but for
927 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000928
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000929 range_header = self.headers.get('Range')
930 if range_header and range_header.startswith('bytes='):
931 # Note this doesn't handle all valid byte range_header values (i.e.
932 # left open ended ones), just enough for what we needed so far.
933 range_header = range_header[6:].split('-')
934 start = int(range_header[0])
935 if range_header[1]:
936 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000937 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000938 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000939
940 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000941 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
942 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000943 self.send_header('Content-Range', content_range)
944 data = data[start: end + 1]
945 else:
946 self.send_response(200)
947
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000948 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000949 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000950 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000951 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000952 self.end_headers()
953
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000954 if (self.command != 'HEAD'):
955 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000956
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000957 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000958 return True
959
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000960 def SetCookieHandler(self):
961 """This handler just sets a cookie, for testing cookie handling."""
962
963 if not self._ShouldHandleRequest("/set-cookie"):
964 return False
965
966 query_char = self.path.find('?')
967 if query_char != -1:
968 cookie_values = self.path[query_char + 1:].split('&')
969 else:
970 cookie_values = ("",)
971 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000972 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000973 for cookie_value in cookie_values:
974 self.send_header('Set-Cookie', '%s' % cookie_value)
975 self.end_headers()
976 for cookie_value in cookie_values:
977 self.wfile.write('%s' % cookie_value)
978 return True
979
mattm@chromium.org983fc462012-06-30 00:52:08 +0000980 def ExpectAndSetCookieHandler(self):
981 """Expects some cookies to be sent, and if they are, sets more cookies.
982
983 The expect parameter specifies a required cookie. May be specified multiple
984 times.
985 The set parameter specifies a cookie to set if all required cookies are
986 preset. May be specified multiple times.
987 The data parameter specifies the response body data to be returned."""
988
989 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
990 return False
991
992 _, _, _, _, query, _ = urlparse.urlparse(self.path)
993 query_dict = cgi.parse_qs(query)
994 cookies = set()
995 if 'Cookie' in self.headers:
996 cookie_header = self.headers.getheader('Cookie')
997 cookies.update([s.strip() for s in cookie_header.split(';')])
998 got_all_expected_cookies = True
999 for expected_cookie in query_dict.get('expect', []):
1000 if expected_cookie not in cookies:
1001 got_all_expected_cookies = False
1002 self.send_response(200)
1003 self.send_header('Content-Type', 'text/html')
1004 if got_all_expected_cookies:
1005 for cookie_value in query_dict.get('set', []):
1006 self.send_header('Set-Cookie', '%s' % cookie_value)
1007 self.end_headers()
1008 for data_value in query_dict.get('data', []):
1009 self.wfile.write(data_value)
1010 return True
1011
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001012 def SetHeaderHandler(self):
1013 """This handler sets a response header. Parameters are in the
1014 key%3A%20value&key2%3A%20value2 format."""
1015
1016 if not self._ShouldHandleRequest("/set-header"):
1017 return False
1018
1019 query_char = self.path.find('?')
1020 if query_char != -1:
1021 headers_values = self.path[query_char + 1:].split('&')
1022 else:
1023 headers_values = ("",)
1024 self.send_response(200)
1025 self.send_header('Content-Type', 'text/html')
1026 for header_value in headers_values:
1027 header_value = urllib.unquote(header_value)
1028 (key, value) = header_value.split(': ', 1)
1029 self.send_header(key, value)
1030 self.end_headers()
1031 for header_value in headers_values:
1032 self.wfile.write('%s' % header_value)
1033 return True
1034
initial.commit94958cf2008-07-26 22:42:52 +00001035 def AuthBasicHandler(self):
1036 """This handler tests 'Basic' authentication. It just sends a page with
1037 title 'user/pass' if you succeed."""
1038
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001039 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001040 return False
1041
1042 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001043 expected_password = 'secret'
1044 realm = 'testrealm'
1045 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001046
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001047 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1048 query_params = cgi.parse_qs(query, True)
1049 if 'set-cookie-if-challenged' in query_params:
1050 set_cookie_if_challenged = True
1051 if 'password' in query_params:
1052 expected_password = query_params['password'][0]
1053 if 'realm' in query_params:
1054 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001055
initial.commit94958cf2008-07-26 22:42:52 +00001056 auth = self.headers.getheader('authorization')
1057 try:
1058 if not auth:
1059 raise Exception('no auth')
1060 b64str = re.findall(r'Basic (\S+)', auth)[0]
1061 userpass = base64.b64decode(b64str)
1062 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001063 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001064 raise Exception('wrong password')
1065 except Exception, e:
1066 # Authentication failed.
1067 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001068 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001069 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001070 if set_cookie_if_challenged:
1071 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001072 self.end_headers()
1073 self.wfile.write('<html><head>')
1074 self.wfile.write('<title>Denied: %s</title>' % e)
1075 self.wfile.write('</head><body>')
1076 self.wfile.write('auth=%s<p>' % auth)
1077 self.wfile.write('b64str=%s<p>' % b64str)
1078 self.wfile.write('username: %s<p>' % username)
1079 self.wfile.write('userpass: %s<p>' % userpass)
1080 self.wfile.write('password: %s<p>' % password)
1081 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1082 self.wfile.write('</body></html>')
1083 return True
1084
1085 # Authentication successful. (Return a cachable response to allow for
1086 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001087 old_protocol_version = self.protocol_version
1088 self.protocol_version = "HTTP/1.1"
1089
initial.commit94958cf2008-07-26 22:42:52 +00001090 if_none_match = self.headers.getheader('if-none-match')
1091 if if_none_match == "abc":
1092 self.send_response(304)
1093 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001094 elif url_path.endswith(".gif"):
1095 # Using chrome/test/data/google/logo.gif as the test image
1096 test_image_path = ['google', 'logo.gif']
1097 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1098 if not os.path.isfile(gif_path):
1099 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001100 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001101 return True
1102
1103 f = open(gif_path, "rb")
1104 data = f.read()
1105 f.close()
1106
1107 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001108 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001109 self.send_header('Cache-control', 'max-age=60000')
1110 self.send_header('Etag', 'abc')
1111 self.end_headers()
1112 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001113 else:
1114 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001115 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001116 self.send_header('Cache-control', 'max-age=60000')
1117 self.send_header('Etag', 'abc')
1118 self.end_headers()
1119 self.wfile.write('<html><head>')
1120 self.wfile.write('<title>%s/%s</title>' % (username, password))
1121 self.wfile.write('</head><body>')
1122 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001123 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001124 self.wfile.write('</body></html>')
1125
rvargas@google.com54453b72011-05-19 01:11:11 +00001126 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001127 return True
1128
tonyg@chromium.org75054202010-03-31 22:06:10 +00001129 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001130 """Returns a nonce that's stable per request path for the server's lifetime.
1131 This is a fake implementation. A real implementation would only use a given
1132 nonce a single time (hence the name n-once). However, for the purposes of
1133 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001134
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001135 Args:
1136 force_reset: Iff set, the nonce will be changed. Useful for testing the
1137 "stale" response.
1138 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001139
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001140 if force_reset or not self.server.nonce_time:
1141 self.server.nonce_time = time.time()
1142 return hashlib.md5('privatekey%s%d' %
1143 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001144
1145 def AuthDigestHandler(self):
1146 """This handler tests 'Digest' authentication.
1147
1148 It just sends a page with title 'user/pass' if you succeed.
1149
1150 A stale response is sent iff "stale" is present in the request path.
1151 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001152
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001153 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001154 return False
1155
tonyg@chromium.org75054202010-03-31 22:06:10 +00001156 stale = 'stale' in self.path
1157 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001158 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001159 password = 'secret'
1160 realm = 'testrealm'
1161
1162 auth = self.headers.getheader('authorization')
1163 pairs = {}
1164 try:
1165 if not auth:
1166 raise Exception('no auth')
1167 if not auth.startswith('Digest'):
1168 raise Exception('not digest')
1169 # Pull out all the name="value" pairs as a dictionary.
1170 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1171
1172 # Make sure it's all valid.
1173 if pairs['nonce'] != nonce:
1174 raise Exception('wrong nonce')
1175 if pairs['opaque'] != opaque:
1176 raise Exception('wrong opaque')
1177
1178 # Check the 'response' value and make sure it matches our magic hash.
1179 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001180 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001181 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001182 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001183 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001184 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001185 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1186 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001187 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001188
1189 if pairs['response'] != response:
1190 raise Exception('wrong password')
1191 except Exception, e:
1192 # Authentication failed.
1193 self.send_response(401)
1194 hdr = ('Digest '
1195 'realm="%s", '
1196 'domain="/", '
1197 'qop="auth", '
1198 'algorithm=MD5, '
1199 'nonce="%s", '
1200 'opaque="%s"') % (realm, nonce, opaque)
1201 if stale:
1202 hdr += ', stale="TRUE"'
1203 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001204 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001205 self.end_headers()
1206 self.wfile.write('<html><head>')
1207 self.wfile.write('<title>Denied: %s</title>' % e)
1208 self.wfile.write('</head><body>')
1209 self.wfile.write('auth=%s<p>' % auth)
1210 self.wfile.write('pairs=%s<p>' % pairs)
1211 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1212 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1213 self.wfile.write('</body></html>')
1214 return True
1215
1216 # Authentication successful.
1217 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001218 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001219 self.end_headers()
1220 self.wfile.write('<html><head>')
1221 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1222 self.wfile.write('</head><body>')
1223 self.wfile.write('auth=%s<p>' % auth)
1224 self.wfile.write('pairs=%s<p>' % pairs)
1225 self.wfile.write('</body></html>')
1226
1227 return True
1228
1229 def SlowServerHandler(self):
1230 """Wait for the user suggested time before responding. The syntax is
1231 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001232
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001233 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001234 return False
1235 query_char = self.path.find('?')
1236 wait_sec = 1.0
1237 if query_char >= 0:
1238 try:
davidben05f82202015-03-31 13:48:07 -07001239 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001240 except ValueError:
1241 pass
1242 time.sleep(wait_sec)
1243 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001244 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001245 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001246 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001247 return True
1248
creis@google.com2f4f6a42011-03-25 19:44:19 +00001249 def NoContentHandler(self):
1250 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001251
creis@google.com2f4f6a42011-03-25 19:44:19 +00001252 if not self._ShouldHandleRequest("/nocontent"):
1253 return False
1254 self.send_response(204)
1255 self.end_headers()
1256 return True
1257
initial.commit94958cf2008-07-26 22:42:52 +00001258 def ServerRedirectHandler(self):
1259 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001260 '/server-redirect?http://foo.bar/asdf' to redirect to
1261 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001262
1263 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001264 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001265 return False
1266
1267 query_char = self.path.find('?')
1268 if query_char < 0 or len(self.path) <= query_char + 1:
1269 self.sendRedirectHelp(test_name)
1270 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001271 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001272
1273 self.send_response(301) # moved permanently
1274 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001275 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001276 self.end_headers()
1277 self.wfile.write('<html><head>')
1278 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1279
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001280 return True
initial.commit94958cf2008-07-26 22:42:52 +00001281
naskoe7a0d0d2014-09-29 08:53:05 -07001282 def CrossSiteRedirectHandler(self):
1283 """Sends a server redirect to the given site. The syntax is
1284 '/cross-site/hostname/...' to redirect to //hostname/...
1285 It is used to navigate between different Sites, causing
1286 cross-site/cross-process navigations in the browser."""
1287
1288 test_name = "/cross-site"
1289 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001290 return False
1291
1292 params = urllib.unquote(self.path[(len(test_name) + 1):])
1293 slash = params.find('/')
1294 if slash < 0:
1295 self.sendRedirectHelp(test_name)
1296 return True
1297
1298 host = params[:slash]
1299 path = params[(slash+1):]
1300 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1301
1302 self.send_response(301) # moved permanently
1303 self.send_header('Location', dest)
1304 self.send_header('Content-Type', 'text/html')
1305 self.end_headers()
1306 self.wfile.write('<html><head>')
1307 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1308
1309 return True
1310
initial.commit94958cf2008-07-26 22:42:52 +00001311 def ClientRedirectHandler(self):
1312 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001313 '/client-redirect?http://foo.bar/asdf' to redirect to
1314 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001315
1316 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001317 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001318 return False
1319
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001320 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001321 if query_char < 0 or len(self.path) <= query_char + 1:
1322 self.sendRedirectHelp(test_name)
1323 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001324 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001325
1326 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001327 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001328 self.end_headers()
1329 self.wfile.write('<html><head>')
1330 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1331 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1332
1333 return True
1334
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001335 def GetSSLSessionCacheHandler(self):
1336 """Send a reply containing a log of the session cache operations."""
1337
1338 if not self._ShouldHandleRequest('/ssl-session-cache'):
1339 return False
1340
1341 self.send_response(200)
1342 self.send_header('Content-Type', 'text/plain')
1343 self.end_headers()
1344 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001345 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001346 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001347 self.wfile.write('Pass --https-record-resume in order to use' +
1348 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001349 return True
1350
1351 for (action, sessionID) in log:
1352 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001353 return True
1354
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001355 def SSLManySmallRecords(self):
1356 """Sends a reply consisting of a variety of small writes. These will be
1357 translated into a series of small SSL records when used over an HTTPS
1358 server."""
1359
1360 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1361 return False
1362
1363 self.send_response(200)
1364 self.send_header('Content-Type', 'text/plain')
1365 self.end_headers()
1366
1367 # Write ~26K of data, in 1350 byte chunks
1368 for i in xrange(20):
1369 self.wfile.write('*' * 1350)
1370 self.wfile.flush()
1371 return True
1372
agl@chromium.org04700be2013-03-02 18:40:41 +00001373 def GetChannelID(self):
1374 """Send a reply containing the hashed ChannelID that the client provided."""
1375
1376 if not self._ShouldHandleRequest('/channel-id'):
1377 return False
1378
1379 self.send_response(200)
1380 self.send_header('Content-Type', 'text/plain')
1381 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001382 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001383 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1384 return True
1385
pneubeckfd4f0442015-08-07 04:55:10 -07001386 def GetClientCert(self):
1387 """Send a reply whether a client certificate was provided."""
1388
1389 if not self._ShouldHandleRequest('/client-cert'):
1390 return False
1391
1392 self.send_response(200)
1393 self.send_header('Content-Type', 'text/plain')
1394 self.end_headers()
1395
1396 cert_chain = self.server.tlsConnection.session.clientCertChain
1397 if cert_chain != None:
1398 self.wfile.write('got client cert with fingerprint: ' +
1399 cert_chain.getFingerprint())
1400 else:
1401 self.wfile.write('got no client cert')
1402 return True
1403
davidben599e7e72014-09-03 16:19:09 -07001404 def ClientCipherListHandler(self):
1405 """Send a reply containing the cipher suite list that the client
1406 provided. Each cipher suite value is serialized in decimal, followed by a
1407 newline."""
1408
1409 if not self._ShouldHandleRequest('/client-cipher-list'):
1410 return False
1411
1412 self.send_response(200)
1413 self.send_header('Content-Type', 'text/plain')
1414 self.end_headers()
1415
davidben11682512014-10-06 21:09:11 -07001416 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1417 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001418 return True
1419
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001420 def CloseSocketHandler(self):
1421 """Closes the socket without sending anything."""
1422
1423 if not self._ShouldHandleRequest('/close-socket'):
1424 return False
1425
1426 self.wfile.close()
1427 return True
1428
initial.commit94958cf2008-07-26 22:42:52 +00001429 def DefaultResponseHandler(self):
1430 """This is the catch-all response handler for requests that aren't handled
1431 by one of the special handlers above.
1432 Note that we specify the content-length as without it the https connection
1433 is not closed properly (and the browser keeps expecting data)."""
1434
1435 contents = "Default response given for path: " + self.path
1436 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001437 self.send_header('Content-Type', 'text/html')
1438 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001439 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001440 if (self.command != 'HEAD'):
1441 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001442 return True
1443
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001444 def RedirectConnectHandler(self):
1445 """Sends a redirect to the CONNECT request for www.redirect.com. This
1446 response is not specified by the RFC, so the browser should not follow
1447 the redirect."""
1448
1449 if (self.path.find("www.redirect.com") < 0):
1450 return False
1451
1452 dest = "http://www.destination.com/foo.js"
1453
1454 self.send_response(302) # moved temporarily
1455 self.send_header('Location', dest)
1456 self.send_header('Connection', 'close')
1457 self.end_headers()
1458 return True
1459
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001460 def ServerAuthConnectHandler(self):
1461 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1462 response doesn't make sense because the proxy server cannot request
1463 server authentication."""
1464
1465 if (self.path.find("www.server-auth.com") < 0):
1466 return False
1467
1468 challenge = 'Basic realm="WallyWorld"'
1469
1470 self.send_response(401) # unauthorized
1471 self.send_header('WWW-Authenticate', challenge)
1472 self.send_header('Connection', 'close')
1473 self.end_headers()
1474 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001475
1476 def DefaultConnectResponseHandler(self):
1477 """This is the catch-all response handler for CONNECT requests that aren't
1478 handled by one of the special handlers above. Real Web servers respond
1479 with 400 to CONNECT requests."""
1480
1481 contents = "Your client has issued a malformed or illegal request."
1482 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001483 self.send_header('Content-Type', 'text/html')
1484 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001485 self.end_headers()
1486 self.wfile.write(contents)
1487 return True
1488
initial.commit94958cf2008-07-26 22:42:52 +00001489 # called by the redirect handling function when there is no parameter
1490 def sendRedirectHelp(self, redirect_name):
1491 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001492 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001493 self.end_headers()
1494 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1495 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1496 self.wfile.write('</body></html>')
1497
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001498 # called by chunked handling function
1499 def sendChunkHelp(self, chunk):
1500 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1501 self.wfile.write('%X\r\n' % len(chunk))
1502 self.wfile.write(chunk)
1503 self.wfile.write('\r\n')
1504
akalin@chromium.org154bb132010-11-12 02:20:27 +00001505
Adam Rice9476b8c2018-08-02 15:28:43 +00001506class ProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1507 """A request handler that behaves as a proxy server. Only CONNECT, GET and
1508 HEAD methods are supported.
bashi@chromium.org33233532012-09-08 17:37:24 +00001509 """
1510
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001511 redirect_connect_to_localhost = False;
bashi@chromium.org33233532012-09-08 17:37:24 +00001512
bashi@chromium.org33233532012-09-08 17:37:24 +00001513 def _start_read_write(self, sock):
1514 sock.setblocking(0)
1515 self.request.setblocking(0)
1516 rlist = [self.request, sock]
1517 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001518 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001519 if errors:
1520 self.send_response(500)
1521 self.end_headers()
1522 return
1523 for s in ready_sockets:
1524 received = s.recv(1024)
1525 if len(received) == 0:
1526 return
1527 if s == self.request:
1528 other = sock
1529 else:
1530 other = self.request
Adam Rice54443aa2018-06-06 00:11:54 +00001531 # This will lose data if the kernel write buffer fills up.
1532 # TODO(ricea): Correctly use the return value to track how much was
1533 # written and buffer the rest. Use select to determine when the socket
1534 # becomes writable again.
bashi@chromium.org33233532012-09-08 17:37:24 +00001535 other.send(received)
1536
1537 def _do_common_method(self):
1538 url = urlparse.urlparse(self.path)
1539 port = url.port
1540 if not port:
1541 if url.scheme == 'http':
1542 port = 80
1543 elif url.scheme == 'https':
1544 port = 443
1545 if not url.hostname or not port:
1546 self.send_response(400)
1547 self.end_headers()
1548 return
1549
1550 if len(url.path) == 0:
1551 path = '/'
1552 else:
1553 path = url.path
1554 if len(url.query) > 0:
1555 path = '%s?%s' % (url.path, url.query)
1556
1557 sock = None
1558 try:
1559 sock = socket.create_connection((url.hostname, port))
1560 sock.send('%s %s %s\r\n' % (
1561 self.command, path, self.protocol_version))
1562 for header in self.headers.headers:
1563 header = header.strip()
1564 if (header.lower().startswith('connection') or
1565 header.lower().startswith('proxy')):
1566 continue
1567 sock.send('%s\r\n' % header)
1568 sock.send('\r\n')
Adam Rice54443aa2018-06-06 00:11:54 +00001569 # This is wrong: it will pass through connection-level headers and
1570 # misbehave on connection reuse. The only reason it works at all is that
1571 # our test servers have never supported connection reuse.
1572 # TODO(ricea): Use a proper HTTP client library instead.
bashi@chromium.org33233532012-09-08 17:37:24 +00001573 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001574 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001575 logging.exception('failure in common method: %s %s', self.command, path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001576 self.send_response(500)
1577 self.end_headers()
1578 finally:
1579 if sock is not None:
1580 sock.close()
1581
1582 def do_CONNECT(self):
1583 try:
1584 pos = self.path.rfind(':')
1585 host = self.path[:pos]
1586 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001587 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001588 self.send_response(400)
1589 self.end_headers()
1590
Adam Rice9476b8c2018-08-02 15:28:43 +00001591 if ProxyRequestHandler.redirect_connect_to_localhost:
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001592 host = "127.0.0.1"
1593
Adam Rice54443aa2018-06-06 00:11:54 +00001594 sock = None
bashi@chromium.org33233532012-09-08 17:37:24 +00001595 try:
1596 sock = socket.create_connection((host, port))
1597 self.send_response(200, 'Connection established')
1598 self.end_headers()
1599 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001600 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001601 logging.exception('failure in CONNECT: %s', path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001602 self.send_response(500)
1603 self.end_headers()
1604 finally:
Adam Rice54443aa2018-06-06 00:11:54 +00001605 if sock is not None:
1606 sock.close()
bashi@chromium.org33233532012-09-08 17:37:24 +00001607
1608 def do_GET(self):
1609 self._do_common_method()
1610
1611 def do_HEAD(self):
1612 self._do_common_method()
1613
Adam Rice9476b8c2018-08-02 15:28:43 +00001614class BasicAuthProxyRequestHandler(ProxyRequestHandler):
1615 """A request handler that behaves as a proxy server which requires
1616 basic authentication.
1617 """
1618
1619 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1620
1621 def parse_request(self):
1622 """Overrides parse_request to check credential."""
1623
1624 if not ProxyRequestHandler.parse_request(self):
1625 return False
1626
1627 auth = self.headers.getheader('Proxy-Authorization')
1628 if auth != self._AUTH_CREDENTIAL:
1629 self.send_response(407)
1630 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1631 self.end_headers()
1632 return False
1633
1634 return True
1635
bashi@chromium.org33233532012-09-08 17:37:24 +00001636
mattm@chromium.org830a3712012-11-07 23:00:07 +00001637class ServerRunner(testserver_base.TestServerRunner):
1638 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001639
mattm@chromium.org830a3712012-11-07 23:00:07 +00001640 def __init__(self):
1641 super(ServerRunner, self).__init__()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001642
mattm@chromium.org830a3712012-11-07 23:00:07 +00001643 def __make_data_dir(self):
1644 if self.options.data_dir:
1645 if not os.path.isdir(self.options.data_dir):
1646 raise testserver_base.OptionError('specified data dir not found: ' +
1647 self.options.data_dir + ' exiting...')
1648 my_data_dir = self.options.data_dir
1649 else:
1650 # Create the default path to our data dir, relative to the exe dir.
Asanka Herath0ec37152019-08-02 15:23:57 +00001651 my_data_dir = os.path.join(BASE_DIR, "..", "..", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001652
mattm@chromium.org830a3712012-11-07 23:00:07 +00001653 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001654
mattm@chromium.org830a3712012-11-07 23:00:07 +00001655 def create_server(self, server_data):
1656 port = self.options.port
1657 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001658
Adam Rice54443aa2018-06-06 00:11:54 +00001659 logging.basicConfig()
1660
estark21667d62015-04-08 21:00:16 -07001661 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1662 # will result in a call to |getaddrinfo|, which fails with "nodename
1663 # nor servname provided" for localhost:0 on 10.6.
Adam Rice54443aa2018-06-06 00:11:54 +00001664 # TODO(ricea): Remove this if no longer needed.
estark21667d62015-04-08 21:00:16 -07001665 if self.options.server_type == SERVER_WEBSOCKET and \
1666 host == "localhost" and \
1667 port == 0:
1668 host = "127.0.0.1"
1669
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001670 # Construct the subjectAltNames for any ad-hoc generated certificates.
1671 # As host can be either a DNS name or IP address, attempt to determine
1672 # which it is, so it can be placed in the appropriate SAN.
1673 dns_sans = None
1674 ip_sans = None
1675 ip = None
1676 try:
1677 ip = socket.inet_aton(host)
1678 ip_sans = [ip]
1679 except socket.error:
1680 pass
1681 if ip is None:
1682 dns_sans = [host]
1683
mattm@chromium.org830a3712012-11-07 23:00:07 +00001684 if self.options.server_type == SERVER_HTTP:
1685 if self.options.https:
David Benjamin8ed48d12021-09-14 21:28:30 +00001686 if not self.options.cert_and_key_file:
1687 raise testserver_base.OptionError('server cert file not specified')
1688 if not os.path.isfile(self.options.cert_and_key_file):
1689 raise testserver_base.OptionError(
1690 'specified server cert file not found: ' +
1691 self.options.cert_and_key_file + ' exiting...')
1692 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
mattm@chromium.org830a3712012-11-07 23:00:07 +00001693
1694 for ca_cert in self.options.ssl_client_ca:
1695 if not os.path.isfile(ca_cert):
1696 raise testserver_base.OptionError(
1697 'specified trusted client CA file not found: ' + ca_cert +
1698 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001699
1700 stapled_ocsp_response = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001701 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1702 self.options.ssl_client_auth,
1703 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00001704 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00001705 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00001706 self.options.ssl_key_exchange,
bnc5fb33bd2016-08-05 12:09:21 -07001707 self.options.alpn_protocols,
bnc609ad4c2015-10-02 05:11:24 -07001708 self.options.npn_protocols,
mattm@chromium.org830a3712012-11-07 23:00:07 +00001709 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00001710 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00001711 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001712 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00001713 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001714 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07001715 stapled_ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -07001716 self.options.alert_after_handshake,
1717 self.options.disable_channel_id,
David Benjaminf839f1c2018-10-16 06:01:29 +00001718 self.options.disable_extended_master_secret,
1719 self.options.simulate_tls13_downgrade,
1720 self.options.simulate_tls12_downgrade,
1721 self.options.tls_max_version)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001722 print 'HTTPS server started on https://%s:%d...' % \
1723 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001724 else:
1725 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001726 print 'HTTP server started on http://%s:%d...' % \
1727 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001728
1729 server.data_dir = self.__make_data_dir()
1730 server.file_root_url = self.options.file_root_url
1731 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001732 elif self.options.server_type == SERVER_WEBSOCKET:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001733 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1734 # is required to work correctly. It should be fixed from pywebsocket side.
1735 os.chdir(self.__make_data_dir())
1736 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00001737 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00001738 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00001739 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00001740 websocket_options.use_tls = True
Sergey Ulanovdd8b8ea2017-09-08 22:53:25 +00001741 key_path = os.path.join(ROOT_DIR, self.options.cert_and_key_file)
1742 if not os.path.isfile(key_path):
1743 raise testserver_base.OptionError(
1744 'specified server cert file not found: ' +
1745 self.options.cert_and_key_file + ' exiting...')
1746 websocket_options.private_key = key_path
1747 websocket_options.certificate = key_path
1748
mattm@chromium.org830a3712012-11-07 23:00:07 +00001749 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00001750 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00001751 websocket_options.tls_client_auth = True
1752 if len(self.options.ssl_client_ca) != 1:
1753 raise testserver_base.OptionError(
1754 'one trusted client CA file should be specified')
1755 if not os.path.isfile(self.options.ssl_client_ca[0]):
1756 raise testserver_base.OptionError(
1757 'specified trusted client CA file not found: ' +
1758 self.options.ssl_client_ca[0] + ' exiting...')
1759 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07001760 print 'Trying to start websocket server on %s://%s:%d...' % \
1761 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001762 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001763 print 'WebSocket server started on %s://%s:%d...' % \
1764 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001765 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00001766 websocket_options.use_basic_auth = self.options.ws_basic_auth
Adam Rice9476b8c2018-08-02 15:28:43 +00001767 elif self.options.server_type == SERVER_PROXY:
1768 ProxyRequestHandler.redirect_connect_to_localhost = \
1769 self.options.redirect_connect_to_localhost
1770 server = ThreadingHTTPServer((host, port), ProxyRequestHandler)
1771 print 'Proxy server started on port %d...' % server.server_port
1772 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001773 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
Adam Rice9476b8c2018-08-02 15:28:43 +00001774 ProxyRequestHandler.redirect_connect_to_localhost = \
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001775 self.options.redirect_connect_to_localhost
Adam Rice34b2e312018-04-06 16:48:30 +00001776 server = ThreadingHTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001777 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001778 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001779 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001780 raise testserver_base.OptionError('unknown server type' +
1781 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00001782
mattm@chromium.org830a3712012-11-07 23:00:07 +00001783 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001784
mattm@chromium.org830a3712012-11-07 23:00:07 +00001785 def add_options(self):
1786 testserver_base.TestServerRunner.add_options(self)
Adam Rice9476b8c2018-08-02 15:28:43 +00001787 self.option_parser.add_option('--proxy', action='store_const',
1788 const=SERVER_PROXY,
1789 default=SERVER_HTTP, dest='server_type',
1790 help='start up a proxy server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001791 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
1792 const=SERVER_BASIC_AUTH_PROXY,
1793 default=SERVER_HTTP, dest='server_type',
1794 help='start up a proxy server which requires '
1795 'basic authentication.')
1796 self.option_parser.add_option('--websocket', action='store_const',
1797 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
1798 dest='server_type',
1799 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001800 self.option_parser.add_option('--https', action='store_true',
1801 dest='https', help='Specify that https '
1802 'should be used.')
1803 self.option_parser.add_option('--cert-and-key-file',
1804 dest='cert_and_key_file', help='specify the '
1805 'path to the file containing the certificate '
1806 'and private key for the server in PEM '
1807 'format')
agl@chromium.orgdf778142013-07-31 21:57:28 +00001808 self.option_parser.add_option('--cert-serial', dest='cert_serial',
1809 default=0, type=int,
1810 help='If non-zero then the generated '
1811 'certificate will have this serial number')
Adam Langley1b622652017-12-05 23:57:33 +00001812 self.option_parser.add_option('--cert-common-name', dest='cert_common_name',
1813 default="127.0.0.1",
1814 help='The generated certificate will have '
1815 'this common name')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001816 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
1817 default='0', type='int',
1818 help='If nonzero, certain TLS connections '
1819 'will be aborted in order to test version '
1820 'fallback. 1 means all TLS versions will be '
1821 'aborted. 2 means TLS 1.1 or higher will be '
1822 'aborted. 3 means TLS 1.2 or higher will be '
davidbendf2e3862017-04-12 15:23:34 -07001823 'aborted. 4 means TLS 1.3 or higher will be '
mattm@chromium.org830a3712012-11-07 23:00:07 +00001824 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00001825 self.option_parser.add_option('--tls-intolerance-type',
1826 dest='tls_intolerance_type',
1827 default="alert",
1828 help='Controls how the server reacts to a '
1829 'TLS version it is intolerant to. Valid '
1830 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001831 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
1832 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00001833 default='',
1834 help='Base64 encoded SCT list. If set, '
1835 'server will respond with a '
1836 'signed_certificate_timestamp TLS extension '
1837 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00001838 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
1839 default=False, const=True,
1840 action='store_const',
1841 help='If given, TLS_FALLBACK_SCSV support '
1842 'will be enabled. This causes the server to '
1843 'reject fallback connections from compatible '
1844 'clients (e.g. Chrome).')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001845 self.option_parser.add_option('--https-record-resume',
1846 dest='record_resume', const=True,
1847 default=False, action='store_const',
1848 help='Record resumption cache events rather '
1849 'than resuming as normal. Allows the use of '
1850 'the /ssl-session-cache request')
1851 self.option_parser.add_option('--ssl-client-auth', action='store_true',
1852 help='Require SSL client auth on every '
1853 'connection.')
1854 self.option_parser.add_option('--ssl-client-ca', action='append',
1855 default=[], help='Specify that the client '
1856 'certificate request should include the CA '
1857 'named in the subject of the DER-encoded '
1858 'certificate contained in the specified '
1859 'file. This option may appear multiple '
1860 'times, indicating multiple CA names should '
1861 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00001862 self.option_parser.add_option('--ssl-client-cert-type', action='append',
1863 default=[], help='Specify that the client '
1864 'certificate request should include the '
1865 'specified certificate_type value. This '
1866 'option may appear multiple times, '
1867 'indicating multiple values should be send '
1868 'in the request. Valid values are '
1869 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
1870 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001871 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
1872 help='Specify the bulk encryption '
1873 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08001874 'SSL server. Valid values are "aes128gcm", '
1875 '"aes256", "aes128", "3des", "rc4". If '
1876 'omitted, all algorithms will be used. This '
1877 'option may appear multiple times, '
1878 'indicating multiple algorithms should be '
1879 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00001880 self.option_parser.add_option('--ssl-key-exchange', action='append',
1881 help='Specify the key exchange algorithm(s)'
1882 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07001883 'Valid values are "rsa", "dhe_rsa", '
1884 '"ecdhe_rsa". If omitted, all algorithms '
1885 'will be used. This option may appear '
1886 'multiple times, indicating multiple '
1887 'algorithms should be enabled.');
bnc5fb33bd2016-08-05 12:09:21 -07001888 self.option_parser.add_option('--alpn-protocols', action='append',
1889 help='Specify the list of ALPN protocols. '
1890 'The server will not send an ALPN response '
1891 'if this list does not overlap with the '
1892 'list of protocols the client advertises.')
bnc609ad4c2015-10-02 05:11:24 -07001893 self.option_parser.add_option('--npn-protocols', action='append',
bnc5fb33bd2016-08-05 12:09:21 -07001894 help='Specify the list of protocols sent in '
bnc609ad4c2015-10-02 05:11:24 -07001895 'an NPN response. The server will not'
1896 'support NPN if the list is empty.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001897 self.option_parser.add_option('--file-root-url', default='/files/',
1898 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00001899 # TODO(ricea): Generalize this to support basic auth for HTTP too.
1900 self.option_parser.add_option('--ws-basic-auth', action='store_true',
1901 dest='ws_basic_auth',
1902 help='Enable basic-auth for WebSocket')
davidben21cda342015-03-17 18:04:28 -07001903 self.option_parser.add_option('--alert-after-handshake',
1904 dest='alert_after_handshake',
1905 default=False, action='store_true',
1906 help='If set, the server will send a fatal '
1907 'alert immediately after the handshake.')
nharper1e8bf4b2015-09-18 12:23:02 -07001908 self.option_parser.add_option('--disable-channel-id', action='store_true')
1909 self.option_parser.add_option('--disable-extended-master-secret',
1910 action='store_true')
David Benjaminf839f1c2018-10-16 06:01:29 +00001911 self.option_parser.add_option('--simulate-tls13-downgrade',
1912 action='store_true')
1913 self.option_parser.add_option('--simulate-tls12-downgrade',
1914 action='store_true')
1915 self.option_parser.add_option('--tls-max-version', default='0', type='int',
1916 help='If non-zero, the maximum TLS version '
1917 'to support. 1 means TLS 1.0, 2 means '
1918 'TLS 1.1, and 3 means TLS 1.2.')
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001919 self.option_parser.add_option('--redirect-connect-to-localhost',
1920 dest='redirect_connect_to_localhost',
1921 default=False, action='store_true',
1922 help='If set, the Proxy server will connect '
1923 'to localhost instead of the requested URL '
1924 'on CONNECT requests')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001925
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00001926
initial.commit94958cf2008-07-26 22:42:52 +00001927if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00001928 sys.exit(ServerRunner().main())