blob: c7af46b06360d8bab3187aa89a544131c61b40a1 [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,
tony@chromium.org03266982010-03-05 03:18:42 +0000332 self.MultipartHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000333 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000334 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000335 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000336 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000337 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000338 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000339 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000340 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000341 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000342 self.PostOnlyFileHandler,
343 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000344 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000345 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000346 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000347 head_handlers = [
348 self.FileHandler,
349 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000350
maruel@google.come250a9b2009-03-10 17:39:46 +0000351 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000352 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000353 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000354 'gif': 'image/gif',
355 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000356 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000357 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000358 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000359 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000360 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000361 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000362 }
initial.commit94958cf2008-07-26 22:42:52 +0000363 self._default_mime_type = 'text/html'
364
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000365 testserver_base.BasePageHandler.__init__(self, request, client_address,
366 socket_server, connect_handlers,
367 get_handlers, head_handlers,
368 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000369
initial.commit94958cf2008-07-26 22:42:52 +0000370 def GetMIMETypeFromName(self, file_name):
371 """Returns the mime type for the specified file_name. So far it only looks
372 at the file extension."""
373
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000374 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000375 if len(extension) == 0:
376 # no extension.
377 return self._default_mime_type
378
ericroman@google.comc17ca532009-05-07 03:51:05 +0000379 # extension starts with a dot, so we need to remove it
380 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000381
initial.commit94958cf2008-07-26 22:42:52 +0000382 def NoCacheMaxAgeTimeHandler(self):
383 """This request handler yields a page with the title set to the current
384 system time, and no caching requested."""
385
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000386 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000387 return False
388
389 self.send_response(200)
390 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000391 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000392 self.end_headers()
393
maruel@google.come250a9b2009-03-10 17:39:46 +0000394 self.wfile.write('<html><head><title>%s</title></head></html>' %
395 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000396
397 return True
398
399 def NoCacheTimeHandler(self):
400 """This request handler yields a page with the title set to the current
401 system time, and no caching requested."""
402
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000403 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000404 return False
405
406 self.send_response(200)
407 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000408 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000409 self.end_headers()
410
maruel@google.come250a9b2009-03-10 17:39:46 +0000411 self.wfile.write('<html><head><title>%s</title></head></html>' %
412 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000413
414 return True
415
416 def CacheTimeHandler(self):
417 """This request handler yields a page with the title set to the current
418 system time, and allows caching for one minute."""
419
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000420 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000421 return False
422
423 self.send_response(200)
424 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000425 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000426 self.end_headers()
427
maruel@google.come250a9b2009-03-10 17:39:46 +0000428 self.wfile.write('<html><head><title>%s</title></head></html>' %
429 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000430
431 return True
432
433 def CacheExpiresHandler(self):
434 """This request handler yields a page with the title set to the current
435 system time, and set the page to expire on 1 Jan 2099."""
436
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000437 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000438 return False
439
440 self.send_response(200)
441 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000442 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000443 self.end_headers()
444
maruel@google.come250a9b2009-03-10 17:39:46 +0000445 self.wfile.write('<html><head><title>%s</title></head></html>' %
446 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000447
448 return True
449
450 def CacheProxyRevalidateHandler(self):
451 """This request handler yields a page with the title set to the current
452 system time, and allows caching for 60 seconds"""
453
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000454 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000455 return False
456
457 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000458 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000459 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
460 self.end_headers()
461
maruel@google.come250a9b2009-03-10 17:39:46 +0000462 self.wfile.write('<html><head><title>%s</title></head></html>' %
463 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000464
465 return True
466
467 def CachePrivateHandler(self):
468 """This request handler yields a page with the title set to the current
469 system time, and allows caching for 5 seconds."""
470
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000471 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000472 return False
473
474 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000475 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000476 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000477 self.end_headers()
478
maruel@google.come250a9b2009-03-10 17:39:46 +0000479 self.wfile.write('<html><head><title>%s</title></head></html>' %
480 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000481
482 return True
483
484 def CachePublicHandler(self):
485 """This request handler yields a page with the title set to the current
486 system time, and allows caching for 5 seconds."""
487
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000488 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000489 return False
490
491 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000492 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000493 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000494 self.end_headers()
495
maruel@google.come250a9b2009-03-10 17:39:46 +0000496 self.wfile.write('<html><head><title>%s</title></head></html>' %
497 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000498
499 return True
500
501 def CacheSMaxAgeHandler(self):
502 """This request handler yields a page with the title set to the current
503 system time, and does not allow for caching."""
504
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000505 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000506 return False
507
508 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000509 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000510 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
511 self.end_headers()
512
maruel@google.come250a9b2009-03-10 17:39:46 +0000513 self.wfile.write('<html><head><title>%s</title></head></html>' %
514 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000515
516 return True
517
518 def CacheMustRevalidateHandler(self):
519 """This request handler yields a page with the title set to the current
520 system time, and does not allow caching."""
521
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000522 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000523 return False
524
525 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000526 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000527 self.send_header('Cache-Control', 'must-revalidate')
528 self.end_headers()
529
maruel@google.come250a9b2009-03-10 17:39:46 +0000530 self.wfile.write('<html><head><title>%s</title></head></html>' %
531 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000532
533 return True
534
535 def CacheMustRevalidateMaxAgeHandler(self):
536 """This request handler yields a page with the title set to the current
537 system time, and does not allow caching event though max-age of 60
538 seconds is specified."""
539
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000540 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000541 return False
542
543 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000544 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000545 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
546 self.end_headers()
547
maruel@google.come250a9b2009-03-10 17:39:46 +0000548 self.wfile.write('<html><head><title>%s</title></head></html>' %
549 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000550
551 return True
552
initial.commit94958cf2008-07-26 22:42:52 +0000553 def CacheNoStoreHandler(self):
554 """This request handler yields a page with the title set to the current
555 system time, and does not allow the page to be stored."""
556
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000557 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000558 return False
559
560 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000561 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000562 self.send_header('Cache-Control', 'no-store')
563 self.end_headers()
564
maruel@google.come250a9b2009-03-10 17:39:46 +0000565 self.wfile.write('<html><head><title>%s</title></head></html>' %
566 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000567
568 return True
569
570 def CacheNoStoreMaxAgeHandler(self):
571 """This request handler yields a page with the title set to the current
572 system time, and does not allow the page to be stored even though max-age
573 of 60 seconds is specified."""
574
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000575 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000576 return False
577
578 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000579 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000580 self.send_header('Cache-Control', 'max-age=60, no-store')
581 self.end_headers()
582
maruel@google.come250a9b2009-03-10 17:39:46 +0000583 self.wfile.write('<html><head><title>%s</title></head></html>' %
584 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000585
586 return True
587
588
589 def CacheNoTransformHandler(self):
590 """This request handler yields a page with the title set to the current
591 system time, and does not allow the content to transformed during
592 user-agent caching"""
593
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000594 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000595 return False
596
597 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000598 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000599 self.send_header('Cache-Control', 'no-transform')
600 self.end_headers()
601
maruel@google.come250a9b2009-03-10 17:39:46 +0000602 self.wfile.write('<html><head><title>%s</title></head></html>' %
603 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000604
605 return True
606
607 def EchoHeader(self):
608 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000609
ananta@chromium.org219b2062009-10-23 16:09:41 +0000610 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000611
ananta@chromium.org56812d02011-04-07 17:52:05 +0000612 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000613 """This function echoes back the value of a specific request header while
614 allowing caching for 16 hours."""
615
ananta@chromium.org56812d02011-04-07 17:52:05 +0000616 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000617
618 def EchoHeaderHelper(self, echo_header):
619 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000620
ananta@chromium.org219b2062009-10-23 16:09:41 +0000621 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000622 return False
623
624 query_char = self.path.find('?')
625 if query_char != -1:
626 header_name = self.path[query_char+1:]
627
628 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000629 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000630 if echo_header == '/echoheadercache':
631 self.send_header('Cache-control', 'max-age=60000')
632 else:
633 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000634 # insert a vary header to properly indicate that the cachability of this
635 # request is subject to value of the request header being echoed.
636 if len(header_name) > 0:
637 self.send_header('Vary', header_name)
638 self.end_headers()
639
640 if len(header_name) > 0:
641 self.wfile.write(self.headers.getheader(header_name))
642
643 return True
644
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000645 def ReadRequestBody(self):
646 """This function reads the body of the current HTTP request, handling
647 both plain and chunked transfer encoded requests."""
648
649 if self.headers.getheader('transfer-encoding') != 'chunked':
650 length = int(self.headers.getheader('content-length'))
651 return self.rfile.read(length)
652
653 # Read the request body as chunks.
654 body = ""
655 while True:
656 line = self.rfile.readline()
657 length = int(line, 16)
658 if length == 0:
659 self.rfile.readline()
660 break
661 body += self.rfile.read(length)
662 self.rfile.read(2)
663 return body
664
initial.commit94958cf2008-07-26 22:42:52 +0000665 def EchoHandler(self):
666 """This handler just echoes back the payload of the request, for testing
667 form submission."""
668
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000669 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000670 return False
671
672 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000673 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000674 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000675 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000676 return True
677
678 def EchoTitleHandler(self):
679 """This handler is like Echo, but sets the page title to the request."""
680
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000681 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000682 return False
683
684 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000685 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000686 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000687 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000688 self.wfile.write('<html><head><title>')
689 self.wfile.write(request)
690 self.wfile.write('</title></head></html>')
691 return True
692
693 def EchoAllHandler(self):
694 """This handler yields a (more) human-readable page listing information
695 about the request header & contents."""
696
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000697 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000698 return False
699
700 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000701 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000702 self.end_headers()
703 self.wfile.write('<html><head><style>'
704 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
705 '</style></head><body>'
706 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000707 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000708 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000709
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000710 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000711 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000712 params = cgi.parse_qs(qs, keep_blank_values=1)
713
714 for param in params:
715 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000716
717 self.wfile.write('</pre>')
718
719 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
720
721 self.wfile.write('</body></html>')
722 return True
723
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000724 def EchoMultipartPostHandler(self):
725 """This handler echoes received multipart post data as json format."""
726
727 if not (self._ShouldHandleRequest("/echomultipartpost") or
728 self._ShouldHandleRequest("/searchbyimage")):
729 return False
730
731 content_type, parameters = cgi.parse_header(
732 self.headers.getheader('content-type'))
733 if content_type == 'multipart/form-data':
734 post_multipart = cgi.parse_multipart(self.rfile, parameters)
735 elif content_type == 'application/x-www-form-urlencoded':
736 raise Exception('POST by application/x-www-form-urlencoded is '
737 'not implemented.')
738 else:
739 post_multipart = {}
740
741 # Since the data can be binary, we encode them by base64.
742 post_multipart_base64_encoded = {}
743 for field, values in post_multipart.items():
744 post_multipart_base64_encoded[field] = [base64.b64encode(value)
745 for value in values]
746
747 result = {'POST_multipart' : post_multipart_base64_encoded}
748
749 self.send_response(200)
750 self.send_header("Content-type", "text/plain")
751 self.end_headers()
752 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
753 return True
754
initial.commit94958cf2008-07-26 22:42:52 +0000755 def DownloadHandler(self):
756 """This handler sends a downloadable file with or without reporting
757 the size (6K)."""
758
759 if self.path.startswith("/download-unknown-size"):
760 send_length = False
761 elif self.path.startswith("/download-known-size"):
762 send_length = True
763 else:
764 return False
765
766 #
767 # The test which uses this functionality is attempting to send
768 # small chunks of data to the client. Use a fairly large buffer
769 # so that we'll fill chrome's IO buffer enough to force it to
770 # actually write the data.
771 # See also the comments in the client-side of this test in
772 # download_uitest.cc
773 #
774 size_chunk1 = 35*1024
775 size_chunk2 = 10*1024
776
777 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000778 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000779 self.send_header('Cache-Control', 'max-age=0')
780 if send_length:
781 self.send_header('Content-Length', size_chunk1 + size_chunk2)
782 self.end_headers()
783
784 # First chunk of data:
785 self.wfile.write("*" * size_chunk1)
786 self.wfile.flush()
787
788 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000789 self.server.wait_for_download = True
790 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000791 self.server.handle_request()
792
793 # Second chunk of data:
794 self.wfile.write("*" * size_chunk2)
795 return True
796
797 def DownloadFinishHandler(self):
798 """This handler just tells the server to finish the current download."""
799
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000800 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000801 return False
802
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000803 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000804 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000805 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000806 self.send_header('Cache-Control', 'max-age=0')
807 self.end_headers()
808 return True
809
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000810 def _ReplaceFileData(self, data, query_parameters):
811 """Replaces matching substrings in a file.
812
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000813 If the 'replace_text' URL query parameter is present, it is expected to be
814 of the form old_text:new_text, which indicates that any old_text strings in
815 the file are replaced with new_text. Multiple 'replace_text' parameters may
816 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000817
818 If the parameters are not present, |data| is returned.
819 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000820
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000821 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000822 replace_text_values = query_dict.get('replace_text', [])
823 for replace_text_value in replace_text_values:
824 replace_text_args = replace_text_value.split(':')
825 if len(replace_text_args) != 2:
826 raise ValueError(
827 'replace_text must be of form old_text:new_text. Actual value: %s' %
828 replace_text_value)
829 old_text_b64, new_text_b64 = replace_text_args
830 old_text = base64.urlsafe_b64decode(old_text_b64)
831 new_text = base64.urlsafe_b64decode(new_text_b64)
832 data = data.replace(old_text, new_text)
833 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000834
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000835 def ZipFileHandler(self):
836 """This handler sends the contents of the requested file in compressed form.
837 Can pass in a parameter that specifies that the content length be
838 C - the compressed size (OK),
839 U - the uncompressed size (Non-standard, but handled),
840 S - less than compressed (OK because we keep going),
841 M - larger than compressed but less than uncompressed (an error),
842 L - larger than uncompressed (an error)
843 Example: compressedfiles/Picture_1.doc?C
844 """
845
846 prefix = "/compressedfiles/"
847 if not self.path.startswith(prefix):
848 return False
849
850 # Consume a request body if present.
851 if self.command == 'POST' or self.command == 'PUT' :
852 self.ReadRequestBody()
853
854 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
855
856 if not query in ('C', 'U', 'S', 'M', 'L'):
857 return False
858
859 sub_path = url_path[len(prefix):]
860 entries = sub_path.split('/')
861 file_path = os.path.join(self.server.data_dir, *entries)
862 if os.path.isdir(file_path):
863 file_path = os.path.join(file_path, 'index.html')
864
865 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000866 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000867 self.send_error(404)
868 return True
869
870 f = open(file_path, "rb")
871 data = f.read()
872 uncompressed_len = len(data)
873 f.close()
874
875 # Compress the data.
876 data = zlib.compress(data)
877 compressed_len = len(data)
878
879 content_length = compressed_len
880 if query == 'U':
881 content_length = uncompressed_len
882 elif query == 'S':
883 content_length = compressed_len / 2
884 elif query == 'M':
885 content_length = (compressed_len + uncompressed_len) / 2
886 elif query == 'L':
887 content_length = compressed_len + uncompressed_len
888
889 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000890 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000891 self.send_header('Content-encoding', 'deflate')
892 self.send_header('Connection', 'close')
893 self.send_header('Content-Length', content_length)
894 self.send_header('ETag', '\'' + file_path + '\'')
895 self.end_headers()
896
897 self.wfile.write(data)
898
899 return True
900
initial.commit94958cf2008-07-26 22:42:52 +0000901 def FileHandler(self):
902 """This handler sends the contents of the requested file. Wow, it's like
903 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000904
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000905 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000906 if not self.path.startswith(prefix):
907 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000908 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000909
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000910 def PostOnlyFileHandler(self):
911 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000912
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000913 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000914 if not self.path.startswith(prefix):
915 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000916 return self._FileHandlerHelper(prefix)
917
918 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000919 request_body = ''
920 if self.command == 'POST' or self.command == 'PUT':
921 # Consume a request body if present.
922 request_body = self.ReadRequestBody()
923
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000924 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000925 query_dict = cgi.parse_qs(query)
926
927 expected_body = query_dict.get('expected_body', [])
928 if expected_body and request_body not in expected_body:
929 self.send_response(404)
930 self.end_headers()
931 self.wfile.write('')
932 return True
933
934 expected_headers = query_dict.get('expected_headers', [])
935 for expected_header in expected_headers:
936 header_name, expected_value = expected_header.split(':')
937 if self.headers.getheader(header_name) != expected_value:
938 self.send_response(404)
939 self.end_headers()
940 self.wfile.write('')
941 return True
942
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000943 sub_path = url_path[len(prefix):]
944 entries = sub_path.split('/')
945 file_path = os.path.join(self.server.data_dir, *entries)
946 if os.path.isdir(file_path):
947 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000948
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000949 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000950 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000951 self.send_error(404)
952 return True
953
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000954 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000955 data = f.read()
956 f.close()
957
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000958 data = self._ReplaceFileData(data, query)
959
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000960 old_protocol_version = self.protocol_version
961
initial.commit94958cf2008-07-26 22:42:52 +0000962 # If file.mock-http-headers exists, it contains the headers we
963 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000964 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000965 if os.path.isfile(headers_path):
966 f = open(headers_path, "r")
967
968 # "HTTP/1.1 200 OK"
969 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000970 http_major, http_minor, status_code = re.findall(
971 'HTTP/(\d+).(\d+) (\d+)', response)[0]
972 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000973 self.send_response(int(status_code))
974
975 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000976 header_values = re.findall('(\S+):\s*(.*)', line)
977 if len(header_values) > 0:
978 # "name: value"
979 name, value = header_values[0]
980 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000981 f.close()
982 else:
983 # Could be more generic once we support mime-type sniffing, but for
984 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000985
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000986 range_header = self.headers.get('Range')
987 if range_header and range_header.startswith('bytes='):
988 # Note this doesn't handle all valid byte range_header values (i.e.
989 # left open ended ones), just enough for what we needed so far.
990 range_header = range_header[6:].split('-')
991 start = int(range_header[0])
992 if range_header[1]:
993 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000994 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000995 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000996
997 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000998 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
999 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001000 self.send_header('Content-Range', content_range)
1001 data = data[start: end + 1]
1002 else:
1003 self.send_response(200)
1004
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001005 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001006 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001007 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001008 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001009 self.end_headers()
1010
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001011 if (self.command != 'HEAD'):
1012 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001013
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001014 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001015 return True
1016
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001017 def SetCookieHandler(self):
1018 """This handler just sets a cookie, for testing cookie handling."""
1019
1020 if not self._ShouldHandleRequest("/set-cookie"):
1021 return False
1022
1023 query_char = self.path.find('?')
1024 if query_char != -1:
1025 cookie_values = self.path[query_char + 1:].split('&')
1026 else:
1027 cookie_values = ("",)
1028 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001029 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001030 for cookie_value in cookie_values:
1031 self.send_header('Set-Cookie', '%s' % cookie_value)
1032 self.end_headers()
1033 for cookie_value in cookie_values:
1034 self.wfile.write('%s' % cookie_value)
1035 return True
1036
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001037 def SetManyCookiesHandler(self):
1038 """This handler just sets a given number of cookies, for testing handling
1039 of large numbers of cookies."""
1040
1041 if not self._ShouldHandleRequest("/set-many-cookies"):
1042 return False
1043
1044 query_char = self.path.find('?')
1045 if query_char != -1:
1046 num_cookies = int(self.path[query_char + 1:])
1047 else:
1048 num_cookies = 0
1049 self.send_response(200)
1050 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001051 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001052 self.send_header('Set-Cookie', 'a=')
1053 self.end_headers()
1054 self.wfile.write('%d cookies were sent' % num_cookies)
1055 return True
1056
mattm@chromium.org983fc462012-06-30 00:52:08 +00001057 def ExpectAndSetCookieHandler(self):
1058 """Expects some cookies to be sent, and if they are, sets more cookies.
1059
1060 The expect parameter specifies a required cookie. May be specified multiple
1061 times.
1062 The set parameter specifies a cookie to set if all required cookies are
1063 preset. May be specified multiple times.
1064 The data parameter specifies the response body data to be returned."""
1065
1066 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1067 return False
1068
1069 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1070 query_dict = cgi.parse_qs(query)
1071 cookies = set()
1072 if 'Cookie' in self.headers:
1073 cookie_header = self.headers.getheader('Cookie')
1074 cookies.update([s.strip() for s in cookie_header.split(';')])
1075 got_all_expected_cookies = True
1076 for expected_cookie in query_dict.get('expect', []):
1077 if expected_cookie not in cookies:
1078 got_all_expected_cookies = False
1079 self.send_response(200)
1080 self.send_header('Content-Type', 'text/html')
1081 if got_all_expected_cookies:
1082 for cookie_value in query_dict.get('set', []):
1083 self.send_header('Set-Cookie', '%s' % cookie_value)
1084 self.end_headers()
1085 for data_value in query_dict.get('data', []):
1086 self.wfile.write(data_value)
1087 return True
1088
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001089 def SetHeaderHandler(self):
1090 """This handler sets a response header. Parameters are in the
1091 key%3A%20value&key2%3A%20value2 format."""
1092
1093 if not self._ShouldHandleRequest("/set-header"):
1094 return False
1095
1096 query_char = self.path.find('?')
1097 if query_char != -1:
1098 headers_values = self.path[query_char + 1:].split('&')
1099 else:
1100 headers_values = ("",)
1101 self.send_response(200)
1102 self.send_header('Content-Type', 'text/html')
1103 for header_value in headers_values:
1104 header_value = urllib.unquote(header_value)
1105 (key, value) = header_value.split(': ', 1)
1106 self.send_header(key, value)
1107 self.end_headers()
1108 for header_value in headers_values:
1109 self.wfile.write('%s' % header_value)
1110 return True
1111
initial.commit94958cf2008-07-26 22:42:52 +00001112 def AuthBasicHandler(self):
1113 """This handler tests 'Basic' authentication. It just sends a page with
1114 title 'user/pass' if you succeed."""
1115
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001116 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001117 return False
1118
1119 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001120 expected_password = 'secret'
1121 realm = 'testrealm'
1122 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001123
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001124 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1125 query_params = cgi.parse_qs(query, True)
1126 if 'set-cookie-if-challenged' in query_params:
1127 set_cookie_if_challenged = True
1128 if 'password' in query_params:
1129 expected_password = query_params['password'][0]
1130 if 'realm' in query_params:
1131 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001132
initial.commit94958cf2008-07-26 22:42:52 +00001133 auth = self.headers.getheader('authorization')
1134 try:
1135 if not auth:
1136 raise Exception('no auth')
1137 b64str = re.findall(r'Basic (\S+)', auth)[0]
1138 userpass = base64.b64decode(b64str)
1139 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001140 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001141 raise Exception('wrong password')
1142 except Exception, e:
1143 # Authentication failed.
1144 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001145 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001146 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001147 if set_cookie_if_challenged:
1148 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001149 self.end_headers()
1150 self.wfile.write('<html><head>')
1151 self.wfile.write('<title>Denied: %s</title>' % e)
1152 self.wfile.write('</head><body>')
1153 self.wfile.write('auth=%s<p>' % auth)
1154 self.wfile.write('b64str=%s<p>' % b64str)
1155 self.wfile.write('username: %s<p>' % username)
1156 self.wfile.write('userpass: %s<p>' % userpass)
1157 self.wfile.write('password: %s<p>' % password)
1158 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1159 self.wfile.write('</body></html>')
1160 return True
1161
1162 # Authentication successful. (Return a cachable response to allow for
1163 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001164 old_protocol_version = self.protocol_version
1165 self.protocol_version = "HTTP/1.1"
1166
initial.commit94958cf2008-07-26 22:42:52 +00001167 if_none_match = self.headers.getheader('if-none-match')
1168 if if_none_match == "abc":
1169 self.send_response(304)
1170 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001171 elif url_path.endswith(".gif"):
1172 # Using chrome/test/data/google/logo.gif as the test image
1173 test_image_path = ['google', 'logo.gif']
1174 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1175 if not os.path.isfile(gif_path):
1176 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001177 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001178 return True
1179
1180 f = open(gif_path, "rb")
1181 data = f.read()
1182 f.close()
1183
1184 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001185 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001186 self.send_header('Cache-control', 'max-age=60000')
1187 self.send_header('Etag', 'abc')
1188 self.end_headers()
1189 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001190 else:
1191 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001192 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001193 self.send_header('Cache-control', 'max-age=60000')
1194 self.send_header('Etag', 'abc')
1195 self.end_headers()
1196 self.wfile.write('<html><head>')
1197 self.wfile.write('<title>%s/%s</title>' % (username, password))
1198 self.wfile.write('</head><body>')
1199 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001200 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001201 self.wfile.write('</body></html>')
1202
rvargas@google.com54453b72011-05-19 01:11:11 +00001203 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001204 return True
1205
tonyg@chromium.org75054202010-03-31 22:06:10 +00001206 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001207 """Returns a nonce that's stable per request path for the server's lifetime.
1208 This is a fake implementation. A real implementation would only use a given
1209 nonce a single time (hence the name n-once). However, for the purposes of
1210 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001211
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001212 Args:
1213 force_reset: Iff set, the nonce will be changed. Useful for testing the
1214 "stale" response.
1215 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001216
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001217 if force_reset or not self.server.nonce_time:
1218 self.server.nonce_time = time.time()
1219 return hashlib.md5('privatekey%s%d' %
1220 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001221
1222 def AuthDigestHandler(self):
1223 """This handler tests 'Digest' authentication.
1224
1225 It just sends a page with title 'user/pass' if you succeed.
1226
1227 A stale response is sent iff "stale" is present in the request path.
1228 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001229
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001230 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001231 return False
1232
tonyg@chromium.org75054202010-03-31 22:06:10 +00001233 stale = 'stale' in self.path
1234 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001235 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001236 password = 'secret'
1237 realm = 'testrealm'
1238
1239 auth = self.headers.getheader('authorization')
1240 pairs = {}
1241 try:
1242 if not auth:
1243 raise Exception('no auth')
1244 if not auth.startswith('Digest'):
1245 raise Exception('not digest')
1246 # Pull out all the name="value" pairs as a dictionary.
1247 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1248
1249 # Make sure it's all valid.
1250 if pairs['nonce'] != nonce:
1251 raise Exception('wrong nonce')
1252 if pairs['opaque'] != opaque:
1253 raise Exception('wrong opaque')
1254
1255 # Check the 'response' value and make sure it matches our magic hash.
1256 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001257 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001258 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001259 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001260 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001261 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001262 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1263 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001264 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001265
1266 if pairs['response'] != response:
1267 raise Exception('wrong password')
1268 except Exception, e:
1269 # Authentication failed.
1270 self.send_response(401)
1271 hdr = ('Digest '
1272 'realm="%s", '
1273 'domain="/", '
1274 'qop="auth", '
1275 'algorithm=MD5, '
1276 'nonce="%s", '
1277 'opaque="%s"') % (realm, nonce, opaque)
1278 if stale:
1279 hdr += ', stale="TRUE"'
1280 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001281 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001282 self.end_headers()
1283 self.wfile.write('<html><head>')
1284 self.wfile.write('<title>Denied: %s</title>' % e)
1285 self.wfile.write('</head><body>')
1286 self.wfile.write('auth=%s<p>' % auth)
1287 self.wfile.write('pairs=%s<p>' % pairs)
1288 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1289 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1290 self.wfile.write('</body></html>')
1291 return True
1292
1293 # Authentication successful.
1294 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001295 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001296 self.end_headers()
1297 self.wfile.write('<html><head>')
1298 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1299 self.wfile.write('</head><body>')
1300 self.wfile.write('auth=%s<p>' % auth)
1301 self.wfile.write('pairs=%s<p>' % pairs)
1302 self.wfile.write('</body></html>')
1303
1304 return True
1305
1306 def SlowServerHandler(self):
1307 """Wait for the user suggested time before responding. The syntax is
1308 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001309
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001310 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001311 return False
1312 query_char = self.path.find('?')
1313 wait_sec = 1.0
1314 if query_char >= 0:
1315 try:
1316 wait_sec = int(self.path[query_char + 1:])
1317 except ValueError:
1318 pass
1319 time.sleep(wait_sec)
1320 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001321 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001322 self.end_headers()
1323 self.wfile.write("waited %d seconds" % wait_sec)
1324 return True
1325
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001326 def ChunkedServerHandler(self):
1327 """Send chunked response. Allows to specify chunks parameters:
1328 - waitBeforeHeaders - ms to wait before sending headers
1329 - waitBetweenChunks - ms to wait between chunks
1330 - chunkSize - size of each chunk in bytes
1331 - chunksNumber - number of chunks
1332 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1333 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001334
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001335 if not self._ShouldHandleRequest("/chunked"):
1336 return False
1337 query_char = self.path.find('?')
1338 chunkedSettings = {'waitBeforeHeaders' : 0,
1339 'waitBetweenChunks' : 0,
1340 'chunkSize' : 5,
1341 'chunksNumber' : 5}
1342 if query_char >= 0:
1343 params = self.path[query_char + 1:].split('&')
1344 for param in params:
1345 keyValue = param.split('=')
1346 if len(keyValue) == 2:
1347 try:
1348 chunkedSettings[keyValue[0]] = int(keyValue[1])
1349 except ValueError:
1350 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001351 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001352 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1353 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001354 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001355 self.send_header('Connection', 'close')
1356 self.send_header('Transfer-Encoding', 'chunked')
1357 self.end_headers()
1358 # Chunked encoding: sending all chunks, then final zero-length chunk and
1359 # then final CRLF.
1360 for i in range(0, chunkedSettings['chunksNumber']):
1361 if i > 0:
1362 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1363 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001364 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001365 self.sendChunkHelp('')
1366 return True
1367
initial.commit94958cf2008-07-26 22:42:52 +00001368 def ContentTypeHandler(self):
1369 """Returns a string of html with the given content type. E.g.,
1370 /contenttype?text/css returns an html file with the Content-Type
1371 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001372
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001373 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001374 return False
1375 query_char = self.path.find('?')
1376 content_type = self.path[query_char + 1:].strip()
1377 if not content_type:
1378 content_type = 'text/html'
1379 self.send_response(200)
1380 self.send_header('Content-Type', content_type)
1381 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001382 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001383 return True
1384
creis@google.com2f4f6a42011-03-25 19:44:19 +00001385 def NoContentHandler(self):
1386 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001387
creis@google.com2f4f6a42011-03-25 19:44:19 +00001388 if not self._ShouldHandleRequest("/nocontent"):
1389 return False
1390 self.send_response(204)
1391 self.end_headers()
1392 return True
1393
initial.commit94958cf2008-07-26 22:42:52 +00001394 def ServerRedirectHandler(self):
1395 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001396 '/server-redirect?http://foo.bar/asdf' to redirect to
1397 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001398
1399 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001400 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001401 return False
1402
1403 query_char = self.path.find('?')
1404 if query_char < 0 or len(self.path) <= query_char + 1:
1405 self.sendRedirectHelp(test_name)
1406 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001407 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001408
1409 self.send_response(301) # moved permanently
1410 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001411 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001412 self.end_headers()
1413 self.wfile.write('<html><head>')
1414 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1415
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001416 return True
initial.commit94958cf2008-07-26 22:42:52 +00001417
1418 def ClientRedirectHandler(self):
1419 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001420 '/client-redirect?http://foo.bar/asdf' to redirect to
1421 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001422
1423 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001424 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001425 return False
1426
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001427 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001428 if query_char < 0 or len(self.path) <= query_char + 1:
1429 self.sendRedirectHelp(test_name)
1430 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001431 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001432
1433 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001434 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001435 self.end_headers()
1436 self.wfile.write('<html><head>')
1437 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1438 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1439
1440 return True
1441
tony@chromium.org03266982010-03-05 03:18:42 +00001442 def MultipartHandler(self):
1443 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001444
tony@chromium.org4cb88302011-09-27 22:13:49 +00001445 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001446 if not self._ShouldHandleRequest(test_name):
1447 return False
1448
1449 num_frames = 10
1450 bound = '12345'
1451 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001452 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001453 'multipart/x-mixed-replace;boundary=' + bound)
1454 self.end_headers()
1455
1456 for i in xrange(num_frames):
1457 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001458 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001459 self.wfile.write('<title>page ' + str(i) + '</title>')
1460 self.wfile.write('page ' + str(i))
1461
1462 self.wfile.write('--' + bound + '--')
1463 return True
1464
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001465 def GetSSLSessionCacheHandler(self):
1466 """Send a reply containing a log of the session cache operations."""
1467
1468 if not self._ShouldHandleRequest('/ssl-session-cache'):
1469 return False
1470
1471 self.send_response(200)
1472 self.send_header('Content-Type', 'text/plain')
1473 self.end_headers()
1474 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001475 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001476 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001477 self.wfile.write('Pass --https-record-resume in order to use' +
1478 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001479 return True
1480
1481 for (action, sessionID) in log:
1482 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001483 return True
1484
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001485 def SSLManySmallRecords(self):
1486 """Sends a reply consisting of a variety of small writes. These will be
1487 translated into a series of small SSL records when used over an HTTPS
1488 server."""
1489
1490 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1491 return False
1492
1493 self.send_response(200)
1494 self.send_header('Content-Type', 'text/plain')
1495 self.end_headers()
1496
1497 # Write ~26K of data, in 1350 byte chunks
1498 for i in xrange(20):
1499 self.wfile.write('*' * 1350)
1500 self.wfile.flush()
1501 return True
1502
agl@chromium.org04700be2013-03-02 18:40:41 +00001503 def GetChannelID(self):
1504 """Send a reply containing the hashed ChannelID that the client provided."""
1505
1506 if not self._ShouldHandleRequest('/channel-id'):
1507 return False
1508
1509 self.send_response(200)
1510 self.send_header('Content-Type', 'text/plain')
1511 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001512 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001513 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1514 return True
1515
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001516 def CloseSocketHandler(self):
1517 """Closes the socket without sending anything."""
1518
1519 if not self._ShouldHandleRequest('/close-socket'):
1520 return False
1521
1522 self.wfile.close()
1523 return True
1524
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001525 def RangeResetHandler(self):
1526 """Send data broken up by connection resets every N (default 4K) bytes.
1527 Support range requests. If the data requested doesn't straddle a reset
1528 boundary, it will all be sent. Used for testing resuming downloads."""
1529
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001530 def DataForRange(start, end):
1531 """Data to be provided for a particular range of bytes."""
1532 # Offset and scale to avoid too obvious (and hence potentially
1533 # collidable) data.
1534 return ''.join([chr(y % 256)
1535 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1536
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001537 if not self._ShouldHandleRequest('/rangereset'):
1538 return False
1539
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001540 # HTTP/1.1 is required for ETag and range support.
1541 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001542 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1543
1544 # Defaults
1545 size = 8000
1546 # Note that the rst is sent just before sending the rst_boundary byte.
1547 rst_boundary = 4000
1548 respond_to_range = True
1549 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001550 rst_limit = -1
1551 token = 'DEFAULT'
1552 fail_precondition = 0
1553 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001554
1555 # Parse the query
1556 qdict = urlparse.parse_qs(query, True)
1557 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001558 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001559 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001560 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001561 if 'token' in qdict:
1562 # Identifying token for stateful tests.
1563 token = qdict['token'][0]
1564 if 'rst_limit' in qdict:
1565 # Max number of rsts for a given token.
1566 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001567 if 'bounce_range' in qdict:
1568 respond_to_range = False
1569 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001570 # Note that hold_for_signal will not work with null range requests;
1571 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001572 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001573 if 'no_verifiers' in qdict:
1574 send_verifiers = False
1575 if 'fail_precondition' in qdict:
1576 fail_precondition = int(qdict['fail_precondition'][0])
1577
1578 # Record already set information, or set it.
1579 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1580 if rst_limit != 0:
1581 TestPageHandler.rst_limits[token] -= 1
1582 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1583 token, fail_precondition)
1584 if fail_precondition != 0:
1585 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001586
1587 first_byte = 0
1588 last_byte = size - 1
1589
1590 # Does that define what we want to return, or do we need to apply
1591 # a range?
1592 range_response = False
1593 range_header = self.headers.getheader('range')
1594 if range_header and respond_to_range:
1595 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1596 if mo.group(1):
1597 first_byte = int(mo.group(1))
1598 if mo.group(2):
1599 last_byte = int(mo.group(2))
1600 if last_byte > size - 1:
1601 last_byte = size - 1
1602 range_response = True
1603 if last_byte < first_byte:
1604 return False
1605
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001606 if (fail_precondition and
1607 (self.headers.getheader('If-Modified-Since') or
1608 self.headers.getheader('If-Match'))):
1609 self.send_response(412)
1610 self.end_headers()
1611 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001612
1613 if range_response:
1614 self.send_response(206)
1615 self.send_header('Content-Range',
1616 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1617 else:
1618 self.send_response(200)
1619 self.send_header('Content-Type', 'application/octet-stream')
1620 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001621 if send_verifiers:
asanka@chromium.org3ffced92013-12-13 22:20:27 +00001622 # If fail_precondition is non-zero, then the ETag for each request will be
1623 # different.
1624 etag = "%s%d" % (token, fail_precondition)
1625 self.send_header('ETag', etag)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001626 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001627 self.end_headers()
1628
1629 if hold_for_signal:
1630 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1631 # a single byte, the self.server.handle_request() below hangs
1632 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001633 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001634 first_byte = first_byte + 1
1635 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001636 self.server.wait_for_download = True
1637 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001638 self.server.handle_request()
1639
1640 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001641 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001642 # No RST has been requested in this range, so we don't need to
1643 # do anything fancy; just write the data and let the python
1644 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001645 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001646 self.wfile.flush()
1647 return True
1648
1649 # We're resetting the connection part way in; go to the RST
1650 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001651 # Because socket semantics do not guarantee that all the data will be
1652 # sent when using the linger semantics to hard close a socket,
1653 # we send the data and then wait for our peer to release us
1654 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001655 data = DataForRange(first_byte, possible_rst)
1656 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001657 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001658 self.server.wait_for_download = True
1659 while self.server.wait_for_download:
1660 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001661 l_onoff = 1 # Linger is active.
1662 l_linger = 0 # Seconds to linger for.
1663 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1664 struct.pack('ii', l_onoff, l_linger))
1665
1666 # Close all duplicates of the underlying socket to force the RST.
1667 self.wfile.close()
1668 self.rfile.close()
1669 self.connection.close()
1670
1671 return True
1672
initial.commit94958cf2008-07-26 22:42:52 +00001673 def DefaultResponseHandler(self):
1674 """This is the catch-all response handler for requests that aren't handled
1675 by one of the special handlers above.
1676 Note that we specify the content-length as without it the https connection
1677 is not closed properly (and the browser keeps expecting data)."""
1678
1679 contents = "Default response given for path: " + self.path
1680 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001681 self.send_header('Content-Type', 'text/html')
1682 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001683 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001684 if (self.command != 'HEAD'):
1685 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001686 return True
1687
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001688 def RedirectConnectHandler(self):
1689 """Sends a redirect to the CONNECT request for www.redirect.com. This
1690 response is not specified by the RFC, so the browser should not follow
1691 the redirect."""
1692
1693 if (self.path.find("www.redirect.com") < 0):
1694 return False
1695
1696 dest = "http://www.destination.com/foo.js"
1697
1698 self.send_response(302) # moved temporarily
1699 self.send_header('Location', dest)
1700 self.send_header('Connection', 'close')
1701 self.end_headers()
1702 return True
1703
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001704 def ServerAuthConnectHandler(self):
1705 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1706 response doesn't make sense because the proxy server cannot request
1707 server authentication."""
1708
1709 if (self.path.find("www.server-auth.com") < 0):
1710 return False
1711
1712 challenge = 'Basic realm="WallyWorld"'
1713
1714 self.send_response(401) # unauthorized
1715 self.send_header('WWW-Authenticate', challenge)
1716 self.send_header('Connection', 'close')
1717 self.end_headers()
1718 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001719
1720 def DefaultConnectResponseHandler(self):
1721 """This is the catch-all response handler for CONNECT requests that aren't
1722 handled by one of the special handlers above. Real Web servers respond
1723 with 400 to CONNECT requests."""
1724
1725 contents = "Your client has issued a malformed or illegal request."
1726 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001727 self.send_header('Content-Type', 'text/html')
1728 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001729 self.end_headers()
1730 self.wfile.write(contents)
1731 return True
1732
initial.commit94958cf2008-07-26 22:42:52 +00001733 # called by the redirect handling function when there is no parameter
1734 def sendRedirectHelp(self, redirect_name):
1735 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001736 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001737 self.end_headers()
1738 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1739 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1740 self.wfile.write('</body></html>')
1741
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001742 # called by chunked handling function
1743 def sendChunkHelp(self, chunk):
1744 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1745 self.wfile.write('%X\r\n' % len(chunk))
1746 self.wfile.write(chunk)
1747 self.wfile.write('\r\n')
1748
akalin@chromium.org154bb132010-11-12 02:20:27 +00001749
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001750class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001751 def __init__(self, request, client_address, socket_server):
1752 handlers = [self.OCSPResponse]
1753 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001754 testserver_base.BasePageHandler.__init__(self, request, client_address,
1755 socket_server, [], handlers, [],
1756 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001757
1758 def OCSPResponse(self):
1759 self.send_response(200)
1760 self.send_header('Content-Type', 'application/ocsp-response')
1761 self.send_header('Content-Length', str(len(self.ocsp_response)))
1762 self.end_headers()
1763
1764 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001765
mattm@chromium.org830a3712012-11-07 23:00:07 +00001766
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001767class TCPEchoHandler(SocketServer.BaseRequestHandler):
1768 """The RequestHandler class for TCP echo server.
1769
1770 It is instantiated once per connection to the server, and overrides the
1771 handle() method to implement communication to the client.
1772 """
1773
1774 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001775 """Handles the request from the client and constructs a response."""
1776
1777 data = self.request.recv(65536).strip()
1778 # 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:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001783 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001784 except ValueError:
1785 return
1786
1787 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001788
1789
1790class UDPEchoHandler(SocketServer.BaseRequestHandler):
1791 """The RequestHandler class for UDP echo server.
1792
1793 It is instantiated once per connection to the server, and overrides the
1794 handle() method to implement communication to the client.
1795 """
1796
1797 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001798 """Handles the request from the client and constructs a response."""
1799
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001800 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001801 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001802 # Verify the "echo request" message received from the client. Send back
1803 # "echo response" message if "echo request" message is valid.
1804 try:
1805 return_data = echo_message.GetEchoResponseData(data)
1806 if not return_data:
1807 return
1808 except ValueError:
1809 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001810 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001811
1812
bashi@chromium.org33233532012-09-08 17:37:24 +00001813class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1814 """A request handler that behaves as a proxy server which requires
1815 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1816 """
1817
1818 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1819
1820 def parse_request(self):
1821 """Overrides parse_request to check credential."""
1822
1823 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1824 return False
1825
1826 auth = self.headers.getheader('Proxy-Authorization')
1827 if auth != self._AUTH_CREDENTIAL:
1828 self.send_response(407)
1829 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1830 self.end_headers()
1831 return False
1832
1833 return True
1834
1835 def _start_read_write(self, sock):
1836 sock.setblocking(0)
1837 self.request.setblocking(0)
1838 rlist = [self.request, sock]
1839 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001840 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001841 if errors:
1842 self.send_response(500)
1843 self.end_headers()
1844 return
1845 for s in ready_sockets:
1846 received = s.recv(1024)
1847 if len(received) == 0:
1848 return
1849 if s == self.request:
1850 other = sock
1851 else:
1852 other = self.request
1853 other.send(received)
1854
1855 def _do_common_method(self):
1856 url = urlparse.urlparse(self.path)
1857 port = url.port
1858 if not port:
1859 if url.scheme == 'http':
1860 port = 80
1861 elif url.scheme == 'https':
1862 port = 443
1863 if not url.hostname or not port:
1864 self.send_response(400)
1865 self.end_headers()
1866 return
1867
1868 if len(url.path) == 0:
1869 path = '/'
1870 else:
1871 path = url.path
1872 if len(url.query) > 0:
1873 path = '%s?%s' % (url.path, url.query)
1874
1875 sock = None
1876 try:
1877 sock = socket.create_connection((url.hostname, port))
1878 sock.send('%s %s %s\r\n' % (
1879 self.command, path, self.protocol_version))
1880 for header in self.headers.headers:
1881 header = header.strip()
1882 if (header.lower().startswith('connection') or
1883 header.lower().startswith('proxy')):
1884 continue
1885 sock.send('%s\r\n' % header)
1886 sock.send('\r\n')
1887 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001888 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001889 self.send_response(500)
1890 self.end_headers()
1891 finally:
1892 if sock is not None:
1893 sock.close()
1894
1895 def do_CONNECT(self):
1896 try:
1897 pos = self.path.rfind(':')
1898 host = self.path[:pos]
1899 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001900 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001901 self.send_response(400)
1902 self.end_headers()
1903
1904 try:
1905 sock = socket.create_connection((host, port))
1906 self.send_response(200, 'Connection established')
1907 self.end_headers()
1908 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001909 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001910 self.send_response(500)
1911 self.end_headers()
1912 finally:
1913 sock.close()
1914
1915 def do_GET(self):
1916 self._do_common_method()
1917
1918 def do_HEAD(self):
1919 self._do_common_method()
1920
1921
mattm@chromium.org830a3712012-11-07 23:00:07 +00001922class ServerRunner(testserver_base.TestServerRunner):
1923 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001924
mattm@chromium.org830a3712012-11-07 23:00:07 +00001925 def __init__(self):
1926 super(ServerRunner, self).__init__()
1927 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001928
mattm@chromium.org830a3712012-11-07 23:00:07 +00001929 def __make_data_dir(self):
1930 if self.options.data_dir:
1931 if not os.path.isdir(self.options.data_dir):
1932 raise testserver_base.OptionError('specified data dir not found: ' +
1933 self.options.data_dir + ' exiting...')
1934 my_data_dir = self.options.data_dir
1935 else:
1936 # Create the default path to our data dir, relative to the exe dir.
1937 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1938 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001939
mattm@chromium.org830a3712012-11-07 23:00:07 +00001940 #TODO(ibrar): Must use Find* funtion defined in google\tools
1941 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001942
mattm@chromium.org830a3712012-11-07 23:00:07 +00001943 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001944
mattm@chromium.org830a3712012-11-07 23:00:07 +00001945 def create_server(self, server_data):
1946 port = self.options.port
1947 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001948
mattm@chromium.org830a3712012-11-07 23:00:07 +00001949 if self.options.server_type == SERVER_HTTP:
1950 if self.options.https:
1951 pem_cert_and_key = None
1952 if self.options.cert_and_key_file:
1953 if not os.path.isfile(self.options.cert_and_key_file):
1954 raise testserver_base.OptionError(
1955 'specified server cert file not found: ' +
1956 self.options.cert_and_key_file + ' exiting...')
1957 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001958 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001959 # generate a new certificate and run an OCSP server for it.
1960 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001961 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001962 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001963
mattm@chromium.org830a3712012-11-07 23:00:07 +00001964 ocsp_der = None
1965 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001966
mattm@chromium.org830a3712012-11-07 23:00:07 +00001967 if self.options.ocsp == 'ok':
1968 ocsp_state = minica.OCSP_STATE_GOOD
1969 elif self.options.ocsp == 'revoked':
1970 ocsp_state = minica.OCSP_STATE_REVOKED
1971 elif self.options.ocsp == 'invalid':
1972 ocsp_state = minica.OCSP_STATE_INVALID
1973 elif self.options.ocsp == 'unauthorized':
1974 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1975 elif self.options.ocsp == 'unknown':
1976 ocsp_state = minica.OCSP_STATE_UNKNOWN
1977 else:
1978 raise testserver_base.OptionError('unknown OCSP status: ' +
1979 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001980
mattm@chromium.org830a3712012-11-07 23:00:07 +00001981 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1982 subject = "127.0.0.1",
1983 ocsp_url = ("http://%s:%d/ocsp" %
1984 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00001985 ocsp_state = ocsp_state,
1986 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001987
1988 self.__ocsp_server.ocsp_response = ocsp_der
1989
1990 for ca_cert in self.options.ssl_client_ca:
1991 if not os.path.isfile(ca_cert):
1992 raise testserver_base.OptionError(
1993 'specified trusted client CA file not found: ' + ca_cert +
1994 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001995
1996 stapled_ocsp_response = None
1997 if self.__ocsp_server and self.options.staple_ocsp_response:
1998 stapled_ocsp_response = self.__ocsp_server.ocsp_response
1999
mattm@chromium.org830a3712012-11-07 23:00:07 +00002000 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2001 self.options.ssl_client_auth,
2002 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002003 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002004 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002005 self.options.ssl_key_exchange,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002006 self.options.enable_npn,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002007 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002008 self.options.tls_intolerant,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002009 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002010 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002011 self.options.fallback_scsv,
2012 stapled_ocsp_response)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002013 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002014 else:
2015 server = HTTPServer((host, port), TestPageHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002016 print 'HTTP server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002017
2018 server.data_dir = self.__make_data_dir()
2019 server.file_root_url = self.options.file_root_url
2020 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002021 elif self.options.server_type == SERVER_WEBSOCKET:
2022 # Launch pywebsocket via WebSocketServer.
2023 logger = logging.getLogger()
2024 logger.addHandler(logging.StreamHandler())
2025 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2026 # is required to work correctly. It should be fixed from pywebsocket side.
2027 os.chdir(self.__make_data_dir())
2028 websocket_options = WebSocketOptions(host, port, '.')
2029 if self.options.cert_and_key_file:
2030 websocket_options.use_tls = True
2031 websocket_options.private_key = self.options.cert_and_key_file
2032 websocket_options.certificate = self.options.cert_and_key_file
2033 if self.options.ssl_client_auth:
2034 websocket_options.tls_client_auth = True
2035 if len(self.options.ssl_client_ca) != 1:
2036 raise testserver_base.OptionError(
2037 'one trusted client CA file should be specified')
2038 if not os.path.isfile(self.options.ssl_client_ca[0]):
2039 raise testserver_base.OptionError(
2040 'specified trusted client CA file not found: ' +
2041 self.options.ssl_client_ca[0] + ' exiting...')
2042 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2043 server = WebSocketServer(websocket_options)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002044 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002045 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002046 elif self.options.server_type == SERVER_TCP_ECHO:
2047 # Used for generating the key (randomly) that encodes the "echo request"
2048 # message.
2049 random.seed()
2050 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002051 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002052 server_data['port'] = server.server_port
2053 elif self.options.server_type == SERVER_UDP_ECHO:
2054 # Used for generating the key (randomly) that encodes the "echo request"
2055 # message.
2056 random.seed()
2057 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002058 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002059 server_data['port'] = server.server_port
2060 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2061 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002062 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002063 server_data['port'] = server.server_port
2064 elif self.options.server_type == SERVER_FTP:
2065 my_data_dir = self.__make_data_dir()
2066
2067 # Instantiate a dummy authorizer for managing 'virtual' users
2068 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2069
2070 # Define a new user having full r/w permissions and a read-only
2071 # anonymous user
2072 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2073
2074 authorizer.add_anonymous(my_data_dir)
2075
2076 # Instantiate FTP handler class
2077 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2078 ftp_handler.authorizer = authorizer
2079
2080 # Define a customized banner (string returned when client connects)
2081 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2082 pyftpdlib.ftpserver.__ver__)
2083
2084 # Instantiate FTP server class and listen to address:port
2085 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2086 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002087 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002088 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002089 raise testserver_base.OptionError('unknown server type' +
2090 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002091
mattm@chromium.org830a3712012-11-07 23:00:07 +00002092 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002093
mattm@chromium.org830a3712012-11-07 23:00:07 +00002094 def run_server(self):
2095 if self.__ocsp_server:
2096 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002097
mattm@chromium.org830a3712012-11-07 23:00:07 +00002098 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002099
mattm@chromium.org830a3712012-11-07 23:00:07 +00002100 if self.__ocsp_server:
2101 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002102
mattm@chromium.org830a3712012-11-07 23:00:07 +00002103 def add_options(self):
2104 testserver_base.TestServerRunner.add_options(self)
2105 self.option_parser.add_option('-f', '--ftp', action='store_const',
2106 const=SERVER_FTP, default=SERVER_HTTP,
2107 dest='server_type',
2108 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002109 self.option_parser.add_option('--tcp-echo', action='store_const',
2110 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2111 dest='server_type',
2112 help='start up a tcp echo server.')
2113 self.option_parser.add_option('--udp-echo', action='store_const',
2114 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2115 dest='server_type',
2116 help='start up a udp echo server.')
2117 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2118 const=SERVER_BASIC_AUTH_PROXY,
2119 default=SERVER_HTTP, dest='server_type',
2120 help='start up a proxy server which requires '
2121 'basic authentication.')
2122 self.option_parser.add_option('--websocket', action='store_const',
2123 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2124 dest='server_type',
2125 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002126 self.option_parser.add_option('--https', action='store_true',
2127 dest='https', help='Specify that https '
2128 'should be used.')
2129 self.option_parser.add_option('--cert-and-key-file',
2130 dest='cert_and_key_file', help='specify the '
2131 'path to the file containing the certificate '
2132 'and private key for the server in PEM '
2133 'format')
2134 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2135 help='The type of OCSP response generated '
2136 'for the automatically generated '
2137 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002138 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2139 default=0, type=int,
2140 help='If non-zero then the generated '
2141 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002142 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2143 default='0', type='int',
2144 help='If nonzero, certain TLS connections '
2145 'will be aborted in order to test version '
2146 'fallback. 1 means all TLS versions will be '
2147 'aborted. 2 means TLS 1.1 or higher will be '
2148 'aborted. 3 means TLS 1.2 or higher will be '
2149 'aborted.')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002150 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2151 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002152 default='',
2153 help='Base64 encoded SCT list. If set, '
2154 'server will respond with a '
2155 'signed_certificate_timestamp TLS extension '
2156 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002157 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2158 default=False, const=True,
2159 action='store_const',
2160 help='If given, TLS_FALLBACK_SCSV support '
2161 'will be enabled. This causes the server to '
2162 'reject fallback connections from compatible '
2163 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002164 self.option_parser.add_option('--staple-ocsp-response',
2165 dest='staple_ocsp_response',
2166 default=False, action='store_true',
2167 help='If set, server will staple the OCSP '
2168 'response whenever OCSP is on and the client '
2169 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002170 self.option_parser.add_option('--https-record-resume',
2171 dest='record_resume', const=True,
2172 default=False, action='store_const',
2173 help='Record resumption cache events rather '
2174 'than resuming as normal. Allows the use of '
2175 'the /ssl-session-cache request')
2176 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2177 help='Require SSL client auth on every '
2178 'connection.')
2179 self.option_parser.add_option('--ssl-client-ca', action='append',
2180 default=[], help='Specify that the client '
2181 'certificate request should include the CA '
2182 'named in the subject of the DER-encoded '
2183 'certificate contained in the specified '
2184 'file. This option may appear multiple '
2185 'times, indicating multiple CA names should '
2186 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002187 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2188 default=[], help='Specify that the client '
2189 'certificate request should include the '
2190 'specified certificate_type value. This '
2191 'option may appear multiple times, '
2192 'indicating multiple values should be send '
2193 'in the request. Valid values are '
2194 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2195 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002196 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2197 help='Specify the bulk encryption '
2198 'algorithm(s) that will be accepted by the '
2199 'SSL server. Valid values are "aes256", '
2200 '"aes128", "3des", "rc4". If omitted, all '
2201 'algorithms will be used. This option may '
2202 'appear multiple times, indicating '
2203 'multiple algorithms should be enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002204 self.option_parser.add_option('--ssl-key-exchange', action='append',
2205 help='Specify the key exchange algorithm(s)'
2206 'that will be accepted by the SSL server. '
2207 'Valid values are "rsa", "dhe_rsa". If '
2208 'omitted, all algorithms will be used. This '
2209 'option may appear multiple times, '
2210 'indicating multiple algorithms should be '
2211 'enabled.');
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002212 # TODO(davidben): Add ALPN support to tlslite.
2213 self.option_parser.add_option('--enable-npn', dest='enable_npn',
2214 default=False, const=True,
2215 action='store_const',
2216 help='Enable server support for the NPN '
2217 'extension. The server will advertise '
2218 'support for exactly one protocol, http/1.1')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002219 self.option_parser.add_option('--file-root-url', default='/files/',
2220 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002221
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002222
initial.commit94958cf2008-07-26 22:42:52 +00002223if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002224 sys.exit(ServerRunner().main())