blob: 9e86cf209b24781553e7b835e401ccec72f0e83c [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.org74aa8dd2014-04-11 07:20:26 +0000155 ssl_client_auth, ssl_client_cas,
davidben@chromium.org6b1a39c2014-04-11 20:49:33 +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.org6b1a39c2014-04-11 20:49:33 +0000170 if enable_npn:
171 self.next_protos = ['http/1.1']
172 else:
173 self.next_protos = None
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000174 if tls_intolerant == 0:
175 self.tls_intolerant = None
176 else:
177 self.tls_intolerant = (3, tls_intolerant)
ekasper@google.com24aa8222013-11-28 13:43:26 +0000178 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000179 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000180 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000181
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000182 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000183 s = open(ca_file).read()
184 x509 = tlslite.api.X509()
185 x509.parse(s)
186 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000187 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
188 if ssl_bulk_ciphers is not None:
189 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000190 if ssl_key_exchanges is not None:
191 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
initial.commit94958cf2008-07-26 22:42:52 +0000192
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000193 if record_resume_info:
194 # If record_resume_info is true then we'll replace the session cache with
195 # an object that records the lookups and inserts that it sees.
196 self.session_cache = RecordingSSLSessionCache()
197 else:
198 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000199 testserver_base.StoppableHTTPServer.__init__(self,
200 server_address,
201 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000202
203 def handshake(self, tlsConnection):
204 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000205
initial.commit94958cf2008-07-26 22:42:52 +0000206 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000207 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000208 tlsConnection.handshakeServer(certChain=self.cert_chain,
209 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000210 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000211 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000212 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000213 reqCAs=self.ssl_client_cas,
davidben@chromium.org6b1a39c2014-04-11 20:49:33 +0000214 nextProtos=self.next_protos,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000215 tlsIntolerant=self.tls_intolerant,
216 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000217 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000218 fallbackSCSV=self.fallback_scsv_enabled,
219 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000220 tlsConnection.ignoreAbruptClose = True
221 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000222 except tlslite.api.TLSAbruptCloseError:
223 # Ignore abrupt close.
224 return True
initial.commit94958cf2008-07-26 22:42:52 +0000225 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000226 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000227 return False
228
akalin@chromium.org154bb132010-11-12 02:20:27 +0000229
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000230class FTPServer(testserver_base.ClientRestrictingServerMixIn,
231 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000232 """This is a specialization of FTPServer that adds client verification."""
233
234 pass
235
236
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000237class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
238 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000239 """A TCP echo server that echoes back what it has received."""
240
241 def server_bind(self):
242 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000243
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000244 SocketServer.TCPServer.server_bind(self)
245 host, port = self.socket.getsockname()[:2]
246 self.server_name = socket.getfqdn(host)
247 self.server_port = port
248
249 def serve_forever(self):
250 self.stop = False
251 self.nonce_time = None
252 while not self.stop:
253 self.handle_request()
254 self.socket.close()
255
256
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000257class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
258 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000259 """A UDP echo server that echoes back what it has received."""
260
261 def server_bind(self):
262 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000263
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000264 SocketServer.UDPServer.server_bind(self)
265 host, port = self.socket.getsockname()[:2]
266 self.server_name = socket.getfqdn(host)
267 self.server_port = port
268
269 def serve_forever(self):
270 self.stop = False
271 self.nonce_time = None
272 while not self.stop:
273 self.handle_request()
274 self.socket.close()
275
276
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000277class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000278 # Class variables to allow for persistence state between page handler
279 # invocations
280 rst_limits = {}
281 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000282
283 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000284 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000285 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000286 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000287 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000288 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000289 self.NoCacheMaxAgeTimeHandler,
290 self.NoCacheTimeHandler,
291 self.CacheTimeHandler,
292 self.CacheExpiresHandler,
293 self.CacheProxyRevalidateHandler,
294 self.CachePrivateHandler,
295 self.CachePublicHandler,
296 self.CacheSMaxAgeHandler,
297 self.CacheMustRevalidateHandler,
298 self.CacheMustRevalidateMaxAgeHandler,
299 self.CacheNoStoreHandler,
300 self.CacheNoStoreMaxAgeHandler,
301 self.CacheNoTransformHandler,
302 self.DownloadHandler,
303 self.DownloadFinishHandler,
304 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000305 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000306 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000307 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000308 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000309 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000310 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000311 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000312 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000313 self.AuthBasicHandler,
314 self.AuthDigestHandler,
315 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000316 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000317 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000318 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000319 self.ServerRedirectHandler,
320 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000321 self.MultipartHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000322 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000323 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000324 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000325 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000326 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000327 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000328 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000329 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000330 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000331 self.PostOnlyFileHandler,
332 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000333 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000334 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000335 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000336 head_handlers = [
337 self.FileHandler,
338 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000339
maruel@google.come250a9b2009-03-10 17:39:46 +0000340 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000341 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000342 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000343 'gif': 'image/gif',
344 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000345 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000346 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000347 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000348 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000349 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000350 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000351 }
initial.commit94958cf2008-07-26 22:42:52 +0000352 self._default_mime_type = 'text/html'
353
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000354 testserver_base.BasePageHandler.__init__(self, request, client_address,
355 socket_server, connect_handlers,
356 get_handlers, head_handlers,
357 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000358
initial.commit94958cf2008-07-26 22:42:52 +0000359 def GetMIMETypeFromName(self, file_name):
360 """Returns the mime type for the specified file_name. So far it only looks
361 at the file extension."""
362
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000363 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000364 if len(extension) == 0:
365 # no extension.
366 return self._default_mime_type
367
ericroman@google.comc17ca532009-05-07 03:51:05 +0000368 # extension starts with a dot, so we need to remove it
369 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000370
initial.commit94958cf2008-07-26 22:42:52 +0000371 def NoCacheMaxAgeTimeHandler(self):
372 """This request handler yields a page with the title set to the current
373 system time, and no caching requested."""
374
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000375 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000376 return False
377
378 self.send_response(200)
379 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000380 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000381 self.end_headers()
382
maruel@google.come250a9b2009-03-10 17:39:46 +0000383 self.wfile.write('<html><head><title>%s</title></head></html>' %
384 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000385
386 return True
387
388 def NoCacheTimeHandler(self):
389 """This request handler yields a page with the title set to the current
390 system time, and no caching requested."""
391
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000392 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000393 return False
394
395 self.send_response(200)
396 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000397 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000398 self.end_headers()
399
maruel@google.come250a9b2009-03-10 17:39:46 +0000400 self.wfile.write('<html><head><title>%s</title></head></html>' %
401 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000402
403 return True
404
405 def CacheTimeHandler(self):
406 """This request handler yields a page with the title set to the current
407 system time, and allows caching for one minute."""
408
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000409 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000410 return False
411
412 self.send_response(200)
413 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000414 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000415 self.end_headers()
416
maruel@google.come250a9b2009-03-10 17:39:46 +0000417 self.wfile.write('<html><head><title>%s</title></head></html>' %
418 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000419
420 return True
421
422 def CacheExpiresHandler(self):
423 """This request handler yields a page with the title set to the current
424 system time, and set the page to expire on 1 Jan 2099."""
425
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000426 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000427 return False
428
429 self.send_response(200)
430 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000431 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000432 self.end_headers()
433
maruel@google.come250a9b2009-03-10 17:39:46 +0000434 self.wfile.write('<html><head><title>%s</title></head></html>' %
435 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000436
437 return True
438
439 def CacheProxyRevalidateHandler(self):
440 """This request handler yields a page with the title set to the current
441 system time, and allows caching for 60 seconds"""
442
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000443 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000444 return False
445
446 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000447 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000448 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
449 self.end_headers()
450
maruel@google.come250a9b2009-03-10 17:39:46 +0000451 self.wfile.write('<html><head><title>%s</title></head></html>' %
452 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000453
454 return True
455
456 def CachePrivateHandler(self):
457 """This request handler yields a page with the title set to the current
458 system time, and allows caching for 5 seconds."""
459
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000460 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000461 return False
462
463 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000464 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000465 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000466 self.end_headers()
467
maruel@google.come250a9b2009-03-10 17:39:46 +0000468 self.wfile.write('<html><head><title>%s</title></head></html>' %
469 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000470
471 return True
472
473 def CachePublicHandler(self):
474 """This request handler yields a page with the title set to the current
475 system time, and allows caching for 5 seconds."""
476
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000477 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000478 return False
479
480 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000481 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000482 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000483 self.end_headers()
484
maruel@google.come250a9b2009-03-10 17:39:46 +0000485 self.wfile.write('<html><head><title>%s</title></head></html>' %
486 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000487
488 return True
489
490 def CacheSMaxAgeHandler(self):
491 """This request handler yields a page with the title set to the current
492 system time, and does not allow for caching."""
493
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000494 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000495 return False
496
497 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000498 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000499 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
500 self.end_headers()
501
maruel@google.come250a9b2009-03-10 17:39:46 +0000502 self.wfile.write('<html><head><title>%s</title></head></html>' %
503 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000504
505 return True
506
507 def CacheMustRevalidateHandler(self):
508 """This request handler yields a page with the title set to the current
509 system time, and does not allow caching."""
510
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000511 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000512 return False
513
514 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000515 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000516 self.send_header('Cache-Control', 'must-revalidate')
517 self.end_headers()
518
maruel@google.come250a9b2009-03-10 17:39:46 +0000519 self.wfile.write('<html><head><title>%s</title></head></html>' %
520 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000521
522 return True
523
524 def CacheMustRevalidateMaxAgeHandler(self):
525 """This request handler yields a page with the title set to the current
526 system time, and does not allow caching event though max-age of 60
527 seconds is specified."""
528
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000529 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000530 return False
531
532 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000533 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000534 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
535 self.end_headers()
536
maruel@google.come250a9b2009-03-10 17:39:46 +0000537 self.wfile.write('<html><head><title>%s</title></head></html>' %
538 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000539
540 return True
541
initial.commit94958cf2008-07-26 22:42:52 +0000542 def CacheNoStoreHandler(self):
543 """This request handler yields a page with the title set to the current
544 system time, and does not allow the page to be stored."""
545
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000546 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000547 return False
548
549 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000550 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000551 self.send_header('Cache-Control', 'no-store')
552 self.end_headers()
553
maruel@google.come250a9b2009-03-10 17:39:46 +0000554 self.wfile.write('<html><head><title>%s</title></head></html>' %
555 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000556
557 return True
558
559 def CacheNoStoreMaxAgeHandler(self):
560 """This request handler yields a page with the title set to the current
561 system time, and does not allow the page to be stored even though max-age
562 of 60 seconds is specified."""
563
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000564 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000565 return False
566
567 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000568 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000569 self.send_header('Cache-Control', 'max-age=60, no-store')
570 self.end_headers()
571
maruel@google.come250a9b2009-03-10 17:39:46 +0000572 self.wfile.write('<html><head><title>%s</title></head></html>' %
573 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000574
575 return True
576
577
578 def CacheNoTransformHandler(self):
579 """This request handler yields a page with the title set to the current
580 system time, and does not allow the content to transformed during
581 user-agent caching"""
582
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000583 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000584 return False
585
586 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000587 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000588 self.send_header('Cache-Control', 'no-transform')
589 self.end_headers()
590
maruel@google.come250a9b2009-03-10 17:39:46 +0000591 self.wfile.write('<html><head><title>%s</title></head></html>' %
592 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000593
594 return True
595
596 def EchoHeader(self):
597 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000598
ananta@chromium.org219b2062009-10-23 16:09:41 +0000599 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000600
ananta@chromium.org56812d02011-04-07 17:52:05 +0000601 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000602 """This function echoes back the value of a specific request header while
603 allowing caching for 16 hours."""
604
ananta@chromium.org56812d02011-04-07 17:52:05 +0000605 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000606
607 def EchoHeaderHelper(self, echo_header):
608 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000609
ananta@chromium.org219b2062009-10-23 16:09:41 +0000610 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000611 return False
612
613 query_char = self.path.find('?')
614 if query_char != -1:
615 header_name = self.path[query_char+1:]
616
617 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000618 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000619 if echo_header == '/echoheadercache':
620 self.send_header('Cache-control', 'max-age=60000')
621 else:
622 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000623 # insert a vary header to properly indicate that the cachability of this
624 # request is subject to value of the request header being echoed.
625 if len(header_name) > 0:
626 self.send_header('Vary', header_name)
627 self.end_headers()
628
629 if len(header_name) > 0:
630 self.wfile.write(self.headers.getheader(header_name))
631
632 return True
633
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000634 def ReadRequestBody(self):
635 """This function reads the body of the current HTTP request, handling
636 both plain and chunked transfer encoded requests."""
637
638 if self.headers.getheader('transfer-encoding') != 'chunked':
639 length = int(self.headers.getheader('content-length'))
640 return self.rfile.read(length)
641
642 # Read the request body as chunks.
643 body = ""
644 while True:
645 line = self.rfile.readline()
646 length = int(line, 16)
647 if length == 0:
648 self.rfile.readline()
649 break
650 body += self.rfile.read(length)
651 self.rfile.read(2)
652 return body
653
initial.commit94958cf2008-07-26 22:42:52 +0000654 def EchoHandler(self):
655 """This handler just echoes back the payload of the request, for testing
656 form submission."""
657
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000658 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000659 return False
660
661 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000662 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000663 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000664 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000665 return True
666
667 def EchoTitleHandler(self):
668 """This handler is like Echo, but sets the page title to the request."""
669
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000670 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000671 return False
672
673 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000674 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000675 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000676 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000677 self.wfile.write('<html><head><title>')
678 self.wfile.write(request)
679 self.wfile.write('</title></head></html>')
680 return True
681
682 def EchoAllHandler(self):
683 """This handler yields a (more) human-readable page listing information
684 about the request header & contents."""
685
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000686 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000687 return False
688
689 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000690 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000691 self.end_headers()
692 self.wfile.write('<html><head><style>'
693 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
694 '</style></head><body>'
695 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000696 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000697 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000698
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000699 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000700 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000701 params = cgi.parse_qs(qs, keep_blank_values=1)
702
703 for param in params:
704 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000705
706 self.wfile.write('</pre>')
707
708 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
709
710 self.wfile.write('</body></html>')
711 return True
712
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000713 def EchoMultipartPostHandler(self):
714 """This handler echoes received multipart post data as json format."""
715
716 if not (self._ShouldHandleRequest("/echomultipartpost") or
717 self._ShouldHandleRequest("/searchbyimage")):
718 return False
719
720 content_type, parameters = cgi.parse_header(
721 self.headers.getheader('content-type'))
722 if content_type == 'multipart/form-data':
723 post_multipart = cgi.parse_multipart(self.rfile, parameters)
724 elif content_type == 'application/x-www-form-urlencoded':
725 raise Exception('POST by application/x-www-form-urlencoded is '
726 'not implemented.')
727 else:
728 post_multipart = {}
729
730 # Since the data can be binary, we encode them by base64.
731 post_multipart_base64_encoded = {}
732 for field, values in post_multipart.items():
733 post_multipart_base64_encoded[field] = [base64.b64encode(value)
734 for value in values]
735
736 result = {'POST_multipart' : post_multipart_base64_encoded}
737
738 self.send_response(200)
739 self.send_header("Content-type", "text/plain")
740 self.end_headers()
741 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
742 return True
743
initial.commit94958cf2008-07-26 22:42:52 +0000744 def DownloadHandler(self):
745 """This handler sends a downloadable file with or without reporting
746 the size (6K)."""
747
748 if self.path.startswith("/download-unknown-size"):
749 send_length = False
750 elif self.path.startswith("/download-known-size"):
751 send_length = True
752 else:
753 return False
754
755 #
756 # The test which uses this functionality is attempting to send
757 # small chunks of data to the client. Use a fairly large buffer
758 # so that we'll fill chrome's IO buffer enough to force it to
759 # actually write the data.
760 # See also the comments in the client-side of this test in
761 # download_uitest.cc
762 #
763 size_chunk1 = 35*1024
764 size_chunk2 = 10*1024
765
766 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000767 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000768 self.send_header('Cache-Control', 'max-age=0')
769 if send_length:
770 self.send_header('Content-Length', size_chunk1 + size_chunk2)
771 self.end_headers()
772
773 # First chunk of data:
774 self.wfile.write("*" * size_chunk1)
775 self.wfile.flush()
776
777 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000778 self.server.wait_for_download = True
779 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000780 self.server.handle_request()
781
782 # Second chunk of data:
783 self.wfile.write("*" * size_chunk2)
784 return True
785
786 def DownloadFinishHandler(self):
787 """This handler just tells the server to finish the current download."""
788
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000789 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000790 return False
791
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000792 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000793 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000794 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000795 self.send_header('Cache-Control', 'max-age=0')
796 self.end_headers()
797 return True
798
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000799 def _ReplaceFileData(self, data, query_parameters):
800 """Replaces matching substrings in a file.
801
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000802 If the 'replace_text' URL query parameter is present, it is expected to be
803 of the form old_text:new_text, which indicates that any old_text strings in
804 the file are replaced with new_text. Multiple 'replace_text' parameters may
805 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000806
807 If the parameters are not present, |data| is returned.
808 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000809
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000810 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000811 replace_text_values = query_dict.get('replace_text', [])
812 for replace_text_value in replace_text_values:
813 replace_text_args = replace_text_value.split(':')
814 if len(replace_text_args) != 2:
815 raise ValueError(
816 'replace_text must be of form old_text:new_text. Actual value: %s' %
817 replace_text_value)
818 old_text_b64, new_text_b64 = replace_text_args
819 old_text = base64.urlsafe_b64decode(old_text_b64)
820 new_text = base64.urlsafe_b64decode(new_text_b64)
821 data = data.replace(old_text, new_text)
822 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000823
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000824 def ZipFileHandler(self):
825 """This handler sends the contents of the requested file in compressed form.
826 Can pass in a parameter that specifies that the content length be
827 C - the compressed size (OK),
828 U - the uncompressed size (Non-standard, but handled),
829 S - less than compressed (OK because we keep going),
830 M - larger than compressed but less than uncompressed (an error),
831 L - larger than uncompressed (an error)
832 Example: compressedfiles/Picture_1.doc?C
833 """
834
835 prefix = "/compressedfiles/"
836 if not self.path.startswith(prefix):
837 return False
838
839 # Consume a request body if present.
840 if self.command == 'POST' or self.command == 'PUT' :
841 self.ReadRequestBody()
842
843 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
844
845 if not query in ('C', 'U', 'S', 'M', 'L'):
846 return False
847
848 sub_path = url_path[len(prefix):]
849 entries = sub_path.split('/')
850 file_path = os.path.join(self.server.data_dir, *entries)
851 if os.path.isdir(file_path):
852 file_path = os.path.join(file_path, 'index.html')
853
854 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000855 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000856 self.send_error(404)
857 return True
858
859 f = open(file_path, "rb")
860 data = f.read()
861 uncompressed_len = len(data)
862 f.close()
863
864 # Compress the data.
865 data = zlib.compress(data)
866 compressed_len = len(data)
867
868 content_length = compressed_len
869 if query == 'U':
870 content_length = uncompressed_len
871 elif query == 'S':
872 content_length = compressed_len / 2
873 elif query == 'M':
874 content_length = (compressed_len + uncompressed_len) / 2
875 elif query == 'L':
876 content_length = compressed_len + uncompressed_len
877
878 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000879 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000880 self.send_header('Content-encoding', 'deflate')
881 self.send_header('Connection', 'close')
882 self.send_header('Content-Length', content_length)
883 self.send_header('ETag', '\'' + file_path + '\'')
884 self.end_headers()
885
886 self.wfile.write(data)
887
888 return True
889
initial.commit94958cf2008-07-26 22:42:52 +0000890 def FileHandler(self):
891 """This handler sends the contents of the requested file. Wow, it's like
892 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000893
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000894 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000895 if not self.path.startswith(prefix):
896 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000897 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000898
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000899 def PostOnlyFileHandler(self):
900 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000901
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000902 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000903 if not self.path.startswith(prefix):
904 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000905 return self._FileHandlerHelper(prefix)
906
907 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000908 request_body = ''
909 if self.command == 'POST' or self.command == 'PUT':
910 # Consume a request body if present.
911 request_body = self.ReadRequestBody()
912
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000913 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000914 query_dict = cgi.parse_qs(query)
915
916 expected_body = query_dict.get('expected_body', [])
917 if expected_body and request_body not in expected_body:
918 self.send_response(404)
919 self.end_headers()
920 self.wfile.write('')
921 return True
922
923 expected_headers = query_dict.get('expected_headers', [])
924 for expected_header in expected_headers:
925 header_name, expected_value = expected_header.split(':')
926 if self.headers.getheader(header_name) != expected_value:
927 self.send_response(404)
928 self.end_headers()
929 self.wfile.write('')
930 return True
931
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000932 sub_path = url_path[len(prefix):]
933 entries = sub_path.split('/')
934 file_path = os.path.join(self.server.data_dir, *entries)
935 if os.path.isdir(file_path):
936 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000937
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000938 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000939 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000940 self.send_error(404)
941 return True
942
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000943 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000944 data = f.read()
945 f.close()
946
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000947 data = self._ReplaceFileData(data, query)
948
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000949 old_protocol_version = self.protocol_version
950
initial.commit94958cf2008-07-26 22:42:52 +0000951 # If file.mock-http-headers exists, it contains the headers we
952 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000953 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000954 if os.path.isfile(headers_path):
955 f = open(headers_path, "r")
956
957 # "HTTP/1.1 200 OK"
958 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000959 http_major, http_minor, status_code = re.findall(
960 'HTTP/(\d+).(\d+) (\d+)', response)[0]
961 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000962 self.send_response(int(status_code))
963
964 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000965 header_values = re.findall('(\S+):\s*(.*)', line)
966 if len(header_values) > 0:
967 # "name: value"
968 name, value = header_values[0]
969 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000970 f.close()
971 else:
972 # Could be more generic once we support mime-type sniffing, but for
973 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000974
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000975 range_header = self.headers.get('Range')
976 if range_header and range_header.startswith('bytes='):
977 # Note this doesn't handle all valid byte range_header values (i.e.
978 # left open ended ones), just enough for what we needed so far.
979 range_header = range_header[6:].split('-')
980 start = int(range_header[0])
981 if range_header[1]:
982 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000983 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000984 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000985
986 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000987 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
988 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000989 self.send_header('Content-Range', content_range)
990 data = data[start: end + 1]
991 else:
992 self.send_response(200)
993
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000994 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000995 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000996 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000997 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000998 self.end_headers()
999
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001000 if (self.command != 'HEAD'):
1001 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001002
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001003 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001004 return True
1005
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001006 def SetCookieHandler(self):
1007 """This handler just sets a cookie, for testing cookie handling."""
1008
1009 if not self._ShouldHandleRequest("/set-cookie"):
1010 return False
1011
1012 query_char = self.path.find('?')
1013 if query_char != -1:
1014 cookie_values = self.path[query_char + 1:].split('&')
1015 else:
1016 cookie_values = ("",)
1017 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001018 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001019 for cookie_value in cookie_values:
1020 self.send_header('Set-Cookie', '%s' % cookie_value)
1021 self.end_headers()
1022 for cookie_value in cookie_values:
1023 self.wfile.write('%s' % cookie_value)
1024 return True
1025
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001026 def SetManyCookiesHandler(self):
1027 """This handler just sets a given number of cookies, for testing handling
1028 of large numbers of cookies."""
1029
1030 if not self._ShouldHandleRequest("/set-many-cookies"):
1031 return False
1032
1033 query_char = self.path.find('?')
1034 if query_char != -1:
1035 num_cookies = int(self.path[query_char + 1:])
1036 else:
1037 num_cookies = 0
1038 self.send_response(200)
1039 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001040 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001041 self.send_header('Set-Cookie', 'a=')
1042 self.end_headers()
1043 self.wfile.write('%d cookies were sent' % num_cookies)
1044 return True
1045
mattm@chromium.org983fc462012-06-30 00:52:08 +00001046 def ExpectAndSetCookieHandler(self):
1047 """Expects some cookies to be sent, and if they are, sets more cookies.
1048
1049 The expect parameter specifies a required cookie. May be specified multiple
1050 times.
1051 The set parameter specifies a cookie to set if all required cookies are
1052 preset. May be specified multiple times.
1053 The data parameter specifies the response body data to be returned."""
1054
1055 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1056 return False
1057
1058 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1059 query_dict = cgi.parse_qs(query)
1060 cookies = set()
1061 if 'Cookie' in self.headers:
1062 cookie_header = self.headers.getheader('Cookie')
1063 cookies.update([s.strip() for s in cookie_header.split(';')])
1064 got_all_expected_cookies = True
1065 for expected_cookie in query_dict.get('expect', []):
1066 if expected_cookie not in cookies:
1067 got_all_expected_cookies = False
1068 self.send_response(200)
1069 self.send_header('Content-Type', 'text/html')
1070 if got_all_expected_cookies:
1071 for cookie_value in query_dict.get('set', []):
1072 self.send_header('Set-Cookie', '%s' % cookie_value)
1073 self.end_headers()
1074 for data_value in query_dict.get('data', []):
1075 self.wfile.write(data_value)
1076 return True
1077
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001078 def SetHeaderHandler(self):
1079 """This handler sets a response header. Parameters are in the
1080 key%3A%20value&key2%3A%20value2 format."""
1081
1082 if not self._ShouldHandleRequest("/set-header"):
1083 return False
1084
1085 query_char = self.path.find('?')
1086 if query_char != -1:
1087 headers_values = self.path[query_char + 1:].split('&')
1088 else:
1089 headers_values = ("",)
1090 self.send_response(200)
1091 self.send_header('Content-Type', 'text/html')
1092 for header_value in headers_values:
1093 header_value = urllib.unquote(header_value)
1094 (key, value) = header_value.split(': ', 1)
1095 self.send_header(key, value)
1096 self.end_headers()
1097 for header_value in headers_values:
1098 self.wfile.write('%s' % header_value)
1099 return True
1100
initial.commit94958cf2008-07-26 22:42:52 +00001101 def AuthBasicHandler(self):
1102 """This handler tests 'Basic' authentication. It just sends a page with
1103 title 'user/pass' if you succeed."""
1104
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001105 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001106 return False
1107
1108 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001109 expected_password = 'secret'
1110 realm = 'testrealm'
1111 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001112
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001113 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1114 query_params = cgi.parse_qs(query, True)
1115 if 'set-cookie-if-challenged' in query_params:
1116 set_cookie_if_challenged = True
1117 if 'password' in query_params:
1118 expected_password = query_params['password'][0]
1119 if 'realm' in query_params:
1120 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001121
initial.commit94958cf2008-07-26 22:42:52 +00001122 auth = self.headers.getheader('authorization')
1123 try:
1124 if not auth:
1125 raise Exception('no auth')
1126 b64str = re.findall(r'Basic (\S+)', auth)[0]
1127 userpass = base64.b64decode(b64str)
1128 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001129 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001130 raise Exception('wrong password')
1131 except Exception, e:
1132 # Authentication failed.
1133 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001134 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001135 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001136 if set_cookie_if_challenged:
1137 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001138 self.end_headers()
1139 self.wfile.write('<html><head>')
1140 self.wfile.write('<title>Denied: %s</title>' % e)
1141 self.wfile.write('</head><body>')
1142 self.wfile.write('auth=%s<p>' % auth)
1143 self.wfile.write('b64str=%s<p>' % b64str)
1144 self.wfile.write('username: %s<p>' % username)
1145 self.wfile.write('userpass: %s<p>' % userpass)
1146 self.wfile.write('password: %s<p>' % password)
1147 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1148 self.wfile.write('</body></html>')
1149 return True
1150
1151 # Authentication successful. (Return a cachable response to allow for
1152 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001153 old_protocol_version = self.protocol_version
1154 self.protocol_version = "HTTP/1.1"
1155
initial.commit94958cf2008-07-26 22:42:52 +00001156 if_none_match = self.headers.getheader('if-none-match')
1157 if if_none_match == "abc":
1158 self.send_response(304)
1159 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001160 elif url_path.endswith(".gif"):
1161 # Using chrome/test/data/google/logo.gif as the test image
1162 test_image_path = ['google', 'logo.gif']
1163 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1164 if not os.path.isfile(gif_path):
1165 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001166 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001167 return True
1168
1169 f = open(gif_path, "rb")
1170 data = f.read()
1171 f.close()
1172
1173 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001174 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001175 self.send_header('Cache-control', 'max-age=60000')
1176 self.send_header('Etag', 'abc')
1177 self.end_headers()
1178 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001179 else:
1180 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001181 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001182 self.send_header('Cache-control', 'max-age=60000')
1183 self.send_header('Etag', 'abc')
1184 self.end_headers()
1185 self.wfile.write('<html><head>')
1186 self.wfile.write('<title>%s/%s</title>' % (username, password))
1187 self.wfile.write('</head><body>')
1188 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001189 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001190 self.wfile.write('</body></html>')
1191
rvargas@google.com54453b72011-05-19 01:11:11 +00001192 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001193 return True
1194
tonyg@chromium.org75054202010-03-31 22:06:10 +00001195 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001196 """Returns a nonce that's stable per request path for the server's lifetime.
1197 This is a fake implementation. A real implementation would only use a given
1198 nonce a single time (hence the name n-once). However, for the purposes of
1199 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001200
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001201 Args:
1202 force_reset: Iff set, the nonce will be changed. Useful for testing the
1203 "stale" response.
1204 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001205
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001206 if force_reset or not self.server.nonce_time:
1207 self.server.nonce_time = time.time()
1208 return hashlib.md5('privatekey%s%d' %
1209 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001210
1211 def AuthDigestHandler(self):
1212 """This handler tests 'Digest' authentication.
1213
1214 It just sends a page with title 'user/pass' if you succeed.
1215
1216 A stale response is sent iff "stale" is present in the request path.
1217 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001218
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001219 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001220 return False
1221
tonyg@chromium.org75054202010-03-31 22:06:10 +00001222 stale = 'stale' in self.path
1223 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001224 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001225 password = 'secret'
1226 realm = 'testrealm'
1227
1228 auth = self.headers.getheader('authorization')
1229 pairs = {}
1230 try:
1231 if not auth:
1232 raise Exception('no auth')
1233 if not auth.startswith('Digest'):
1234 raise Exception('not digest')
1235 # Pull out all the name="value" pairs as a dictionary.
1236 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1237
1238 # Make sure it's all valid.
1239 if pairs['nonce'] != nonce:
1240 raise Exception('wrong nonce')
1241 if pairs['opaque'] != opaque:
1242 raise Exception('wrong opaque')
1243
1244 # Check the 'response' value and make sure it matches our magic hash.
1245 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001246 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001247 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001248 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001249 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001250 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001251 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1252 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001253 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001254
1255 if pairs['response'] != response:
1256 raise Exception('wrong password')
1257 except Exception, e:
1258 # Authentication failed.
1259 self.send_response(401)
1260 hdr = ('Digest '
1261 'realm="%s", '
1262 'domain="/", '
1263 'qop="auth", '
1264 'algorithm=MD5, '
1265 'nonce="%s", '
1266 'opaque="%s"') % (realm, nonce, opaque)
1267 if stale:
1268 hdr += ', stale="TRUE"'
1269 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001270 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001271 self.end_headers()
1272 self.wfile.write('<html><head>')
1273 self.wfile.write('<title>Denied: %s</title>' % e)
1274 self.wfile.write('</head><body>')
1275 self.wfile.write('auth=%s<p>' % auth)
1276 self.wfile.write('pairs=%s<p>' % pairs)
1277 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1278 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1279 self.wfile.write('</body></html>')
1280 return True
1281
1282 # Authentication successful.
1283 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001284 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001285 self.end_headers()
1286 self.wfile.write('<html><head>')
1287 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1288 self.wfile.write('</head><body>')
1289 self.wfile.write('auth=%s<p>' % auth)
1290 self.wfile.write('pairs=%s<p>' % pairs)
1291 self.wfile.write('</body></html>')
1292
1293 return True
1294
1295 def SlowServerHandler(self):
1296 """Wait for the user suggested time before responding. The syntax is
1297 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001298
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001299 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001300 return False
1301 query_char = self.path.find('?')
1302 wait_sec = 1.0
1303 if query_char >= 0:
1304 try:
1305 wait_sec = int(self.path[query_char + 1:])
1306 except ValueError:
1307 pass
1308 time.sleep(wait_sec)
1309 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001310 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001311 self.end_headers()
1312 self.wfile.write("waited %d seconds" % wait_sec)
1313 return True
1314
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001315 def ChunkedServerHandler(self):
1316 """Send chunked response. Allows to specify chunks parameters:
1317 - waitBeforeHeaders - ms to wait before sending headers
1318 - waitBetweenChunks - ms to wait between chunks
1319 - chunkSize - size of each chunk in bytes
1320 - chunksNumber - number of chunks
1321 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1322 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001323
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001324 if not self._ShouldHandleRequest("/chunked"):
1325 return False
1326 query_char = self.path.find('?')
1327 chunkedSettings = {'waitBeforeHeaders' : 0,
1328 'waitBetweenChunks' : 0,
1329 'chunkSize' : 5,
1330 'chunksNumber' : 5}
1331 if query_char >= 0:
1332 params = self.path[query_char + 1:].split('&')
1333 for param in params:
1334 keyValue = param.split('=')
1335 if len(keyValue) == 2:
1336 try:
1337 chunkedSettings[keyValue[0]] = int(keyValue[1])
1338 except ValueError:
1339 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001340 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001341 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1342 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001343 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001344 self.send_header('Connection', 'close')
1345 self.send_header('Transfer-Encoding', 'chunked')
1346 self.end_headers()
1347 # Chunked encoding: sending all chunks, then final zero-length chunk and
1348 # then final CRLF.
1349 for i in range(0, chunkedSettings['chunksNumber']):
1350 if i > 0:
1351 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1352 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001353 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001354 self.sendChunkHelp('')
1355 return True
1356
initial.commit94958cf2008-07-26 22:42:52 +00001357 def ContentTypeHandler(self):
1358 """Returns a string of html with the given content type. E.g.,
1359 /contenttype?text/css returns an html file with the Content-Type
1360 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001361
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001362 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001363 return False
1364 query_char = self.path.find('?')
1365 content_type = self.path[query_char + 1:].strip()
1366 if not content_type:
1367 content_type = 'text/html'
1368 self.send_response(200)
1369 self.send_header('Content-Type', content_type)
1370 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001371 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001372 return True
1373
creis@google.com2f4f6a42011-03-25 19:44:19 +00001374 def NoContentHandler(self):
1375 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001376
creis@google.com2f4f6a42011-03-25 19:44:19 +00001377 if not self._ShouldHandleRequest("/nocontent"):
1378 return False
1379 self.send_response(204)
1380 self.end_headers()
1381 return True
1382
initial.commit94958cf2008-07-26 22:42:52 +00001383 def ServerRedirectHandler(self):
1384 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001385 '/server-redirect?http://foo.bar/asdf' to redirect to
1386 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001387
1388 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001389 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001390 return False
1391
1392 query_char = self.path.find('?')
1393 if query_char < 0 or len(self.path) <= query_char + 1:
1394 self.sendRedirectHelp(test_name)
1395 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001396 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001397
1398 self.send_response(301) # moved permanently
1399 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001400 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001401 self.end_headers()
1402 self.wfile.write('<html><head>')
1403 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1404
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001405 return True
initial.commit94958cf2008-07-26 22:42:52 +00001406
1407 def ClientRedirectHandler(self):
1408 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001409 '/client-redirect?http://foo.bar/asdf' to redirect to
1410 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001411
1412 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001413 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001414 return False
1415
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001416 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001417 if query_char < 0 or len(self.path) <= query_char + 1:
1418 self.sendRedirectHelp(test_name)
1419 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001420 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001421
1422 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001423 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001424 self.end_headers()
1425 self.wfile.write('<html><head>')
1426 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1427 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1428
1429 return True
1430
tony@chromium.org03266982010-03-05 03:18:42 +00001431 def MultipartHandler(self):
1432 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001433
tony@chromium.org4cb88302011-09-27 22:13:49 +00001434 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001435 if not self._ShouldHandleRequest(test_name):
1436 return False
1437
1438 num_frames = 10
1439 bound = '12345'
1440 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001441 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001442 'multipart/x-mixed-replace;boundary=' + bound)
1443 self.end_headers()
1444
1445 for i in xrange(num_frames):
1446 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001447 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001448 self.wfile.write('<title>page ' + str(i) + '</title>')
1449 self.wfile.write('page ' + str(i))
1450
1451 self.wfile.write('--' + bound + '--')
1452 return True
1453
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001454 def GetSSLSessionCacheHandler(self):
1455 """Send a reply containing a log of the session cache operations."""
1456
1457 if not self._ShouldHandleRequest('/ssl-session-cache'):
1458 return False
1459
1460 self.send_response(200)
1461 self.send_header('Content-Type', 'text/plain')
1462 self.end_headers()
1463 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001464 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001465 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001466 self.wfile.write('Pass --https-record-resume in order to use' +
1467 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001468 return True
1469
1470 for (action, sessionID) in log:
1471 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001472 return True
1473
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001474 def SSLManySmallRecords(self):
1475 """Sends a reply consisting of a variety of small writes. These will be
1476 translated into a series of small SSL records when used over an HTTPS
1477 server."""
1478
1479 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1480 return False
1481
1482 self.send_response(200)
1483 self.send_header('Content-Type', 'text/plain')
1484 self.end_headers()
1485
1486 # Write ~26K of data, in 1350 byte chunks
1487 for i in xrange(20):
1488 self.wfile.write('*' * 1350)
1489 self.wfile.flush()
1490 return True
1491
agl@chromium.org04700be2013-03-02 18:40:41 +00001492 def GetChannelID(self):
1493 """Send a reply containing the hashed ChannelID that the client provided."""
1494
1495 if not self._ShouldHandleRequest('/channel-id'):
1496 return False
1497
1498 self.send_response(200)
1499 self.send_header('Content-Type', 'text/plain')
1500 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001501 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001502 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1503 return True
1504
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001505 def CloseSocketHandler(self):
1506 """Closes the socket without sending anything."""
1507
1508 if not self._ShouldHandleRequest('/close-socket'):
1509 return False
1510
1511 self.wfile.close()
1512 return True
1513
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001514 def RangeResetHandler(self):
1515 """Send data broken up by connection resets every N (default 4K) bytes.
1516 Support range requests. If the data requested doesn't straddle a reset
1517 boundary, it will all be sent. Used for testing resuming downloads."""
1518
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001519 def DataForRange(start, end):
1520 """Data to be provided for a particular range of bytes."""
1521 # Offset and scale to avoid too obvious (and hence potentially
1522 # collidable) data.
1523 return ''.join([chr(y % 256)
1524 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1525
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001526 if not self._ShouldHandleRequest('/rangereset'):
1527 return False
1528
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001529 # HTTP/1.1 is required for ETag and range support.
1530 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001531 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1532
1533 # Defaults
1534 size = 8000
1535 # Note that the rst is sent just before sending the rst_boundary byte.
1536 rst_boundary = 4000
1537 respond_to_range = True
1538 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001539 rst_limit = -1
1540 token = 'DEFAULT'
1541 fail_precondition = 0
1542 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001543
1544 # Parse the query
1545 qdict = urlparse.parse_qs(query, True)
1546 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001547 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001548 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001549 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001550 if 'token' in qdict:
1551 # Identifying token for stateful tests.
1552 token = qdict['token'][0]
1553 if 'rst_limit' in qdict:
1554 # Max number of rsts for a given token.
1555 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001556 if 'bounce_range' in qdict:
1557 respond_to_range = False
1558 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001559 # Note that hold_for_signal will not work with null range requests;
1560 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001561 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001562 if 'no_verifiers' in qdict:
1563 send_verifiers = False
1564 if 'fail_precondition' in qdict:
1565 fail_precondition = int(qdict['fail_precondition'][0])
1566
1567 # Record already set information, or set it.
1568 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1569 if rst_limit != 0:
1570 TestPageHandler.rst_limits[token] -= 1
1571 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1572 token, fail_precondition)
1573 if fail_precondition != 0:
1574 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001575
1576 first_byte = 0
1577 last_byte = size - 1
1578
1579 # Does that define what we want to return, or do we need to apply
1580 # a range?
1581 range_response = False
1582 range_header = self.headers.getheader('range')
1583 if range_header and respond_to_range:
1584 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1585 if mo.group(1):
1586 first_byte = int(mo.group(1))
1587 if mo.group(2):
1588 last_byte = int(mo.group(2))
1589 if last_byte > size - 1:
1590 last_byte = size - 1
1591 range_response = True
1592 if last_byte < first_byte:
1593 return False
1594
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001595 if (fail_precondition and
1596 (self.headers.getheader('If-Modified-Since') or
1597 self.headers.getheader('If-Match'))):
1598 self.send_response(412)
1599 self.end_headers()
1600 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001601
1602 if range_response:
1603 self.send_response(206)
1604 self.send_header('Content-Range',
1605 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1606 else:
1607 self.send_response(200)
1608 self.send_header('Content-Type', 'application/octet-stream')
1609 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001610 if send_verifiers:
asanka@chromium.org3ffced92013-12-13 22:20:27 +00001611 # If fail_precondition is non-zero, then the ETag for each request will be
1612 # different.
1613 etag = "%s%d" % (token, fail_precondition)
1614 self.send_header('ETag', etag)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001615 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001616 self.end_headers()
1617
1618 if hold_for_signal:
1619 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1620 # a single byte, the self.server.handle_request() below hangs
1621 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001622 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001623 first_byte = first_byte + 1
1624 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001625 self.server.wait_for_download = True
1626 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001627 self.server.handle_request()
1628
1629 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001630 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001631 # No RST has been requested in this range, so we don't need to
1632 # do anything fancy; just write the data and let the python
1633 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001634 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001635 self.wfile.flush()
1636 return True
1637
1638 # We're resetting the connection part way in; go to the RST
1639 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001640 # Because socket semantics do not guarantee that all the data will be
1641 # sent when using the linger semantics to hard close a socket,
1642 # we send the data and then wait for our peer to release us
1643 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001644 data = DataForRange(first_byte, possible_rst)
1645 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001646 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001647 self.server.wait_for_download = True
1648 while self.server.wait_for_download:
1649 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001650 l_onoff = 1 # Linger is active.
1651 l_linger = 0 # Seconds to linger for.
1652 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1653 struct.pack('ii', l_onoff, l_linger))
1654
1655 # Close all duplicates of the underlying socket to force the RST.
1656 self.wfile.close()
1657 self.rfile.close()
1658 self.connection.close()
1659
1660 return True
1661
initial.commit94958cf2008-07-26 22:42:52 +00001662 def DefaultResponseHandler(self):
1663 """This is the catch-all response handler for requests that aren't handled
1664 by one of the special handlers above.
1665 Note that we specify the content-length as without it the https connection
1666 is not closed properly (and the browser keeps expecting data)."""
1667
1668 contents = "Default response given for path: " + self.path
1669 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001670 self.send_header('Content-Type', 'text/html')
1671 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001672 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001673 if (self.command != 'HEAD'):
1674 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001675 return True
1676
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001677 def RedirectConnectHandler(self):
1678 """Sends a redirect to the CONNECT request for www.redirect.com. This
1679 response is not specified by the RFC, so the browser should not follow
1680 the redirect."""
1681
1682 if (self.path.find("www.redirect.com") < 0):
1683 return False
1684
1685 dest = "http://www.destination.com/foo.js"
1686
1687 self.send_response(302) # moved temporarily
1688 self.send_header('Location', dest)
1689 self.send_header('Connection', 'close')
1690 self.end_headers()
1691 return True
1692
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001693 def ServerAuthConnectHandler(self):
1694 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1695 response doesn't make sense because the proxy server cannot request
1696 server authentication."""
1697
1698 if (self.path.find("www.server-auth.com") < 0):
1699 return False
1700
1701 challenge = 'Basic realm="WallyWorld"'
1702
1703 self.send_response(401) # unauthorized
1704 self.send_header('WWW-Authenticate', challenge)
1705 self.send_header('Connection', 'close')
1706 self.end_headers()
1707 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001708
1709 def DefaultConnectResponseHandler(self):
1710 """This is the catch-all response handler for CONNECT requests that aren't
1711 handled by one of the special handlers above. Real Web servers respond
1712 with 400 to CONNECT requests."""
1713
1714 contents = "Your client has issued a malformed or illegal request."
1715 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001716 self.send_header('Content-Type', 'text/html')
1717 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001718 self.end_headers()
1719 self.wfile.write(contents)
1720 return True
1721
initial.commit94958cf2008-07-26 22:42:52 +00001722 # called by the redirect handling function when there is no parameter
1723 def sendRedirectHelp(self, redirect_name):
1724 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001725 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001726 self.end_headers()
1727 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1728 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1729 self.wfile.write('</body></html>')
1730
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001731 # called by chunked handling function
1732 def sendChunkHelp(self, chunk):
1733 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1734 self.wfile.write('%X\r\n' % len(chunk))
1735 self.wfile.write(chunk)
1736 self.wfile.write('\r\n')
1737
akalin@chromium.org154bb132010-11-12 02:20:27 +00001738
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001739class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001740 def __init__(self, request, client_address, socket_server):
1741 handlers = [self.OCSPResponse]
1742 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001743 testserver_base.BasePageHandler.__init__(self, request, client_address,
1744 socket_server, [], handlers, [],
1745 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001746
1747 def OCSPResponse(self):
1748 self.send_response(200)
1749 self.send_header('Content-Type', 'application/ocsp-response')
1750 self.send_header('Content-Length', str(len(self.ocsp_response)))
1751 self.end_headers()
1752
1753 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001754
mattm@chromium.org830a3712012-11-07 23:00:07 +00001755
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001756class TCPEchoHandler(SocketServer.BaseRequestHandler):
1757 """The RequestHandler class for TCP echo server.
1758
1759 It is instantiated once per connection to the server, and overrides the
1760 handle() method to implement communication to the client.
1761 """
1762
1763 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001764 """Handles the request from the client and constructs a response."""
1765
1766 data = self.request.recv(65536).strip()
1767 # Verify the "echo request" message received from the client. Send back
1768 # "echo response" message if "echo request" message is valid.
1769 try:
1770 return_data = echo_message.GetEchoResponseData(data)
1771 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001772 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001773 except ValueError:
1774 return
1775
1776 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001777
1778
1779class UDPEchoHandler(SocketServer.BaseRequestHandler):
1780 """The RequestHandler class for UDP echo server.
1781
1782 It is instantiated once per connection to the server, and overrides the
1783 handle() method to implement communication to the client.
1784 """
1785
1786 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001787 """Handles the request from the client and constructs a response."""
1788
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001789 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001790 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001791 # Verify the "echo request" message received from the client. Send back
1792 # "echo response" message if "echo request" message is valid.
1793 try:
1794 return_data = echo_message.GetEchoResponseData(data)
1795 if not return_data:
1796 return
1797 except ValueError:
1798 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001799 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001800
1801
bashi@chromium.org33233532012-09-08 17:37:24 +00001802class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1803 """A request handler that behaves as a proxy server which requires
1804 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1805 """
1806
1807 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1808
1809 def parse_request(self):
1810 """Overrides parse_request to check credential."""
1811
1812 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1813 return False
1814
1815 auth = self.headers.getheader('Proxy-Authorization')
1816 if auth != self._AUTH_CREDENTIAL:
1817 self.send_response(407)
1818 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1819 self.end_headers()
1820 return False
1821
1822 return True
1823
1824 def _start_read_write(self, sock):
1825 sock.setblocking(0)
1826 self.request.setblocking(0)
1827 rlist = [self.request, sock]
1828 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001829 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001830 if errors:
1831 self.send_response(500)
1832 self.end_headers()
1833 return
1834 for s in ready_sockets:
1835 received = s.recv(1024)
1836 if len(received) == 0:
1837 return
1838 if s == self.request:
1839 other = sock
1840 else:
1841 other = self.request
1842 other.send(received)
1843
1844 def _do_common_method(self):
1845 url = urlparse.urlparse(self.path)
1846 port = url.port
1847 if not port:
1848 if url.scheme == 'http':
1849 port = 80
1850 elif url.scheme == 'https':
1851 port = 443
1852 if not url.hostname or not port:
1853 self.send_response(400)
1854 self.end_headers()
1855 return
1856
1857 if len(url.path) == 0:
1858 path = '/'
1859 else:
1860 path = url.path
1861 if len(url.query) > 0:
1862 path = '%s?%s' % (url.path, url.query)
1863
1864 sock = None
1865 try:
1866 sock = socket.create_connection((url.hostname, port))
1867 sock.send('%s %s %s\r\n' % (
1868 self.command, path, self.protocol_version))
1869 for header in self.headers.headers:
1870 header = header.strip()
1871 if (header.lower().startswith('connection') or
1872 header.lower().startswith('proxy')):
1873 continue
1874 sock.send('%s\r\n' % header)
1875 sock.send('\r\n')
1876 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001877 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001878 self.send_response(500)
1879 self.end_headers()
1880 finally:
1881 if sock is not None:
1882 sock.close()
1883
1884 def do_CONNECT(self):
1885 try:
1886 pos = self.path.rfind(':')
1887 host = self.path[:pos]
1888 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001889 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001890 self.send_response(400)
1891 self.end_headers()
1892
1893 try:
1894 sock = socket.create_connection((host, port))
1895 self.send_response(200, 'Connection established')
1896 self.end_headers()
1897 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001898 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001899 self.send_response(500)
1900 self.end_headers()
1901 finally:
1902 sock.close()
1903
1904 def do_GET(self):
1905 self._do_common_method()
1906
1907 def do_HEAD(self):
1908 self._do_common_method()
1909
1910
mattm@chromium.org830a3712012-11-07 23:00:07 +00001911class ServerRunner(testserver_base.TestServerRunner):
1912 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001913
mattm@chromium.org830a3712012-11-07 23:00:07 +00001914 def __init__(self):
1915 super(ServerRunner, self).__init__()
1916 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001917
mattm@chromium.org830a3712012-11-07 23:00:07 +00001918 def __make_data_dir(self):
1919 if self.options.data_dir:
1920 if not os.path.isdir(self.options.data_dir):
1921 raise testserver_base.OptionError('specified data dir not found: ' +
1922 self.options.data_dir + ' exiting...')
1923 my_data_dir = self.options.data_dir
1924 else:
1925 # Create the default path to our data dir, relative to the exe dir.
1926 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1927 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001928
mattm@chromium.org830a3712012-11-07 23:00:07 +00001929 #TODO(ibrar): Must use Find* funtion defined in google\tools
1930 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001931
mattm@chromium.org830a3712012-11-07 23:00:07 +00001932 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001933
mattm@chromium.org830a3712012-11-07 23:00:07 +00001934 def create_server(self, server_data):
1935 port = self.options.port
1936 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001937
mattm@chromium.org830a3712012-11-07 23:00:07 +00001938 if self.options.server_type == SERVER_HTTP:
1939 if self.options.https:
1940 pem_cert_and_key = None
1941 if self.options.cert_and_key_file:
1942 if not os.path.isfile(self.options.cert_and_key_file):
1943 raise testserver_base.OptionError(
1944 'specified server cert file not found: ' +
1945 self.options.cert_and_key_file + ' exiting...')
1946 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001947 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001948 # generate a new certificate and run an OCSP server for it.
1949 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001950 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001951 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001952
mattm@chromium.org830a3712012-11-07 23:00:07 +00001953 ocsp_der = None
1954 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001955
mattm@chromium.org830a3712012-11-07 23:00:07 +00001956 if self.options.ocsp == 'ok':
1957 ocsp_state = minica.OCSP_STATE_GOOD
1958 elif self.options.ocsp == 'revoked':
1959 ocsp_state = minica.OCSP_STATE_REVOKED
1960 elif self.options.ocsp == 'invalid':
1961 ocsp_state = minica.OCSP_STATE_INVALID
1962 elif self.options.ocsp == 'unauthorized':
1963 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1964 elif self.options.ocsp == 'unknown':
1965 ocsp_state = minica.OCSP_STATE_UNKNOWN
1966 else:
1967 raise testserver_base.OptionError('unknown OCSP status: ' +
1968 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001969
mattm@chromium.org830a3712012-11-07 23:00:07 +00001970 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1971 subject = "127.0.0.1",
1972 ocsp_url = ("http://%s:%d/ocsp" %
1973 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00001974 ocsp_state = ocsp_state,
1975 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001976
1977 self.__ocsp_server.ocsp_response = ocsp_der
1978
1979 for ca_cert in self.options.ssl_client_ca:
1980 if not os.path.isfile(ca_cert):
1981 raise testserver_base.OptionError(
1982 'specified trusted client CA file not found: ' + ca_cert +
1983 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001984
1985 stapled_ocsp_response = None
1986 if self.__ocsp_server and self.options.staple_ocsp_response:
1987 stapled_ocsp_response = self.__ocsp_server.ocsp_response
1988
mattm@chromium.org830a3712012-11-07 23:00:07 +00001989 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1990 self.options.ssl_client_auth,
1991 self.options.ssl_client_ca,
1992 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00001993 self.options.ssl_key_exchange,
davidben@chromium.org6b1a39c2014-04-11 20:49:33 +00001994 self.options.enable_npn,
mattm@chromium.org830a3712012-11-07 23:00:07 +00001995 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00001996 self.options.tls_intolerant,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001997 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00001998 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001999 self.options.fallback_scsv,
2000 stapled_ocsp_response)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002001 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002002 else:
2003 server = HTTPServer((host, port), TestPageHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002004 print 'HTTP server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002005
2006 server.data_dir = self.__make_data_dir()
2007 server.file_root_url = self.options.file_root_url
2008 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002009 elif self.options.server_type == SERVER_WEBSOCKET:
2010 # Launch pywebsocket via WebSocketServer.
2011 logger = logging.getLogger()
2012 logger.addHandler(logging.StreamHandler())
2013 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2014 # is required to work correctly. It should be fixed from pywebsocket side.
2015 os.chdir(self.__make_data_dir())
2016 websocket_options = WebSocketOptions(host, port, '.')
2017 if self.options.cert_and_key_file:
2018 websocket_options.use_tls = True
2019 websocket_options.private_key = self.options.cert_and_key_file
2020 websocket_options.certificate = self.options.cert_and_key_file
2021 if self.options.ssl_client_auth:
2022 websocket_options.tls_client_auth = True
2023 if len(self.options.ssl_client_ca) != 1:
2024 raise testserver_base.OptionError(
2025 'one trusted client CA file should be specified')
2026 if not os.path.isfile(self.options.ssl_client_ca[0]):
2027 raise testserver_base.OptionError(
2028 'specified trusted client CA file not found: ' +
2029 self.options.ssl_client_ca[0] + ' exiting...')
2030 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2031 server = WebSocketServer(websocket_options)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002032 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002033 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002034 elif self.options.server_type == SERVER_TCP_ECHO:
2035 # Used for generating the key (randomly) that encodes the "echo request"
2036 # message.
2037 random.seed()
2038 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002039 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002040 server_data['port'] = server.server_port
2041 elif self.options.server_type == SERVER_UDP_ECHO:
2042 # Used for generating the key (randomly) that encodes the "echo request"
2043 # message.
2044 random.seed()
2045 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002046 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002047 server_data['port'] = server.server_port
2048 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2049 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002050 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002051 server_data['port'] = server.server_port
2052 elif self.options.server_type == SERVER_FTP:
2053 my_data_dir = self.__make_data_dir()
2054
2055 # Instantiate a dummy authorizer for managing 'virtual' users
2056 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2057
2058 # Define a new user having full r/w permissions and a read-only
2059 # anonymous user
2060 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2061
2062 authorizer.add_anonymous(my_data_dir)
2063
2064 # Instantiate FTP handler class
2065 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2066 ftp_handler.authorizer = authorizer
2067
2068 # Define a customized banner (string returned when client connects)
2069 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2070 pyftpdlib.ftpserver.__ver__)
2071
2072 # Instantiate FTP server class and listen to address:port
2073 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2074 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002075 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002076 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002077 raise testserver_base.OptionError('unknown server type' +
2078 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002079
mattm@chromium.org830a3712012-11-07 23:00:07 +00002080 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002081
mattm@chromium.org830a3712012-11-07 23:00:07 +00002082 def run_server(self):
2083 if self.__ocsp_server:
2084 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002085
mattm@chromium.org830a3712012-11-07 23:00:07 +00002086 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002087
mattm@chromium.org830a3712012-11-07 23:00:07 +00002088 if self.__ocsp_server:
2089 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002090
mattm@chromium.org830a3712012-11-07 23:00:07 +00002091 def add_options(self):
2092 testserver_base.TestServerRunner.add_options(self)
2093 self.option_parser.add_option('-f', '--ftp', action='store_const',
2094 const=SERVER_FTP, default=SERVER_HTTP,
2095 dest='server_type',
2096 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002097 self.option_parser.add_option('--tcp-echo', action='store_const',
2098 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2099 dest='server_type',
2100 help='start up a tcp echo server.')
2101 self.option_parser.add_option('--udp-echo', action='store_const',
2102 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2103 dest='server_type',
2104 help='start up a udp echo server.')
2105 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2106 const=SERVER_BASIC_AUTH_PROXY,
2107 default=SERVER_HTTP, dest='server_type',
2108 help='start up a proxy server which requires '
2109 'basic authentication.')
2110 self.option_parser.add_option('--websocket', action='store_const',
2111 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2112 dest='server_type',
2113 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002114 self.option_parser.add_option('--https', action='store_true',
2115 dest='https', help='Specify that https '
2116 'should be used.')
2117 self.option_parser.add_option('--cert-and-key-file',
2118 dest='cert_and_key_file', help='specify the '
2119 'path to the file containing the certificate '
2120 'and private key for the server in PEM '
2121 'format')
2122 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2123 help='The type of OCSP response generated '
2124 'for the automatically generated '
2125 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002126 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2127 default=0, type=int,
2128 help='If non-zero then the generated '
2129 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002130 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2131 default='0', type='int',
2132 help='If nonzero, certain TLS connections '
2133 'will be aborted in order to test version '
2134 'fallback. 1 means all TLS versions will be '
2135 'aborted. 2 means TLS 1.1 or higher will be '
2136 'aborted. 3 means TLS 1.2 or higher will be '
2137 'aborted.')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002138 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2139 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002140 default='',
2141 help='Base64 encoded SCT list. If set, '
2142 'server will respond with a '
2143 'signed_certificate_timestamp TLS extension '
2144 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002145 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2146 default=False, const=True,
2147 action='store_const',
2148 help='If given, TLS_FALLBACK_SCSV support '
2149 'will be enabled. This causes the server to '
2150 'reject fallback connections from compatible '
2151 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002152 self.option_parser.add_option('--staple-ocsp-response',
2153 dest='staple_ocsp_response',
2154 default=False, action='store_true',
2155 help='If set, server will staple the OCSP '
2156 'response whenever OCSP is on and the client '
2157 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002158 self.option_parser.add_option('--https-record-resume',
2159 dest='record_resume', const=True,
2160 default=False, action='store_const',
2161 help='Record resumption cache events rather '
2162 'than resuming as normal. Allows the use of '
2163 'the /ssl-session-cache request')
2164 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2165 help='Require SSL client auth on every '
2166 'connection.')
2167 self.option_parser.add_option('--ssl-client-ca', action='append',
2168 default=[], help='Specify that the client '
2169 'certificate request should include the CA '
2170 'named in the subject of the DER-encoded '
2171 'certificate contained in the specified '
2172 'file. This option may appear multiple '
2173 'times, indicating multiple CA names should '
2174 'be sent in the request.')
2175 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2176 help='Specify the bulk encryption '
2177 'algorithm(s) that will be accepted by the '
2178 'SSL server. Valid values are "aes256", '
2179 '"aes128", "3des", "rc4". If omitted, all '
2180 'algorithms will be used. This option may '
2181 'appear multiple times, indicating '
2182 'multiple algorithms should be enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002183 self.option_parser.add_option('--ssl-key-exchange', action='append',
2184 help='Specify the key exchange algorithm(s)'
2185 'that will be accepted by the SSL server. '
2186 'Valid values are "rsa", "dhe_rsa". If '
2187 'omitted, all algorithms will be used. This '
2188 'option may appear multiple times, '
2189 'indicating multiple algorithms should be '
2190 'enabled.');
davidben@chromium.org6b1a39c2014-04-11 20:49:33 +00002191 # TODO(davidben): Add ALPN support to tlslite.
2192 self.option_parser.add_option('--enable-npn', dest='enable_npn',
2193 default=False, const=True,
2194 action='store_const',
2195 help='Enable server support for the NPN '
2196 'extension. The server will advertise '
2197 'support for exactly one protocol, http/1.1')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002198 self.option_parser.add_option('--file-root-url', default='/files/',
2199 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002200
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002201
initial.commit94958cf2008-07-26 22:42:52 +00002202if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002203 sys.exit(ServerRunner().main())