blob: 33faf463d3a8c83c78cc7d1d14cc2a69d4ac3089 [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,
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000157 record_resume_info, tls_intolerant, signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000158 fallback_scsv_enabled, ocsp_response):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000159 self.cert_chain = tlslite.api.X509CertChain()
160 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000161 # Force using only python implementation - otherwise behavior is different
162 # depending on whether m2crypto Python module is present (error is thrown
163 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
164 # the hood.
165 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
166 private=True,
167 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000168 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000169 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000170 self.ssl_client_cert_types = []
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000171 if enable_npn:
172 self.next_protos = ['http/1.1']
173 else:
174 self.next_protos = None
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000175 if tls_intolerant == 0:
176 self.tls_intolerant = None
177 else:
178 self.tls_intolerant = (3, tls_intolerant)
ekasper@google.com24aa8222013-11-28 13:43:26 +0000179 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000180 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000181 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000182
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000183 if ssl_client_auth:
184 for ca_file in ssl_client_cas:
185 s = open(ca_file).read()
186 x509 = tlslite.api.X509()
187 x509.parse(s)
188 self.ssl_client_cas.append(x509.subject)
189
190 for cert_type in ssl_client_cert_types:
191 self.ssl_client_cert_types.append({
192 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
193 "dss_sign": tlslite.api.ClientCertificateType.dss_sign,
194 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
195 }[cert_type])
196
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000197 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
198 if ssl_bulk_ciphers is not None:
199 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000200 if ssl_key_exchanges is not None:
201 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
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 tlsIntolerant=self.tls_intolerant,
227 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000228 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000229 fallbackSCSV=self.fallback_scsv_enabled,
230 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000231 tlsConnection.ignoreAbruptClose = True
232 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000233 except tlslite.api.TLSAbruptCloseError:
234 # Ignore abrupt close.
235 return True
initial.commit94958cf2008-07-26 22:42:52 +0000236 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000237 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000238 return False
239
akalin@chromium.org154bb132010-11-12 02:20:27 +0000240
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000241class FTPServer(testserver_base.ClientRestrictingServerMixIn,
242 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000243 """This is a specialization of FTPServer that adds client verification."""
244
245 pass
246
247
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000248class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
249 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000250 """A TCP echo server that echoes back what it has received."""
251
252 def server_bind(self):
253 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000254
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000255 SocketServer.TCPServer.server_bind(self)
256 host, port = self.socket.getsockname()[:2]
257 self.server_name = socket.getfqdn(host)
258 self.server_port = port
259
260 def serve_forever(self):
261 self.stop = False
262 self.nonce_time = None
263 while not self.stop:
264 self.handle_request()
265 self.socket.close()
266
267
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000268class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
269 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000270 """A UDP echo server that echoes back what it has received."""
271
272 def server_bind(self):
273 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000274
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000275 SocketServer.UDPServer.server_bind(self)
276 host, port = self.socket.getsockname()[:2]
277 self.server_name = socket.getfqdn(host)
278 self.server_port = port
279
280 def serve_forever(self):
281 self.stop = False
282 self.nonce_time = None
283 while not self.stop:
284 self.handle_request()
285 self.socket.close()
286
287
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000288class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000289 # Class variables to allow for persistence state between page handler
290 # invocations
291 rst_limits = {}
292 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000293
294 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000295 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000296 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000297 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000298 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000299 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000300 self.NoCacheMaxAgeTimeHandler,
301 self.NoCacheTimeHandler,
302 self.CacheTimeHandler,
303 self.CacheExpiresHandler,
304 self.CacheProxyRevalidateHandler,
305 self.CachePrivateHandler,
306 self.CachePublicHandler,
307 self.CacheSMaxAgeHandler,
308 self.CacheMustRevalidateHandler,
309 self.CacheMustRevalidateMaxAgeHandler,
310 self.CacheNoStoreHandler,
311 self.CacheNoStoreMaxAgeHandler,
312 self.CacheNoTransformHandler,
313 self.DownloadHandler,
314 self.DownloadFinishHandler,
315 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000316 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000317 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000318 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000319 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000320 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000321 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000322 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000323 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000324 self.AuthBasicHandler,
325 self.AuthDigestHandler,
326 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000327 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000328 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000329 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000330 self.ServerRedirectHandler,
331 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000332 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000333 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000334 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000335 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000336 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000337 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000338 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000339 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000340 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000341 self.PostOnlyFileHandler,
342 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000343 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000344 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000345 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000346 head_handlers = [
347 self.FileHandler,
348 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000349
maruel@google.come250a9b2009-03-10 17:39:46 +0000350 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000351 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000352 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000353 'gif': 'image/gif',
354 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000355 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000356 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000357 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000358 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000359 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000360 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000361 }
initial.commit94958cf2008-07-26 22:42:52 +0000362 self._default_mime_type = 'text/html'
363
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000364 testserver_base.BasePageHandler.__init__(self, request, client_address,
365 socket_server, connect_handlers,
366 get_handlers, head_handlers,
367 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000368
initial.commit94958cf2008-07-26 22:42:52 +0000369 def GetMIMETypeFromName(self, file_name):
370 """Returns the mime type for the specified file_name. So far it only looks
371 at the file extension."""
372
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000373 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000374 if len(extension) == 0:
375 # no extension.
376 return self._default_mime_type
377
ericroman@google.comc17ca532009-05-07 03:51:05 +0000378 # extension starts with a dot, so we need to remove it
379 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000380
initial.commit94958cf2008-07-26 22:42:52 +0000381 def NoCacheMaxAgeTimeHandler(self):
382 """This request handler yields a page with the title set to the current
383 system time, and no caching requested."""
384
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000385 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000386 return False
387
388 self.send_response(200)
389 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000390 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000391 self.end_headers()
392
maruel@google.come250a9b2009-03-10 17:39:46 +0000393 self.wfile.write('<html><head><title>%s</title></head></html>' %
394 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000395
396 return True
397
398 def NoCacheTimeHandler(self):
399 """This request handler yields a page with the title set to the current
400 system time, and no caching requested."""
401
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000402 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000403 return False
404
405 self.send_response(200)
406 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000407 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000408 self.end_headers()
409
maruel@google.come250a9b2009-03-10 17:39:46 +0000410 self.wfile.write('<html><head><title>%s</title></head></html>' %
411 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000412
413 return True
414
415 def CacheTimeHandler(self):
416 """This request handler yields a page with the title set to the current
417 system time, and allows caching for one minute."""
418
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000419 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000420 return False
421
422 self.send_response(200)
423 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000424 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000425 self.end_headers()
426
maruel@google.come250a9b2009-03-10 17:39:46 +0000427 self.wfile.write('<html><head><title>%s</title></head></html>' %
428 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000429
430 return True
431
432 def CacheExpiresHandler(self):
433 """This request handler yields a page with the title set to the current
434 system time, and set the page to expire on 1 Jan 2099."""
435
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000436 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000437 return False
438
439 self.send_response(200)
440 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000441 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000442 self.end_headers()
443
maruel@google.come250a9b2009-03-10 17:39:46 +0000444 self.wfile.write('<html><head><title>%s</title></head></html>' %
445 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000446
447 return True
448
449 def CacheProxyRevalidateHandler(self):
450 """This request handler yields a page with the title set to the current
451 system time, and allows caching for 60 seconds"""
452
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000453 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000454 return False
455
456 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000457 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000458 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
459 self.end_headers()
460
maruel@google.come250a9b2009-03-10 17:39:46 +0000461 self.wfile.write('<html><head><title>%s</title></head></html>' %
462 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000463
464 return True
465
466 def CachePrivateHandler(self):
467 """This request handler yields a page with the title set to the current
468 system time, and allows caching for 5 seconds."""
469
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000470 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000471 return False
472
473 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000474 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000475 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000476 self.end_headers()
477
maruel@google.come250a9b2009-03-10 17:39:46 +0000478 self.wfile.write('<html><head><title>%s</title></head></html>' %
479 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000480
481 return True
482
483 def CachePublicHandler(self):
484 """This request handler yields a page with the title set to the current
485 system time, and allows caching for 5 seconds."""
486
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000487 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000488 return False
489
490 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000491 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000492 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000493 self.end_headers()
494
maruel@google.come250a9b2009-03-10 17:39:46 +0000495 self.wfile.write('<html><head><title>%s</title></head></html>' %
496 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000497
498 return True
499
500 def CacheSMaxAgeHandler(self):
501 """This request handler yields a page with the title set to the current
502 system time, and does not allow for caching."""
503
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000504 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000505 return False
506
507 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000508 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000509 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
510 self.end_headers()
511
maruel@google.come250a9b2009-03-10 17:39:46 +0000512 self.wfile.write('<html><head><title>%s</title></head></html>' %
513 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000514
515 return True
516
517 def CacheMustRevalidateHandler(self):
518 """This request handler yields a page with the title set to the current
519 system time, and does not allow caching."""
520
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000521 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000522 return False
523
524 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000525 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000526 self.send_header('Cache-Control', 'must-revalidate')
527 self.end_headers()
528
maruel@google.come250a9b2009-03-10 17:39:46 +0000529 self.wfile.write('<html><head><title>%s</title></head></html>' %
530 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000531
532 return True
533
534 def CacheMustRevalidateMaxAgeHandler(self):
535 """This request handler yields a page with the title set to the current
536 system time, and does not allow caching event though max-age of 60
537 seconds is specified."""
538
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000539 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000540 return False
541
542 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000543 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000544 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
545 self.end_headers()
546
maruel@google.come250a9b2009-03-10 17:39:46 +0000547 self.wfile.write('<html><head><title>%s</title></head></html>' %
548 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000549
550 return True
551
initial.commit94958cf2008-07-26 22:42:52 +0000552 def CacheNoStoreHandler(self):
553 """This request handler yields a page with the title set to the current
554 system time, and does not allow the page to be stored."""
555
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000556 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000557 return False
558
559 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000560 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000561 self.send_header('Cache-Control', 'no-store')
562 self.end_headers()
563
maruel@google.come250a9b2009-03-10 17:39:46 +0000564 self.wfile.write('<html><head><title>%s</title></head></html>' %
565 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000566
567 return True
568
569 def CacheNoStoreMaxAgeHandler(self):
570 """This request handler yields a page with the title set to the current
571 system time, and does not allow the page to be stored even though max-age
572 of 60 seconds is specified."""
573
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000574 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000575 return False
576
577 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000578 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000579 self.send_header('Cache-Control', 'max-age=60, no-store')
580 self.end_headers()
581
maruel@google.come250a9b2009-03-10 17:39:46 +0000582 self.wfile.write('<html><head><title>%s</title></head></html>' %
583 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000584
585 return True
586
587
588 def CacheNoTransformHandler(self):
589 """This request handler yields a page with the title set to the current
590 system time, and does not allow the content to transformed during
591 user-agent caching"""
592
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000593 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000594 return False
595
596 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000597 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000598 self.send_header('Cache-Control', 'no-transform')
599 self.end_headers()
600
maruel@google.come250a9b2009-03-10 17:39:46 +0000601 self.wfile.write('<html><head><title>%s</title></head></html>' %
602 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000603
604 return True
605
606 def EchoHeader(self):
607 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000608
ananta@chromium.org219b2062009-10-23 16:09:41 +0000609 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000610
ananta@chromium.org56812d02011-04-07 17:52:05 +0000611 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000612 """This function echoes back the value of a specific request header while
613 allowing caching for 16 hours."""
614
ananta@chromium.org56812d02011-04-07 17:52:05 +0000615 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000616
617 def EchoHeaderHelper(self, echo_header):
618 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000619
ananta@chromium.org219b2062009-10-23 16:09:41 +0000620 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000621 return False
622
623 query_char = self.path.find('?')
624 if query_char != -1:
625 header_name = self.path[query_char+1:]
626
627 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000628 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000629 if echo_header == '/echoheadercache':
630 self.send_header('Cache-control', 'max-age=60000')
631 else:
632 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000633 # insert a vary header to properly indicate that the cachability of this
634 # request is subject to value of the request header being echoed.
635 if len(header_name) > 0:
636 self.send_header('Vary', header_name)
637 self.end_headers()
638
639 if len(header_name) > 0:
640 self.wfile.write(self.headers.getheader(header_name))
641
642 return True
643
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000644 def ReadRequestBody(self):
645 """This function reads the body of the current HTTP request, handling
646 both plain and chunked transfer encoded requests."""
647
648 if self.headers.getheader('transfer-encoding') != 'chunked':
649 length = int(self.headers.getheader('content-length'))
650 return self.rfile.read(length)
651
652 # Read the request body as chunks.
653 body = ""
654 while True:
655 line = self.rfile.readline()
656 length = int(line, 16)
657 if length == 0:
658 self.rfile.readline()
659 break
660 body += self.rfile.read(length)
661 self.rfile.read(2)
662 return body
663
initial.commit94958cf2008-07-26 22:42:52 +0000664 def EchoHandler(self):
665 """This handler just echoes back the payload of the request, for testing
666 form submission."""
667
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000668 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000669 return False
670
671 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000672 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000673 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000674 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000675 return True
676
677 def EchoTitleHandler(self):
678 """This handler is like Echo, but sets the page title to the request."""
679
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000680 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000681 return False
682
683 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000684 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000685 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000686 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000687 self.wfile.write('<html><head><title>')
688 self.wfile.write(request)
689 self.wfile.write('</title></head></html>')
690 return True
691
692 def EchoAllHandler(self):
693 """This handler yields a (more) human-readable page listing information
694 about the request header & contents."""
695
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000696 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000697 return False
698
699 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000700 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000701 self.end_headers()
702 self.wfile.write('<html><head><style>'
703 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
704 '</style></head><body>'
705 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000706 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000707 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000708
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000709 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000710 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000711 params = cgi.parse_qs(qs, keep_blank_values=1)
712
713 for param in params:
714 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000715
716 self.wfile.write('</pre>')
717
718 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
719
720 self.wfile.write('</body></html>')
721 return True
722
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000723 def EchoMultipartPostHandler(self):
724 """This handler echoes received multipart post data as json format."""
725
726 if not (self._ShouldHandleRequest("/echomultipartpost") or
727 self._ShouldHandleRequest("/searchbyimage")):
728 return False
729
730 content_type, parameters = cgi.parse_header(
731 self.headers.getheader('content-type'))
732 if content_type == 'multipart/form-data':
733 post_multipart = cgi.parse_multipart(self.rfile, parameters)
734 elif content_type == 'application/x-www-form-urlencoded':
735 raise Exception('POST by application/x-www-form-urlencoded is '
736 'not implemented.')
737 else:
738 post_multipart = {}
739
740 # Since the data can be binary, we encode them by base64.
741 post_multipart_base64_encoded = {}
742 for field, values in post_multipart.items():
743 post_multipart_base64_encoded[field] = [base64.b64encode(value)
744 for value in values]
745
746 result = {'POST_multipart' : post_multipart_base64_encoded}
747
748 self.send_response(200)
749 self.send_header("Content-type", "text/plain")
750 self.end_headers()
751 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
752 return True
753
initial.commit94958cf2008-07-26 22:42:52 +0000754 def DownloadHandler(self):
755 """This handler sends a downloadable file with or without reporting
756 the size (6K)."""
757
758 if self.path.startswith("/download-unknown-size"):
759 send_length = False
760 elif self.path.startswith("/download-known-size"):
761 send_length = True
762 else:
763 return False
764
765 #
766 # The test which uses this functionality is attempting to send
767 # small chunks of data to the client. Use a fairly large buffer
768 # so that we'll fill chrome's IO buffer enough to force it to
769 # actually write the data.
770 # See also the comments in the client-side of this test in
771 # download_uitest.cc
772 #
773 size_chunk1 = 35*1024
774 size_chunk2 = 10*1024
775
776 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000777 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000778 self.send_header('Cache-Control', 'max-age=0')
779 if send_length:
780 self.send_header('Content-Length', size_chunk1 + size_chunk2)
781 self.end_headers()
782
783 # First chunk of data:
784 self.wfile.write("*" * size_chunk1)
785 self.wfile.flush()
786
787 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000788 self.server.wait_for_download = True
789 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000790 self.server.handle_request()
791
792 # Second chunk of data:
793 self.wfile.write("*" * size_chunk2)
794 return True
795
796 def DownloadFinishHandler(self):
797 """This handler just tells the server to finish the current download."""
798
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000799 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000800 return False
801
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000802 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000803 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000804 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000805 self.send_header('Cache-Control', 'max-age=0')
806 self.end_headers()
807 return True
808
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000809 def _ReplaceFileData(self, data, query_parameters):
810 """Replaces matching substrings in a file.
811
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000812 If the 'replace_text' URL query parameter is present, it is expected to be
813 of the form old_text:new_text, which indicates that any old_text strings in
814 the file are replaced with new_text. Multiple 'replace_text' parameters may
815 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000816
817 If the parameters are not present, |data| is returned.
818 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000819
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000820 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000821 replace_text_values = query_dict.get('replace_text', [])
822 for replace_text_value in replace_text_values:
823 replace_text_args = replace_text_value.split(':')
824 if len(replace_text_args) != 2:
825 raise ValueError(
826 'replace_text must be of form old_text:new_text. Actual value: %s' %
827 replace_text_value)
828 old_text_b64, new_text_b64 = replace_text_args
829 old_text = base64.urlsafe_b64decode(old_text_b64)
830 new_text = base64.urlsafe_b64decode(new_text_b64)
831 data = data.replace(old_text, new_text)
832 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000833
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000834 def ZipFileHandler(self):
835 """This handler sends the contents of the requested file in compressed form.
836 Can pass in a parameter that specifies that the content length be
837 C - the compressed size (OK),
838 U - the uncompressed size (Non-standard, but handled),
839 S - less than compressed (OK because we keep going),
840 M - larger than compressed but less than uncompressed (an error),
841 L - larger than uncompressed (an error)
842 Example: compressedfiles/Picture_1.doc?C
843 """
844
845 prefix = "/compressedfiles/"
846 if not self.path.startswith(prefix):
847 return False
848
849 # Consume a request body if present.
850 if self.command == 'POST' or self.command == 'PUT' :
851 self.ReadRequestBody()
852
853 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
854
855 if not query in ('C', 'U', 'S', 'M', 'L'):
856 return False
857
858 sub_path = url_path[len(prefix):]
859 entries = sub_path.split('/')
860 file_path = os.path.join(self.server.data_dir, *entries)
861 if os.path.isdir(file_path):
862 file_path = os.path.join(file_path, 'index.html')
863
864 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000865 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000866 self.send_error(404)
867 return True
868
869 f = open(file_path, "rb")
870 data = f.read()
871 uncompressed_len = len(data)
872 f.close()
873
874 # Compress the data.
875 data = zlib.compress(data)
876 compressed_len = len(data)
877
878 content_length = compressed_len
879 if query == 'U':
880 content_length = uncompressed_len
881 elif query == 'S':
882 content_length = compressed_len / 2
883 elif query == 'M':
884 content_length = (compressed_len + uncompressed_len) / 2
885 elif query == 'L':
886 content_length = compressed_len + uncompressed_len
887
888 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000889 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000890 self.send_header('Content-encoding', 'deflate')
891 self.send_header('Connection', 'close')
892 self.send_header('Content-Length', content_length)
893 self.send_header('ETag', '\'' + file_path + '\'')
894 self.end_headers()
895
896 self.wfile.write(data)
897
898 return True
899
initial.commit94958cf2008-07-26 22:42:52 +0000900 def FileHandler(self):
901 """This handler sends the contents of the requested file. Wow, it's like
902 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000903
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000904 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000905 if not self.path.startswith(prefix):
906 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000907 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000908
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000909 def PostOnlyFileHandler(self):
910 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000911
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000912 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000913 if not self.path.startswith(prefix):
914 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000915 return self._FileHandlerHelper(prefix)
916
917 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000918 request_body = ''
919 if self.command == 'POST' or self.command == 'PUT':
920 # Consume a request body if present.
921 request_body = self.ReadRequestBody()
922
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000923 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000924 query_dict = cgi.parse_qs(query)
925
926 expected_body = query_dict.get('expected_body', [])
927 if expected_body and request_body not in expected_body:
928 self.send_response(404)
929 self.end_headers()
930 self.wfile.write('')
931 return True
932
933 expected_headers = query_dict.get('expected_headers', [])
934 for expected_header in expected_headers:
935 header_name, expected_value = expected_header.split(':')
936 if self.headers.getheader(header_name) != expected_value:
937 self.send_response(404)
938 self.end_headers()
939 self.wfile.write('')
940 return True
941
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000942 sub_path = url_path[len(prefix):]
943 entries = sub_path.split('/')
944 file_path = os.path.join(self.server.data_dir, *entries)
945 if os.path.isdir(file_path):
946 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000947
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000948 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000949 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000950 self.send_error(404)
951 return True
952
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000953 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000954 data = f.read()
955 f.close()
956
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000957 data = self._ReplaceFileData(data, query)
958
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000959 old_protocol_version = self.protocol_version
960
initial.commit94958cf2008-07-26 22:42:52 +0000961 # If file.mock-http-headers exists, it contains the headers we
962 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000963 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000964 if os.path.isfile(headers_path):
965 f = open(headers_path, "r")
966
967 # "HTTP/1.1 200 OK"
968 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000969 http_major, http_minor, status_code = re.findall(
970 'HTTP/(\d+).(\d+) (\d+)', response)[0]
971 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000972 self.send_response(int(status_code))
973
974 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000975 header_values = re.findall('(\S+):\s*(.*)', line)
976 if len(header_values) > 0:
977 # "name: value"
978 name, value = header_values[0]
979 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000980 f.close()
981 else:
982 # Could be more generic once we support mime-type sniffing, but for
983 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000984
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000985 range_header = self.headers.get('Range')
986 if range_header and range_header.startswith('bytes='):
987 # Note this doesn't handle all valid byte range_header values (i.e.
988 # left open ended ones), just enough for what we needed so far.
989 range_header = range_header[6:].split('-')
990 start = int(range_header[0])
991 if range_header[1]:
992 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000993 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000994 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000995
996 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000997 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
998 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000999 self.send_header('Content-Range', content_range)
1000 data = data[start: end + 1]
1001 else:
1002 self.send_response(200)
1003
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001004 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001005 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001006 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001007 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001008 self.end_headers()
1009
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001010 if (self.command != 'HEAD'):
1011 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001012
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001013 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001014 return True
1015
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001016 def SetCookieHandler(self):
1017 """This handler just sets a cookie, for testing cookie handling."""
1018
1019 if not self._ShouldHandleRequest("/set-cookie"):
1020 return False
1021
1022 query_char = self.path.find('?')
1023 if query_char != -1:
1024 cookie_values = self.path[query_char + 1:].split('&')
1025 else:
1026 cookie_values = ("",)
1027 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001028 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001029 for cookie_value in cookie_values:
1030 self.send_header('Set-Cookie', '%s' % cookie_value)
1031 self.end_headers()
1032 for cookie_value in cookie_values:
1033 self.wfile.write('%s' % cookie_value)
1034 return True
1035
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001036 def SetManyCookiesHandler(self):
1037 """This handler just sets a given number of cookies, for testing handling
1038 of large numbers of cookies."""
1039
1040 if not self._ShouldHandleRequest("/set-many-cookies"):
1041 return False
1042
1043 query_char = self.path.find('?')
1044 if query_char != -1:
1045 num_cookies = int(self.path[query_char + 1:])
1046 else:
1047 num_cookies = 0
1048 self.send_response(200)
1049 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001050 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001051 self.send_header('Set-Cookie', 'a=')
1052 self.end_headers()
1053 self.wfile.write('%d cookies were sent' % num_cookies)
1054 return True
1055
mattm@chromium.org983fc462012-06-30 00:52:08 +00001056 def ExpectAndSetCookieHandler(self):
1057 """Expects some cookies to be sent, and if they are, sets more cookies.
1058
1059 The expect parameter specifies a required cookie. May be specified multiple
1060 times.
1061 The set parameter specifies a cookie to set if all required cookies are
1062 preset. May be specified multiple times.
1063 The data parameter specifies the response body data to be returned."""
1064
1065 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1066 return False
1067
1068 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1069 query_dict = cgi.parse_qs(query)
1070 cookies = set()
1071 if 'Cookie' in self.headers:
1072 cookie_header = self.headers.getheader('Cookie')
1073 cookies.update([s.strip() for s in cookie_header.split(';')])
1074 got_all_expected_cookies = True
1075 for expected_cookie in query_dict.get('expect', []):
1076 if expected_cookie not in cookies:
1077 got_all_expected_cookies = False
1078 self.send_response(200)
1079 self.send_header('Content-Type', 'text/html')
1080 if got_all_expected_cookies:
1081 for cookie_value in query_dict.get('set', []):
1082 self.send_header('Set-Cookie', '%s' % cookie_value)
1083 self.end_headers()
1084 for data_value in query_dict.get('data', []):
1085 self.wfile.write(data_value)
1086 return True
1087
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001088 def SetHeaderHandler(self):
1089 """This handler sets a response header. Parameters are in the
1090 key%3A%20value&key2%3A%20value2 format."""
1091
1092 if not self._ShouldHandleRequest("/set-header"):
1093 return False
1094
1095 query_char = self.path.find('?')
1096 if query_char != -1:
1097 headers_values = self.path[query_char + 1:].split('&')
1098 else:
1099 headers_values = ("",)
1100 self.send_response(200)
1101 self.send_header('Content-Type', 'text/html')
1102 for header_value in headers_values:
1103 header_value = urllib.unquote(header_value)
1104 (key, value) = header_value.split(': ', 1)
1105 self.send_header(key, value)
1106 self.end_headers()
1107 for header_value in headers_values:
1108 self.wfile.write('%s' % header_value)
1109 return True
1110
initial.commit94958cf2008-07-26 22:42:52 +00001111 def AuthBasicHandler(self):
1112 """This handler tests 'Basic' authentication. It just sends a page with
1113 title 'user/pass' if you succeed."""
1114
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001115 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001116 return False
1117
1118 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001119 expected_password = 'secret'
1120 realm = 'testrealm'
1121 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001122
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001123 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1124 query_params = cgi.parse_qs(query, True)
1125 if 'set-cookie-if-challenged' in query_params:
1126 set_cookie_if_challenged = True
1127 if 'password' in query_params:
1128 expected_password = query_params['password'][0]
1129 if 'realm' in query_params:
1130 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001131
initial.commit94958cf2008-07-26 22:42:52 +00001132 auth = self.headers.getheader('authorization')
1133 try:
1134 if not auth:
1135 raise Exception('no auth')
1136 b64str = re.findall(r'Basic (\S+)', auth)[0]
1137 userpass = base64.b64decode(b64str)
1138 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001139 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001140 raise Exception('wrong password')
1141 except Exception, e:
1142 # Authentication failed.
1143 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001144 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001145 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001146 if set_cookie_if_challenged:
1147 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001148 self.end_headers()
1149 self.wfile.write('<html><head>')
1150 self.wfile.write('<title>Denied: %s</title>' % e)
1151 self.wfile.write('</head><body>')
1152 self.wfile.write('auth=%s<p>' % auth)
1153 self.wfile.write('b64str=%s<p>' % b64str)
1154 self.wfile.write('username: %s<p>' % username)
1155 self.wfile.write('userpass: %s<p>' % userpass)
1156 self.wfile.write('password: %s<p>' % password)
1157 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1158 self.wfile.write('</body></html>')
1159 return True
1160
1161 # Authentication successful. (Return a cachable response to allow for
1162 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001163 old_protocol_version = self.protocol_version
1164 self.protocol_version = "HTTP/1.1"
1165
initial.commit94958cf2008-07-26 22:42:52 +00001166 if_none_match = self.headers.getheader('if-none-match')
1167 if if_none_match == "abc":
1168 self.send_response(304)
1169 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001170 elif url_path.endswith(".gif"):
1171 # Using chrome/test/data/google/logo.gif as the test image
1172 test_image_path = ['google', 'logo.gif']
1173 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1174 if not os.path.isfile(gif_path):
1175 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001176 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001177 return True
1178
1179 f = open(gif_path, "rb")
1180 data = f.read()
1181 f.close()
1182
1183 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001184 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001185 self.send_header('Cache-control', 'max-age=60000')
1186 self.send_header('Etag', 'abc')
1187 self.end_headers()
1188 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001189 else:
1190 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001191 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001192 self.send_header('Cache-control', 'max-age=60000')
1193 self.send_header('Etag', 'abc')
1194 self.end_headers()
1195 self.wfile.write('<html><head>')
1196 self.wfile.write('<title>%s/%s</title>' % (username, password))
1197 self.wfile.write('</head><body>')
1198 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001199 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001200 self.wfile.write('</body></html>')
1201
rvargas@google.com54453b72011-05-19 01:11:11 +00001202 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001203 return True
1204
tonyg@chromium.org75054202010-03-31 22:06:10 +00001205 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001206 """Returns a nonce that's stable per request path for the server's lifetime.
1207 This is a fake implementation. A real implementation would only use a given
1208 nonce a single time (hence the name n-once). However, for the purposes of
1209 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001210
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001211 Args:
1212 force_reset: Iff set, the nonce will be changed. Useful for testing the
1213 "stale" response.
1214 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001215
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001216 if force_reset or not self.server.nonce_time:
1217 self.server.nonce_time = time.time()
1218 return hashlib.md5('privatekey%s%d' %
1219 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001220
1221 def AuthDigestHandler(self):
1222 """This handler tests 'Digest' authentication.
1223
1224 It just sends a page with title 'user/pass' if you succeed.
1225
1226 A stale response is sent iff "stale" is present in the request path.
1227 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001228
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001229 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001230 return False
1231
tonyg@chromium.org75054202010-03-31 22:06:10 +00001232 stale = 'stale' in self.path
1233 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001234 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001235 password = 'secret'
1236 realm = 'testrealm'
1237
1238 auth = self.headers.getheader('authorization')
1239 pairs = {}
1240 try:
1241 if not auth:
1242 raise Exception('no auth')
1243 if not auth.startswith('Digest'):
1244 raise Exception('not digest')
1245 # Pull out all the name="value" pairs as a dictionary.
1246 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1247
1248 # Make sure it's all valid.
1249 if pairs['nonce'] != nonce:
1250 raise Exception('wrong nonce')
1251 if pairs['opaque'] != opaque:
1252 raise Exception('wrong opaque')
1253
1254 # Check the 'response' value and make sure it matches our magic hash.
1255 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001256 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001257 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001258 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001259 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001260 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001261 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1262 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001263 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001264
1265 if pairs['response'] != response:
1266 raise Exception('wrong password')
1267 except Exception, e:
1268 # Authentication failed.
1269 self.send_response(401)
1270 hdr = ('Digest '
1271 'realm="%s", '
1272 'domain="/", '
1273 'qop="auth", '
1274 'algorithm=MD5, '
1275 'nonce="%s", '
1276 'opaque="%s"') % (realm, nonce, opaque)
1277 if stale:
1278 hdr += ', stale="TRUE"'
1279 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001280 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001281 self.end_headers()
1282 self.wfile.write('<html><head>')
1283 self.wfile.write('<title>Denied: %s</title>' % e)
1284 self.wfile.write('</head><body>')
1285 self.wfile.write('auth=%s<p>' % auth)
1286 self.wfile.write('pairs=%s<p>' % pairs)
1287 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1288 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1289 self.wfile.write('</body></html>')
1290 return True
1291
1292 # Authentication successful.
1293 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001294 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001295 self.end_headers()
1296 self.wfile.write('<html><head>')
1297 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1298 self.wfile.write('</head><body>')
1299 self.wfile.write('auth=%s<p>' % auth)
1300 self.wfile.write('pairs=%s<p>' % pairs)
1301 self.wfile.write('</body></html>')
1302
1303 return True
1304
1305 def SlowServerHandler(self):
1306 """Wait for the user suggested time before responding. The syntax is
1307 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001308
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001309 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001310 return False
1311 query_char = self.path.find('?')
1312 wait_sec = 1.0
1313 if query_char >= 0:
1314 try:
1315 wait_sec = int(self.path[query_char + 1:])
1316 except ValueError:
1317 pass
1318 time.sleep(wait_sec)
1319 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001320 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001321 self.end_headers()
1322 self.wfile.write("waited %d seconds" % wait_sec)
1323 return True
1324
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001325 def ChunkedServerHandler(self):
1326 """Send chunked response. Allows to specify chunks parameters:
1327 - waitBeforeHeaders - ms to wait before sending headers
1328 - waitBetweenChunks - ms to wait between chunks
1329 - chunkSize - size of each chunk in bytes
1330 - chunksNumber - number of chunks
1331 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1332 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001333
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001334 if not self._ShouldHandleRequest("/chunked"):
1335 return False
1336 query_char = self.path.find('?')
1337 chunkedSettings = {'waitBeforeHeaders' : 0,
1338 'waitBetweenChunks' : 0,
1339 'chunkSize' : 5,
1340 'chunksNumber' : 5}
1341 if query_char >= 0:
1342 params = self.path[query_char + 1:].split('&')
1343 for param in params:
1344 keyValue = param.split('=')
1345 if len(keyValue) == 2:
1346 try:
1347 chunkedSettings[keyValue[0]] = int(keyValue[1])
1348 except ValueError:
1349 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001350 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001351 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1352 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001353 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001354 self.send_header('Connection', 'close')
1355 self.send_header('Transfer-Encoding', 'chunked')
1356 self.end_headers()
1357 # Chunked encoding: sending all chunks, then final zero-length chunk and
1358 # then final CRLF.
1359 for i in range(0, chunkedSettings['chunksNumber']):
1360 if i > 0:
1361 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1362 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001363 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001364 self.sendChunkHelp('')
1365 return True
1366
initial.commit94958cf2008-07-26 22:42:52 +00001367 def ContentTypeHandler(self):
1368 """Returns a string of html with the given content type. E.g.,
1369 /contenttype?text/css returns an html file with the Content-Type
1370 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001371
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001372 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001373 return False
1374 query_char = self.path.find('?')
1375 content_type = self.path[query_char + 1:].strip()
1376 if not content_type:
1377 content_type = 'text/html'
1378 self.send_response(200)
1379 self.send_header('Content-Type', content_type)
1380 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001381 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001382 return True
1383
creis@google.com2f4f6a42011-03-25 19:44:19 +00001384 def NoContentHandler(self):
1385 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001386
creis@google.com2f4f6a42011-03-25 19:44:19 +00001387 if not self._ShouldHandleRequest("/nocontent"):
1388 return False
1389 self.send_response(204)
1390 self.end_headers()
1391 return True
1392
initial.commit94958cf2008-07-26 22:42:52 +00001393 def ServerRedirectHandler(self):
1394 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001395 '/server-redirect?http://foo.bar/asdf' to redirect to
1396 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001397
1398 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001399 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001400 return False
1401
1402 query_char = self.path.find('?')
1403 if query_char < 0 or len(self.path) <= query_char + 1:
1404 self.sendRedirectHelp(test_name)
1405 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001406 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001407
1408 self.send_response(301) # moved permanently
1409 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001410 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001411 self.end_headers()
1412 self.wfile.write('<html><head>')
1413 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1414
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001415 return True
initial.commit94958cf2008-07-26 22:42:52 +00001416
1417 def ClientRedirectHandler(self):
1418 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001419 '/client-redirect?http://foo.bar/asdf' to redirect to
1420 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001421
1422 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001423 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001424 return False
1425
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001426 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001427 if query_char < 0 or len(self.path) <= query_char + 1:
1428 self.sendRedirectHelp(test_name)
1429 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001430 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001431
1432 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001433 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001434 self.end_headers()
1435 self.wfile.write('<html><head>')
1436 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1437 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1438
1439 return True
1440
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001441 def GetSSLSessionCacheHandler(self):
1442 """Send a reply containing a log of the session cache operations."""
1443
1444 if not self._ShouldHandleRequest('/ssl-session-cache'):
1445 return False
1446
1447 self.send_response(200)
1448 self.send_header('Content-Type', 'text/plain')
1449 self.end_headers()
1450 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001451 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001452 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001453 self.wfile.write('Pass --https-record-resume in order to use' +
1454 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001455 return True
1456
1457 for (action, sessionID) in log:
1458 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001459 return True
1460
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001461 def SSLManySmallRecords(self):
1462 """Sends a reply consisting of a variety of small writes. These will be
1463 translated into a series of small SSL records when used over an HTTPS
1464 server."""
1465
1466 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1467 return False
1468
1469 self.send_response(200)
1470 self.send_header('Content-Type', 'text/plain')
1471 self.end_headers()
1472
1473 # Write ~26K of data, in 1350 byte chunks
1474 for i in xrange(20):
1475 self.wfile.write('*' * 1350)
1476 self.wfile.flush()
1477 return True
1478
agl@chromium.org04700be2013-03-02 18:40:41 +00001479 def GetChannelID(self):
1480 """Send a reply containing the hashed ChannelID that the client provided."""
1481
1482 if not self._ShouldHandleRequest('/channel-id'):
1483 return False
1484
1485 self.send_response(200)
1486 self.send_header('Content-Type', 'text/plain')
1487 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001488 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001489 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1490 return True
1491
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001492 def CloseSocketHandler(self):
1493 """Closes the socket without sending anything."""
1494
1495 if not self._ShouldHandleRequest('/close-socket'):
1496 return False
1497
1498 self.wfile.close()
1499 return True
1500
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001501 def RangeResetHandler(self):
1502 """Send data broken up by connection resets every N (default 4K) bytes.
1503 Support range requests. If the data requested doesn't straddle a reset
1504 boundary, it will all be sent. Used for testing resuming downloads."""
1505
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001506 def DataForRange(start, end):
1507 """Data to be provided for a particular range of bytes."""
1508 # Offset and scale to avoid too obvious (and hence potentially
1509 # collidable) data.
1510 return ''.join([chr(y % 256)
1511 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1512
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001513 if not self._ShouldHandleRequest('/rangereset'):
1514 return False
1515
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001516 # HTTP/1.1 is required for ETag and range support.
1517 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001518 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1519
1520 # Defaults
1521 size = 8000
1522 # Note that the rst is sent just before sending the rst_boundary byte.
1523 rst_boundary = 4000
1524 respond_to_range = True
1525 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001526 rst_limit = -1
1527 token = 'DEFAULT'
1528 fail_precondition = 0
1529 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001530
1531 # Parse the query
1532 qdict = urlparse.parse_qs(query, True)
1533 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001534 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001535 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001536 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001537 if 'token' in qdict:
1538 # Identifying token for stateful tests.
1539 token = qdict['token'][0]
1540 if 'rst_limit' in qdict:
1541 # Max number of rsts for a given token.
1542 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001543 if 'bounce_range' in qdict:
1544 respond_to_range = False
1545 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001546 # Note that hold_for_signal will not work with null range requests;
1547 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001548 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001549 if 'no_verifiers' in qdict:
1550 send_verifiers = False
1551 if 'fail_precondition' in qdict:
1552 fail_precondition = int(qdict['fail_precondition'][0])
1553
1554 # Record already set information, or set it.
1555 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1556 if rst_limit != 0:
1557 TestPageHandler.rst_limits[token] -= 1
1558 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1559 token, fail_precondition)
1560 if fail_precondition != 0:
1561 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001562
1563 first_byte = 0
1564 last_byte = size - 1
1565
1566 # Does that define what we want to return, or do we need to apply
1567 # a range?
1568 range_response = False
1569 range_header = self.headers.getheader('range')
1570 if range_header and respond_to_range:
1571 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1572 if mo.group(1):
1573 first_byte = int(mo.group(1))
1574 if mo.group(2):
1575 last_byte = int(mo.group(2))
1576 if last_byte > size - 1:
1577 last_byte = size - 1
1578 range_response = True
1579 if last_byte < first_byte:
1580 return False
1581
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001582 if (fail_precondition and
1583 (self.headers.getheader('If-Modified-Since') or
1584 self.headers.getheader('If-Match'))):
1585 self.send_response(412)
1586 self.end_headers()
1587 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001588
1589 if range_response:
1590 self.send_response(206)
1591 self.send_header('Content-Range',
1592 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1593 else:
1594 self.send_response(200)
1595 self.send_header('Content-Type', 'application/octet-stream')
1596 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001597 if send_verifiers:
asanka@chromium.org3ffced92013-12-13 22:20:27 +00001598 # If fail_precondition is non-zero, then the ETag for each request will be
1599 # different.
1600 etag = "%s%d" % (token, fail_precondition)
1601 self.send_header('ETag', etag)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001602 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001603 self.end_headers()
1604
1605 if hold_for_signal:
1606 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1607 # a single byte, the self.server.handle_request() below hangs
1608 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001609 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001610 first_byte = first_byte + 1
1611 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001612 self.server.wait_for_download = True
1613 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001614 self.server.handle_request()
1615
1616 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001617 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001618 # No RST has been requested in this range, so we don't need to
1619 # do anything fancy; just write the data and let the python
1620 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001621 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001622 self.wfile.flush()
1623 return True
1624
1625 # We're resetting the connection part way in; go to the RST
1626 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001627 # Because socket semantics do not guarantee that all the data will be
1628 # sent when using the linger semantics to hard close a socket,
1629 # we send the data and then wait for our peer to release us
1630 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001631 data = DataForRange(first_byte, possible_rst)
1632 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001633 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001634 self.server.wait_for_download = True
1635 while self.server.wait_for_download:
1636 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001637 l_onoff = 1 # Linger is active.
1638 l_linger = 0 # Seconds to linger for.
1639 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1640 struct.pack('ii', l_onoff, l_linger))
1641
1642 # Close all duplicates of the underlying socket to force the RST.
1643 self.wfile.close()
1644 self.rfile.close()
1645 self.connection.close()
1646
1647 return True
1648
initial.commit94958cf2008-07-26 22:42:52 +00001649 def DefaultResponseHandler(self):
1650 """This is the catch-all response handler for requests that aren't handled
1651 by one of the special handlers above.
1652 Note that we specify the content-length as without it the https connection
1653 is not closed properly (and the browser keeps expecting data)."""
1654
1655 contents = "Default response given for path: " + self.path
1656 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001657 self.send_header('Content-Type', 'text/html')
1658 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001659 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001660 if (self.command != 'HEAD'):
1661 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001662 return True
1663
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001664 def RedirectConnectHandler(self):
1665 """Sends a redirect to the CONNECT request for www.redirect.com. This
1666 response is not specified by the RFC, so the browser should not follow
1667 the redirect."""
1668
1669 if (self.path.find("www.redirect.com") < 0):
1670 return False
1671
1672 dest = "http://www.destination.com/foo.js"
1673
1674 self.send_response(302) # moved temporarily
1675 self.send_header('Location', dest)
1676 self.send_header('Connection', 'close')
1677 self.end_headers()
1678 return True
1679
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001680 def ServerAuthConnectHandler(self):
1681 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1682 response doesn't make sense because the proxy server cannot request
1683 server authentication."""
1684
1685 if (self.path.find("www.server-auth.com") < 0):
1686 return False
1687
1688 challenge = 'Basic realm="WallyWorld"'
1689
1690 self.send_response(401) # unauthorized
1691 self.send_header('WWW-Authenticate', challenge)
1692 self.send_header('Connection', 'close')
1693 self.end_headers()
1694 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001695
1696 def DefaultConnectResponseHandler(self):
1697 """This is the catch-all response handler for CONNECT requests that aren't
1698 handled by one of the special handlers above. Real Web servers respond
1699 with 400 to CONNECT requests."""
1700
1701 contents = "Your client has issued a malformed or illegal request."
1702 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001703 self.send_header('Content-Type', 'text/html')
1704 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001705 self.end_headers()
1706 self.wfile.write(contents)
1707 return True
1708
initial.commit94958cf2008-07-26 22:42:52 +00001709 # called by the redirect handling function when there is no parameter
1710 def sendRedirectHelp(self, redirect_name):
1711 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001712 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001713 self.end_headers()
1714 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1715 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1716 self.wfile.write('</body></html>')
1717
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001718 # called by chunked handling function
1719 def sendChunkHelp(self, chunk):
1720 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1721 self.wfile.write('%X\r\n' % len(chunk))
1722 self.wfile.write(chunk)
1723 self.wfile.write('\r\n')
1724
akalin@chromium.org154bb132010-11-12 02:20:27 +00001725
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001726class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001727 def __init__(self, request, client_address, socket_server):
1728 handlers = [self.OCSPResponse]
1729 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001730 testserver_base.BasePageHandler.__init__(self, request, client_address,
1731 socket_server, [], handlers, [],
1732 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001733
1734 def OCSPResponse(self):
1735 self.send_response(200)
1736 self.send_header('Content-Type', 'application/ocsp-response')
1737 self.send_header('Content-Length', str(len(self.ocsp_response)))
1738 self.end_headers()
1739
1740 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001741
mattm@chromium.org830a3712012-11-07 23:00:07 +00001742
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001743class TCPEchoHandler(SocketServer.BaseRequestHandler):
1744 """The RequestHandler class for TCP echo server.
1745
1746 It is instantiated once per connection to the server, and overrides the
1747 handle() method to implement communication to the client.
1748 """
1749
1750 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001751 """Handles the request from the client and constructs a response."""
1752
1753 data = self.request.recv(65536).strip()
1754 # Verify the "echo request" message received from the client. Send back
1755 # "echo response" message if "echo request" message is valid.
1756 try:
1757 return_data = echo_message.GetEchoResponseData(data)
1758 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001759 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001760 except ValueError:
1761 return
1762
1763 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001764
1765
1766class UDPEchoHandler(SocketServer.BaseRequestHandler):
1767 """The RequestHandler class for UDP echo server.
1768
1769 It is instantiated once per connection to the server, and overrides the
1770 handle() method to implement communication to the client.
1771 """
1772
1773 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001774 """Handles the request from the client and constructs a response."""
1775
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001776 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001777 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001778 # Verify the "echo request" message received from the client. Send back
1779 # "echo response" message if "echo request" message is valid.
1780 try:
1781 return_data = echo_message.GetEchoResponseData(data)
1782 if not return_data:
1783 return
1784 except ValueError:
1785 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001786 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001787
1788
bashi@chromium.org33233532012-09-08 17:37:24 +00001789class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1790 """A request handler that behaves as a proxy server which requires
1791 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1792 """
1793
1794 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1795
1796 def parse_request(self):
1797 """Overrides parse_request to check credential."""
1798
1799 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1800 return False
1801
1802 auth = self.headers.getheader('Proxy-Authorization')
1803 if auth != self._AUTH_CREDENTIAL:
1804 self.send_response(407)
1805 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1806 self.end_headers()
1807 return False
1808
1809 return True
1810
1811 def _start_read_write(self, sock):
1812 sock.setblocking(0)
1813 self.request.setblocking(0)
1814 rlist = [self.request, sock]
1815 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001816 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001817 if errors:
1818 self.send_response(500)
1819 self.end_headers()
1820 return
1821 for s in ready_sockets:
1822 received = s.recv(1024)
1823 if len(received) == 0:
1824 return
1825 if s == self.request:
1826 other = sock
1827 else:
1828 other = self.request
1829 other.send(received)
1830
1831 def _do_common_method(self):
1832 url = urlparse.urlparse(self.path)
1833 port = url.port
1834 if not port:
1835 if url.scheme == 'http':
1836 port = 80
1837 elif url.scheme == 'https':
1838 port = 443
1839 if not url.hostname or not port:
1840 self.send_response(400)
1841 self.end_headers()
1842 return
1843
1844 if len(url.path) == 0:
1845 path = '/'
1846 else:
1847 path = url.path
1848 if len(url.query) > 0:
1849 path = '%s?%s' % (url.path, url.query)
1850
1851 sock = None
1852 try:
1853 sock = socket.create_connection((url.hostname, port))
1854 sock.send('%s %s %s\r\n' % (
1855 self.command, path, self.protocol_version))
1856 for header in self.headers.headers:
1857 header = header.strip()
1858 if (header.lower().startswith('connection') or
1859 header.lower().startswith('proxy')):
1860 continue
1861 sock.send('%s\r\n' % header)
1862 sock.send('\r\n')
1863 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001864 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001865 self.send_response(500)
1866 self.end_headers()
1867 finally:
1868 if sock is not None:
1869 sock.close()
1870
1871 def do_CONNECT(self):
1872 try:
1873 pos = self.path.rfind(':')
1874 host = self.path[:pos]
1875 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001876 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001877 self.send_response(400)
1878 self.end_headers()
1879
1880 try:
1881 sock = socket.create_connection((host, port))
1882 self.send_response(200, 'Connection established')
1883 self.end_headers()
1884 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001885 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001886 self.send_response(500)
1887 self.end_headers()
1888 finally:
1889 sock.close()
1890
1891 def do_GET(self):
1892 self._do_common_method()
1893
1894 def do_HEAD(self):
1895 self._do_common_method()
1896
1897
mattm@chromium.org830a3712012-11-07 23:00:07 +00001898class ServerRunner(testserver_base.TestServerRunner):
1899 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001900
mattm@chromium.org830a3712012-11-07 23:00:07 +00001901 def __init__(self):
1902 super(ServerRunner, self).__init__()
1903 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001904
mattm@chromium.org830a3712012-11-07 23:00:07 +00001905 def __make_data_dir(self):
1906 if self.options.data_dir:
1907 if not os.path.isdir(self.options.data_dir):
1908 raise testserver_base.OptionError('specified data dir not found: ' +
1909 self.options.data_dir + ' exiting...')
1910 my_data_dir = self.options.data_dir
1911 else:
1912 # Create the default path to our data dir, relative to the exe dir.
1913 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1914 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001915
mattm@chromium.org830a3712012-11-07 23:00:07 +00001916 #TODO(ibrar): Must use Find* funtion defined in google\tools
1917 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001918
mattm@chromium.org830a3712012-11-07 23:00:07 +00001919 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001920
mattm@chromium.org830a3712012-11-07 23:00:07 +00001921 def create_server(self, server_data):
1922 port = self.options.port
1923 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001924
mattm@chromium.org830a3712012-11-07 23:00:07 +00001925 if self.options.server_type == SERVER_HTTP:
1926 if self.options.https:
1927 pem_cert_and_key = None
1928 if self.options.cert_and_key_file:
1929 if not os.path.isfile(self.options.cert_and_key_file):
1930 raise testserver_base.OptionError(
1931 'specified server cert file not found: ' +
1932 self.options.cert_and_key_file + ' exiting...')
1933 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001934 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001935 # generate a new certificate and run an OCSP server for it.
1936 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001937 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001938 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001939
mattm@chromium.org830a3712012-11-07 23:00:07 +00001940 ocsp_der = None
1941 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001942
mattm@chromium.org830a3712012-11-07 23:00:07 +00001943 if self.options.ocsp == 'ok':
1944 ocsp_state = minica.OCSP_STATE_GOOD
1945 elif self.options.ocsp == 'revoked':
1946 ocsp_state = minica.OCSP_STATE_REVOKED
1947 elif self.options.ocsp == 'invalid':
1948 ocsp_state = minica.OCSP_STATE_INVALID
1949 elif self.options.ocsp == 'unauthorized':
1950 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1951 elif self.options.ocsp == 'unknown':
1952 ocsp_state = minica.OCSP_STATE_UNKNOWN
1953 else:
1954 raise testserver_base.OptionError('unknown OCSP status: ' +
1955 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001956
mattm@chromium.org830a3712012-11-07 23:00:07 +00001957 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1958 subject = "127.0.0.1",
1959 ocsp_url = ("http://%s:%d/ocsp" %
1960 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00001961 ocsp_state = ocsp_state,
1962 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001963
1964 self.__ocsp_server.ocsp_response = ocsp_der
1965
1966 for ca_cert in self.options.ssl_client_ca:
1967 if not os.path.isfile(ca_cert):
1968 raise testserver_base.OptionError(
1969 'specified trusted client CA file not found: ' + ca_cert +
1970 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001971
1972 stapled_ocsp_response = None
1973 if self.__ocsp_server and self.options.staple_ocsp_response:
1974 stapled_ocsp_response = self.__ocsp_server.ocsp_response
1975
mattm@chromium.org830a3712012-11-07 23:00:07 +00001976 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1977 self.options.ssl_client_auth,
1978 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00001979 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00001980 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00001981 self.options.ssl_key_exchange,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00001982 self.options.enable_npn,
mattm@chromium.org830a3712012-11-07 23:00:07 +00001983 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00001984 self.options.tls_intolerant,
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.')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002131 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2132 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002133 default='',
2134 help='Base64 encoded SCT list. If set, '
2135 'server will respond with a '
2136 'signed_certificate_timestamp TLS extension '
2137 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002138 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2139 default=False, const=True,
2140 action='store_const',
2141 help='If given, TLS_FALLBACK_SCSV support '
2142 'will be enabled. This causes the server to '
2143 'reject fallback connections from compatible '
2144 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002145 self.option_parser.add_option('--staple-ocsp-response',
2146 dest='staple_ocsp_response',
2147 default=False, action='store_true',
2148 help='If set, server will staple the OCSP '
2149 'response whenever OCSP is on and the client '
2150 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002151 self.option_parser.add_option('--https-record-resume',
2152 dest='record_resume', const=True,
2153 default=False, action='store_const',
2154 help='Record resumption cache events rather '
2155 'than resuming as normal. Allows the use of '
2156 'the /ssl-session-cache request')
2157 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2158 help='Require SSL client auth on every '
2159 'connection.')
2160 self.option_parser.add_option('--ssl-client-ca', action='append',
2161 default=[], help='Specify that the client '
2162 'certificate request should include the CA '
2163 'named in the subject of the DER-encoded '
2164 'certificate contained in the specified '
2165 'file. This option may appear multiple '
2166 'times, indicating multiple CA names should '
2167 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002168 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2169 default=[], help='Specify that the client '
2170 'certificate request should include the '
2171 'specified certificate_type value. This '
2172 'option may appear multiple times, '
2173 'indicating multiple values should be send '
2174 'in the request. Valid values are '
2175 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2176 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002177 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2178 help='Specify the bulk encryption '
2179 'algorithm(s) that will be accepted by the '
2180 'SSL server. Valid values are "aes256", '
2181 '"aes128", "3des", "rc4". If omitted, all '
2182 'algorithms will be used. This option may '
2183 'appear multiple times, indicating '
2184 'multiple algorithms should be enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002185 self.option_parser.add_option('--ssl-key-exchange', action='append',
2186 help='Specify the key exchange algorithm(s)'
2187 'that will be accepted by the SSL server. '
2188 'Valid values are "rsa", "dhe_rsa". If '
2189 'omitted, all algorithms will be used. This '
2190 'option may appear multiple times, '
2191 'indicating multiple algorithms should be '
2192 'enabled.');
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002193 # TODO(davidben): Add ALPN support to tlslite.
2194 self.option_parser.add_option('--enable-npn', dest='enable_npn',
2195 default=False, const=True,
2196 action='store_const',
2197 help='Enable server support for the NPN '
2198 'extension. The server will advertise '
2199 'support for exactly one protocol, http/1.1')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002200 self.option_parser.add_option('--file-root-url', default='/files/',
2201 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002202
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002203
initial.commit94958cf2008-07-26 22:42:52 +00002204if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002205 sys.exit(ServerRunner().main())