blob: 345c4cd421bbcb4597f42bd731826cf5f1c49966 [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00002# Copyright 2013 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00006"""This is a simple HTTP/FTP/TCP/UDP/BASIC_AUTH_PROXY/WEBSOCKET server used for
7testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00008
9It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000010By default, it listens on an ephemeral port and sends the port number back to
11the originating process over a pipe. The originating process can specify an
12explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000013It can use https if you specify the flag --https=CERT where CERT is the path
14to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000015"""
16
17import base64
18import BaseHTTPServer
19import cgi
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000020import hashlib
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000021import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000022import minica
initial.commit94958cf2008-07-26 22:42:52 +000023import os
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +000024import json
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000025import random
initial.commit94958cf2008-07-26 22:42:52 +000026import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000028import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000029import SocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000030import ssl
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000031import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000032import sys
33import threading
initial.commit94958cf2008-07-26 22:42:52 +000034import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000035import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000036import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000037import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000038
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000039BASE_DIR = os.path.dirname(os.path.abspath(__file__))
40ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(BASE_DIR)))
41
davidben@chromium.org7d53b542014-04-10 17:56:44 +000042# Insert at the beginning of the path, we want to use our copies of the library
Robert Iannucci0e7ec952018-01-18 22:44:16 +000043# unconditionally (since they contain modifications from anything that might be
44# obtained from e.g. PyPi).
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000045sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'pywebsocket', 'src'))
davidben@chromium.org7d53b542014-04-10 17:56:44 +000046sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'tlslite'))
47
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000048import mod_pywebsocket.standalone
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000049from mod_pywebsocket.standalone import WebSocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000050# import manually
51mod_pywebsocket.standalone.ssl = ssl
davidben@chromium.org06fcf202010-09-22 18:15:23 +000052
davidben@chromium.org7d53b542014-04-10 17:56:44 +000053import pyftpdlib.ftpserver
54
55import tlslite
56import tlslite.api
57
58import echo_message
59import testserver_base
60
maruel@chromium.org756cf982009-03-05 12:46:38 +000061SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000062SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000063SERVER_TCP_ECHO = 2
64SERVER_UDP_ECHO = 3
65SERVER_BASIC_AUTH_PROXY = 4
66SERVER_WEBSOCKET = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000067
68# Default request queue size for WebSocketServer.
69_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000070
dadrian4ccf51c2016-07-20 15:36:58 -070071OCSP_STATES_NO_SINGLE_RESPONSE = {
72 minica.OCSP_STATE_INVALID_RESPONSE,
73 minica.OCSP_STATE_UNAUTHORIZED,
74 minica.OCSP_STATE_TRY_LATER,
75 minica.OCSP_STATE_INVALID_RESPONSE_DATA,
76}
77
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000078class WebSocketOptions:
79 """Holds options for WebSocketServer."""
80
81 def __init__(self, host, port, data_dir):
82 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
83 self.server_host = host
84 self.port = port
85 self.websock_handlers = data_dir
86 self.scan_dir = None
87 self.allow_handlers_outside_root_dir = False
88 self.websock_handlers_map_file = None
89 self.cgi_directories = []
90 self.is_executable_method = None
91 self.allow_draft75 = False
92 self.strict = True
93
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000094 self.use_tls = False
95 self.private_key = None
96 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000097 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000098 self.tls_client_ca = None
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000099 self.tls_module = 'ssl'
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000100 self.use_basic_auth = False
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +0000101 self.basic_auth_credential = 'Basic ' + base64.b64encode('test:test')
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000102
mattm@chromium.org830a3712012-11-07 23:00:07 +0000103
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000104class RecordingSSLSessionCache(object):
105 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
106 lookups and inserts in order to test session cache behaviours."""
107
108 def __init__(self):
109 self.log = []
110
111 def __getitem__(self, sessionID):
112 self.log.append(('lookup', sessionID))
113 raise KeyError()
114
115 def __setitem__(self, sessionID, session):
116 self.log.append(('insert', sessionID))
117
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000118
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000119class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
120 testserver_base.BrokenPipeHandlerMixIn,
121 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000122 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000123 verification."""
124
125 pass
126
Adam Rice34b2e312018-04-06 16:48:30 +0000127class ThreadingHTTPServer(SocketServer.ThreadingMixIn,
128 HTTPServer):
129 """This variant of HTTPServer creates a new thread for every connection. It
130 should only be used with handlers that are known to be threadsafe."""
131
132 pass
133
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000134class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
135 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000136 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000137 """This is a specialization of HTTPServer that serves an
138 OCSP response"""
139
140 def serve_forever_on_thread(self):
141 self.thread = threading.Thread(target = self.serve_forever,
142 name = "OCSPServerThread")
143 self.thread.start()
144
145 def stop_serving(self):
146 self.shutdown()
147 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000148
mattm@chromium.org830a3712012-11-07 23:00:07 +0000149
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000150class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000151 testserver_base.ClientRestrictingServerMixIn,
152 testserver_base.BrokenPipeHandlerMixIn,
153 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000154 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000155 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000156
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000157 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000158 ssl_client_auth, ssl_client_cas, ssl_client_cert_types,
bnc5fb33bd2016-08-05 12:09:21 -0700159 ssl_bulk_ciphers, ssl_key_exchanges, alpn_protocols,
160 npn_protocols, record_resume_info, tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000161 tls_intolerance_type, signed_cert_timestamps,
davidben21cda342015-03-17 18:04:28 -0700162 fallback_scsv_enabled, ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -0700163 alert_after_handshake, disable_channel_id, disable_ems,
164 token_binding_params):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000165 self.cert_chain = tlslite.api.X509CertChain()
166 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000167 # Force using only python implementation - otherwise behavior is different
168 # depending on whether m2crypto Python module is present (error is thrown
169 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
170 # the hood.
171 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
172 private=True,
173 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000174 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000175 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000176 self.ssl_client_cert_types = []
bnc609ad4c2015-10-02 05:11:24 -0700177 self.npn_protocols = npn_protocols
ekasper@google.com24aa8222013-11-28 13:43:26 +0000178 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000179 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000180 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000181
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000182 if ssl_client_auth:
183 for ca_file in ssl_client_cas:
184 s = open(ca_file).read()
185 x509 = tlslite.api.X509()
186 x509.parse(s)
187 self.ssl_client_cas.append(x509.subject)
188
189 for cert_type in ssl_client_cert_types:
190 self.ssl_client_cert_types.append({
191 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000192 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
193 }[cert_type])
194
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000195 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800196 # Enable SSLv3 for testing purposes.
197 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000198 if ssl_bulk_ciphers is not None:
199 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000200 if ssl_key_exchanges is not None:
201 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000202 if tls_intolerant != 0:
203 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
204 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
davidben21cda342015-03-17 18:04:28 -0700205 if alert_after_handshake:
206 self.ssl_handshake_settings.alertAfterHandshake = True
nharper1e8bf4b2015-09-18 12:23:02 -0700207 if disable_channel_id:
208 self.ssl_handshake_settings.enableChannelID = False
209 if disable_ems:
210 self.ssl_handshake_settings.enableExtendedMasterSecret = False
211 self.ssl_handshake_settings.supportedTokenBindingParams = \
212 token_binding_params
bnc5fb33bd2016-08-05 12:09:21 -0700213 self.ssl_handshake_settings.alpnProtos=alpn_protocols;
initial.commit94958cf2008-07-26 22:42:52 +0000214
rsleevi8146efa2015-03-16 12:31:24 -0700215 if record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000216 # If record_resume_info is true then we'll replace the session cache with
217 # an object that records the lookups and inserts that it sees.
218 self.session_cache = RecordingSSLSessionCache()
219 else:
220 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000221 testserver_base.StoppableHTTPServer.__init__(self,
222 server_address,
223 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000224
225 def handshake(self, tlsConnection):
226 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000227
initial.commit94958cf2008-07-26 22:42:52 +0000228 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000229 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000230 tlsConnection.handshakeServer(certChain=self.cert_chain,
231 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000232 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000233 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000234 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000235 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000236 reqCertTypes=self.ssl_client_cert_types,
bnc609ad4c2015-10-02 05:11:24 -0700237 nextProtos=self.npn_protocols,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000238 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000239 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000240 fallbackSCSV=self.fallback_scsv_enabled,
241 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000242 tlsConnection.ignoreAbruptClose = True
243 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000244 except tlslite.api.TLSAbruptCloseError:
245 # Ignore abrupt close.
246 return True
initial.commit94958cf2008-07-26 22:42:52 +0000247 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000248 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000249 return False
250
akalin@chromium.org154bb132010-11-12 02:20:27 +0000251
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000252class FTPServer(testserver_base.ClientRestrictingServerMixIn,
253 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000254 """This is a specialization of FTPServer that adds client verification."""
255
256 pass
257
258
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000259class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
260 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000261 """A TCP echo server that echoes back what it has received."""
262
263 def server_bind(self):
264 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000265
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000266 SocketServer.TCPServer.server_bind(self)
267 host, port = self.socket.getsockname()[:2]
268 self.server_name = socket.getfqdn(host)
269 self.server_port = port
270
271 def serve_forever(self):
272 self.stop = False
273 self.nonce_time = None
274 while not self.stop:
275 self.handle_request()
276 self.socket.close()
277
278
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000279class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
280 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000281 """A UDP echo server that echoes back what it has received."""
282
283 def server_bind(self):
284 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000285
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000286 SocketServer.UDPServer.server_bind(self)
287 host, port = self.socket.getsockname()[:2]
288 self.server_name = socket.getfqdn(host)
289 self.server_port = port
290
291 def serve_forever(self):
292 self.stop = False
293 self.nonce_time = None
294 while not self.stop:
295 self.handle_request()
296 self.socket.close()
297
298
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000299class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000300 # Class variables to allow for persistence state between page handler
301 # invocations
302 rst_limits = {}
303 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000304
305 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000306 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000307 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000308 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000309 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000310 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000311 self.NoCacheMaxAgeTimeHandler,
312 self.NoCacheTimeHandler,
313 self.CacheTimeHandler,
314 self.CacheExpiresHandler,
315 self.CacheProxyRevalidateHandler,
316 self.CachePrivateHandler,
317 self.CachePublicHandler,
318 self.CacheSMaxAgeHandler,
319 self.CacheMustRevalidateHandler,
320 self.CacheMustRevalidateMaxAgeHandler,
321 self.CacheNoStoreHandler,
322 self.CacheNoStoreMaxAgeHandler,
323 self.CacheNoTransformHandler,
324 self.DownloadHandler,
325 self.DownloadFinishHandler,
326 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000327 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000328 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000329 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000330 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000331 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000332 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000333 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000334 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000335 self.AuthBasicHandler,
336 self.AuthDigestHandler,
337 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000338 self.ChunkedServerHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000339 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000340 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700341 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000342 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000343 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000344 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000345 self.GetChannelID,
nharper08eae822016-01-25 15:54:14 -0800346 self.GetTokenBindingEKM,
nharpercb1adc32016-03-30 16:05:48 -0700347 self.ForwardTokenBindingHeader,
pneubeckfd4f0442015-08-07 04:55:10 -0700348 self.GetClientCert,
davidben599e7e72014-09-03 16:19:09 -0700349 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000350 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000351 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000352 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000353 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000354 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000355 self.PostOnlyFileHandler,
356 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000357 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000358 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000359 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000360 head_handlers = [
361 self.FileHandler,
362 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000363
maruel@google.come250a9b2009-03-10 17:39:46 +0000364 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000365 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000366 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000367 'gif': 'image/gif',
368 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000369 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700370 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000371 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000372 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000373 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000374 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000375 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000376 }
initial.commit94958cf2008-07-26 22:42:52 +0000377 self._default_mime_type = 'text/html'
378
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000379 testserver_base.BasePageHandler.__init__(self, request, client_address,
380 socket_server, connect_handlers,
381 get_handlers, head_handlers,
382 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000383
initial.commit94958cf2008-07-26 22:42:52 +0000384 def GetMIMETypeFromName(self, file_name):
385 """Returns the mime type for the specified file_name. So far it only looks
386 at the file extension."""
387
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000388 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000389 if len(extension) == 0:
390 # no extension.
391 return self._default_mime_type
392
ericroman@google.comc17ca532009-05-07 03:51:05 +0000393 # extension starts with a dot, so we need to remove it
394 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000395
initial.commit94958cf2008-07-26 22:42:52 +0000396 def NoCacheMaxAgeTimeHandler(self):
397 """This request handler yields a page with the title set to the current
398 system time, and no caching requested."""
399
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000400 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000401 return False
402
403 self.send_response(200)
404 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000405 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000406 self.end_headers()
407
maruel@google.come250a9b2009-03-10 17:39:46 +0000408 self.wfile.write('<html><head><title>%s</title></head></html>' %
409 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000410
411 return True
412
413 def NoCacheTimeHandler(self):
414 """This request handler yields a page with the title set to the current
415 system time, and no caching requested."""
416
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000417 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000418 return False
419
420 self.send_response(200)
421 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000422 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000423 self.end_headers()
424
maruel@google.come250a9b2009-03-10 17:39:46 +0000425 self.wfile.write('<html><head><title>%s</title></head></html>' %
426 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000427
428 return True
429
430 def CacheTimeHandler(self):
431 """This request handler yields a page with the title set to the current
432 system time, and allows caching for one minute."""
433
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000434 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000435 return False
436
437 self.send_response(200)
438 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000439 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000440 self.end_headers()
441
maruel@google.come250a9b2009-03-10 17:39:46 +0000442 self.wfile.write('<html><head><title>%s</title></head></html>' %
443 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000444
445 return True
446
447 def CacheExpiresHandler(self):
448 """This request handler yields a page with the title set to the current
449 system time, and set the page to expire on 1 Jan 2099."""
450
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000451 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000452 return False
453
454 self.send_response(200)
455 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000456 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000457 self.end_headers()
458
maruel@google.come250a9b2009-03-10 17:39:46 +0000459 self.wfile.write('<html><head><title>%s</title></head></html>' %
460 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000461
462 return True
463
464 def CacheProxyRevalidateHandler(self):
465 """This request handler yields a page with the title set to the current
466 system time, and allows caching for 60 seconds"""
467
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000468 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000469 return False
470
471 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000472 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000473 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
474 self.end_headers()
475
maruel@google.come250a9b2009-03-10 17:39:46 +0000476 self.wfile.write('<html><head><title>%s</title></head></html>' %
477 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000478
479 return True
480
481 def CachePrivateHandler(self):
482 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700483 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000484
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000485 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000486 return False
487
488 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000489 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000490 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000491 self.end_headers()
492
maruel@google.come250a9b2009-03-10 17:39:46 +0000493 self.wfile.write('<html><head><title>%s</title></head></html>' %
494 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000495
496 return True
497
498 def CachePublicHandler(self):
499 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700500 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000501
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000502 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000503 return False
504
505 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000506 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000507 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000508 self.end_headers()
509
maruel@google.come250a9b2009-03-10 17:39:46 +0000510 self.wfile.write('<html><head><title>%s</title></head></html>' %
511 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000512
513 return True
514
515 def CacheSMaxAgeHandler(self):
516 """This request handler yields a page with the title set to the current
517 system time, and does not allow for caching."""
518
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000519 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000520 return False
521
522 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000523 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000524 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
525 self.end_headers()
526
maruel@google.come250a9b2009-03-10 17:39:46 +0000527 self.wfile.write('<html><head><title>%s</title></head></html>' %
528 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000529
530 return True
531
532 def CacheMustRevalidateHandler(self):
533 """This request handler yields a page with the title set to the current
534 system time, and does not allow caching."""
535
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000536 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000537 return False
538
539 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000540 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000541 self.send_header('Cache-Control', 'must-revalidate')
542 self.end_headers()
543
maruel@google.come250a9b2009-03-10 17:39:46 +0000544 self.wfile.write('<html><head><title>%s</title></head></html>' %
545 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000546
547 return True
548
549 def CacheMustRevalidateMaxAgeHandler(self):
550 """This request handler yields a page with the title set to the current
551 system time, and does not allow caching event though max-age of 60
552 seconds is specified."""
553
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000554 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000555 return False
556
557 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000558 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000559 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
560 self.end_headers()
561
maruel@google.come250a9b2009-03-10 17:39:46 +0000562 self.wfile.write('<html><head><title>%s</title></head></html>' %
563 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000564
565 return True
566
initial.commit94958cf2008-07-26 22:42:52 +0000567 def CacheNoStoreHandler(self):
568 """This request handler yields a page with the title set to the current
569 system time, and does not allow the page to be stored."""
570
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000571 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000572 return False
573
574 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000575 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000576 self.send_header('Cache-Control', 'no-store')
577 self.end_headers()
578
maruel@google.come250a9b2009-03-10 17:39:46 +0000579 self.wfile.write('<html><head><title>%s</title></head></html>' %
580 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000581
582 return True
583
584 def CacheNoStoreMaxAgeHandler(self):
585 """This request handler yields a page with the title set to the current
586 system time, and does not allow the page to be stored even though max-age
587 of 60 seconds is specified."""
588
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000589 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000590 return False
591
592 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000593 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000594 self.send_header('Cache-Control', 'max-age=60, no-store')
595 self.end_headers()
596
maruel@google.come250a9b2009-03-10 17:39:46 +0000597 self.wfile.write('<html><head><title>%s</title></head></html>' %
598 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000599
600 return True
601
602
603 def CacheNoTransformHandler(self):
604 """This request handler yields a page with the title set to the current
605 system time, and does not allow the content to transformed during
606 user-agent caching"""
607
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000608 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000609 return False
610
611 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000612 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000613 self.send_header('Cache-Control', 'no-transform')
614 self.end_headers()
615
maruel@google.come250a9b2009-03-10 17:39:46 +0000616 self.wfile.write('<html><head><title>%s</title></head></html>' %
617 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000618
619 return True
620
621 def EchoHeader(self):
622 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000623
ananta@chromium.org219b2062009-10-23 16:09:41 +0000624 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000625
ananta@chromium.org56812d02011-04-07 17:52:05 +0000626 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000627 """This function echoes back the value of a specific request header while
Eric Romanf6a38402018-02-14 20:19:53 +0000628 allowing caching for 10 hours."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000629
ananta@chromium.org56812d02011-04-07 17:52:05 +0000630 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000631
632 def EchoHeaderHelper(self, echo_header):
633 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000634
ananta@chromium.org219b2062009-10-23 16:09:41 +0000635 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000636 return False
637
638 query_char = self.path.find('?')
639 if query_char != -1:
640 header_name = self.path[query_char+1:]
641
642 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000643 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000644 if echo_header == '/echoheadercache':
645 self.send_header('Cache-control', 'max-age=60000')
646 else:
647 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000648 # insert a vary header to properly indicate that the cachability of this
649 # request is subject to value of the request header being echoed.
650 if len(header_name) > 0:
651 self.send_header('Vary', header_name)
652 self.end_headers()
653
654 if len(header_name) > 0:
655 self.wfile.write(self.headers.getheader(header_name))
656
657 return True
658
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000659 def ReadRequestBody(self):
660 """This function reads the body of the current HTTP request, handling
661 both plain and chunked transfer encoded requests."""
662
663 if self.headers.getheader('transfer-encoding') != 'chunked':
664 length = int(self.headers.getheader('content-length'))
665 return self.rfile.read(length)
666
667 # Read the request body as chunks.
668 body = ""
669 while True:
670 line = self.rfile.readline()
671 length = int(line, 16)
672 if length == 0:
673 self.rfile.readline()
674 break
675 body += self.rfile.read(length)
676 self.rfile.read(2)
677 return body
678
initial.commit94958cf2008-07-26 22:42:52 +0000679 def EchoHandler(self):
680 """This handler just echoes back the payload of the request, for testing
681 form submission."""
682
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000683 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000684 return False
685
hirono2838c572015-01-21 12:18:11 -0800686 _, _, _, _, query, _ = urlparse.urlparse(self.path)
687 query_params = cgi.parse_qs(query, True)
688 if 'status' in query_params:
689 self.send_response(int(query_params['status'][0]))
690 else:
691 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000692 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000693 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000694 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000695 return True
696
697 def EchoTitleHandler(self):
698 """This handler is like Echo, but sets the page title to the request."""
699
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000700 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000701 return False
702
703 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000704 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000705 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000706 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000707 self.wfile.write('<html><head><title>')
708 self.wfile.write(request)
709 self.wfile.write('</title></head></html>')
710 return True
711
712 def EchoAllHandler(self):
713 """This handler yields a (more) human-readable page listing information
714 about the request header & contents."""
715
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000716 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000717 return False
718
719 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000720 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000721 self.end_headers()
722 self.wfile.write('<html><head><style>'
723 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
724 '</style></head><body>'
725 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000726 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000727 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000728
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000729 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000730 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000731 params = cgi.parse_qs(qs, keep_blank_values=1)
732
733 for param in params:
734 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000735
736 self.wfile.write('</pre>')
737
738 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
739
740 self.wfile.write('</body></html>')
741 return True
742
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000743 def EchoMultipartPostHandler(self):
744 """This handler echoes received multipart post data as json format."""
745
746 if not (self._ShouldHandleRequest("/echomultipartpost") or
747 self._ShouldHandleRequest("/searchbyimage")):
748 return False
749
750 content_type, parameters = cgi.parse_header(
751 self.headers.getheader('content-type'))
752 if content_type == 'multipart/form-data':
753 post_multipart = cgi.parse_multipart(self.rfile, parameters)
754 elif content_type == 'application/x-www-form-urlencoded':
755 raise Exception('POST by application/x-www-form-urlencoded is '
756 'not implemented.')
757 else:
758 post_multipart = {}
759
760 # Since the data can be binary, we encode them by base64.
761 post_multipart_base64_encoded = {}
762 for field, values in post_multipart.items():
763 post_multipart_base64_encoded[field] = [base64.b64encode(value)
764 for value in values]
765
766 result = {'POST_multipart' : post_multipart_base64_encoded}
767
768 self.send_response(200)
769 self.send_header("Content-type", "text/plain")
770 self.end_headers()
771 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
772 return True
773
initial.commit94958cf2008-07-26 22:42:52 +0000774 def DownloadHandler(self):
775 """This handler sends a downloadable file with or without reporting
776 the size (6K)."""
777
778 if self.path.startswith("/download-unknown-size"):
779 send_length = False
780 elif self.path.startswith("/download-known-size"):
781 send_length = True
782 else:
783 return False
784
785 #
786 # The test which uses this functionality is attempting to send
787 # small chunks of data to the client. Use a fairly large buffer
788 # so that we'll fill chrome's IO buffer enough to force it to
789 # actually write the data.
790 # See also the comments in the client-side of this test in
791 # download_uitest.cc
792 #
793 size_chunk1 = 35*1024
794 size_chunk2 = 10*1024
795
796 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000797 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000798 self.send_header('Cache-Control', 'max-age=0')
799 if send_length:
800 self.send_header('Content-Length', size_chunk1 + size_chunk2)
801 self.end_headers()
802
803 # First chunk of data:
804 self.wfile.write("*" * size_chunk1)
805 self.wfile.flush()
806
807 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000808 self.server.wait_for_download = True
809 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000810 self.server.handle_request()
811
812 # Second chunk of data:
813 self.wfile.write("*" * size_chunk2)
814 return True
815
816 def DownloadFinishHandler(self):
817 """This handler just tells the server to finish the current download."""
818
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000819 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000820 return False
821
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000822 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000823 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000824 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000825 self.send_header('Cache-Control', 'max-age=0')
826 self.end_headers()
827 return True
828
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000829 def _ReplaceFileData(self, data, query_parameters):
830 """Replaces matching substrings in a file.
831
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000832 If the 'replace_text' URL query parameter is present, it is expected to be
833 of the form old_text:new_text, which indicates that any old_text strings in
834 the file are replaced with new_text. Multiple 'replace_text' parameters may
835 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000836
837 If the parameters are not present, |data| is returned.
838 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000839
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000840 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000841 replace_text_values = query_dict.get('replace_text', [])
842 for replace_text_value in replace_text_values:
843 replace_text_args = replace_text_value.split(':')
844 if len(replace_text_args) != 2:
845 raise ValueError(
846 'replace_text must be of form old_text:new_text. Actual value: %s' %
847 replace_text_value)
848 old_text_b64, new_text_b64 = replace_text_args
849 old_text = base64.urlsafe_b64decode(old_text_b64)
850 new_text = base64.urlsafe_b64decode(new_text_b64)
851 data = data.replace(old_text, new_text)
852 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000853
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000854 def ZipFileHandler(self):
855 """This handler sends the contents of the requested file in compressed form.
856 Can pass in a parameter that specifies that the content length be
857 C - the compressed size (OK),
858 U - the uncompressed size (Non-standard, but handled),
859 S - less than compressed (OK because we keep going),
860 M - larger than compressed but less than uncompressed (an error),
861 L - larger than uncompressed (an error)
862 Example: compressedfiles/Picture_1.doc?C
863 """
864
865 prefix = "/compressedfiles/"
866 if not self.path.startswith(prefix):
867 return False
868
869 # Consume a request body if present.
870 if self.command == 'POST' or self.command == 'PUT' :
871 self.ReadRequestBody()
872
873 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
874
875 if not query in ('C', 'U', 'S', 'M', 'L'):
876 return False
877
878 sub_path = url_path[len(prefix):]
879 entries = sub_path.split('/')
880 file_path = os.path.join(self.server.data_dir, *entries)
881 if os.path.isdir(file_path):
882 file_path = os.path.join(file_path, 'index.html')
883
884 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000885 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000886 self.send_error(404)
887 return True
888
889 f = open(file_path, "rb")
890 data = f.read()
891 uncompressed_len = len(data)
892 f.close()
893
894 # Compress the data.
895 data = zlib.compress(data)
896 compressed_len = len(data)
897
898 content_length = compressed_len
899 if query == 'U':
900 content_length = uncompressed_len
901 elif query == 'S':
902 content_length = compressed_len / 2
903 elif query == 'M':
904 content_length = (compressed_len + uncompressed_len) / 2
905 elif query == 'L':
906 content_length = compressed_len + uncompressed_len
907
908 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000909 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000910 self.send_header('Content-encoding', 'deflate')
911 self.send_header('Connection', 'close')
912 self.send_header('Content-Length', content_length)
913 self.send_header('ETag', '\'' + file_path + '\'')
914 self.end_headers()
915
916 self.wfile.write(data)
917
918 return True
919
initial.commit94958cf2008-07-26 22:42:52 +0000920 def FileHandler(self):
921 """This handler sends the contents of the requested file. Wow, it's like
922 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000923
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000924 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000925 if not self.path.startswith(prefix):
926 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000927 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000928
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000929 def PostOnlyFileHandler(self):
930 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000931
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000932 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000933 if not self.path.startswith(prefix):
934 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000935 return self._FileHandlerHelper(prefix)
936
937 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000938 request_body = ''
939 if self.command == 'POST' or self.command == 'PUT':
940 # Consume a request body if present.
941 request_body = self.ReadRequestBody()
942
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000943 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000944 query_dict = cgi.parse_qs(query)
945
946 expected_body = query_dict.get('expected_body', [])
947 if expected_body and request_body not in expected_body:
948 self.send_response(404)
949 self.end_headers()
950 self.wfile.write('')
951 return True
952
953 expected_headers = query_dict.get('expected_headers', [])
954 for expected_header in expected_headers:
955 header_name, expected_value = expected_header.split(':')
956 if self.headers.getheader(header_name) != expected_value:
957 self.send_response(404)
958 self.end_headers()
959 self.wfile.write('')
960 return True
961
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000962 sub_path = url_path[len(prefix):]
963 entries = sub_path.split('/')
964 file_path = os.path.join(self.server.data_dir, *entries)
965 if os.path.isdir(file_path):
966 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000967
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000968 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000969 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000970 self.send_error(404)
971 return True
972
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000973 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000974 data = f.read()
975 f.close()
976
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000977 data = self._ReplaceFileData(data, query)
978
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000979 old_protocol_version = self.protocol_version
980
initial.commit94958cf2008-07-26 22:42:52 +0000981 # If file.mock-http-headers exists, it contains the headers we
982 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000983 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000984 if os.path.isfile(headers_path):
985 f = open(headers_path, "r")
986
987 # "HTTP/1.1 200 OK"
988 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000989 http_major, http_minor, status_code = re.findall(
990 'HTTP/(\d+).(\d+) (\d+)', response)[0]
991 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000992 self.send_response(int(status_code))
993
994 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000995 header_values = re.findall('(\S+):\s*(.*)', line)
996 if len(header_values) > 0:
997 # "name: value"
998 name, value = header_values[0]
999 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001000 f.close()
1001 else:
1002 # Could be more generic once we support mime-type sniffing, but for
1003 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001004
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001005 range_header = self.headers.get('Range')
1006 if range_header and range_header.startswith('bytes='):
1007 # Note this doesn't handle all valid byte range_header values (i.e.
1008 # left open ended ones), just enough for what we needed so far.
1009 range_header = range_header[6:].split('-')
1010 start = int(range_header[0])
1011 if range_header[1]:
1012 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001013 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001014 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001015
1016 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001017 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1018 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001019 self.send_header('Content-Range', content_range)
1020 data = data[start: end + 1]
1021 else:
1022 self.send_response(200)
1023
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001024 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001025 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001026 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001027 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001028 self.end_headers()
1029
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001030 if (self.command != 'HEAD'):
1031 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001032
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001033 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001034 return True
1035
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001036 def SetCookieHandler(self):
1037 """This handler just sets a cookie, for testing cookie handling."""
1038
1039 if not self._ShouldHandleRequest("/set-cookie"):
1040 return False
1041
1042 query_char = self.path.find('?')
1043 if query_char != -1:
1044 cookie_values = self.path[query_char + 1:].split('&')
1045 else:
1046 cookie_values = ("",)
1047 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001048 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001049 for cookie_value in cookie_values:
1050 self.send_header('Set-Cookie', '%s' % cookie_value)
1051 self.end_headers()
1052 for cookie_value in cookie_values:
1053 self.wfile.write('%s' % cookie_value)
1054 return True
1055
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001056 def SetManyCookiesHandler(self):
1057 """This handler just sets a given number of cookies, for testing handling
1058 of large numbers of cookies."""
1059
1060 if not self._ShouldHandleRequest("/set-many-cookies"):
1061 return False
1062
1063 query_char = self.path.find('?')
1064 if query_char != -1:
1065 num_cookies = int(self.path[query_char + 1:])
1066 else:
1067 num_cookies = 0
1068 self.send_response(200)
1069 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001070 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001071 self.send_header('Set-Cookie', 'a=')
1072 self.end_headers()
1073 self.wfile.write('%d cookies were sent' % num_cookies)
1074 return True
1075
mattm@chromium.org983fc462012-06-30 00:52:08 +00001076 def ExpectAndSetCookieHandler(self):
1077 """Expects some cookies to be sent, and if they are, sets more cookies.
1078
1079 The expect parameter specifies a required cookie. May be specified multiple
1080 times.
1081 The set parameter specifies a cookie to set if all required cookies are
1082 preset. May be specified multiple times.
1083 The data parameter specifies the response body data to be returned."""
1084
1085 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1086 return False
1087
1088 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1089 query_dict = cgi.parse_qs(query)
1090 cookies = set()
1091 if 'Cookie' in self.headers:
1092 cookie_header = self.headers.getheader('Cookie')
1093 cookies.update([s.strip() for s in cookie_header.split(';')])
1094 got_all_expected_cookies = True
1095 for expected_cookie in query_dict.get('expect', []):
1096 if expected_cookie not in cookies:
1097 got_all_expected_cookies = False
1098 self.send_response(200)
1099 self.send_header('Content-Type', 'text/html')
1100 if got_all_expected_cookies:
1101 for cookie_value in query_dict.get('set', []):
1102 self.send_header('Set-Cookie', '%s' % cookie_value)
1103 self.end_headers()
1104 for data_value in query_dict.get('data', []):
1105 self.wfile.write(data_value)
1106 return True
1107
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001108 def SetHeaderHandler(self):
1109 """This handler sets a response header. Parameters are in the
1110 key%3A%20value&key2%3A%20value2 format."""
1111
1112 if not self._ShouldHandleRequest("/set-header"):
1113 return False
1114
1115 query_char = self.path.find('?')
1116 if query_char != -1:
1117 headers_values = self.path[query_char + 1:].split('&')
1118 else:
1119 headers_values = ("",)
1120 self.send_response(200)
1121 self.send_header('Content-Type', 'text/html')
1122 for header_value in headers_values:
1123 header_value = urllib.unquote(header_value)
1124 (key, value) = header_value.split(': ', 1)
1125 self.send_header(key, value)
1126 self.end_headers()
1127 for header_value in headers_values:
1128 self.wfile.write('%s' % header_value)
1129 return True
1130
initial.commit94958cf2008-07-26 22:42:52 +00001131 def AuthBasicHandler(self):
1132 """This handler tests 'Basic' authentication. It just sends a page with
1133 title 'user/pass' if you succeed."""
1134
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001135 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001136 return False
1137
1138 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001139 expected_password = 'secret'
1140 realm = 'testrealm'
1141 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001142
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001143 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1144 query_params = cgi.parse_qs(query, True)
1145 if 'set-cookie-if-challenged' in query_params:
1146 set_cookie_if_challenged = True
1147 if 'password' in query_params:
1148 expected_password = query_params['password'][0]
1149 if 'realm' in query_params:
1150 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001151
initial.commit94958cf2008-07-26 22:42:52 +00001152 auth = self.headers.getheader('authorization')
1153 try:
1154 if not auth:
1155 raise Exception('no auth')
1156 b64str = re.findall(r'Basic (\S+)', auth)[0]
1157 userpass = base64.b64decode(b64str)
1158 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001159 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001160 raise Exception('wrong password')
1161 except Exception, e:
1162 # Authentication failed.
1163 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001164 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001165 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001166 if set_cookie_if_challenged:
1167 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001168 self.end_headers()
1169 self.wfile.write('<html><head>')
1170 self.wfile.write('<title>Denied: %s</title>' % e)
1171 self.wfile.write('</head><body>')
1172 self.wfile.write('auth=%s<p>' % auth)
1173 self.wfile.write('b64str=%s<p>' % b64str)
1174 self.wfile.write('username: %s<p>' % username)
1175 self.wfile.write('userpass: %s<p>' % userpass)
1176 self.wfile.write('password: %s<p>' % password)
1177 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1178 self.wfile.write('</body></html>')
1179 return True
1180
1181 # Authentication successful. (Return a cachable response to allow for
1182 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001183 old_protocol_version = self.protocol_version
1184 self.protocol_version = "HTTP/1.1"
1185
initial.commit94958cf2008-07-26 22:42:52 +00001186 if_none_match = self.headers.getheader('if-none-match')
1187 if if_none_match == "abc":
1188 self.send_response(304)
1189 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001190 elif url_path.endswith(".gif"):
1191 # Using chrome/test/data/google/logo.gif as the test image
1192 test_image_path = ['google', 'logo.gif']
1193 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1194 if not os.path.isfile(gif_path):
1195 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001196 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001197 return True
1198
1199 f = open(gif_path, "rb")
1200 data = f.read()
1201 f.close()
1202
1203 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001204 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001205 self.send_header('Cache-control', 'max-age=60000')
1206 self.send_header('Etag', 'abc')
1207 self.end_headers()
1208 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001209 else:
1210 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001211 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001212 self.send_header('Cache-control', 'max-age=60000')
1213 self.send_header('Etag', 'abc')
1214 self.end_headers()
1215 self.wfile.write('<html><head>')
1216 self.wfile.write('<title>%s/%s</title>' % (username, password))
1217 self.wfile.write('</head><body>')
1218 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001219 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001220 self.wfile.write('</body></html>')
1221
rvargas@google.com54453b72011-05-19 01:11:11 +00001222 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001223 return True
1224
tonyg@chromium.org75054202010-03-31 22:06:10 +00001225 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001226 """Returns a nonce that's stable per request path for the server's lifetime.
1227 This is a fake implementation. A real implementation would only use a given
1228 nonce a single time (hence the name n-once). However, for the purposes of
1229 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001230
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001231 Args:
1232 force_reset: Iff set, the nonce will be changed. Useful for testing the
1233 "stale" response.
1234 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001235
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001236 if force_reset or not self.server.nonce_time:
1237 self.server.nonce_time = time.time()
1238 return hashlib.md5('privatekey%s%d' %
1239 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001240
1241 def AuthDigestHandler(self):
1242 """This handler tests 'Digest' authentication.
1243
1244 It just sends a page with title 'user/pass' if you succeed.
1245
1246 A stale response is sent iff "stale" is present in the request path.
1247 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001248
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001249 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001250 return False
1251
tonyg@chromium.org75054202010-03-31 22:06:10 +00001252 stale = 'stale' in self.path
1253 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001254 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001255 password = 'secret'
1256 realm = 'testrealm'
1257
1258 auth = self.headers.getheader('authorization')
1259 pairs = {}
1260 try:
1261 if not auth:
1262 raise Exception('no auth')
1263 if not auth.startswith('Digest'):
1264 raise Exception('not digest')
1265 # Pull out all the name="value" pairs as a dictionary.
1266 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1267
1268 # Make sure it's all valid.
1269 if pairs['nonce'] != nonce:
1270 raise Exception('wrong nonce')
1271 if pairs['opaque'] != opaque:
1272 raise Exception('wrong opaque')
1273
1274 # Check the 'response' value and make sure it matches our magic hash.
1275 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001276 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001277 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001278 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001279 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001280 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001281 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1282 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001283 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001284
1285 if pairs['response'] != response:
1286 raise Exception('wrong password')
1287 except Exception, e:
1288 # Authentication failed.
1289 self.send_response(401)
1290 hdr = ('Digest '
1291 'realm="%s", '
1292 'domain="/", '
1293 'qop="auth", '
1294 'algorithm=MD5, '
1295 'nonce="%s", '
1296 'opaque="%s"') % (realm, nonce, opaque)
1297 if stale:
1298 hdr += ', stale="TRUE"'
1299 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001300 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001301 self.end_headers()
1302 self.wfile.write('<html><head>')
1303 self.wfile.write('<title>Denied: %s</title>' % e)
1304 self.wfile.write('</head><body>')
1305 self.wfile.write('auth=%s<p>' % auth)
1306 self.wfile.write('pairs=%s<p>' % pairs)
1307 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1308 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1309 self.wfile.write('</body></html>')
1310 return True
1311
1312 # Authentication successful.
1313 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001314 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001315 self.end_headers()
1316 self.wfile.write('<html><head>')
1317 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1318 self.wfile.write('</head><body>')
1319 self.wfile.write('auth=%s<p>' % auth)
1320 self.wfile.write('pairs=%s<p>' % pairs)
1321 self.wfile.write('</body></html>')
1322
1323 return True
1324
1325 def SlowServerHandler(self):
1326 """Wait for the user suggested time before responding. The syntax is
1327 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001328
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001329 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001330 return False
1331 query_char = self.path.find('?')
1332 wait_sec = 1.0
1333 if query_char >= 0:
1334 try:
davidben05f82202015-03-31 13:48:07 -07001335 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001336 except ValueError:
1337 pass
1338 time.sleep(wait_sec)
1339 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001340 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001341 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001342 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001343 return True
1344
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001345 def ChunkedServerHandler(self):
1346 """Send chunked response. Allows to specify chunks parameters:
1347 - waitBeforeHeaders - ms to wait before sending headers
1348 - waitBetweenChunks - ms to wait between chunks
1349 - chunkSize - size of each chunk in bytes
1350 - chunksNumber - number of chunks
1351 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1352 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001353
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001354 if not self._ShouldHandleRequest("/chunked"):
1355 return False
1356 query_char = self.path.find('?')
1357 chunkedSettings = {'waitBeforeHeaders' : 0,
1358 'waitBetweenChunks' : 0,
1359 'chunkSize' : 5,
1360 'chunksNumber' : 5}
1361 if query_char >= 0:
1362 params = self.path[query_char + 1:].split('&')
1363 for param in params:
1364 keyValue = param.split('=')
1365 if len(keyValue) == 2:
1366 try:
1367 chunkedSettings[keyValue[0]] = int(keyValue[1])
1368 except ValueError:
1369 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001370 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001371 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1372 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001373 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001374 self.send_header('Connection', 'close')
1375 self.send_header('Transfer-Encoding', 'chunked')
1376 self.end_headers()
1377 # Chunked encoding: sending all chunks, then final zero-length chunk and
1378 # then final CRLF.
1379 for i in range(0, chunkedSettings['chunksNumber']):
1380 if i > 0:
1381 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1382 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001383 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001384 self.sendChunkHelp('')
1385 return True
1386
creis@google.com2f4f6a42011-03-25 19:44:19 +00001387 def NoContentHandler(self):
1388 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001389
creis@google.com2f4f6a42011-03-25 19:44:19 +00001390 if not self._ShouldHandleRequest("/nocontent"):
1391 return False
1392 self.send_response(204)
1393 self.end_headers()
1394 return True
1395
initial.commit94958cf2008-07-26 22:42:52 +00001396 def ServerRedirectHandler(self):
1397 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001398 '/server-redirect?http://foo.bar/asdf' to redirect to
1399 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001400
1401 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001402 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001403 return False
1404
1405 query_char = self.path.find('?')
1406 if query_char < 0 or len(self.path) <= query_char + 1:
1407 self.sendRedirectHelp(test_name)
1408 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001409 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001410
1411 self.send_response(301) # moved permanently
1412 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001413 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001414 self.end_headers()
1415 self.wfile.write('<html><head>')
1416 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1417
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001418 return True
initial.commit94958cf2008-07-26 22:42:52 +00001419
naskoe7a0d0d2014-09-29 08:53:05 -07001420 def CrossSiteRedirectHandler(self):
1421 """Sends a server redirect to the given site. The syntax is
1422 '/cross-site/hostname/...' to redirect to //hostname/...
1423 It is used to navigate between different Sites, causing
1424 cross-site/cross-process navigations in the browser."""
1425
1426 test_name = "/cross-site"
1427 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001428 return False
1429
1430 params = urllib.unquote(self.path[(len(test_name) + 1):])
1431 slash = params.find('/')
1432 if slash < 0:
1433 self.sendRedirectHelp(test_name)
1434 return True
1435
1436 host = params[:slash]
1437 path = params[(slash+1):]
1438 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1439
1440 self.send_response(301) # moved permanently
1441 self.send_header('Location', dest)
1442 self.send_header('Content-Type', 'text/html')
1443 self.end_headers()
1444 self.wfile.write('<html><head>')
1445 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1446
1447 return True
1448
initial.commit94958cf2008-07-26 22:42:52 +00001449 def ClientRedirectHandler(self):
1450 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001451 '/client-redirect?http://foo.bar/asdf' to redirect to
1452 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001453
1454 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001455 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001456 return False
1457
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001458 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001459 if query_char < 0 or len(self.path) <= query_char + 1:
1460 self.sendRedirectHelp(test_name)
1461 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001462 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001463
1464 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001465 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001466 self.end_headers()
1467 self.wfile.write('<html><head>')
1468 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1469 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1470
1471 return True
1472
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001473 def GetSSLSessionCacheHandler(self):
1474 """Send a reply containing a log of the session cache operations."""
1475
1476 if not self._ShouldHandleRequest('/ssl-session-cache'):
1477 return False
1478
1479 self.send_response(200)
1480 self.send_header('Content-Type', 'text/plain')
1481 self.end_headers()
1482 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001483 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001484 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001485 self.wfile.write('Pass --https-record-resume in order to use' +
1486 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001487 return True
1488
1489 for (action, sessionID) in log:
1490 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001491 return True
1492
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001493 def SSLManySmallRecords(self):
1494 """Sends a reply consisting of a variety of small writes. These will be
1495 translated into a series of small SSL records when used over an HTTPS
1496 server."""
1497
1498 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1499 return False
1500
1501 self.send_response(200)
1502 self.send_header('Content-Type', 'text/plain')
1503 self.end_headers()
1504
1505 # Write ~26K of data, in 1350 byte chunks
1506 for i in xrange(20):
1507 self.wfile.write('*' * 1350)
1508 self.wfile.flush()
1509 return True
1510
agl@chromium.org04700be2013-03-02 18:40:41 +00001511 def GetChannelID(self):
1512 """Send a reply containing the hashed ChannelID that the client provided."""
1513
1514 if not self._ShouldHandleRequest('/channel-id'):
1515 return False
1516
1517 self.send_response(200)
1518 self.send_header('Content-Type', 'text/plain')
1519 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001520 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001521 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1522 return True
1523
nharper08eae822016-01-25 15:54:14 -08001524 def GetTokenBindingEKM(self):
1525 """Send a reply containing the EKM value for token binding from the TLS
1526 layer."""
1527
1528 if not self._ShouldHandleRequest('/tokbind-ekm'):
1529 return False
1530
1531 ekm = self.server.tlsConnection.exportKeyingMaterial(
1532 "EXPORTER-Token-Binding", "", False, 32)
1533 self.send_response(200)
1534 self.send_header('Content-Type', 'application/octet-stream')
1535 self.end_headers()
1536 self.wfile.write(ekm)
1537 return True
1538
nharpercb1adc32016-03-30 16:05:48 -07001539 def ForwardTokenBindingHeader(self):
nharpere758cd12016-07-13 17:49:36 -07001540 """Send a redirect that sets the Include-Referred-Token-Binding-ID
nharpercb1adc32016-03-30 16:05:48 -07001541 header."""
1542
1543 test_name = '/forward-tokbind'
1544 if not self._ShouldHandleRequest(test_name):
1545 return False
1546
1547 query_char = self.path.find('?')
1548 if query_char < 0 or len(self.path) <= query_char + 1:
1549 self.sendRedirectHelp(test_name)
1550 return True
1551 dest = urllib.unquote(self.path[query_char + 1:])
1552
1553 self.send_response(302)
1554 self.send_header('Location', dest)
nharpere758cd12016-07-13 17:49:36 -07001555 self.send_header('Include-Referred-Token-Binding-ID', 'true')
nharpercb1adc32016-03-30 16:05:48 -07001556 self.end_headers()
1557 return True
1558
pneubeckfd4f0442015-08-07 04:55:10 -07001559 def GetClientCert(self):
1560 """Send a reply whether a client certificate was provided."""
1561
1562 if not self._ShouldHandleRequest('/client-cert'):
1563 return False
1564
1565 self.send_response(200)
1566 self.send_header('Content-Type', 'text/plain')
1567 self.end_headers()
1568
1569 cert_chain = self.server.tlsConnection.session.clientCertChain
1570 if cert_chain != None:
1571 self.wfile.write('got client cert with fingerprint: ' +
1572 cert_chain.getFingerprint())
1573 else:
1574 self.wfile.write('got no client cert')
1575 return True
1576
davidben599e7e72014-09-03 16:19:09 -07001577 def ClientCipherListHandler(self):
1578 """Send a reply containing the cipher suite list that the client
1579 provided. Each cipher suite value is serialized in decimal, followed by a
1580 newline."""
1581
1582 if not self._ShouldHandleRequest('/client-cipher-list'):
1583 return False
1584
1585 self.send_response(200)
1586 self.send_header('Content-Type', 'text/plain')
1587 self.end_headers()
1588
davidben11682512014-10-06 21:09:11 -07001589 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1590 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001591 return True
1592
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001593 def CloseSocketHandler(self):
1594 """Closes the socket without sending anything."""
1595
1596 if not self._ShouldHandleRequest('/close-socket'):
1597 return False
1598
1599 self.wfile.close()
1600 return True
1601
initial.commit94958cf2008-07-26 22:42:52 +00001602 def DefaultResponseHandler(self):
1603 """This is the catch-all response handler for requests that aren't handled
1604 by one of the special handlers above.
1605 Note that we specify the content-length as without it the https connection
1606 is not closed properly (and the browser keeps expecting data)."""
1607
1608 contents = "Default response given for path: " + self.path
1609 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001610 self.send_header('Content-Type', 'text/html')
1611 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001612 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001613 if (self.command != 'HEAD'):
1614 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001615 return True
1616
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001617 def RedirectConnectHandler(self):
1618 """Sends a redirect to the CONNECT request for www.redirect.com. This
1619 response is not specified by the RFC, so the browser should not follow
1620 the redirect."""
1621
1622 if (self.path.find("www.redirect.com") < 0):
1623 return False
1624
1625 dest = "http://www.destination.com/foo.js"
1626
1627 self.send_response(302) # moved temporarily
1628 self.send_header('Location', dest)
1629 self.send_header('Connection', 'close')
1630 self.end_headers()
1631 return True
1632
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001633 def ServerAuthConnectHandler(self):
1634 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1635 response doesn't make sense because the proxy server cannot request
1636 server authentication."""
1637
1638 if (self.path.find("www.server-auth.com") < 0):
1639 return False
1640
1641 challenge = 'Basic realm="WallyWorld"'
1642
1643 self.send_response(401) # unauthorized
1644 self.send_header('WWW-Authenticate', challenge)
1645 self.send_header('Connection', 'close')
1646 self.end_headers()
1647 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001648
1649 def DefaultConnectResponseHandler(self):
1650 """This is the catch-all response handler for CONNECT requests that aren't
1651 handled by one of the special handlers above. Real Web servers respond
1652 with 400 to CONNECT requests."""
1653
1654 contents = "Your client has issued a malformed or illegal request."
1655 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001656 self.send_header('Content-Type', 'text/html')
1657 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001658 self.end_headers()
1659 self.wfile.write(contents)
1660 return True
1661
initial.commit94958cf2008-07-26 22:42:52 +00001662 # called by the redirect handling function when there is no parameter
1663 def sendRedirectHelp(self, redirect_name):
1664 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001665 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001666 self.end_headers()
1667 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1668 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1669 self.wfile.write('</body></html>')
1670
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001671 # called by chunked handling function
1672 def sendChunkHelp(self, chunk):
1673 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1674 self.wfile.write('%X\r\n' % len(chunk))
1675 self.wfile.write(chunk)
1676 self.wfile.write('\r\n')
1677
akalin@chromium.org154bb132010-11-12 02:20:27 +00001678
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001679class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001680 def __init__(self, request, client_address, socket_server):
mattm10ede842016-11-29 11:57:16 -08001681 handlers = [self.OCSPResponse, self.CaIssuersResponse]
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001682 self.ocsp_response = socket_server.ocsp_response
mattm10ede842016-11-29 11:57:16 -08001683 self.ca_issuers_response = socket_server.ca_issuers_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001684 testserver_base.BasePageHandler.__init__(self, request, client_address,
1685 socket_server, [], handlers, [],
1686 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001687
1688 def OCSPResponse(self):
mattm10ede842016-11-29 11:57:16 -08001689 if not self._ShouldHandleRequest("/ocsp"):
1690 return False
1691 print 'handling ocsp request'
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001692 self.send_response(200)
1693 self.send_header('Content-Type', 'application/ocsp-response')
1694 self.send_header('Content-Length', str(len(self.ocsp_response)))
1695 self.end_headers()
1696
1697 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001698
mattm10ede842016-11-29 11:57:16 -08001699 def CaIssuersResponse(self):
1700 if not self._ShouldHandleRequest("/ca_issuers"):
1701 return False
1702 print 'handling ca_issuers request'
1703 self.send_response(200)
1704 self.send_header('Content-Type', 'application/pkix-cert')
1705 self.send_header('Content-Length', str(len(self.ca_issuers_response)))
1706 self.end_headers()
1707
1708 self.wfile.write(self.ca_issuers_response)
1709
mattm@chromium.org830a3712012-11-07 23:00:07 +00001710
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001711class TCPEchoHandler(SocketServer.BaseRequestHandler):
1712 """The RequestHandler class for TCP echo server.
1713
1714 It is instantiated once per connection to the server, and overrides the
1715 handle() method to implement communication to the client.
1716 """
1717
1718 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001719 """Handles the request from the client and constructs a response."""
1720
1721 data = self.request.recv(65536).strip()
1722 # Verify the "echo request" message received from the client. Send back
1723 # "echo response" message if "echo request" message is valid.
1724 try:
1725 return_data = echo_message.GetEchoResponseData(data)
1726 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001727 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001728 except ValueError:
1729 return
1730
1731 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001732
1733
1734class UDPEchoHandler(SocketServer.BaseRequestHandler):
1735 """The RequestHandler class for UDP echo server.
1736
1737 It is instantiated once per connection to the server, and overrides the
1738 handle() method to implement communication to the client.
1739 """
1740
1741 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001742 """Handles the request from the client and constructs a response."""
1743
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001744 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001745 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001746 # Verify the "echo request" message received from the client. Send back
1747 # "echo response" message if "echo request" message is valid.
1748 try:
1749 return_data = echo_message.GetEchoResponseData(data)
1750 if not return_data:
1751 return
1752 except ValueError:
1753 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001754 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001755
1756
bashi@chromium.org33233532012-09-08 17:37:24 +00001757class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1758 """A request handler that behaves as a proxy server which requires
1759 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1760 """
1761
1762 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001763 redirect_connect_to_localhost = False;
bashi@chromium.org33233532012-09-08 17:37:24 +00001764
1765 def parse_request(self):
1766 """Overrides parse_request to check credential."""
1767
1768 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1769 return False
1770
1771 auth = self.headers.getheader('Proxy-Authorization')
1772 if auth != self._AUTH_CREDENTIAL:
1773 self.send_response(407)
1774 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1775 self.end_headers()
1776 return False
1777
1778 return True
1779
1780 def _start_read_write(self, sock):
1781 sock.setblocking(0)
1782 self.request.setblocking(0)
1783 rlist = [self.request, sock]
1784 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001785 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001786 if errors:
1787 self.send_response(500)
1788 self.end_headers()
1789 return
1790 for s in ready_sockets:
1791 received = s.recv(1024)
1792 if len(received) == 0:
1793 return
1794 if s == self.request:
1795 other = sock
1796 else:
1797 other = self.request
1798 other.send(received)
1799
1800 def _do_common_method(self):
1801 url = urlparse.urlparse(self.path)
1802 port = url.port
1803 if not port:
1804 if url.scheme == 'http':
1805 port = 80
1806 elif url.scheme == 'https':
1807 port = 443
1808 if not url.hostname or not port:
1809 self.send_response(400)
1810 self.end_headers()
1811 return
1812
1813 if len(url.path) == 0:
1814 path = '/'
1815 else:
1816 path = url.path
1817 if len(url.query) > 0:
1818 path = '%s?%s' % (url.path, url.query)
1819
1820 sock = None
1821 try:
1822 sock = socket.create_connection((url.hostname, port))
1823 sock.send('%s %s %s\r\n' % (
1824 self.command, path, self.protocol_version))
1825 for header in self.headers.headers:
1826 header = header.strip()
1827 if (header.lower().startswith('connection') or
1828 header.lower().startswith('proxy')):
1829 continue
1830 sock.send('%s\r\n' % header)
1831 sock.send('\r\n')
1832 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001833 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001834 self.send_response(500)
1835 self.end_headers()
1836 finally:
1837 if sock is not None:
1838 sock.close()
1839
1840 def do_CONNECT(self):
1841 try:
1842 pos = self.path.rfind(':')
1843 host = self.path[:pos]
1844 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001845 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001846 self.send_response(400)
1847 self.end_headers()
1848
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001849 if BasicAuthProxyRequestHandler.redirect_connect_to_localhost:
1850 host = "127.0.0.1"
1851
bashi@chromium.org33233532012-09-08 17:37:24 +00001852 try:
1853 sock = socket.create_connection((host, port))
1854 self.send_response(200, 'Connection established')
1855 self.end_headers()
1856 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001857 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001858 self.send_response(500)
1859 self.end_headers()
1860 finally:
1861 sock.close()
1862
1863 def do_GET(self):
1864 self._do_common_method()
1865
1866 def do_HEAD(self):
1867 self._do_common_method()
1868
1869
mattm@chromium.org830a3712012-11-07 23:00:07 +00001870class ServerRunner(testserver_base.TestServerRunner):
1871 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001872
mattm@chromium.org830a3712012-11-07 23:00:07 +00001873 def __init__(self):
1874 super(ServerRunner, self).__init__()
1875 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001876
mattm@chromium.org830a3712012-11-07 23:00:07 +00001877 def __make_data_dir(self):
1878 if self.options.data_dir:
1879 if not os.path.isdir(self.options.data_dir):
1880 raise testserver_base.OptionError('specified data dir not found: ' +
1881 self.options.data_dir + ' exiting...')
1882 my_data_dir = self.options.data_dir
1883 else:
1884 # Create the default path to our data dir, relative to the exe dir.
1885 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1886 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001887
mattm@chromium.org830a3712012-11-07 23:00:07 +00001888 #TODO(ibrar): Must use Find* funtion defined in google\tools
1889 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001890
mattm@chromium.org830a3712012-11-07 23:00:07 +00001891 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001892
mattm@chromium.org830a3712012-11-07 23:00:07 +00001893 def create_server(self, server_data):
1894 port = self.options.port
1895 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001896
estark21667d62015-04-08 21:00:16 -07001897 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1898 # will result in a call to |getaddrinfo|, which fails with "nodename
1899 # nor servname provided" for localhost:0 on 10.6.
1900 if self.options.server_type == SERVER_WEBSOCKET and \
1901 host == "localhost" and \
1902 port == 0:
1903 host = "127.0.0.1"
1904
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001905 # Construct the subjectAltNames for any ad-hoc generated certificates.
1906 # As host can be either a DNS name or IP address, attempt to determine
1907 # which it is, so it can be placed in the appropriate SAN.
1908 dns_sans = None
1909 ip_sans = None
1910 ip = None
1911 try:
1912 ip = socket.inet_aton(host)
1913 ip_sans = [ip]
1914 except socket.error:
1915 pass
1916 if ip is None:
1917 dns_sans = [host]
1918
mattm@chromium.org830a3712012-11-07 23:00:07 +00001919 if self.options.server_type == SERVER_HTTP:
1920 if self.options.https:
1921 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08001922 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001923 if self.options.cert_and_key_file:
1924 if not os.path.isfile(self.options.cert_and_key_file):
1925 raise testserver_base.OptionError(
1926 'specified server cert file not found: ' +
1927 self.options.cert_and_key_file + ' exiting...')
1928 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
mattm10ede842016-11-29 11:57:16 -08001929 elif self.options.aia_intermediate:
1930 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
1931 print ('AIA server started on %s:%d...' %
1932 (host, self.__ocsp_server.server_port))
1933
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001934 ocsp_server_port = self.__ocsp_server.server_port
1935 if self.options.ocsp_proxy_port_number != 0:
1936 ocsp_server_port = self.options.ocsp_proxy_port_number
1937 server_data['ocsp_port'] = self.__ocsp_server.server_port
1938
mattm10ede842016-11-29 11:57:16 -08001939 (pem_cert_and_key, intermediate_cert_der) = \
1940 minica.GenerateCertKeyAndIntermediate(
Adam Langley1b622652017-12-05 23:57:33 +00001941 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001942 ip_sans=ip_sans, dns_sans=dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001943 ca_issuers_url =
1944 ("http://%s:%d/ca_issuers" % (host, ocsp_server_port)),
mattm10ede842016-11-29 11:57:16 -08001945 serial = self.options.cert_serial)
1946
1947 self.__ocsp_server.ocsp_response = None
1948 self.__ocsp_server.ca_issuers_response = intermediate_cert_der
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001949 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001950 # generate a new certificate and run an OCSP server for it.
1951 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001952 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001953 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001954
dadrian4ccf51c2016-07-20 15:36:58 -07001955 ocsp_states = list()
1956 for ocsp_state_arg in self.options.ocsp.split(':'):
1957 if ocsp_state_arg == 'ok':
1958 ocsp_state = minica.OCSP_STATE_GOOD
1959 elif ocsp_state_arg == 'revoked':
1960 ocsp_state = minica.OCSP_STATE_REVOKED
1961 elif ocsp_state_arg == 'invalid':
1962 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE
1963 elif ocsp_state_arg == 'unauthorized':
1964 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1965 elif ocsp_state_arg == 'unknown':
1966 ocsp_state = minica.OCSP_STATE_UNKNOWN
1967 elif ocsp_state_arg == 'later':
1968 ocsp_state = minica.OCSP_STATE_TRY_LATER
1969 elif ocsp_state_arg == 'invalid_data':
1970 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE_DATA
1971 elif ocsp_state_arg == "mismatched_serial":
1972 ocsp_state = minica.OCSP_STATE_MISMATCHED_SERIAL
1973 else:
1974 raise testserver_base.OptionError('unknown OCSP status: ' +
1975 ocsp_state_arg)
1976 ocsp_states.append(ocsp_state)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001977
dadrian4ccf51c2016-07-20 15:36:58 -07001978 if len(ocsp_states) > 1:
1979 if set(ocsp_states) & OCSP_STATES_NO_SINGLE_RESPONSE:
1980 raise testserver_base.OptionError('Multiple OCSP responses '
1981 'incompatible with states ' + str(ocsp_states))
1982
1983 ocsp_dates = list()
1984 for ocsp_date_arg in self.options.ocsp_date.split(':'):
1985 if ocsp_date_arg == 'valid':
1986 ocsp_date = minica.OCSP_DATE_VALID
1987 elif ocsp_date_arg == 'old':
1988 ocsp_date = minica.OCSP_DATE_OLD
1989 elif ocsp_date_arg == 'early':
1990 ocsp_date = minica.OCSP_DATE_EARLY
1991 elif ocsp_date_arg == 'long':
1992 ocsp_date = minica.OCSP_DATE_LONG
dadrian4ccf51c2016-07-20 15:36:58 -07001993 else:
1994 raise testserver_base.OptionError('unknown OCSP date: ' +
1995 ocsp_date_arg)
1996 ocsp_dates.append(ocsp_date)
1997
1998 if len(ocsp_states) != len(ocsp_dates):
1999 raise testserver_base.OptionError('mismatched ocsp and ocsp-date '
2000 'count')
2001
2002 ocsp_produced = None
2003 if self.options.ocsp_produced == 'valid':
2004 ocsp_produced = minica.OCSP_PRODUCED_VALID
2005 elif self.options.ocsp_produced == 'before':
2006 ocsp_produced = minica.OCSP_PRODUCED_BEFORE_CERT
2007 elif self.options.ocsp_produced == 'after':
2008 ocsp_produced = minica.OCSP_PRODUCED_AFTER_CERT
mattm@chromium.org830a3712012-11-07 23:00:07 +00002009 else:
dadrian4ccf51c2016-07-20 15:36:58 -07002010 raise testserver_base.OptionError('unknown OCSP produced: ' +
2011 self.options.ocsp_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002012
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002013 ocsp_server_port = self.__ocsp_server.server_port
2014 if self.options.ocsp_proxy_port_number != 0:
2015 ocsp_server_port = self.options.ocsp_proxy_port_number
2016 server_data['ocsp_port'] = self.__ocsp_server.server_port
2017
mattm@chromium.org830a3712012-11-07 23:00:07 +00002018 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
Adam Langley1b622652017-12-05 23:57:33 +00002019 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00002020 ip_sans = ip_sans,
2021 dns_sans = dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002022 ocsp_url = ("http://%s:%d/ocsp" % (host, ocsp_server_port)),
dadrian4ccf51c2016-07-20 15:36:58 -07002023 ocsp_states = ocsp_states,
2024 ocsp_dates = ocsp_dates,
2025 ocsp_produced = ocsp_produced,
agl@chromium.orgdf778142013-07-31 21:57:28 +00002026 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002027
davidben3e2564a2014-11-07 18:51:00 -08002028 if self.options.ocsp_server_unavailable:
2029 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
2030 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
2031 else:
2032 self.__ocsp_server.ocsp_response = ocsp_der
mattm10ede842016-11-29 11:57:16 -08002033 self.__ocsp_server.ca_issuers_response = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00002034
2035 for ca_cert in self.options.ssl_client_ca:
2036 if not os.path.isfile(ca_cert):
2037 raise testserver_base.OptionError(
2038 'specified trusted client CA file not found: ' + ca_cert +
2039 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002040
2041 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08002042 if self.options.staple_ocsp_response:
2043 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002044
mattm@chromium.org830a3712012-11-07 23:00:07 +00002045 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2046 self.options.ssl_client_auth,
2047 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002048 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002049 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002050 self.options.ssl_key_exchange,
bnc5fb33bd2016-08-05 12:09:21 -07002051 self.options.alpn_protocols,
bnc609ad4c2015-10-02 05:11:24 -07002052 self.options.npn_protocols,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002053 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002054 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002055 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002056 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002057 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002058 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07002059 stapled_ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -07002060 self.options.alert_after_handshake,
2061 self.options.disable_channel_id,
2062 self.options.disable_extended_master_secret,
2063 self.options.token_binding_params)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002064 print 'HTTPS server started on https://%s:%d...' % \
2065 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002066 else:
2067 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002068 print 'HTTP server started on http://%s:%d...' % \
2069 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002070
2071 server.data_dir = self.__make_data_dir()
2072 server.file_root_url = self.options.file_root_url
2073 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002074 elif self.options.server_type == SERVER_WEBSOCKET:
2075 # Launch pywebsocket via WebSocketServer.
2076 logger = logging.getLogger()
2077 logger.addHandler(logging.StreamHandler())
2078 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2079 # is required to work correctly. It should be fixed from pywebsocket side.
2080 os.chdir(self.__make_data_dir())
2081 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002082 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002083 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002084 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002085 websocket_options.use_tls = True
Sergey Ulanovdd8b8ea2017-09-08 22:53:25 +00002086 key_path = os.path.join(ROOT_DIR, self.options.cert_and_key_file)
2087 if not os.path.isfile(key_path):
2088 raise testserver_base.OptionError(
2089 'specified server cert file not found: ' +
2090 self.options.cert_and_key_file + ' exiting...')
2091 websocket_options.private_key = key_path
2092 websocket_options.certificate = key_path
2093
mattm@chromium.org830a3712012-11-07 23:00:07 +00002094 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002095 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002096 websocket_options.tls_client_auth = True
2097 if len(self.options.ssl_client_ca) != 1:
2098 raise testserver_base.OptionError(
2099 'one trusted client CA file should be specified')
2100 if not os.path.isfile(self.options.ssl_client_ca[0]):
2101 raise testserver_base.OptionError(
2102 'specified trusted client CA file not found: ' +
2103 self.options.ssl_client_ca[0] + ' exiting...')
2104 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07002105 print 'Trying to start websocket server on %s://%s:%d...' % \
2106 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002107 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002108 print 'WebSocket server started on %s://%s:%d...' % \
2109 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002110 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002111 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002112 elif self.options.server_type == SERVER_TCP_ECHO:
2113 # Used for generating the key (randomly) that encodes the "echo request"
2114 # message.
2115 random.seed()
2116 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002117 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002118 server_data['port'] = server.server_port
2119 elif self.options.server_type == SERVER_UDP_ECHO:
2120 # Used for generating the key (randomly) that encodes the "echo request"
2121 # message.
2122 random.seed()
2123 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002124 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002125 server_data['port'] = server.server_port
2126 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002127 BasicAuthProxyRequestHandler.redirect_connect_to_localhost = \
2128 self.options.redirect_connect_to_localhost
Adam Rice34b2e312018-04-06 16:48:30 +00002129 server = ThreadingHTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002130 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002131 server_data['port'] = server.server_port
2132 elif self.options.server_type == SERVER_FTP:
2133 my_data_dir = self.__make_data_dir()
2134
2135 # Instantiate a dummy authorizer for managing 'virtual' users
2136 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2137
xleng9d4c45f2015-05-04 16:26:12 -07002138 # Define a new user having full r/w permissions
mattm@chromium.org830a3712012-11-07 23:00:07 +00002139 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2140
xleng9d4c45f2015-05-04 16:26:12 -07002141 # Define a read-only anonymous user unless disabled
2142 if not self.options.no_anonymous_ftp_user:
2143 authorizer.add_anonymous(my_data_dir)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002144
2145 # Instantiate FTP handler class
2146 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2147 ftp_handler.authorizer = authorizer
2148
2149 # Define a customized banner (string returned when client connects)
2150 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2151 pyftpdlib.ftpserver.__ver__)
2152
2153 # Instantiate FTP server class and listen to address:port
2154 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2155 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002156 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002157 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002158 raise testserver_base.OptionError('unknown server type' +
2159 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002160
mattm@chromium.org830a3712012-11-07 23:00:07 +00002161 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002162
mattm@chromium.org830a3712012-11-07 23:00:07 +00002163 def run_server(self):
2164 if self.__ocsp_server:
2165 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002166
mattm@chromium.org830a3712012-11-07 23:00:07 +00002167 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002168
mattm@chromium.org830a3712012-11-07 23:00:07 +00002169 if self.__ocsp_server:
2170 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002171
mattm@chromium.org830a3712012-11-07 23:00:07 +00002172 def add_options(self):
2173 testserver_base.TestServerRunner.add_options(self)
2174 self.option_parser.add_option('-f', '--ftp', action='store_const',
2175 const=SERVER_FTP, default=SERVER_HTTP,
2176 dest='server_type',
2177 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002178 self.option_parser.add_option('--tcp-echo', action='store_const',
2179 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2180 dest='server_type',
2181 help='start up a tcp echo server.')
2182 self.option_parser.add_option('--udp-echo', action='store_const',
2183 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2184 dest='server_type',
2185 help='start up a udp echo server.')
2186 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2187 const=SERVER_BASIC_AUTH_PROXY,
2188 default=SERVER_HTTP, dest='server_type',
2189 help='start up a proxy server which requires '
2190 'basic authentication.')
2191 self.option_parser.add_option('--websocket', action='store_const',
2192 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2193 dest='server_type',
2194 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002195 self.option_parser.add_option('--https', action='store_true',
2196 dest='https', help='Specify that https '
2197 'should be used.')
2198 self.option_parser.add_option('--cert-and-key-file',
2199 dest='cert_and_key_file', help='specify the '
2200 'path to the file containing the certificate '
2201 'and private key for the server in PEM '
2202 'format')
mattm10ede842016-11-29 11:57:16 -08002203 self.option_parser.add_option('--aia-intermediate', action='store_true',
2204 dest='aia_intermediate',
2205 help='generate a certificate chain that '
2206 'requires AIA cert fetching, and run a '
2207 'server to respond to the AIA request.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002208 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2209 help='The type of OCSP response generated '
2210 'for the automatically generated '
2211 'certificate. One of [ok,revoked,invalid]')
dadrian4ccf51c2016-07-20 15:36:58 -07002212 self.option_parser.add_option('--ocsp-date', dest='ocsp_date',
2213 default='valid', help='The validity of the '
2214 'range between thisUpdate and nextUpdate')
2215 self.option_parser.add_option('--ocsp-produced', dest='ocsp_produced',
2216 default='valid', help='producedAt relative '
2217 'to certificate expiry')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002218 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2219 default=0, type=int,
2220 help='If non-zero then the generated '
2221 'certificate will have this serial number')
Adam Langley1b622652017-12-05 23:57:33 +00002222 self.option_parser.add_option('--cert-common-name', dest='cert_common_name',
2223 default="127.0.0.1",
2224 help='The generated certificate will have '
2225 'this common name')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002226 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2227 default='0', type='int',
2228 help='If nonzero, certain TLS connections '
2229 'will be aborted in order to test version '
2230 'fallback. 1 means all TLS versions will be '
2231 'aborted. 2 means TLS 1.1 or higher will be '
2232 'aborted. 3 means TLS 1.2 or higher will be '
davidbendf2e3862017-04-12 15:23:34 -07002233 'aborted. 4 means TLS 1.3 or higher will be '
mattm@chromium.org830a3712012-11-07 23:00:07 +00002234 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002235 self.option_parser.add_option('--tls-intolerance-type',
2236 dest='tls_intolerance_type',
2237 default="alert",
2238 help='Controls how the server reacts to a '
2239 'TLS version it is intolerant to. Valid '
2240 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002241 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2242 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002243 default='',
2244 help='Base64 encoded SCT list. If set, '
2245 'server will respond with a '
2246 'signed_certificate_timestamp TLS extension '
2247 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002248 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2249 default=False, const=True,
2250 action='store_const',
2251 help='If given, TLS_FALLBACK_SCSV support '
2252 'will be enabled. This causes the server to '
2253 'reject fallback connections from compatible '
2254 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002255 self.option_parser.add_option('--staple-ocsp-response',
2256 dest='staple_ocsp_response',
2257 default=False, action='store_true',
2258 help='If set, server will staple the OCSP '
2259 'response whenever OCSP is on and the client '
2260 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002261 self.option_parser.add_option('--https-record-resume',
2262 dest='record_resume', const=True,
2263 default=False, action='store_const',
2264 help='Record resumption cache events rather '
2265 'than resuming as normal. Allows the use of '
2266 'the /ssl-session-cache request')
2267 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2268 help='Require SSL client auth on every '
2269 'connection.')
2270 self.option_parser.add_option('--ssl-client-ca', action='append',
2271 default=[], help='Specify that the client '
2272 'certificate request should include the CA '
2273 'named in the subject of the DER-encoded '
2274 'certificate contained in the specified '
2275 'file. This option may appear multiple '
2276 'times, indicating multiple CA names should '
2277 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002278 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2279 default=[], help='Specify that the client '
2280 'certificate request should include the '
2281 'specified certificate_type value. This '
2282 'option may appear multiple times, '
2283 'indicating multiple values should be send '
2284 'in the request. Valid values are '
2285 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2286 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002287 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2288 help='Specify the bulk encryption '
2289 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08002290 'SSL server. Valid values are "aes128gcm", '
2291 '"aes256", "aes128", "3des", "rc4". If '
2292 'omitted, all algorithms will be used. This '
2293 'option may appear multiple times, '
2294 'indicating multiple algorithms should be '
2295 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002296 self.option_parser.add_option('--ssl-key-exchange', action='append',
2297 help='Specify the key exchange algorithm(s)'
2298 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07002299 'Valid values are "rsa", "dhe_rsa", '
2300 '"ecdhe_rsa". If omitted, all algorithms '
2301 'will be used. This option may appear '
2302 'multiple times, indicating multiple '
2303 'algorithms should be enabled.');
bnc5fb33bd2016-08-05 12:09:21 -07002304 self.option_parser.add_option('--alpn-protocols', action='append',
2305 help='Specify the list of ALPN protocols. '
2306 'The server will not send an ALPN response '
2307 'if this list does not overlap with the '
2308 'list of protocols the client advertises.')
bnc609ad4c2015-10-02 05:11:24 -07002309 self.option_parser.add_option('--npn-protocols', action='append',
bnc5fb33bd2016-08-05 12:09:21 -07002310 help='Specify the list of protocols sent in '
bnc609ad4c2015-10-02 05:11:24 -07002311 'an NPN response. The server will not'
2312 'support NPN if the list is empty.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002313 self.option_parser.add_option('--file-root-url', default='/files/',
2314 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002315 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2316 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2317 dest='ws_basic_auth',
2318 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002319 self.option_parser.add_option('--ocsp-server-unavailable',
2320 dest='ocsp_server_unavailable',
2321 default=False, action='store_true',
2322 help='If set, the OCSP server will return '
2323 'a tryLater status rather than the actual '
2324 'OCSP response.')
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002325 self.option_parser.add_option('--ocsp-proxy-port-number', default=0,
2326 type='int', dest='ocsp_proxy_port_number',
2327 help='Port allocated for OCSP proxy '
2328 'when connection is proxied.')
davidben21cda342015-03-17 18:04:28 -07002329 self.option_parser.add_option('--alert-after-handshake',
2330 dest='alert_after_handshake',
2331 default=False, action='store_true',
2332 help='If set, the server will send a fatal '
2333 'alert immediately after the handshake.')
xleng9d4c45f2015-05-04 16:26:12 -07002334 self.option_parser.add_option('--no-anonymous-ftp-user',
2335 dest='no_anonymous_ftp_user',
2336 default=False, action='store_true',
2337 help='If set, the FTP server will not create '
2338 'an anonymous user.')
nharper1e8bf4b2015-09-18 12:23:02 -07002339 self.option_parser.add_option('--disable-channel-id', action='store_true')
2340 self.option_parser.add_option('--disable-extended-master-secret',
2341 action='store_true')
2342 self.option_parser.add_option('--token-binding-params', action='append',
2343 default=[], type='int')
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002344 self.option_parser.add_option('--redirect-connect-to-localhost',
2345 dest='redirect_connect_to_localhost',
2346 default=False, action='store_true',
2347 help='If set, the Proxy server will connect '
2348 'to localhost instead of the requested URL '
2349 'on CONNECT requests')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002350
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002351
initial.commit94958cf2008-07-26 22:42:52 +00002352if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002353 sys.exit(ServerRunner().main())