blob: 83920d8dd95fb5c5b0bf85bba79598b07e4c426f [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00002# Copyright 2013 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00006"""This is a simple HTTP/FTP/TCP/UDP/BASIC_AUTH_PROXY/WEBSOCKET server used for
7testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00008
9It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000010By default, it listens on an ephemeral port and sends the port number back to
11the originating process over a pipe. The originating process can specify an
12explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000013It can use https if you specify the flag --https=CERT where CERT is the path
14to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000015"""
16
17import base64
18import BaseHTTPServer
19import cgi
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000020import hashlib
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000021import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000022import minica
initial.commit94958cf2008-07-26 22:42:52 +000023import os
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +000024import json
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000025import random
initial.commit94958cf2008-07-26 22:42:52 +000026import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000028import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000029import SocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000030import ssl
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000031import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000032import sys
33import threading
initial.commit94958cf2008-07-26 22:42:52 +000034import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000035import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000036import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000037import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000038
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000039BASE_DIR = os.path.dirname(os.path.abspath(__file__))
40ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(BASE_DIR)))
41
davidben@chromium.org7d53b542014-04-10 17:56:44 +000042# Temporary hack to deal with tlslite 0.3.8 -> 0.4.6 upgrade.
43#
44# TODO(davidben): Remove this when it has cycled through all the bots and
45# developer checkouts or when http://crbug.com/356276 is resolved.
46try:
47 os.remove(os.path.join(ROOT_DIR, 'third_party', 'tlslite',
48 'tlslite', 'utils', 'hmac.pyc'))
49except Exception:
50 pass
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000051
52# Append at the end of sys.path, it's fine to use the system library.
53sys.path.append(os.path.join(ROOT_DIR, 'third_party', 'pyftpdlib', 'src'))
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000054
davidben@chromium.org7d53b542014-04-10 17:56:44 +000055# Insert at the beginning of the path, we want to use our copies of the library
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000056# unconditionally.
57sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'pywebsocket', 'src'))
davidben@chromium.org7d53b542014-04-10 17:56:44 +000058sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'tlslite'))
59
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000060import mod_pywebsocket.standalone
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000061from mod_pywebsocket.standalone import WebSocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000062# import manually
63mod_pywebsocket.standalone.ssl = ssl
davidben@chromium.org06fcf202010-09-22 18:15:23 +000064
davidben@chromium.org7d53b542014-04-10 17:56:44 +000065import pyftpdlib.ftpserver
66
67import tlslite
68import tlslite.api
69
70import echo_message
71import testserver_base
72
maruel@chromium.org756cf982009-03-05 12:46:38 +000073SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000074SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000075SERVER_TCP_ECHO = 2
76SERVER_UDP_ECHO = 3
77SERVER_BASIC_AUTH_PROXY = 4
78SERVER_WEBSOCKET = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000079
80# Default request queue size for WebSocketServer.
81_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000082
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000083class WebSocketOptions:
84 """Holds options for WebSocketServer."""
85
86 def __init__(self, host, port, data_dir):
87 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
88 self.server_host = host
89 self.port = port
90 self.websock_handlers = data_dir
91 self.scan_dir = None
92 self.allow_handlers_outside_root_dir = False
93 self.websock_handlers_map_file = None
94 self.cgi_directories = []
95 self.is_executable_method = None
96 self.allow_draft75 = False
97 self.strict = True
98
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000099 self.use_tls = False
100 self.private_key = None
101 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +0000102 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000103 self.tls_client_ca = None
yhirano@chromium.org51f90d92014-03-24 04:49:23 +0000104 self.tls_module = 'ssl'
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000105 self.use_basic_auth = False
106
mattm@chromium.org830a3712012-11-07 23:00:07 +0000107
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000108class RecordingSSLSessionCache(object):
109 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
110 lookups and inserts in order to test session cache behaviours."""
111
112 def __init__(self):
113 self.log = []
114
115 def __getitem__(self, sessionID):
116 self.log.append(('lookup', sessionID))
117 raise KeyError()
118
119 def __setitem__(self, sessionID, session):
120 self.log.append(('insert', sessionID))
121
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000122
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000123class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
124 testserver_base.BrokenPipeHandlerMixIn,
125 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000126 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000127 verification."""
128
129 pass
130
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000131class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
132 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000133 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000134 """This is a specialization of HTTPServer that serves an
135 OCSP response"""
136
137 def serve_forever_on_thread(self):
138 self.thread = threading.Thread(target = self.serve_forever,
139 name = "OCSPServerThread")
140 self.thread.start()
141
142 def stop_serving(self):
143 self.shutdown()
144 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000145
mattm@chromium.org830a3712012-11-07 23:00:07 +0000146
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000147class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000148 testserver_base.ClientRestrictingServerMixIn,
149 testserver_base.BrokenPipeHandlerMixIn,
150 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000151 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000152 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000153
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000154 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000155 ssl_client_auth, ssl_client_cas, ssl_client_cert_types,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000156 ssl_bulk_ciphers, ssl_key_exchanges, enable_npn,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000157 record_resume_info, tls_intolerant,
158 tls_intolerance_type, signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000159 fallback_scsv_enabled, ocsp_response):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000160 self.cert_chain = tlslite.api.X509CertChain()
161 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000162 # Force using only python implementation - otherwise behavior is different
163 # depending on whether m2crypto Python module is present (error is thrown
164 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
165 # the hood.
166 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
167 private=True,
168 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000169 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000170 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000171 self.ssl_client_cert_types = []
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000172 if enable_npn:
173 self.next_protos = ['http/1.1']
174 else:
175 self.next_protos = None
ekasper@google.com24aa8222013-11-28 13:43:26 +0000176 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000177 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000178 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000179
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000180 if ssl_client_auth:
181 for ca_file in ssl_client_cas:
182 s = open(ca_file).read()
183 x509 = tlslite.api.X509()
184 x509.parse(s)
185 self.ssl_client_cas.append(x509.subject)
186
187 for cert_type in ssl_client_cert_types:
188 self.ssl_client_cert_types.append({
189 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
190 "dss_sign": tlslite.api.ClientCertificateType.dss_sign,
191 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
192 }[cert_type])
193
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000194 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
195 if ssl_bulk_ciphers is not None:
196 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000197 if ssl_key_exchanges is not None:
198 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000199 if tls_intolerant != 0:
200 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
201 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
initial.commit94958cf2008-07-26 22:42:52 +0000202
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000203 if record_resume_info:
204 # If record_resume_info is true then we'll replace the session cache with
205 # an object that records the lookups and inserts that it sees.
206 self.session_cache = RecordingSSLSessionCache()
207 else:
208 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000209 testserver_base.StoppableHTTPServer.__init__(self,
210 server_address,
211 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000212
213 def handshake(self, tlsConnection):
214 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000215
initial.commit94958cf2008-07-26 22:42:52 +0000216 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000217 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000218 tlsConnection.handshakeServer(certChain=self.cert_chain,
219 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000220 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000221 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000222 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000223 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000224 reqCertTypes=self.ssl_client_cert_types,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000225 nextProtos=self.next_protos,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000226 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000227 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000228 fallbackSCSV=self.fallback_scsv_enabled,
229 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000230 tlsConnection.ignoreAbruptClose = True
231 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000232 except tlslite.api.TLSAbruptCloseError:
233 # Ignore abrupt close.
234 return True
initial.commit94958cf2008-07-26 22:42:52 +0000235 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000236 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000237 return False
238
akalin@chromium.org154bb132010-11-12 02:20:27 +0000239
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000240class FTPServer(testserver_base.ClientRestrictingServerMixIn,
241 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000242 """This is a specialization of FTPServer that adds client verification."""
243
244 pass
245
246
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000247class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
248 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000249 """A TCP echo server that echoes back what it has received."""
250
251 def server_bind(self):
252 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000253
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000254 SocketServer.TCPServer.server_bind(self)
255 host, port = self.socket.getsockname()[:2]
256 self.server_name = socket.getfqdn(host)
257 self.server_port = port
258
259 def serve_forever(self):
260 self.stop = False
261 self.nonce_time = None
262 while not self.stop:
263 self.handle_request()
264 self.socket.close()
265
266
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000267class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
268 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000269 """A UDP echo server that echoes back what it has received."""
270
271 def server_bind(self):
272 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000273
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000274 SocketServer.UDPServer.server_bind(self)
275 host, port = self.socket.getsockname()[:2]
276 self.server_name = socket.getfqdn(host)
277 self.server_port = port
278
279 def serve_forever(self):
280 self.stop = False
281 self.nonce_time = None
282 while not self.stop:
283 self.handle_request()
284 self.socket.close()
285
286
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000287class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000288 # Class variables to allow for persistence state between page handler
289 # invocations
290 rst_limits = {}
291 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000292
293 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000294 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000295 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000296 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000297 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000298 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000299 self.NoCacheMaxAgeTimeHandler,
300 self.NoCacheTimeHandler,
301 self.CacheTimeHandler,
302 self.CacheExpiresHandler,
303 self.CacheProxyRevalidateHandler,
304 self.CachePrivateHandler,
305 self.CachePublicHandler,
306 self.CacheSMaxAgeHandler,
307 self.CacheMustRevalidateHandler,
308 self.CacheMustRevalidateMaxAgeHandler,
309 self.CacheNoStoreHandler,
310 self.CacheNoStoreMaxAgeHandler,
311 self.CacheNoTransformHandler,
312 self.DownloadHandler,
313 self.DownloadFinishHandler,
314 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000315 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000316 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000317 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000318 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000319 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000320 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000321 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000322 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000323 self.AuthBasicHandler,
324 self.AuthDigestHandler,
325 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000326 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000327 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000328 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000329 self.ServerRedirectHandler,
330 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000331 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000332 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000333 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000334 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000335 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000336 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000337 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000338 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000339 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000340 self.PostOnlyFileHandler,
341 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000342 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000343 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000344 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000345 head_handlers = [
346 self.FileHandler,
347 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000348
maruel@google.come250a9b2009-03-10 17:39:46 +0000349 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000350 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000351 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000352 'gif': 'image/gif',
353 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000354 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000355 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000356 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000357 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000358 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000359 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000360 }
initial.commit94958cf2008-07-26 22:42:52 +0000361 self._default_mime_type = 'text/html'
362
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000363 testserver_base.BasePageHandler.__init__(self, request, client_address,
364 socket_server, connect_handlers,
365 get_handlers, head_handlers,
366 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000367
initial.commit94958cf2008-07-26 22:42:52 +0000368 def GetMIMETypeFromName(self, file_name):
369 """Returns the mime type for the specified file_name. So far it only looks
370 at the file extension."""
371
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000372 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000373 if len(extension) == 0:
374 # no extension.
375 return self._default_mime_type
376
ericroman@google.comc17ca532009-05-07 03:51:05 +0000377 # extension starts with a dot, so we need to remove it
378 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000379
initial.commit94958cf2008-07-26 22:42:52 +0000380 def NoCacheMaxAgeTimeHandler(self):
381 """This request handler yields a page with the title set to the current
382 system time, and no caching requested."""
383
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000384 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000385 return False
386
387 self.send_response(200)
388 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000389 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000390 self.end_headers()
391
maruel@google.come250a9b2009-03-10 17:39:46 +0000392 self.wfile.write('<html><head><title>%s</title></head></html>' %
393 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000394
395 return True
396
397 def NoCacheTimeHandler(self):
398 """This request handler yields a page with the title set to the current
399 system time, and no caching requested."""
400
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000401 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000402 return False
403
404 self.send_response(200)
405 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000406 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000407 self.end_headers()
408
maruel@google.come250a9b2009-03-10 17:39:46 +0000409 self.wfile.write('<html><head><title>%s</title></head></html>' %
410 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000411
412 return True
413
414 def CacheTimeHandler(self):
415 """This request handler yields a page with the title set to the current
416 system time, and allows caching for one minute."""
417
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000418 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000419 return False
420
421 self.send_response(200)
422 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000423 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000424 self.end_headers()
425
maruel@google.come250a9b2009-03-10 17:39:46 +0000426 self.wfile.write('<html><head><title>%s</title></head></html>' %
427 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000428
429 return True
430
431 def CacheExpiresHandler(self):
432 """This request handler yields a page with the title set to the current
433 system time, and set the page to expire on 1 Jan 2099."""
434
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000435 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000436 return False
437
438 self.send_response(200)
439 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000440 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000441 self.end_headers()
442
maruel@google.come250a9b2009-03-10 17:39:46 +0000443 self.wfile.write('<html><head><title>%s</title></head></html>' %
444 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000445
446 return True
447
448 def CacheProxyRevalidateHandler(self):
449 """This request handler yields a page with the title set to the current
450 system time, and allows caching for 60 seconds"""
451
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000452 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000453 return False
454
455 self.send_response(200)
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.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
458 self.end_headers()
459
maruel@google.come250a9b2009-03-10 17:39:46 +0000460 self.wfile.write('<html><head><title>%s</title></head></html>' %
461 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000462
463 return True
464
465 def CachePrivateHandler(self):
466 """This request handler yields a page with the title set to the current
467 system time, and allows caching for 5 seconds."""
468
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000469 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000470 return False
471
472 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000473 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000474 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000475 self.end_headers()
476
maruel@google.come250a9b2009-03-10 17:39:46 +0000477 self.wfile.write('<html><head><title>%s</title></head></html>' %
478 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000479
480 return True
481
482 def CachePublicHandler(self):
483 """This request handler yields a page with the title set to the current
484 system time, and allows caching for 5 seconds."""
485
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000486 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000487 return False
488
489 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000490 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000491 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000492 self.end_headers()
493
maruel@google.come250a9b2009-03-10 17:39:46 +0000494 self.wfile.write('<html><head><title>%s</title></head></html>' %
495 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000496
497 return True
498
499 def CacheSMaxAgeHandler(self):
500 """This request handler yields a page with the title set to the current
501 system time, and does not allow for caching."""
502
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000503 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000504 return False
505
506 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000507 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000508 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
509 self.end_headers()
510
maruel@google.come250a9b2009-03-10 17:39:46 +0000511 self.wfile.write('<html><head><title>%s</title></head></html>' %
512 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000513
514 return True
515
516 def CacheMustRevalidateHandler(self):
517 """This request handler yields a page with the title set to the current
518 system time, and does not allow caching."""
519
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000520 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000521 return False
522
523 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000524 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000525 self.send_header('Cache-Control', 'must-revalidate')
526 self.end_headers()
527
maruel@google.come250a9b2009-03-10 17:39:46 +0000528 self.wfile.write('<html><head><title>%s</title></head></html>' %
529 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000530
531 return True
532
533 def CacheMustRevalidateMaxAgeHandler(self):
534 """This request handler yields a page with the title set to the current
535 system time, and does not allow caching event though max-age of 60
536 seconds is specified."""
537
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000538 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000539 return False
540
541 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000542 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000543 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
544 self.end_headers()
545
maruel@google.come250a9b2009-03-10 17:39:46 +0000546 self.wfile.write('<html><head><title>%s</title></head></html>' %
547 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000548
549 return True
550
initial.commit94958cf2008-07-26 22:42:52 +0000551 def CacheNoStoreHandler(self):
552 """This request handler yields a page with the title set to the current
553 system time, and does not allow the page to be stored."""
554
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000555 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000556 return False
557
558 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000559 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000560 self.send_header('Cache-Control', 'no-store')
561 self.end_headers()
562
maruel@google.come250a9b2009-03-10 17:39:46 +0000563 self.wfile.write('<html><head><title>%s</title></head></html>' %
564 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000565
566 return True
567
568 def CacheNoStoreMaxAgeHandler(self):
569 """This request handler yields a page with the title set to the current
570 system time, and does not allow the page to be stored even though max-age
571 of 60 seconds is specified."""
572
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000573 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000574 return False
575
576 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000577 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000578 self.send_header('Cache-Control', 'max-age=60, no-store')
579 self.end_headers()
580
maruel@google.come250a9b2009-03-10 17:39:46 +0000581 self.wfile.write('<html><head><title>%s</title></head></html>' %
582 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000583
584 return True
585
586
587 def CacheNoTransformHandler(self):
588 """This request handler yields a page with the title set to the current
589 system time, and does not allow the content to transformed during
590 user-agent caching"""
591
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000592 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000593 return False
594
595 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000596 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000597 self.send_header('Cache-Control', 'no-transform')
598 self.end_headers()
599
maruel@google.come250a9b2009-03-10 17:39:46 +0000600 self.wfile.write('<html><head><title>%s</title></head></html>' %
601 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000602
603 return True
604
605 def EchoHeader(self):
606 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000607
ananta@chromium.org219b2062009-10-23 16:09:41 +0000608 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000609
ananta@chromium.org56812d02011-04-07 17:52:05 +0000610 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000611 """This function echoes back the value of a specific request header while
612 allowing caching for 16 hours."""
613
ananta@chromium.org56812d02011-04-07 17:52:05 +0000614 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000615
616 def EchoHeaderHelper(self, echo_header):
617 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000618
ananta@chromium.org219b2062009-10-23 16:09:41 +0000619 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000620 return False
621
622 query_char = self.path.find('?')
623 if query_char != -1:
624 header_name = self.path[query_char+1:]
625
626 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000627 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000628 if echo_header == '/echoheadercache':
629 self.send_header('Cache-control', 'max-age=60000')
630 else:
631 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000632 # insert a vary header to properly indicate that the cachability of this
633 # request is subject to value of the request header being echoed.
634 if len(header_name) > 0:
635 self.send_header('Vary', header_name)
636 self.end_headers()
637
638 if len(header_name) > 0:
639 self.wfile.write(self.headers.getheader(header_name))
640
641 return True
642
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000643 def ReadRequestBody(self):
644 """This function reads the body of the current HTTP request, handling
645 both plain and chunked transfer encoded requests."""
646
647 if self.headers.getheader('transfer-encoding') != 'chunked':
648 length = int(self.headers.getheader('content-length'))
649 return self.rfile.read(length)
650
651 # Read the request body as chunks.
652 body = ""
653 while True:
654 line = self.rfile.readline()
655 length = int(line, 16)
656 if length == 0:
657 self.rfile.readline()
658 break
659 body += self.rfile.read(length)
660 self.rfile.read(2)
661 return body
662
initial.commit94958cf2008-07-26 22:42:52 +0000663 def EchoHandler(self):
664 """This handler just echoes back the payload of the request, for testing
665 form submission."""
666
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000667 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000668 return False
669
670 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000671 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000672 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000673 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000674 return True
675
676 def EchoTitleHandler(self):
677 """This handler is like Echo, but sets the page title to the request."""
678
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000679 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000680 return False
681
682 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000683 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000684 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000685 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000686 self.wfile.write('<html><head><title>')
687 self.wfile.write(request)
688 self.wfile.write('</title></head></html>')
689 return True
690
691 def EchoAllHandler(self):
692 """This handler yields a (more) human-readable page listing information
693 about the request header & contents."""
694
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000695 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000696 return False
697
698 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000699 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000700 self.end_headers()
701 self.wfile.write('<html><head><style>'
702 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
703 '</style></head><body>'
704 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000705 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000706 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000707
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000708 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000709 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000710 params = cgi.parse_qs(qs, keep_blank_values=1)
711
712 for param in params:
713 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000714
715 self.wfile.write('</pre>')
716
717 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
718
719 self.wfile.write('</body></html>')
720 return True
721
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000722 def EchoMultipartPostHandler(self):
723 """This handler echoes received multipart post data as json format."""
724
725 if not (self._ShouldHandleRequest("/echomultipartpost") or
726 self._ShouldHandleRequest("/searchbyimage")):
727 return False
728
729 content_type, parameters = cgi.parse_header(
730 self.headers.getheader('content-type'))
731 if content_type == 'multipart/form-data':
732 post_multipart = cgi.parse_multipart(self.rfile, parameters)
733 elif content_type == 'application/x-www-form-urlencoded':
734 raise Exception('POST by application/x-www-form-urlencoded is '
735 'not implemented.')
736 else:
737 post_multipart = {}
738
739 # Since the data can be binary, we encode them by base64.
740 post_multipart_base64_encoded = {}
741 for field, values in post_multipart.items():
742 post_multipart_base64_encoded[field] = [base64.b64encode(value)
743 for value in values]
744
745 result = {'POST_multipart' : post_multipart_base64_encoded}
746
747 self.send_response(200)
748 self.send_header("Content-type", "text/plain")
749 self.end_headers()
750 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
751 return True
752
initial.commit94958cf2008-07-26 22:42:52 +0000753 def DownloadHandler(self):
754 """This handler sends a downloadable file with or without reporting
755 the size (6K)."""
756
757 if self.path.startswith("/download-unknown-size"):
758 send_length = False
759 elif self.path.startswith("/download-known-size"):
760 send_length = True
761 else:
762 return False
763
764 #
765 # The test which uses this functionality is attempting to send
766 # small chunks of data to the client. Use a fairly large buffer
767 # so that we'll fill chrome's IO buffer enough to force it to
768 # actually write the data.
769 # See also the comments in the client-side of this test in
770 # download_uitest.cc
771 #
772 size_chunk1 = 35*1024
773 size_chunk2 = 10*1024
774
775 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000776 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000777 self.send_header('Cache-Control', 'max-age=0')
778 if send_length:
779 self.send_header('Content-Length', size_chunk1 + size_chunk2)
780 self.end_headers()
781
782 # First chunk of data:
783 self.wfile.write("*" * size_chunk1)
784 self.wfile.flush()
785
786 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000787 self.server.wait_for_download = True
788 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000789 self.server.handle_request()
790
791 # Second chunk of data:
792 self.wfile.write("*" * size_chunk2)
793 return True
794
795 def DownloadFinishHandler(self):
796 """This handler just tells the server to finish the current download."""
797
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000798 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000799 return False
800
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000801 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000802 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000803 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000804 self.send_header('Cache-Control', 'max-age=0')
805 self.end_headers()
806 return True
807
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000808 def _ReplaceFileData(self, data, query_parameters):
809 """Replaces matching substrings in a file.
810
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000811 If the 'replace_text' URL query parameter is present, it is expected to be
812 of the form old_text:new_text, which indicates that any old_text strings in
813 the file are replaced with new_text. Multiple 'replace_text' parameters may
814 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000815
816 If the parameters are not present, |data| is returned.
817 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000818
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000819 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000820 replace_text_values = query_dict.get('replace_text', [])
821 for replace_text_value in replace_text_values:
822 replace_text_args = replace_text_value.split(':')
823 if len(replace_text_args) != 2:
824 raise ValueError(
825 'replace_text must be of form old_text:new_text. Actual value: %s' %
826 replace_text_value)
827 old_text_b64, new_text_b64 = replace_text_args
828 old_text = base64.urlsafe_b64decode(old_text_b64)
829 new_text = base64.urlsafe_b64decode(new_text_b64)
830 data = data.replace(old_text, new_text)
831 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000832
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000833 def ZipFileHandler(self):
834 """This handler sends the contents of the requested file in compressed form.
835 Can pass in a parameter that specifies that the content length be
836 C - the compressed size (OK),
837 U - the uncompressed size (Non-standard, but handled),
838 S - less than compressed (OK because we keep going),
839 M - larger than compressed but less than uncompressed (an error),
840 L - larger than uncompressed (an error)
841 Example: compressedfiles/Picture_1.doc?C
842 """
843
844 prefix = "/compressedfiles/"
845 if not self.path.startswith(prefix):
846 return False
847
848 # Consume a request body if present.
849 if self.command == 'POST' or self.command == 'PUT' :
850 self.ReadRequestBody()
851
852 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
853
854 if not query in ('C', 'U', 'S', 'M', 'L'):
855 return False
856
857 sub_path = url_path[len(prefix):]
858 entries = sub_path.split('/')
859 file_path = os.path.join(self.server.data_dir, *entries)
860 if os.path.isdir(file_path):
861 file_path = os.path.join(file_path, 'index.html')
862
863 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000864 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000865 self.send_error(404)
866 return True
867
868 f = open(file_path, "rb")
869 data = f.read()
870 uncompressed_len = len(data)
871 f.close()
872
873 # Compress the data.
874 data = zlib.compress(data)
875 compressed_len = len(data)
876
877 content_length = compressed_len
878 if query == 'U':
879 content_length = uncompressed_len
880 elif query == 'S':
881 content_length = compressed_len / 2
882 elif query == 'M':
883 content_length = (compressed_len + uncompressed_len) / 2
884 elif query == 'L':
885 content_length = compressed_len + uncompressed_len
886
887 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000888 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000889 self.send_header('Content-encoding', 'deflate')
890 self.send_header('Connection', 'close')
891 self.send_header('Content-Length', content_length)
892 self.send_header('ETag', '\'' + file_path + '\'')
893 self.end_headers()
894
895 self.wfile.write(data)
896
897 return True
898
initial.commit94958cf2008-07-26 22:42:52 +0000899 def FileHandler(self):
900 """This handler sends the contents of the requested file. Wow, it's like
901 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000902
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000903 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000904 if not self.path.startswith(prefix):
905 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000906 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000907
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000908 def PostOnlyFileHandler(self):
909 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000910
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000911 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000912 if not self.path.startswith(prefix):
913 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000914 return self._FileHandlerHelper(prefix)
915
916 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000917 request_body = ''
918 if self.command == 'POST' or self.command == 'PUT':
919 # Consume a request body if present.
920 request_body = self.ReadRequestBody()
921
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000922 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000923 query_dict = cgi.parse_qs(query)
924
925 expected_body = query_dict.get('expected_body', [])
926 if expected_body and request_body not in expected_body:
927 self.send_response(404)
928 self.end_headers()
929 self.wfile.write('')
930 return True
931
932 expected_headers = query_dict.get('expected_headers', [])
933 for expected_header in expected_headers:
934 header_name, expected_value = expected_header.split(':')
935 if self.headers.getheader(header_name) != expected_value:
936 self.send_response(404)
937 self.end_headers()
938 self.wfile.write('')
939 return True
940
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000941 sub_path = url_path[len(prefix):]
942 entries = sub_path.split('/')
943 file_path = os.path.join(self.server.data_dir, *entries)
944 if os.path.isdir(file_path):
945 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000946
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000947 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000948 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000949 self.send_error(404)
950 return True
951
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000952 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000953 data = f.read()
954 f.close()
955
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000956 data = self._ReplaceFileData(data, query)
957
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000958 old_protocol_version = self.protocol_version
959
initial.commit94958cf2008-07-26 22:42:52 +0000960 # If file.mock-http-headers exists, it contains the headers we
961 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000962 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000963 if os.path.isfile(headers_path):
964 f = open(headers_path, "r")
965
966 # "HTTP/1.1 200 OK"
967 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000968 http_major, http_minor, status_code = re.findall(
969 'HTTP/(\d+).(\d+) (\d+)', response)[0]
970 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000971 self.send_response(int(status_code))
972
973 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000974 header_values = re.findall('(\S+):\s*(.*)', line)
975 if len(header_values) > 0:
976 # "name: value"
977 name, value = header_values[0]
978 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000979 f.close()
980 else:
981 # Could be more generic once we support mime-type sniffing, but for
982 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000983
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000984 range_header = self.headers.get('Range')
985 if range_header and range_header.startswith('bytes='):
986 # Note this doesn't handle all valid byte range_header values (i.e.
987 # left open ended ones), just enough for what we needed so far.
988 range_header = range_header[6:].split('-')
989 start = int(range_header[0])
990 if range_header[1]:
991 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000992 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000993 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000994
995 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000996 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
997 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000998 self.send_header('Content-Range', content_range)
999 data = data[start: end + 1]
1000 else:
1001 self.send_response(200)
1002
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001003 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001004 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001005 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001006 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001007 self.end_headers()
1008
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001009 if (self.command != 'HEAD'):
1010 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001011
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001012 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001013 return True
1014
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001015 def SetCookieHandler(self):
1016 """This handler just sets a cookie, for testing cookie handling."""
1017
1018 if not self._ShouldHandleRequest("/set-cookie"):
1019 return False
1020
1021 query_char = self.path.find('?')
1022 if query_char != -1:
1023 cookie_values = self.path[query_char + 1:].split('&')
1024 else:
1025 cookie_values = ("",)
1026 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001027 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001028 for cookie_value in cookie_values:
1029 self.send_header('Set-Cookie', '%s' % cookie_value)
1030 self.end_headers()
1031 for cookie_value in cookie_values:
1032 self.wfile.write('%s' % cookie_value)
1033 return True
1034
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001035 def SetManyCookiesHandler(self):
1036 """This handler just sets a given number of cookies, for testing handling
1037 of large numbers of cookies."""
1038
1039 if not self._ShouldHandleRequest("/set-many-cookies"):
1040 return False
1041
1042 query_char = self.path.find('?')
1043 if query_char != -1:
1044 num_cookies = int(self.path[query_char + 1:])
1045 else:
1046 num_cookies = 0
1047 self.send_response(200)
1048 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001049 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001050 self.send_header('Set-Cookie', 'a=')
1051 self.end_headers()
1052 self.wfile.write('%d cookies were sent' % num_cookies)
1053 return True
1054
mattm@chromium.org983fc462012-06-30 00:52:08 +00001055 def ExpectAndSetCookieHandler(self):
1056 """Expects some cookies to be sent, and if they are, sets more cookies.
1057
1058 The expect parameter specifies a required cookie. May be specified multiple
1059 times.
1060 The set parameter specifies a cookie to set if all required cookies are
1061 preset. May be specified multiple times.
1062 The data parameter specifies the response body data to be returned."""
1063
1064 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1065 return False
1066
1067 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1068 query_dict = cgi.parse_qs(query)
1069 cookies = set()
1070 if 'Cookie' in self.headers:
1071 cookie_header = self.headers.getheader('Cookie')
1072 cookies.update([s.strip() for s in cookie_header.split(';')])
1073 got_all_expected_cookies = True
1074 for expected_cookie in query_dict.get('expect', []):
1075 if expected_cookie not in cookies:
1076 got_all_expected_cookies = False
1077 self.send_response(200)
1078 self.send_header('Content-Type', 'text/html')
1079 if got_all_expected_cookies:
1080 for cookie_value in query_dict.get('set', []):
1081 self.send_header('Set-Cookie', '%s' % cookie_value)
1082 self.end_headers()
1083 for data_value in query_dict.get('data', []):
1084 self.wfile.write(data_value)
1085 return True
1086
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001087 def SetHeaderHandler(self):
1088 """This handler sets a response header. Parameters are in the
1089 key%3A%20value&key2%3A%20value2 format."""
1090
1091 if not self._ShouldHandleRequest("/set-header"):
1092 return False
1093
1094 query_char = self.path.find('?')
1095 if query_char != -1:
1096 headers_values = self.path[query_char + 1:].split('&')
1097 else:
1098 headers_values = ("",)
1099 self.send_response(200)
1100 self.send_header('Content-Type', 'text/html')
1101 for header_value in headers_values:
1102 header_value = urllib.unquote(header_value)
1103 (key, value) = header_value.split(': ', 1)
1104 self.send_header(key, value)
1105 self.end_headers()
1106 for header_value in headers_values:
1107 self.wfile.write('%s' % header_value)
1108 return True
1109
initial.commit94958cf2008-07-26 22:42:52 +00001110 def AuthBasicHandler(self):
1111 """This handler tests 'Basic' authentication. It just sends a page with
1112 title 'user/pass' if you succeed."""
1113
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001114 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001115 return False
1116
1117 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001118 expected_password = 'secret'
1119 realm = 'testrealm'
1120 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001121
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001122 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1123 query_params = cgi.parse_qs(query, True)
1124 if 'set-cookie-if-challenged' in query_params:
1125 set_cookie_if_challenged = True
1126 if 'password' in query_params:
1127 expected_password = query_params['password'][0]
1128 if 'realm' in query_params:
1129 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001130
initial.commit94958cf2008-07-26 22:42:52 +00001131 auth = self.headers.getheader('authorization')
1132 try:
1133 if not auth:
1134 raise Exception('no auth')
1135 b64str = re.findall(r'Basic (\S+)', auth)[0]
1136 userpass = base64.b64decode(b64str)
1137 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001138 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001139 raise Exception('wrong password')
1140 except Exception, e:
1141 # Authentication failed.
1142 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001143 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001144 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001145 if set_cookie_if_challenged:
1146 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001147 self.end_headers()
1148 self.wfile.write('<html><head>')
1149 self.wfile.write('<title>Denied: %s</title>' % e)
1150 self.wfile.write('</head><body>')
1151 self.wfile.write('auth=%s<p>' % auth)
1152 self.wfile.write('b64str=%s<p>' % b64str)
1153 self.wfile.write('username: %s<p>' % username)
1154 self.wfile.write('userpass: %s<p>' % userpass)
1155 self.wfile.write('password: %s<p>' % password)
1156 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1157 self.wfile.write('</body></html>')
1158 return True
1159
1160 # Authentication successful. (Return a cachable response to allow for
1161 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001162 old_protocol_version = self.protocol_version
1163 self.protocol_version = "HTTP/1.1"
1164
initial.commit94958cf2008-07-26 22:42:52 +00001165 if_none_match = self.headers.getheader('if-none-match')
1166 if if_none_match == "abc":
1167 self.send_response(304)
1168 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001169 elif url_path.endswith(".gif"):
1170 # Using chrome/test/data/google/logo.gif as the test image
1171 test_image_path = ['google', 'logo.gif']
1172 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1173 if not os.path.isfile(gif_path):
1174 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001175 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001176 return True
1177
1178 f = open(gif_path, "rb")
1179 data = f.read()
1180 f.close()
1181
1182 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001183 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001184 self.send_header('Cache-control', 'max-age=60000')
1185 self.send_header('Etag', 'abc')
1186 self.end_headers()
1187 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001188 else:
1189 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001190 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001191 self.send_header('Cache-control', 'max-age=60000')
1192 self.send_header('Etag', 'abc')
1193 self.end_headers()
1194 self.wfile.write('<html><head>')
1195 self.wfile.write('<title>%s/%s</title>' % (username, password))
1196 self.wfile.write('</head><body>')
1197 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001198 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001199 self.wfile.write('</body></html>')
1200
rvargas@google.com54453b72011-05-19 01:11:11 +00001201 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001202 return True
1203
tonyg@chromium.org75054202010-03-31 22:06:10 +00001204 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001205 """Returns a nonce that's stable per request path for the server's lifetime.
1206 This is a fake implementation. A real implementation would only use a given
1207 nonce a single time (hence the name n-once). However, for the purposes of
1208 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001209
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001210 Args:
1211 force_reset: Iff set, the nonce will be changed. Useful for testing the
1212 "stale" response.
1213 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001214
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001215 if force_reset or not self.server.nonce_time:
1216 self.server.nonce_time = time.time()
1217 return hashlib.md5('privatekey%s%d' %
1218 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001219
1220 def AuthDigestHandler(self):
1221 """This handler tests 'Digest' authentication.
1222
1223 It just sends a page with title 'user/pass' if you succeed.
1224
1225 A stale response is sent iff "stale" is present in the request path.
1226 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001227
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001228 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001229 return False
1230
tonyg@chromium.org75054202010-03-31 22:06:10 +00001231 stale = 'stale' in self.path
1232 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001233 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001234 password = 'secret'
1235 realm = 'testrealm'
1236
1237 auth = self.headers.getheader('authorization')
1238 pairs = {}
1239 try:
1240 if not auth:
1241 raise Exception('no auth')
1242 if not auth.startswith('Digest'):
1243 raise Exception('not digest')
1244 # Pull out all the name="value" pairs as a dictionary.
1245 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1246
1247 # Make sure it's all valid.
1248 if pairs['nonce'] != nonce:
1249 raise Exception('wrong nonce')
1250 if pairs['opaque'] != opaque:
1251 raise Exception('wrong opaque')
1252
1253 # Check the 'response' value and make sure it matches our magic hash.
1254 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001255 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001256 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001257 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001258 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001259 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001260 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1261 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001262 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001263
1264 if pairs['response'] != response:
1265 raise Exception('wrong password')
1266 except Exception, e:
1267 # Authentication failed.
1268 self.send_response(401)
1269 hdr = ('Digest '
1270 'realm="%s", '
1271 'domain="/", '
1272 'qop="auth", '
1273 'algorithm=MD5, '
1274 'nonce="%s", '
1275 'opaque="%s"') % (realm, nonce, opaque)
1276 if stale:
1277 hdr += ', stale="TRUE"'
1278 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001279 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001280 self.end_headers()
1281 self.wfile.write('<html><head>')
1282 self.wfile.write('<title>Denied: %s</title>' % e)
1283 self.wfile.write('</head><body>')
1284 self.wfile.write('auth=%s<p>' % auth)
1285 self.wfile.write('pairs=%s<p>' % pairs)
1286 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1287 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1288 self.wfile.write('</body></html>')
1289 return True
1290
1291 # Authentication successful.
1292 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001293 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001294 self.end_headers()
1295 self.wfile.write('<html><head>')
1296 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1297 self.wfile.write('</head><body>')
1298 self.wfile.write('auth=%s<p>' % auth)
1299 self.wfile.write('pairs=%s<p>' % pairs)
1300 self.wfile.write('</body></html>')
1301
1302 return True
1303
1304 def SlowServerHandler(self):
1305 """Wait for the user suggested time before responding. The syntax is
1306 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001307
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001308 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001309 return False
1310 query_char = self.path.find('?')
1311 wait_sec = 1.0
1312 if query_char >= 0:
1313 try:
1314 wait_sec = int(self.path[query_char + 1:])
1315 except ValueError:
1316 pass
1317 time.sleep(wait_sec)
1318 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001319 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001320 self.end_headers()
1321 self.wfile.write("waited %d seconds" % wait_sec)
1322 return True
1323
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001324 def ChunkedServerHandler(self):
1325 """Send chunked response. Allows to specify chunks parameters:
1326 - waitBeforeHeaders - ms to wait before sending headers
1327 - waitBetweenChunks - ms to wait between chunks
1328 - chunkSize - size of each chunk in bytes
1329 - chunksNumber - number of chunks
1330 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1331 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001332
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001333 if not self._ShouldHandleRequest("/chunked"):
1334 return False
1335 query_char = self.path.find('?')
1336 chunkedSettings = {'waitBeforeHeaders' : 0,
1337 'waitBetweenChunks' : 0,
1338 'chunkSize' : 5,
1339 'chunksNumber' : 5}
1340 if query_char >= 0:
1341 params = self.path[query_char + 1:].split('&')
1342 for param in params:
1343 keyValue = param.split('=')
1344 if len(keyValue) == 2:
1345 try:
1346 chunkedSettings[keyValue[0]] = int(keyValue[1])
1347 except ValueError:
1348 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001349 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001350 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1351 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001352 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001353 self.send_header('Connection', 'close')
1354 self.send_header('Transfer-Encoding', 'chunked')
1355 self.end_headers()
1356 # Chunked encoding: sending all chunks, then final zero-length chunk and
1357 # then final CRLF.
1358 for i in range(0, chunkedSettings['chunksNumber']):
1359 if i > 0:
1360 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1361 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001362 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001363 self.sendChunkHelp('')
1364 return True
1365
initial.commit94958cf2008-07-26 22:42:52 +00001366 def ContentTypeHandler(self):
1367 """Returns a string of html with the given content type. E.g.,
1368 /contenttype?text/css returns an html file with the Content-Type
1369 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001370
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001371 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001372 return False
1373 query_char = self.path.find('?')
1374 content_type = self.path[query_char + 1:].strip()
1375 if not content_type:
1376 content_type = 'text/html'
1377 self.send_response(200)
1378 self.send_header('Content-Type', content_type)
1379 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001380 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001381 return True
1382
creis@google.com2f4f6a42011-03-25 19:44:19 +00001383 def NoContentHandler(self):
1384 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001385
creis@google.com2f4f6a42011-03-25 19:44:19 +00001386 if not self._ShouldHandleRequest("/nocontent"):
1387 return False
1388 self.send_response(204)
1389 self.end_headers()
1390 return True
1391
initial.commit94958cf2008-07-26 22:42:52 +00001392 def ServerRedirectHandler(self):
1393 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001394 '/server-redirect?http://foo.bar/asdf' to redirect to
1395 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001396
1397 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001398 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001399 return False
1400
1401 query_char = self.path.find('?')
1402 if query_char < 0 or len(self.path) <= query_char + 1:
1403 self.sendRedirectHelp(test_name)
1404 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001405 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001406
1407 self.send_response(301) # moved permanently
1408 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001409 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001410 self.end_headers()
1411 self.wfile.write('<html><head>')
1412 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1413
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001414 return True
initial.commit94958cf2008-07-26 22:42:52 +00001415
1416 def ClientRedirectHandler(self):
1417 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001418 '/client-redirect?http://foo.bar/asdf' to redirect to
1419 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001420
1421 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001422 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001423 return False
1424
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001425 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001426 if query_char < 0 or len(self.path) <= query_char + 1:
1427 self.sendRedirectHelp(test_name)
1428 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001429 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001430
1431 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001432 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001433 self.end_headers()
1434 self.wfile.write('<html><head>')
1435 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1436 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1437
1438 return True
1439
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001440 def GetSSLSessionCacheHandler(self):
1441 """Send a reply containing a log of the session cache operations."""
1442
1443 if not self._ShouldHandleRequest('/ssl-session-cache'):
1444 return False
1445
1446 self.send_response(200)
1447 self.send_header('Content-Type', 'text/plain')
1448 self.end_headers()
1449 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001450 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001451 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001452 self.wfile.write('Pass --https-record-resume in order to use' +
1453 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001454 return True
1455
1456 for (action, sessionID) in log:
1457 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001458 return True
1459
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001460 def SSLManySmallRecords(self):
1461 """Sends a reply consisting of a variety of small writes. These will be
1462 translated into a series of small SSL records when used over an HTTPS
1463 server."""
1464
1465 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1466 return False
1467
1468 self.send_response(200)
1469 self.send_header('Content-Type', 'text/plain')
1470 self.end_headers()
1471
1472 # Write ~26K of data, in 1350 byte chunks
1473 for i in xrange(20):
1474 self.wfile.write('*' * 1350)
1475 self.wfile.flush()
1476 return True
1477
agl@chromium.org04700be2013-03-02 18:40:41 +00001478 def GetChannelID(self):
1479 """Send a reply containing the hashed ChannelID that the client provided."""
1480
1481 if not self._ShouldHandleRequest('/channel-id'):
1482 return False
1483
1484 self.send_response(200)
1485 self.send_header('Content-Type', 'text/plain')
1486 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001487 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001488 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1489 return True
1490
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001491 def CloseSocketHandler(self):
1492 """Closes the socket without sending anything."""
1493
1494 if not self._ShouldHandleRequest('/close-socket'):
1495 return False
1496
1497 self.wfile.close()
1498 return True
1499
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001500 def RangeResetHandler(self):
1501 """Send data broken up by connection resets every N (default 4K) bytes.
1502 Support range requests. If the data requested doesn't straddle a reset
1503 boundary, it will all be sent. Used for testing resuming downloads."""
1504
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001505 def DataForRange(start, end):
1506 """Data to be provided for a particular range of bytes."""
1507 # Offset and scale to avoid too obvious (and hence potentially
1508 # collidable) data.
1509 return ''.join([chr(y % 256)
1510 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1511
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001512 if not self._ShouldHandleRequest('/rangereset'):
1513 return False
1514
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001515 # HTTP/1.1 is required for ETag and range support.
1516 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001517 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1518
1519 # Defaults
1520 size = 8000
1521 # Note that the rst is sent just before sending the rst_boundary byte.
1522 rst_boundary = 4000
1523 respond_to_range = True
1524 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001525 rst_limit = -1
1526 token = 'DEFAULT'
1527 fail_precondition = 0
1528 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001529
1530 # Parse the query
1531 qdict = urlparse.parse_qs(query, True)
1532 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001533 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001534 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001535 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001536 if 'token' in qdict:
1537 # Identifying token for stateful tests.
1538 token = qdict['token'][0]
1539 if 'rst_limit' in qdict:
1540 # Max number of rsts for a given token.
1541 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001542 if 'bounce_range' in qdict:
1543 respond_to_range = False
1544 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001545 # Note that hold_for_signal will not work with null range requests;
1546 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001547 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001548 if 'no_verifiers' in qdict:
1549 send_verifiers = False
1550 if 'fail_precondition' in qdict:
1551 fail_precondition = int(qdict['fail_precondition'][0])
1552
1553 # Record already set information, or set it.
1554 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1555 if rst_limit != 0:
1556 TestPageHandler.rst_limits[token] -= 1
1557 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1558 token, fail_precondition)
1559 if fail_precondition != 0:
1560 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001561
1562 first_byte = 0
1563 last_byte = size - 1
1564
1565 # Does that define what we want to return, or do we need to apply
1566 # a range?
1567 range_response = False
1568 range_header = self.headers.getheader('range')
1569 if range_header and respond_to_range:
1570 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1571 if mo.group(1):
1572 first_byte = int(mo.group(1))
1573 if mo.group(2):
1574 last_byte = int(mo.group(2))
1575 if last_byte > size - 1:
1576 last_byte = size - 1
1577 range_response = True
1578 if last_byte < first_byte:
1579 return False
1580
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001581 if (fail_precondition and
1582 (self.headers.getheader('If-Modified-Since') or
1583 self.headers.getheader('If-Match'))):
1584 self.send_response(412)
1585 self.end_headers()
1586 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001587
1588 if range_response:
1589 self.send_response(206)
1590 self.send_header('Content-Range',
1591 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1592 else:
1593 self.send_response(200)
1594 self.send_header('Content-Type', 'application/octet-stream')
1595 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001596 if send_verifiers:
asanka@chromium.org3ffced92013-12-13 22:20:27 +00001597 # If fail_precondition is non-zero, then the ETag for each request will be
1598 # different.
1599 etag = "%s%d" % (token, fail_precondition)
1600 self.send_header('ETag', etag)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001601 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001602 self.end_headers()
1603
1604 if hold_for_signal:
1605 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1606 # a single byte, the self.server.handle_request() below hangs
1607 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001608 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001609 first_byte = first_byte + 1
1610 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001611 self.server.wait_for_download = True
1612 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001613 self.server.handle_request()
1614
1615 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001616 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001617 # No RST has been requested in this range, so we don't need to
1618 # do anything fancy; just write the data and let the python
1619 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001620 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001621 self.wfile.flush()
1622 return True
1623
1624 # We're resetting the connection part way in; go to the RST
1625 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001626 # Because socket semantics do not guarantee that all the data will be
1627 # sent when using the linger semantics to hard close a socket,
1628 # we send the data and then wait for our peer to release us
1629 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001630 data = DataForRange(first_byte, possible_rst)
1631 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001632 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001633 self.server.wait_for_download = True
1634 while self.server.wait_for_download:
1635 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001636 l_onoff = 1 # Linger is active.
1637 l_linger = 0 # Seconds to linger for.
1638 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1639 struct.pack('ii', l_onoff, l_linger))
1640
1641 # Close all duplicates of the underlying socket to force the RST.
1642 self.wfile.close()
1643 self.rfile.close()
1644 self.connection.close()
1645
1646 return True
1647
initial.commit94958cf2008-07-26 22:42:52 +00001648 def DefaultResponseHandler(self):
1649 """This is the catch-all response handler for requests that aren't handled
1650 by one of the special handlers above.
1651 Note that we specify the content-length as without it the https connection
1652 is not closed properly (and the browser keeps expecting data)."""
1653
1654 contents = "Default response given for path: " + self.path
1655 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001656 self.send_header('Content-Type', 'text/html')
1657 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001658 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001659 if (self.command != 'HEAD'):
1660 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001661 return True
1662
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001663 def RedirectConnectHandler(self):
1664 """Sends a redirect to the CONNECT request for www.redirect.com. This
1665 response is not specified by the RFC, so the browser should not follow
1666 the redirect."""
1667
1668 if (self.path.find("www.redirect.com") < 0):
1669 return False
1670
1671 dest = "http://www.destination.com/foo.js"
1672
1673 self.send_response(302) # moved temporarily
1674 self.send_header('Location', dest)
1675 self.send_header('Connection', 'close')
1676 self.end_headers()
1677 return True
1678
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001679 def ServerAuthConnectHandler(self):
1680 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1681 response doesn't make sense because the proxy server cannot request
1682 server authentication."""
1683
1684 if (self.path.find("www.server-auth.com") < 0):
1685 return False
1686
1687 challenge = 'Basic realm="WallyWorld"'
1688
1689 self.send_response(401) # unauthorized
1690 self.send_header('WWW-Authenticate', challenge)
1691 self.send_header('Connection', 'close')
1692 self.end_headers()
1693 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001694
1695 def DefaultConnectResponseHandler(self):
1696 """This is the catch-all response handler for CONNECT requests that aren't
1697 handled by one of the special handlers above. Real Web servers respond
1698 with 400 to CONNECT requests."""
1699
1700 contents = "Your client has issued a malformed or illegal request."
1701 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001702 self.send_header('Content-Type', 'text/html')
1703 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001704 self.end_headers()
1705 self.wfile.write(contents)
1706 return True
1707
initial.commit94958cf2008-07-26 22:42:52 +00001708 # called by the redirect handling function when there is no parameter
1709 def sendRedirectHelp(self, redirect_name):
1710 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001711 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001712 self.end_headers()
1713 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1714 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1715 self.wfile.write('</body></html>')
1716
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001717 # called by chunked handling function
1718 def sendChunkHelp(self, chunk):
1719 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1720 self.wfile.write('%X\r\n' % len(chunk))
1721 self.wfile.write(chunk)
1722 self.wfile.write('\r\n')
1723
akalin@chromium.org154bb132010-11-12 02:20:27 +00001724
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001725class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001726 def __init__(self, request, client_address, socket_server):
1727 handlers = [self.OCSPResponse]
1728 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001729 testserver_base.BasePageHandler.__init__(self, request, client_address,
1730 socket_server, [], handlers, [],
1731 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001732
1733 def OCSPResponse(self):
1734 self.send_response(200)
1735 self.send_header('Content-Type', 'application/ocsp-response')
1736 self.send_header('Content-Length', str(len(self.ocsp_response)))
1737 self.end_headers()
1738
1739 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001740
mattm@chromium.org830a3712012-11-07 23:00:07 +00001741
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001742class TCPEchoHandler(SocketServer.BaseRequestHandler):
1743 """The RequestHandler class for TCP echo server.
1744
1745 It is instantiated once per connection to the server, and overrides the
1746 handle() method to implement communication to the client.
1747 """
1748
1749 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001750 """Handles the request from the client and constructs a response."""
1751
1752 data = self.request.recv(65536).strip()
1753 # Verify the "echo request" message received from the client. Send back
1754 # "echo response" message if "echo request" message is valid.
1755 try:
1756 return_data = echo_message.GetEchoResponseData(data)
1757 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001758 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001759 except ValueError:
1760 return
1761
1762 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001763
1764
1765class UDPEchoHandler(SocketServer.BaseRequestHandler):
1766 """The RequestHandler class for UDP echo server.
1767
1768 It is instantiated once per connection to the server, and overrides the
1769 handle() method to implement communication to the client.
1770 """
1771
1772 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001773 """Handles the request from the client and constructs a response."""
1774
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001775 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001776 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001777 # Verify the "echo request" message received from the client. Send back
1778 # "echo response" message if "echo request" message is valid.
1779 try:
1780 return_data = echo_message.GetEchoResponseData(data)
1781 if not return_data:
1782 return
1783 except ValueError:
1784 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001785 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001786
1787
bashi@chromium.org33233532012-09-08 17:37:24 +00001788class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1789 """A request handler that behaves as a proxy server which requires
1790 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1791 """
1792
1793 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1794
1795 def parse_request(self):
1796 """Overrides parse_request to check credential."""
1797
1798 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1799 return False
1800
1801 auth = self.headers.getheader('Proxy-Authorization')
1802 if auth != self._AUTH_CREDENTIAL:
1803 self.send_response(407)
1804 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1805 self.end_headers()
1806 return False
1807
1808 return True
1809
1810 def _start_read_write(self, sock):
1811 sock.setblocking(0)
1812 self.request.setblocking(0)
1813 rlist = [self.request, sock]
1814 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001815 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001816 if errors:
1817 self.send_response(500)
1818 self.end_headers()
1819 return
1820 for s in ready_sockets:
1821 received = s.recv(1024)
1822 if len(received) == 0:
1823 return
1824 if s == self.request:
1825 other = sock
1826 else:
1827 other = self.request
1828 other.send(received)
1829
1830 def _do_common_method(self):
1831 url = urlparse.urlparse(self.path)
1832 port = url.port
1833 if not port:
1834 if url.scheme == 'http':
1835 port = 80
1836 elif url.scheme == 'https':
1837 port = 443
1838 if not url.hostname or not port:
1839 self.send_response(400)
1840 self.end_headers()
1841 return
1842
1843 if len(url.path) == 0:
1844 path = '/'
1845 else:
1846 path = url.path
1847 if len(url.query) > 0:
1848 path = '%s?%s' % (url.path, url.query)
1849
1850 sock = None
1851 try:
1852 sock = socket.create_connection((url.hostname, port))
1853 sock.send('%s %s %s\r\n' % (
1854 self.command, path, self.protocol_version))
1855 for header in self.headers.headers:
1856 header = header.strip()
1857 if (header.lower().startswith('connection') or
1858 header.lower().startswith('proxy')):
1859 continue
1860 sock.send('%s\r\n' % header)
1861 sock.send('\r\n')
1862 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001863 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001864 self.send_response(500)
1865 self.end_headers()
1866 finally:
1867 if sock is not None:
1868 sock.close()
1869
1870 def do_CONNECT(self):
1871 try:
1872 pos = self.path.rfind(':')
1873 host = self.path[:pos]
1874 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001875 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001876 self.send_response(400)
1877 self.end_headers()
1878
1879 try:
1880 sock = socket.create_connection((host, port))
1881 self.send_response(200, 'Connection established')
1882 self.end_headers()
1883 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001884 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001885 self.send_response(500)
1886 self.end_headers()
1887 finally:
1888 sock.close()
1889
1890 def do_GET(self):
1891 self._do_common_method()
1892
1893 def do_HEAD(self):
1894 self._do_common_method()
1895
1896
mattm@chromium.org830a3712012-11-07 23:00:07 +00001897class ServerRunner(testserver_base.TestServerRunner):
1898 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001899
mattm@chromium.org830a3712012-11-07 23:00:07 +00001900 def __init__(self):
1901 super(ServerRunner, self).__init__()
1902 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001903
mattm@chromium.org830a3712012-11-07 23:00:07 +00001904 def __make_data_dir(self):
1905 if self.options.data_dir:
1906 if not os.path.isdir(self.options.data_dir):
1907 raise testserver_base.OptionError('specified data dir not found: ' +
1908 self.options.data_dir + ' exiting...')
1909 my_data_dir = self.options.data_dir
1910 else:
1911 # Create the default path to our data dir, relative to the exe dir.
1912 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1913 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001914
mattm@chromium.org830a3712012-11-07 23:00:07 +00001915 #TODO(ibrar): Must use Find* funtion defined in google\tools
1916 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001917
mattm@chromium.org830a3712012-11-07 23:00:07 +00001918 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001919
mattm@chromium.org830a3712012-11-07 23:00:07 +00001920 def create_server(self, server_data):
1921 port = self.options.port
1922 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001923
mattm@chromium.org830a3712012-11-07 23:00:07 +00001924 if self.options.server_type == SERVER_HTTP:
1925 if self.options.https:
1926 pem_cert_and_key = None
1927 if self.options.cert_and_key_file:
1928 if not os.path.isfile(self.options.cert_and_key_file):
1929 raise testserver_base.OptionError(
1930 'specified server cert file not found: ' +
1931 self.options.cert_and_key_file + ' exiting...')
1932 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001933 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001934 # generate a new certificate and run an OCSP server for it.
1935 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001936 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001937 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001938
mattm@chromium.org830a3712012-11-07 23:00:07 +00001939 ocsp_der = None
1940 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001941
mattm@chromium.org830a3712012-11-07 23:00:07 +00001942 if self.options.ocsp == 'ok':
1943 ocsp_state = minica.OCSP_STATE_GOOD
1944 elif self.options.ocsp == 'revoked':
1945 ocsp_state = minica.OCSP_STATE_REVOKED
1946 elif self.options.ocsp == 'invalid':
1947 ocsp_state = minica.OCSP_STATE_INVALID
1948 elif self.options.ocsp == 'unauthorized':
1949 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1950 elif self.options.ocsp == 'unknown':
1951 ocsp_state = minica.OCSP_STATE_UNKNOWN
1952 else:
1953 raise testserver_base.OptionError('unknown OCSP status: ' +
1954 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001955
mattm@chromium.org830a3712012-11-07 23:00:07 +00001956 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1957 subject = "127.0.0.1",
1958 ocsp_url = ("http://%s:%d/ocsp" %
1959 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00001960 ocsp_state = ocsp_state,
1961 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001962
1963 self.__ocsp_server.ocsp_response = ocsp_der
1964
1965 for ca_cert in self.options.ssl_client_ca:
1966 if not os.path.isfile(ca_cert):
1967 raise testserver_base.OptionError(
1968 'specified trusted client CA file not found: ' + ca_cert +
1969 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001970
1971 stapled_ocsp_response = None
1972 if self.__ocsp_server and self.options.staple_ocsp_response:
1973 stapled_ocsp_response = self.__ocsp_server.ocsp_response
1974
mattm@chromium.org830a3712012-11-07 23:00:07 +00001975 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1976 self.options.ssl_client_auth,
1977 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00001978 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00001979 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00001980 self.options.ssl_key_exchange,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00001981 self.options.enable_npn,
mattm@chromium.org830a3712012-11-07 23:00:07 +00001982 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00001983 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00001984 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001985 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00001986 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001987 self.options.fallback_scsv,
1988 stapled_ocsp_response)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001989 print 'HTTPS server started on https://%s:%d...' % \
1990 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001991 else:
1992 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001993 print 'HTTP server started on http://%s:%d...' % \
1994 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001995
1996 server.data_dir = self.__make_data_dir()
1997 server.file_root_url = self.options.file_root_url
1998 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001999 elif self.options.server_type == SERVER_WEBSOCKET:
2000 # Launch pywebsocket via WebSocketServer.
2001 logger = logging.getLogger()
2002 logger.addHandler(logging.StreamHandler())
2003 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2004 # is required to work correctly. It should be fixed from pywebsocket side.
2005 os.chdir(self.__make_data_dir())
2006 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002007 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002008 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002009 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002010 websocket_options.use_tls = True
2011 websocket_options.private_key = self.options.cert_and_key_file
2012 websocket_options.certificate = self.options.cert_and_key_file
2013 if self.options.ssl_client_auth:
2014 websocket_options.tls_client_auth = True
2015 if len(self.options.ssl_client_ca) != 1:
2016 raise testserver_base.OptionError(
2017 'one trusted client CA file should be specified')
2018 if not os.path.isfile(self.options.ssl_client_ca[0]):
2019 raise testserver_base.OptionError(
2020 'specified trusted client CA file not found: ' +
2021 self.options.ssl_client_ca[0] + ' exiting...')
2022 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2023 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002024 print 'WebSocket server started on %s://%s:%d...' % \
2025 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002026 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002027 elif self.options.server_type == SERVER_TCP_ECHO:
2028 # Used for generating the key (randomly) that encodes the "echo request"
2029 # message.
2030 random.seed()
2031 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002032 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002033 server_data['port'] = server.server_port
2034 elif self.options.server_type == SERVER_UDP_ECHO:
2035 # Used for generating the key (randomly) that encodes the "echo request"
2036 # message.
2037 random.seed()
2038 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002039 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002040 server_data['port'] = server.server_port
2041 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2042 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002043 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002044 server_data['port'] = server.server_port
2045 elif self.options.server_type == SERVER_FTP:
2046 my_data_dir = self.__make_data_dir()
2047
2048 # Instantiate a dummy authorizer for managing 'virtual' users
2049 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2050
2051 # Define a new user having full r/w permissions and a read-only
2052 # anonymous user
2053 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2054
2055 authorizer.add_anonymous(my_data_dir)
2056
2057 # Instantiate FTP handler class
2058 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2059 ftp_handler.authorizer = authorizer
2060
2061 # Define a customized banner (string returned when client connects)
2062 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2063 pyftpdlib.ftpserver.__ver__)
2064
2065 # Instantiate FTP server class and listen to address:port
2066 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2067 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002068 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002069 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002070 raise testserver_base.OptionError('unknown server type' +
2071 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002072
mattm@chromium.org830a3712012-11-07 23:00:07 +00002073 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002074
mattm@chromium.org830a3712012-11-07 23:00:07 +00002075 def run_server(self):
2076 if self.__ocsp_server:
2077 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002078
mattm@chromium.org830a3712012-11-07 23:00:07 +00002079 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002080
mattm@chromium.org830a3712012-11-07 23:00:07 +00002081 if self.__ocsp_server:
2082 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002083
mattm@chromium.org830a3712012-11-07 23:00:07 +00002084 def add_options(self):
2085 testserver_base.TestServerRunner.add_options(self)
2086 self.option_parser.add_option('-f', '--ftp', action='store_const',
2087 const=SERVER_FTP, default=SERVER_HTTP,
2088 dest='server_type',
2089 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002090 self.option_parser.add_option('--tcp-echo', action='store_const',
2091 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2092 dest='server_type',
2093 help='start up a tcp echo server.')
2094 self.option_parser.add_option('--udp-echo', action='store_const',
2095 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2096 dest='server_type',
2097 help='start up a udp echo server.')
2098 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2099 const=SERVER_BASIC_AUTH_PROXY,
2100 default=SERVER_HTTP, dest='server_type',
2101 help='start up a proxy server which requires '
2102 'basic authentication.')
2103 self.option_parser.add_option('--websocket', action='store_const',
2104 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2105 dest='server_type',
2106 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002107 self.option_parser.add_option('--https', action='store_true',
2108 dest='https', help='Specify that https '
2109 'should be used.')
2110 self.option_parser.add_option('--cert-and-key-file',
2111 dest='cert_and_key_file', help='specify the '
2112 'path to the file containing the certificate '
2113 'and private key for the server in PEM '
2114 'format')
2115 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2116 help='The type of OCSP response generated '
2117 'for the automatically generated '
2118 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002119 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2120 default=0, type=int,
2121 help='If non-zero then the generated '
2122 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002123 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2124 default='0', type='int',
2125 help='If nonzero, certain TLS connections '
2126 'will be aborted in order to test version '
2127 'fallback. 1 means all TLS versions will be '
2128 'aborted. 2 means TLS 1.1 or higher will be '
2129 'aborted. 3 means TLS 1.2 or higher will be '
2130 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002131 self.option_parser.add_option('--tls-intolerance-type',
2132 dest='tls_intolerance_type',
2133 default="alert",
2134 help='Controls how the server reacts to a '
2135 'TLS version it is intolerant to. Valid '
2136 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002137 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2138 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002139 default='',
2140 help='Base64 encoded SCT list. If set, '
2141 'server will respond with a '
2142 'signed_certificate_timestamp TLS extension '
2143 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002144 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2145 default=False, const=True,
2146 action='store_const',
2147 help='If given, TLS_FALLBACK_SCSV support '
2148 'will be enabled. This causes the server to '
2149 'reject fallback connections from compatible '
2150 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002151 self.option_parser.add_option('--staple-ocsp-response',
2152 dest='staple_ocsp_response',
2153 default=False, action='store_true',
2154 help='If set, server will staple the OCSP '
2155 'response whenever OCSP is on and the client '
2156 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002157 self.option_parser.add_option('--https-record-resume',
2158 dest='record_resume', const=True,
2159 default=False, action='store_const',
2160 help='Record resumption cache events rather '
2161 'than resuming as normal. Allows the use of '
2162 'the /ssl-session-cache request')
2163 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2164 help='Require SSL client auth on every '
2165 'connection.')
2166 self.option_parser.add_option('--ssl-client-ca', action='append',
2167 default=[], help='Specify that the client '
2168 'certificate request should include the CA '
2169 'named in the subject of the DER-encoded '
2170 'certificate contained in the specified '
2171 'file. This option may appear multiple '
2172 'times, indicating multiple CA names should '
2173 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002174 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2175 default=[], help='Specify that the client '
2176 'certificate request should include the '
2177 'specified certificate_type value. This '
2178 'option may appear multiple times, '
2179 'indicating multiple values should be send '
2180 'in the request. Valid values are '
2181 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2182 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002183 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2184 help='Specify the bulk encryption '
2185 'algorithm(s) that will be accepted by the '
2186 'SSL server. Valid values are "aes256", '
2187 '"aes128", "3des", "rc4". If omitted, all '
2188 'algorithms will be used. This option may '
2189 'appear multiple times, indicating '
2190 'multiple algorithms should be enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002191 self.option_parser.add_option('--ssl-key-exchange', action='append',
2192 help='Specify the key exchange algorithm(s)'
2193 'that will be accepted by the SSL server. '
2194 'Valid values are "rsa", "dhe_rsa". If '
2195 'omitted, all algorithms will be used. This '
2196 'option may appear multiple times, '
2197 'indicating multiple algorithms should be '
2198 'enabled.');
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002199 # TODO(davidben): Add ALPN support to tlslite.
2200 self.option_parser.add_option('--enable-npn', dest='enable_npn',
2201 default=False, const=True,
2202 action='store_const',
2203 help='Enable server support for the NPN '
2204 'extension. The server will advertise '
2205 'support for exactly one protocol, http/1.1')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002206 self.option_parser.add_option('--file-root-url', default='/files/',
2207 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002208
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002209
initial.commit94958cf2008-07-26 22:42:52 +00002210if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002211 sys.exit(ServerRunner().main())