blob: 92aee0dcc6defc5d8741ca29b0a42bf00426aff6 [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,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000155 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000156 record_resume_info, tls_intolerant, signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000157 fallback_scsv_enabled, ocsp_response):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000158 self.cert_chain = tlslite.api.X509CertChain()
159 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000160 # Force using only python implementation - otherwise behavior is different
161 # depending on whether m2crypto Python module is present (error is thrown
162 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
163 # the hood.
164 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
165 private=True,
166 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000167 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000168 self.ssl_client_cas = []
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000169 if tls_intolerant == 0:
170 self.tls_intolerant = None
171 else:
172 self.tls_intolerant = (3, tls_intolerant)
ekasper@google.com24aa8222013-11-28 13:43:26 +0000173 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000174 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000175 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000176
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000177 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000178 s = open(ca_file).read()
179 x509 = tlslite.api.X509()
180 x509.parse(s)
181 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000182 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
183 if ssl_bulk_ciphers is not None:
184 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000185
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000186 if record_resume_info:
187 # If record_resume_info is true then we'll replace the session cache with
188 # an object that records the lookups and inserts that it sees.
189 self.session_cache = RecordingSSLSessionCache()
190 else:
191 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000192 testserver_base.StoppableHTTPServer.__init__(self,
193 server_address,
194 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000195
196 def handshake(self, tlsConnection):
197 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000198
initial.commit94958cf2008-07-26 22:42:52 +0000199 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000200 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000201 tlsConnection.handshakeServer(certChain=self.cert_chain,
202 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000203 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000204 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000205 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000206 reqCAs=self.ssl_client_cas,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000207 tlsIntolerant=self.tls_intolerant,
208 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000209 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000210 fallbackSCSV=self.fallback_scsv_enabled,
211 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000212 tlsConnection.ignoreAbruptClose = True
213 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000214 except tlslite.api.TLSAbruptCloseError:
215 # Ignore abrupt close.
216 return True
initial.commit94958cf2008-07-26 22:42:52 +0000217 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000218 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000219 return False
220
akalin@chromium.org154bb132010-11-12 02:20:27 +0000221
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000222class FTPServer(testserver_base.ClientRestrictingServerMixIn,
223 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000224 """This is a specialization of FTPServer that adds client verification."""
225
226 pass
227
228
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000229class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
230 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000231 """A TCP echo server that echoes back what it has received."""
232
233 def server_bind(self):
234 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000235
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000236 SocketServer.TCPServer.server_bind(self)
237 host, port = self.socket.getsockname()[:2]
238 self.server_name = socket.getfqdn(host)
239 self.server_port = port
240
241 def serve_forever(self):
242 self.stop = False
243 self.nonce_time = None
244 while not self.stop:
245 self.handle_request()
246 self.socket.close()
247
248
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000249class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
250 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000251 """A UDP echo server that echoes back what it has received."""
252
253 def server_bind(self):
254 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000255
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000256 SocketServer.UDPServer.server_bind(self)
257 host, port = self.socket.getsockname()[:2]
258 self.server_name = socket.getfqdn(host)
259 self.server_port = port
260
261 def serve_forever(self):
262 self.stop = False
263 self.nonce_time = None
264 while not self.stop:
265 self.handle_request()
266 self.socket.close()
267
268
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000269class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000270 # Class variables to allow for persistence state between page handler
271 # invocations
272 rst_limits = {}
273 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000274
275 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000276 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000277 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000278 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000279 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000280 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000281 self.NoCacheMaxAgeTimeHandler,
282 self.NoCacheTimeHandler,
283 self.CacheTimeHandler,
284 self.CacheExpiresHandler,
285 self.CacheProxyRevalidateHandler,
286 self.CachePrivateHandler,
287 self.CachePublicHandler,
288 self.CacheSMaxAgeHandler,
289 self.CacheMustRevalidateHandler,
290 self.CacheMustRevalidateMaxAgeHandler,
291 self.CacheNoStoreHandler,
292 self.CacheNoStoreMaxAgeHandler,
293 self.CacheNoTransformHandler,
294 self.DownloadHandler,
295 self.DownloadFinishHandler,
296 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000297 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000298 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000299 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000300 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000301 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000302 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000303 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000304 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000305 self.AuthBasicHandler,
306 self.AuthDigestHandler,
307 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000308 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000309 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000310 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000311 self.ServerRedirectHandler,
312 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000313 self.MultipartHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000314 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000315 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000316 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000317 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000318 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000319 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000320 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000321 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000322 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000323 self.PostOnlyFileHandler,
324 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000325 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000326 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000327 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000328 head_handlers = [
329 self.FileHandler,
330 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000331
maruel@google.come250a9b2009-03-10 17:39:46 +0000332 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000333 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000334 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000335 'gif': 'image/gif',
336 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000337 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000338 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000339 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000340 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000341 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000342 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000343 }
initial.commit94958cf2008-07-26 22:42:52 +0000344 self._default_mime_type = 'text/html'
345
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000346 testserver_base.BasePageHandler.__init__(self, request, client_address,
347 socket_server, connect_handlers,
348 get_handlers, head_handlers,
349 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000350
initial.commit94958cf2008-07-26 22:42:52 +0000351 def GetMIMETypeFromName(self, file_name):
352 """Returns the mime type for the specified file_name. So far it only looks
353 at the file extension."""
354
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000355 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000356 if len(extension) == 0:
357 # no extension.
358 return self._default_mime_type
359
ericroman@google.comc17ca532009-05-07 03:51:05 +0000360 # extension starts with a dot, so we need to remove it
361 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000362
initial.commit94958cf2008-07-26 22:42:52 +0000363 def NoCacheMaxAgeTimeHandler(self):
364 """This request handler yields a page with the title set to the current
365 system time, and no caching requested."""
366
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000367 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000368 return False
369
370 self.send_response(200)
371 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000372 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000373 self.end_headers()
374
maruel@google.come250a9b2009-03-10 17:39:46 +0000375 self.wfile.write('<html><head><title>%s</title></head></html>' %
376 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000377
378 return True
379
380 def NoCacheTimeHandler(self):
381 """This request handler yields a page with the title set to the current
382 system time, and no caching requested."""
383
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000384 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000385 return False
386
387 self.send_response(200)
388 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000389 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000390 self.end_headers()
391
maruel@google.come250a9b2009-03-10 17:39:46 +0000392 self.wfile.write('<html><head><title>%s</title></head></html>' %
393 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000394
395 return True
396
397 def CacheTimeHandler(self):
398 """This request handler yields a page with the title set to the current
399 system time, and allows caching for one minute."""
400
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000401 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000402 return False
403
404 self.send_response(200)
405 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000406 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000407 self.end_headers()
408
maruel@google.come250a9b2009-03-10 17:39:46 +0000409 self.wfile.write('<html><head><title>%s</title></head></html>' %
410 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000411
412 return True
413
414 def CacheExpiresHandler(self):
415 """This request handler yields a page with the title set to the current
416 system time, and set the page to expire on 1 Jan 2099."""
417
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000418 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000419 return False
420
421 self.send_response(200)
422 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000423 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000424 self.end_headers()
425
maruel@google.come250a9b2009-03-10 17:39:46 +0000426 self.wfile.write('<html><head><title>%s</title></head></html>' %
427 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000428
429 return True
430
431 def CacheProxyRevalidateHandler(self):
432 """This request handler yields a page with the title set to the current
433 system time, and allows caching for 60 seconds"""
434
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000435 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000436 return False
437
438 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000439 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000440 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
441 self.end_headers()
442
maruel@google.come250a9b2009-03-10 17:39:46 +0000443 self.wfile.write('<html><head><title>%s</title></head></html>' %
444 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000445
446 return True
447
448 def CachePrivateHandler(self):
449 """This request handler yields a page with the title set to the current
450 system time, and allows caching for 5 seconds."""
451
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000452 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000453 return False
454
455 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000456 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000457 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000458 self.end_headers()
459
maruel@google.come250a9b2009-03-10 17:39:46 +0000460 self.wfile.write('<html><head><title>%s</title></head></html>' %
461 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000462
463 return True
464
465 def CachePublicHandler(self):
466 """This request handler yields a page with the title set to the current
467 system time, and allows caching for 5 seconds."""
468
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000469 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000470 return False
471
472 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000473 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000474 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000475 self.end_headers()
476
maruel@google.come250a9b2009-03-10 17:39:46 +0000477 self.wfile.write('<html><head><title>%s</title></head></html>' %
478 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000479
480 return True
481
482 def CacheSMaxAgeHandler(self):
483 """This request handler yields a page with the title set to the current
484 system time, and does not allow for caching."""
485
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000486 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000487 return False
488
489 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000490 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000491 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
492 self.end_headers()
493
maruel@google.come250a9b2009-03-10 17:39:46 +0000494 self.wfile.write('<html><head><title>%s</title></head></html>' %
495 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000496
497 return True
498
499 def CacheMustRevalidateHandler(self):
500 """This request handler yields a page with the title set to the current
501 system time, and does not allow caching."""
502
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000503 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000504 return False
505
506 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000507 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000508 self.send_header('Cache-Control', 'must-revalidate')
509 self.end_headers()
510
maruel@google.come250a9b2009-03-10 17:39:46 +0000511 self.wfile.write('<html><head><title>%s</title></head></html>' %
512 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000513
514 return True
515
516 def CacheMustRevalidateMaxAgeHandler(self):
517 """This request handler yields a page with the title set to the current
518 system time, and does not allow caching event though max-age of 60
519 seconds is specified."""
520
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000521 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000522 return False
523
524 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000525 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000526 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
527 self.end_headers()
528
maruel@google.come250a9b2009-03-10 17:39:46 +0000529 self.wfile.write('<html><head><title>%s</title></head></html>' %
530 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000531
532 return True
533
initial.commit94958cf2008-07-26 22:42:52 +0000534 def CacheNoStoreHandler(self):
535 """This request handler yields a page with the title set to the current
536 system time, and does not allow the page to be stored."""
537
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000538 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000539 return False
540
541 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000542 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000543 self.send_header('Cache-Control', 'no-store')
544 self.end_headers()
545
maruel@google.come250a9b2009-03-10 17:39:46 +0000546 self.wfile.write('<html><head><title>%s</title></head></html>' %
547 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000548
549 return True
550
551 def CacheNoStoreMaxAgeHandler(self):
552 """This request handler yields a page with the title set to the current
553 system time, and does not allow the page to be stored even though max-age
554 of 60 seconds is specified."""
555
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000556 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000557 return False
558
559 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000560 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000561 self.send_header('Cache-Control', 'max-age=60, no-store')
562 self.end_headers()
563
maruel@google.come250a9b2009-03-10 17:39:46 +0000564 self.wfile.write('<html><head><title>%s</title></head></html>' %
565 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000566
567 return True
568
569
570 def CacheNoTransformHandler(self):
571 """This request handler yields a page with the title set to the current
572 system time, and does not allow the content to transformed during
573 user-agent caching"""
574
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000575 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000576 return False
577
578 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000579 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000580 self.send_header('Cache-Control', 'no-transform')
581 self.end_headers()
582
maruel@google.come250a9b2009-03-10 17:39:46 +0000583 self.wfile.write('<html><head><title>%s</title></head></html>' %
584 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000585
586 return True
587
588 def EchoHeader(self):
589 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000590
ananta@chromium.org219b2062009-10-23 16:09:41 +0000591 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000592
ananta@chromium.org56812d02011-04-07 17:52:05 +0000593 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000594 """This function echoes back the value of a specific request header while
595 allowing caching for 16 hours."""
596
ananta@chromium.org56812d02011-04-07 17:52:05 +0000597 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000598
599 def EchoHeaderHelper(self, echo_header):
600 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000601
ananta@chromium.org219b2062009-10-23 16:09:41 +0000602 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000603 return False
604
605 query_char = self.path.find('?')
606 if query_char != -1:
607 header_name = self.path[query_char+1:]
608
609 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000610 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000611 if echo_header == '/echoheadercache':
612 self.send_header('Cache-control', 'max-age=60000')
613 else:
614 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000615 # insert a vary header to properly indicate that the cachability of this
616 # request is subject to value of the request header being echoed.
617 if len(header_name) > 0:
618 self.send_header('Vary', header_name)
619 self.end_headers()
620
621 if len(header_name) > 0:
622 self.wfile.write(self.headers.getheader(header_name))
623
624 return True
625
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000626 def ReadRequestBody(self):
627 """This function reads the body of the current HTTP request, handling
628 both plain and chunked transfer encoded requests."""
629
630 if self.headers.getheader('transfer-encoding') != 'chunked':
631 length = int(self.headers.getheader('content-length'))
632 return self.rfile.read(length)
633
634 # Read the request body as chunks.
635 body = ""
636 while True:
637 line = self.rfile.readline()
638 length = int(line, 16)
639 if length == 0:
640 self.rfile.readline()
641 break
642 body += self.rfile.read(length)
643 self.rfile.read(2)
644 return body
645
initial.commit94958cf2008-07-26 22:42:52 +0000646 def EchoHandler(self):
647 """This handler just echoes back the payload of the request, for testing
648 form submission."""
649
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000650 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000651 return False
652
653 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000654 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000655 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000656 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000657 return True
658
659 def EchoTitleHandler(self):
660 """This handler is like Echo, but sets the page title to the request."""
661
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000662 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000663 return False
664
665 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000666 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000667 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000668 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000669 self.wfile.write('<html><head><title>')
670 self.wfile.write(request)
671 self.wfile.write('</title></head></html>')
672 return True
673
674 def EchoAllHandler(self):
675 """This handler yields a (more) human-readable page listing information
676 about the request header & contents."""
677
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000678 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000679 return False
680
681 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000682 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000683 self.end_headers()
684 self.wfile.write('<html><head><style>'
685 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
686 '</style></head><body>'
687 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000688 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000689 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000690
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000691 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000692 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000693 params = cgi.parse_qs(qs, keep_blank_values=1)
694
695 for param in params:
696 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000697
698 self.wfile.write('</pre>')
699
700 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
701
702 self.wfile.write('</body></html>')
703 return True
704
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000705 def EchoMultipartPostHandler(self):
706 """This handler echoes received multipart post data as json format."""
707
708 if not (self._ShouldHandleRequest("/echomultipartpost") or
709 self._ShouldHandleRequest("/searchbyimage")):
710 return False
711
712 content_type, parameters = cgi.parse_header(
713 self.headers.getheader('content-type'))
714 if content_type == 'multipart/form-data':
715 post_multipart = cgi.parse_multipart(self.rfile, parameters)
716 elif content_type == 'application/x-www-form-urlencoded':
717 raise Exception('POST by application/x-www-form-urlencoded is '
718 'not implemented.')
719 else:
720 post_multipart = {}
721
722 # Since the data can be binary, we encode them by base64.
723 post_multipart_base64_encoded = {}
724 for field, values in post_multipart.items():
725 post_multipart_base64_encoded[field] = [base64.b64encode(value)
726 for value in values]
727
728 result = {'POST_multipart' : post_multipart_base64_encoded}
729
730 self.send_response(200)
731 self.send_header("Content-type", "text/plain")
732 self.end_headers()
733 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
734 return True
735
initial.commit94958cf2008-07-26 22:42:52 +0000736 def DownloadHandler(self):
737 """This handler sends a downloadable file with or without reporting
738 the size (6K)."""
739
740 if self.path.startswith("/download-unknown-size"):
741 send_length = False
742 elif self.path.startswith("/download-known-size"):
743 send_length = True
744 else:
745 return False
746
747 #
748 # The test which uses this functionality is attempting to send
749 # small chunks of data to the client. Use a fairly large buffer
750 # so that we'll fill chrome's IO buffer enough to force it to
751 # actually write the data.
752 # See also the comments in the client-side of this test in
753 # download_uitest.cc
754 #
755 size_chunk1 = 35*1024
756 size_chunk2 = 10*1024
757
758 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000759 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000760 self.send_header('Cache-Control', 'max-age=0')
761 if send_length:
762 self.send_header('Content-Length', size_chunk1 + size_chunk2)
763 self.end_headers()
764
765 # First chunk of data:
766 self.wfile.write("*" * size_chunk1)
767 self.wfile.flush()
768
769 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000770 self.server.wait_for_download = True
771 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000772 self.server.handle_request()
773
774 # Second chunk of data:
775 self.wfile.write("*" * size_chunk2)
776 return True
777
778 def DownloadFinishHandler(self):
779 """This handler just tells the server to finish the current download."""
780
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000781 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000782 return False
783
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000784 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000785 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000786 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000787 self.send_header('Cache-Control', 'max-age=0')
788 self.end_headers()
789 return True
790
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000791 def _ReplaceFileData(self, data, query_parameters):
792 """Replaces matching substrings in a file.
793
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000794 If the 'replace_text' URL query parameter is present, it is expected to be
795 of the form old_text:new_text, which indicates that any old_text strings in
796 the file are replaced with new_text. Multiple 'replace_text' parameters may
797 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000798
799 If the parameters are not present, |data| is returned.
800 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000801
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000802 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000803 replace_text_values = query_dict.get('replace_text', [])
804 for replace_text_value in replace_text_values:
805 replace_text_args = replace_text_value.split(':')
806 if len(replace_text_args) != 2:
807 raise ValueError(
808 'replace_text must be of form old_text:new_text. Actual value: %s' %
809 replace_text_value)
810 old_text_b64, new_text_b64 = replace_text_args
811 old_text = base64.urlsafe_b64decode(old_text_b64)
812 new_text = base64.urlsafe_b64decode(new_text_b64)
813 data = data.replace(old_text, new_text)
814 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000815
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000816 def ZipFileHandler(self):
817 """This handler sends the contents of the requested file in compressed form.
818 Can pass in a parameter that specifies that the content length be
819 C - the compressed size (OK),
820 U - the uncompressed size (Non-standard, but handled),
821 S - less than compressed (OK because we keep going),
822 M - larger than compressed but less than uncompressed (an error),
823 L - larger than uncompressed (an error)
824 Example: compressedfiles/Picture_1.doc?C
825 """
826
827 prefix = "/compressedfiles/"
828 if not self.path.startswith(prefix):
829 return False
830
831 # Consume a request body if present.
832 if self.command == 'POST' or self.command == 'PUT' :
833 self.ReadRequestBody()
834
835 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
836
837 if not query in ('C', 'U', 'S', 'M', 'L'):
838 return False
839
840 sub_path = url_path[len(prefix):]
841 entries = sub_path.split('/')
842 file_path = os.path.join(self.server.data_dir, *entries)
843 if os.path.isdir(file_path):
844 file_path = os.path.join(file_path, 'index.html')
845
846 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000847 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000848 self.send_error(404)
849 return True
850
851 f = open(file_path, "rb")
852 data = f.read()
853 uncompressed_len = len(data)
854 f.close()
855
856 # Compress the data.
857 data = zlib.compress(data)
858 compressed_len = len(data)
859
860 content_length = compressed_len
861 if query == 'U':
862 content_length = uncompressed_len
863 elif query == 'S':
864 content_length = compressed_len / 2
865 elif query == 'M':
866 content_length = (compressed_len + uncompressed_len) / 2
867 elif query == 'L':
868 content_length = compressed_len + uncompressed_len
869
870 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000871 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000872 self.send_header('Content-encoding', 'deflate')
873 self.send_header('Connection', 'close')
874 self.send_header('Content-Length', content_length)
875 self.send_header('ETag', '\'' + file_path + '\'')
876 self.end_headers()
877
878 self.wfile.write(data)
879
880 return True
881
initial.commit94958cf2008-07-26 22:42:52 +0000882 def FileHandler(self):
883 """This handler sends the contents of the requested file. Wow, it's like
884 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000885
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000886 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000887 if not self.path.startswith(prefix):
888 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000889 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000890
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000891 def PostOnlyFileHandler(self):
892 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000893
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000894 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000895 if not self.path.startswith(prefix):
896 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000897 return self._FileHandlerHelper(prefix)
898
899 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000900 request_body = ''
901 if self.command == 'POST' or self.command == 'PUT':
902 # Consume a request body if present.
903 request_body = self.ReadRequestBody()
904
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000905 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000906 query_dict = cgi.parse_qs(query)
907
908 expected_body = query_dict.get('expected_body', [])
909 if expected_body and request_body not in expected_body:
910 self.send_response(404)
911 self.end_headers()
912 self.wfile.write('')
913 return True
914
915 expected_headers = query_dict.get('expected_headers', [])
916 for expected_header in expected_headers:
917 header_name, expected_value = expected_header.split(':')
918 if self.headers.getheader(header_name) != expected_value:
919 self.send_response(404)
920 self.end_headers()
921 self.wfile.write('')
922 return True
923
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000924 sub_path = url_path[len(prefix):]
925 entries = sub_path.split('/')
926 file_path = os.path.join(self.server.data_dir, *entries)
927 if os.path.isdir(file_path):
928 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000929
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000930 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000931 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000932 self.send_error(404)
933 return True
934
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000935 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000936 data = f.read()
937 f.close()
938
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000939 data = self._ReplaceFileData(data, query)
940
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000941 old_protocol_version = self.protocol_version
942
initial.commit94958cf2008-07-26 22:42:52 +0000943 # If file.mock-http-headers exists, it contains the headers we
944 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000945 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000946 if os.path.isfile(headers_path):
947 f = open(headers_path, "r")
948
949 # "HTTP/1.1 200 OK"
950 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000951 http_major, http_minor, status_code = re.findall(
952 'HTTP/(\d+).(\d+) (\d+)', response)[0]
953 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000954 self.send_response(int(status_code))
955
956 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000957 header_values = re.findall('(\S+):\s*(.*)', line)
958 if len(header_values) > 0:
959 # "name: value"
960 name, value = header_values[0]
961 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000962 f.close()
963 else:
964 # Could be more generic once we support mime-type sniffing, but for
965 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000966
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000967 range_header = self.headers.get('Range')
968 if range_header and range_header.startswith('bytes='):
969 # Note this doesn't handle all valid byte range_header values (i.e.
970 # left open ended ones), just enough for what we needed so far.
971 range_header = range_header[6:].split('-')
972 start = int(range_header[0])
973 if range_header[1]:
974 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000975 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000976 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000977
978 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000979 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
980 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000981 self.send_header('Content-Range', content_range)
982 data = data[start: end + 1]
983 else:
984 self.send_response(200)
985
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000986 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000987 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000988 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000989 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000990 self.end_headers()
991
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000992 if (self.command != 'HEAD'):
993 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000994
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000995 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000996 return True
997
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000998 def SetCookieHandler(self):
999 """This handler just sets a cookie, for testing cookie handling."""
1000
1001 if not self._ShouldHandleRequest("/set-cookie"):
1002 return False
1003
1004 query_char = self.path.find('?')
1005 if query_char != -1:
1006 cookie_values = self.path[query_char + 1:].split('&')
1007 else:
1008 cookie_values = ("",)
1009 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001010 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001011 for cookie_value in cookie_values:
1012 self.send_header('Set-Cookie', '%s' % cookie_value)
1013 self.end_headers()
1014 for cookie_value in cookie_values:
1015 self.wfile.write('%s' % cookie_value)
1016 return True
1017
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001018 def SetManyCookiesHandler(self):
1019 """This handler just sets a given number of cookies, for testing handling
1020 of large numbers of cookies."""
1021
1022 if not self._ShouldHandleRequest("/set-many-cookies"):
1023 return False
1024
1025 query_char = self.path.find('?')
1026 if query_char != -1:
1027 num_cookies = int(self.path[query_char + 1:])
1028 else:
1029 num_cookies = 0
1030 self.send_response(200)
1031 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001032 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001033 self.send_header('Set-Cookie', 'a=')
1034 self.end_headers()
1035 self.wfile.write('%d cookies were sent' % num_cookies)
1036 return True
1037
mattm@chromium.org983fc462012-06-30 00:52:08 +00001038 def ExpectAndSetCookieHandler(self):
1039 """Expects some cookies to be sent, and if they are, sets more cookies.
1040
1041 The expect parameter specifies a required cookie. May be specified multiple
1042 times.
1043 The set parameter specifies a cookie to set if all required cookies are
1044 preset. May be specified multiple times.
1045 The data parameter specifies the response body data to be returned."""
1046
1047 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1048 return False
1049
1050 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1051 query_dict = cgi.parse_qs(query)
1052 cookies = set()
1053 if 'Cookie' in self.headers:
1054 cookie_header = self.headers.getheader('Cookie')
1055 cookies.update([s.strip() for s in cookie_header.split(';')])
1056 got_all_expected_cookies = True
1057 for expected_cookie in query_dict.get('expect', []):
1058 if expected_cookie not in cookies:
1059 got_all_expected_cookies = False
1060 self.send_response(200)
1061 self.send_header('Content-Type', 'text/html')
1062 if got_all_expected_cookies:
1063 for cookie_value in query_dict.get('set', []):
1064 self.send_header('Set-Cookie', '%s' % cookie_value)
1065 self.end_headers()
1066 for data_value in query_dict.get('data', []):
1067 self.wfile.write(data_value)
1068 return True
1069
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001070 def SetHeaderHandler(self):
1071 """This handler sets a response header. Parameters are in the
1072 key%3A%20value&key2%3A%20value2 format."""
1073
1074 if not self._ShouldHandleRequest("/set-header"):
1075 return False
1076
1077 query_char = self.path.find('?')
1078 if query_char != -1:
1079 headers_values = self.path[query_char + 1:].split('&')
1080 else:
1081 headers_values = ("",)
1082 self.send_response(200)
1083 self.send_header('Content-Type', 'text/html')
1084 for header_value in headers_values:
1085 header_value = urllib.unquote(header_value)
1086 (key, value) = header_value.split(': ', 1)
1087 self.send_header(key, value)
1088 self.end_headers()
1089 for header_value in headers_values:
1090 self.wfile.write('%s' % header_value)
1091 return True
1092
initial.commit94958cf2008-07-26 22:42:52 +00001093 def AuthBasicHandler(self):
1094 """This handler tests 'Basic' authentication. It just sends a page with
1095 title 'user/pass' if you succeed."""
1096
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001097 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001098 return False
1099
1100 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001101 expected_password = 'secret'
1102 realm = 'testrealm'
1103 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001104
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001105 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1106 query_params = cgi.parse_qs(query, True)
1107 if 'set-cookie-if-challenged' in query_params:
1108 set_cookie_if_challenged = True
1109 if 'password' in query_params:
1110 expected_password = query_params['password'][0]
1111 if 'realm' in query_params:
1112 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001113
initial.commit94958cf2008-07-26 22:42:52 +00001114 auth = self.headers.getheader('authorization')
1115 try:
1116 if not auth:
1117 raise Exception('no auth')
1118 b64str = re.findall(r'Basic (\S+)', auth)[0]
1119 userpass = base64.b64decode(b64str)
1120 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001121 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001122 raise Exception('wrong password')
1123 except Exception, e:
1124 # Authentication failed.
1125 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001126 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001127 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001128 if set_cookie_if_challenged:
1129 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001130 self.end_headers()
1131 self.wfile.write('<html><head>')
1132 self.wfile.write('<title>Denied: %s</title>' % e)
1133 self.wfile.write('</head><body>')
1134 self.wfile.write('auth=%s<p>' % auth)
1135 self.wfile.write('b64str=%s<p>' % b64str)
1136 self.wfile.write('username: %s<p>' % username)
1137 self.wfile.write('userpass: %s<p>' % userpass)
1138 self.wfile.write('password: %s<p>' % password)
1139 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1140 self.wfile.write('</body></html>')
1141 return True
1142
1143 # Authentication successful. (Return a cachable response to allow for
1144 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001145 old_protocol_version = self.protocol_version
1146 self.protocol_version = "HTTP/1.1"
1147
initial.commit94958cf2008-07-26 22:42:52 +00001148 if_none_match = self.headers.getheader('if-none-match')
1149 if if_none_match == "abc":
1150 self.send_response(304)
1151 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001152 elif url_path.endswith(".gif"):
1153 # Using chrome/test/data/google/logo.gif as the test image
1154 test_image_path = ['google', 'logo.gif']
1155 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1156 if not os.path.isfile(gif_path):
1157 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001158 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001159 return True
1160
1161 f = open(gif_path, "rb")
1162 data = f.read()
1163 f.close()
1164
1165 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001166 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001167 self.send_header('Cache-control', 'max-age=60000')
1168 self.send_header('Etag', 'abc')
1169 self.end_headers()
1170 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001171 else:
1172 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001173 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001174 self.send_header('Cache-control', 'max-age=60000')
1175 self.send_header('Etag', 'abc')
1176 self.end_headers()
1177 self.wfile.write('<html><head>')
1178 self.wfile.write('<title>%s/%s</title>' % (username, password))
1179 self.wfile.write('</head><body>')
1180 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001181 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001182 self.wfile.write('</body></html>')
1183
rvargas@google.com54453b72011-05-19 01:11:11 +00001184 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001185 return True
1186
tonyg@chromium.org75054202010-03-31 22:06:10 +00001187 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001188 """Returns a nonce that's stable per request path for the server's lifetime.
1189 This is a fake implementation. A real implementation would only use a given
1190 nonce a single time (hence the name n-once). However, for the purposes of
1191 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001192
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001193 Args:
1194 force_reset: Iff set, the nonce will be changed. Useful for testing the
1195 "stale" response.
1196 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001197
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001198 if force_reset or not self.server.nonce_time:
1199 self.server.nonce_time = time.time()
1200 return hashlib.md5('privatekey%s%d' %
1201 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001202
1203 def AuthDigestHandler(self):
1204 """This handler tests 'Digest' authentication.
1205
1206 It just sends a page with title 'user/pass' if you succeed.
1207
1208 A stale response is sent iff "stale" is present in the request path.
1209 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001210
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001211 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001212 return False
1213
tonyg@chromium.org75054202010-03-31 22:06:10 +00001214 stale = 'stale' in self.path
1215 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001216 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001217 password = 'secret'
1218 realm = 'testrealm'
1219
1220 auth = self.headers.getheader('authorization')
1221 pairs = {}
1222 try:
1223 if not auth:
1224 raise Exception('no auth')
1225 if not auth.startswith('Digest'):
1226 raise Exception('not digest')
1227 # Pull out all the name="value" pairs as a dictionary.
1228 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1229
1230 # Make sure it's all valid.
1231 if pairs['nonce'] != nonce:
1232 raise Exception('wrong nonce')
1233 if pairs['opaque'] != opaque:
1234 raise Exception('wrong opaque')
1235
1236 # Check the 'response' value and make sure it matches our magic hash.
1237 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001238 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001239 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001240 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001241 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001242 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001243 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1244 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001245 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001246
1247 if pairs['response'] != response:
1248 raise Exception('wrong password')
1249 except Exception, e:
1250 # Authentication failed.
1251 self.send_response(401)
1252 hdr = ('Digest '
1253 'realm="%s", '
1254 'domain="/", '
1255 'qop="auth", '
1256 'algorithm=MD5, '
1257 'nonce="%s", '
1258 'opaque="%s"') % (realm, nonce, opaque)
1259 if stale:
1260 hdr += ', stale="TRUE"'
1261 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001262 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001263 self.end_headers()
1264 self.wfile.write('<html><head>')
1265 self.wfile.write('<title>Denied: %s</title>' % e)
1266 self.wfile.write('</head><body>')
1267 self.wfile.write('auth=%s<p>' % auth)
1268 self.wfile.write('pairs=%s<p>' % pairs)
1269 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1270 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1271 self.wfile.write('</body></html>')
1272 return True
1273
1274 # Authentication successful.
1275 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001276 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001277 self.end_headers()
1278 self.wfile.write('<html><head>')
1279 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1280 self.wfile.write('</head><body>')
1281 self.wfile.write('auth=%s<p>' % auth)
1282 self.wfile.write('pairs=%s<p>' % pairs)
1283 self.wfile.write('</body></html>')
1284
1285 return True
1286
1287 def SlowServerHandler(self):
1288 """Wait for the user suggested time before responding. The syntax is
1289 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001290
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001291 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001292 return False
1293 query_char = self.path.find('?')
1294 wait_sec = 1.0
1295 if query_char >= 0:
1296 try:
1297 wait_sec = int(self.path[query_char + 1:])
1298 except ValueError:
1299 pass
1300 time.sleep(wait_sec)
1301 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001302 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001303 self.end_headers()
1304 self.wfile.write("waited %d seconds" % wait_sec)
1305 return True
1306
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001307 def ChunkedServerHandler(self):
1308 """Send chunked response. Allows to specify chunks parameters:
1309 - waitBeforeHeaders - ms to wait before sending headers
1310 - waitBetweenChunks - ms to wait between chunks
1311 - chunkSize - size of each chunk in bytes
1312 - chunksNumber - number of chunks
1313 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1314 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001315
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001316 if not self._ShouldHandleRequest("/chunked"):
1317 return False
1318 query_char = self.path.find('?')
1319 chunkedSettings = {'waitBeforeHeaders' : 0,
1320 'waitBetweenChunks' : 0,
1321 'chunkSize' : 5,
1322 'chunksNumber' : 5}
1323 if query_char >= 0:
1324 params = self.path[query_char + 1:].split('&')
1325 for param in params:
1326 keyValue = param.split('=')
1327 if len(keyValue) == 2:
1328 try:
1329 chunkedSettings[keyValue[0]] = int(keyValue[1])
1330 except ValueError:
1331 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001332 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001333 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1334 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001335 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001336 self.send_header('Connection', 'close')
1337 self.send_header('Transfer-Encoding', 'chunked')
1338 self.end_headers()
1339 # Chunked encoding: sending all chunks, then final zero-length chunk and
1340 # then final CRLF.
1341 for i in range(0, chunkedSettings['chunksNumber']):
1342 if i > 0:
1343 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1344 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001345 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001346 self.sendChunkHelp('')
1347 return True
1348
initial.commit94958cf2008-07-26 22:42:52 +00001349 def ContentTypeHandler(self):
1350 """Returns a string of html with the given content type. E.g.,
1351 /contenttype?text/css returns an html file with the Content-Type
1352 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001353
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001354 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001355 return False
1356 query_char = self.path.find('?')
1357 content_type = self.path[query_char + 1:].strip()
1358 if not content_type:
1359 content_type = 'text/html'
1360 self.send_response(200)
1361 self.send_header('Content-Type', content_type)
1362 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001363 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001364 return True
1365
creis@google.com2f4f6a42011-03-25 19:44:19 +00001366 def NoContentHandler(self):
1367 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001368
creis@google.com2f4f6a42011-03-25 19:44:19 +00001369 if not self._ShouldHandleRequest("/nocontent"):
1370 return False
1371 self.send_response(204)
1372 self.end_headers()
1373 return True
1374
initial.commit94958cf2008-07-26 22:42:52 +00001375 def ServerRedirectHandler(self):
1376 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001377 '/server-redirect?http://foo.bar/asdf' to redirect to
1378 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001379
1380 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001381 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001382 return False
1383
1384 query_char = self.path.find('?')
1385 if query_char < 0 or len(self.path) <= query_char + 1:
1386 self.sendRedirectHelp(test_name)
1387 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001388 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001389
1390 self.send_response(301) # moved permanently
1391 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001392 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001393 self.end_headers()
1394 self.wfile.write('<html><head>')
1395 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1396
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001397 return True
initial.commit94958cf2008-07-26 22:42:52 +00001398
1399 def ClientRedirectHandler(self):
1400 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001401 '/client-redirect?http://foo.bar/asdf' to redirect to
1402 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001403
1404 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001405 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001406 return False
1407
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001408 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001409 if query_char < 0 or len(self.path) <= query_char + 1:
1410 self.sendRedirectHelp(test_name)
1411 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001412 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001413
1414 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001415 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001416 self.end_headers()
1417 self.wfile.write('<html><head>')
1418 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1419 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1420
1421 return True
1422
tony@chromium.org03266982010-03-05 03:18:42 +00001423 def MultipartHandler(self):
1424 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001425
tony@chromium.org4cb88302011-09-27 22:13:49 +00001426 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001427 if not self._ShouldHandleRequest(test_name):
1428 return False
1429
1430 num_frames = 10
1431 bound = '12345'
1432 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001433 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001434 'multipart/x-mixed-replace;boundary=' + bound)
1435 self.end_headers()
1436
1437 for i in xrange(num_frames):
1438 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001439 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001440 self.wfile.write('<title>page ' + str(i) + '</title>')
1441 self.wfile.write('page ' + str(i))
1442
1443 self.wfile.write('--' + bound + '--')
1444 return True
1445
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001446 def GetSSLSessionCacheHandler(self):
1447 """Send a reply containing a log of the session cache operations."""
1448
1449 if not self._ShouldHandleRequest('/ssl-session-cache'):
1450 return False
1451
1452 self.send_response(200)
1453 self.send_header('Content-Type', 'text/plain')
1454 self.end_headers()
1455 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001456 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001457 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001458 self.wfile.write('Pass --https-record-resume in order to use' +
1459 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001460 return True
1461
1462 for (action, sessionID) in log:
1463 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001464 return True
1465
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001466 def SSLManySmallRecords(self):
1467 """Sends a reply consisting of a variety of small writes. These will be
1468 translated into a series of small SSL records when used over an HTTPS
1469 server."""
1470
1471 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1472 return False
1473
1474 self.send_response(200)
1475 self.send_header('Content-Type', 'text/plain')
1476 self.end_headers()
1477
1478 # Write ~26K of data, in 1350 byte chunks
1479 for i in xrange(20):
1480 self.wfile.write('*' * 1350)
1481 self.wfile.flush()
1482 return True
1483
agl@chromium.org04700be2013-03-02 18:40:41 +00001484 def GetChannelID(self):
1485 """Send a reply containing the hashed ChannelID that the client provided."""
1486
1487 if not self._ShouldHandleRequest('/channel-id'):
1488 return False
1489
1490 self.send_response(200)
1491 self.send_header('Content-Type', 'text/plain')
1492 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001493 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001494 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1495 return True
1496
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001497 def CloseSocketHandler(self):
1498 """Closes the socket without sending anything."""
1499
1500 if not self._ShouldHandleRequest('/close-socket'):
1501 return False
1502
1503 self.wfile.close()
1504 return True
1505
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001506 def RangeResetHandler(self):
1507 """Send data broken up by connection resets every N (default 4K) bytes.
1508 Support range requests. If the data requested doesn't straddle a reset
1509 boundary, it will all be sent. Used for testing resuming downloads."""
1510
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001511 def DataForRange(start, end):
1512 """Data to be provided for a particular range of bytes."""
1513 # Offset and scale to avoid too obvious (and hence potentially
1514 # collidable) data.
1515 return ''.join([chr(y % 256)
1516 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1517
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001518 if not self._ShouldHandleRequest('/rangereset'):
1519 return False
1520
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001521 # HTTP/1.1 is required for ETag and range support.
1522 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001523 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1524
1525 # Defaults
1526 size = 8000
1527 # Note that the rst is sent just before sending the rst_boundary byte.
1528 rst_boundary = 4000
1529 respond_to_range = True
1530 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001531 rst_limit = -1
1532 token = 'DEFAULT'
1533 fail_precondition = 0
1534 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001535
1536 # Parse the query
1537 qdict = urlparse.parse_qs(query, True)
1538 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001539 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001540 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001541 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001542 if 'token' in qdict:
1543 # Identifying token for stateful tests.
1544 token = qdict['token'][0]
1545 if 'rst_limit' in qdict:
1546 # Max number of rsts for a given token.
1547 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001548 if 'bounce_range' in qdict:
1549 respond_to_range = False
1550 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001551 # Note that hold_for_signal will not work with null range requests;
1552 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001553 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001554 if 'no_verifiers' in qdict:
1555 send_verifiers = False
1556 if 'fail_precondition' in qdict:
1557 fail_precondition = int(qdict['fail_precondition'][0])
1558
1559 # Record already set information, or set it.
1560 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1561 if rst_limit != 0:
1562 TestPageHandler.rst_limits[token] -= 1
1563 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1564 token, fail_precondition)
1565 if fail_precondition != 0:
1566 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001567
1568 first_byte = 0
1569 last_byte = size - 1
1570
1571 # Does that define what we want to return, or do we need to apply
1572 # a range?
1573 range_response = False
1574 range_header = self.headers.getheader('range')
1575 if range_header and respond_to_range:
1576 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1577 if mo.group(1):
1578 first_byte = int(mo.group(1))
1579 if mo.group(2):
1580 last_byte = int(mo.group(2))
1581 if last_byte > size - 1:
1582 last_byte = size - 1
1583 range_response = True
1584 if last_byte < first_byte:
1585 return False
1586
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001587 if (fail_precondition and
1588 (self.headers.getheader('If-Modified-Since') or
1589 self.headers.getheader('If-Match'))):
1590 self.send_response(412)
1591 self.end_headers()
1592 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001593
1594 if range_response:
1595 self.send_response(206)
1596 self.send_header('Content-Range',
1597 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1598 else:
1599 self.send_response(200)
1600 self.send_header('Content-Type', 'application/octet-stream')
1601 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001602 if send_verifiers:
asanka@chromium.org3ffced92013-12-13 22:20:27 +00001603 # If fail_precondition is non-zero, then the ETag for each request will be
1604 # different.
1605 etag = "%s%d" % (token, fail_precondition)
1606 self.send_header('ETag', etag)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001607 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001608 self.end_headers()
1609
1610 if hold_for_signal:
1611 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1612 # a single byte, the self.server.handle_request() below hangs
1613 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001614 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001615 first_byte = first_byte + 1
1616 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001617 self.server.wait_for_download = True
1618 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001619 self.server.handle_request()
1620
1621 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001622 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001623 # No RST has been requested in this range, so we don't need to
1624 # do anything fancy; just write the data and let the python
1625 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001626 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001627 self.wfile.flush()
1628 return True
1629
1630 # We're resetting the connection part way in; go to the RST
1631 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001632 # Because socket semantics do not guarantee that all the data will be
1633 # sent when using the linger semantics to hard close a socket,
1634 # we send the data and then wait for our peer to release us
1635 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001636 data = DataForRange(first_byte, possible_rst)
1637 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001638 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001639 self.server.wait_for_download = True
1640 while self.server.wait_for_download:
1641 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001642 l_onoff = 1 # Linger is active.
1643 l_linger = 0 # Seconds to linger for.
1644 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1645 struct.pack('ii', l_onoff, l_linger))
1646
1647 # Close all duplicates of the underlying socket to force the RST.
1648 self.wfile.close()
1649 self.rfile.close()
1650 self.connection.close()
1651
1652 return True
1653
initial.commit94958cf2008-07-26 22:42:52 +00001654 def DefaultResponseHandler(self):
1655 """This is the catch-all response handler for requests that aren't handled
1656 by one of the special handlers above.
1657 Note that we specify the content-length as without it the https connection
1658 is not closed properly (and the browser keeps expecting data)."""
1659
1660 contents = "Default response given for path: " + self.path
1661 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001662 self.send_header('Content-Type', 'text/html')
1663 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001664 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001665 if (self.command != 'HEAD'):
1666 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001667 return True
1668
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001669 def RedirectConnectHandler(self):
1670 """Sends a redirect to the CONNECT request for www.redirect.com. This
1671 response is not specified by the RFC, so the browser should not follow
1672 the redirect."""
1673
1674 if (self.path.find("www.redirect.com") < 0):
1675 return False
1676
1677 dest = "http://www.destination.com/foo.js"
1678
1679 self.send_response(302) # moved temporarily
1680 self.send_header('Location', dest)
1681 self.send_header('Connection', 'close')
1682 self.end_headers()
1683 return True
1684
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001685 def ServerAuthConnectHandler(self):
1686 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1687 response doesn't make sense because the proxy server cannot request
1688 server authentication."""
1689
1690 if (self.path.find("www.server-auth.com") < 0):
1691 return False
1692
1693 challenge = 'Basic realm="WallyWorld"'
1694
1695 self.send_response(401) # unauthorized
1696 self.send_header('WWW-Authenticate', challenge)
1697 self.send_header('Connection', 'close')
1698 self.end_headers()
1699 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001700
1701 def DefaultConnectResponseHandler(self):
1702 """This is the catch-all response handler for CONNECT requests that aren't
1703 handled by one of the special handlers above. Real Web servers respond
1704 with 400 to CONNECT requests."""
1705
1706 contents = "Your client has issued a malformed or illegal request."
1707 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001708 self.send_header('Content-Type', 'text/html')
1709 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001710 self.end_headers()
1711 self.wfile.write(contents)
1712 return True
1713
initial.commit94958cf2008-07-26 22:42:52 +00001714 # called by the redirect handling function when there is no parameter
1715 def sendRedirectHelp(self, redirect_name):
1716 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001717 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001718 self.end_headers()
1719 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1720 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1721 self.wfile.write('</body></html>')
1722
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001723 # called by chunked handling function
1724 def sendChunkHelp(self, chunk):
1725 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1726 self.wfile.write('%X\r\n' % len(chunk))
1727 self.wfile.write(chunk)
1728 self.wfile.write('\r\n')
1729
akalin@chromium.org154bb132010-11-12 02:20:27 +00001730
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001731class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001732 def __init__(self, request, client_address, socket_server):
1733 handlers = [self.OCSPResponse]
1734 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001735 testserver_base.BasePageHandler.__init__(self, request, client_address,
1736 socket_server, [], handlers, [],
1737 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001738
1739 def OCSPResponse(self):
1740 self.send_response(200)
1741 self.send_header('Content-Type', 'application/ocsp-response')
1742 self.send_header('Content-Length', str(len(self.ocsp_response)))
1743 self.end_headers()
1744
1745 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001746
mattm@chromium.org830a3712012-11-07 23:00:07 +00001747
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001748class TCPEchoHandler(SocketServer.BaseRequestHandler):
1749 """The RequestHandler class for TCP echo server.
1750
1751 It is instantiated once per connection to the server, and overrides the
1752 handle() method to implement communication to the client.
1753 """
1754
1755 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001756 """Handles the request from the client and constructs a response."""
1757
1758 data = self.request.recv(65536).strip()
1759 # Verify the "echo request" message received from the client. Send back
1760 # "echo response" message if "echo request" message is valid.
1761 try:
1762 return_data = echo_message.GetEchoResponseData(data)
1763 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001764 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001765 except ValueError:
1766 return
1767
1768 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001769
1770
1771class UDPEchoHandler(SocketServer.BaseRequestHandler):
1772 """The RequestHandler class for UDP echo server.
1773
1774 It is instantiated once per connection to the server, and overrides the
1775 handle() method to implement communication to the client.
1776 """
1777
1778 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001779 """Handles the request from the client and constructs a response."""
1780
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001781 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001782 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001783 # Verify the "echo request" message received from the client. Send back
1784 # "echo response" message if "echo request" message is valid.
1785 try:
1786 return_data = echo_message.GetEchoResponseData(data)
1787 if not return_data:
1788 return
1789 except ValueError:
1790 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001791 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001792
1793
bashi@chromium.org33233532012-09-08 17:37:24 +00001794class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1795 """A request handler that behaves as a proxy server which requires
1796 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1797 """
1798
1799 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1800
1801 def parse_request(self):
1802 """Overrides parse_request to check credential."""
1803
1804 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1805 return False
1806
1807 auth = self.headers.getheader('Proxy-Authorization')
1808 if auth != self._AUTH_CREDENTIAL:
1809 self.send_response(407)
1810 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1811 self.end_headers()
1812 return False
1813
1814 return True
1815
1816 def _start_read_write(self, sock):
1817 sock.setblocking(0)
1818 self.request.setblocking(0)
1819 rlist = [self.request, sock]
1820 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001821 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001822 if errors:
1823 self.send_response(500)
1824 self.end_headers()
1825 return
1826 for s in ready_sockets:
1827 received = s.recv(1024)
1828 if len(received) == 0:
1829 return
1830 if s == self.request:
1831 other = sock
1832 else:
1833 other = self.request
1834 other.send(received)
1835
1836 def _do_common_method(self):
1837 url = urlparse.urlparse(self.path)
1838 port = url.port
1839 if not port:
1840 if url.scheme == 'http':
1841 port = 80
1842 elif url.scheme == 'https':
1843 port = 443
1844 if not url.hostname or not port:
1845 self.send_response(400)
1846 self.end_headers()
1847 return
1848
1849 if len(url.path) == 0:
1850 path = '/'
1851 else:
1852 path = url.path
1853 if len(url.query) > 0:
1854 path = '%s?%s' % (url.path, url.query)
1855
1856 sock = None
1857 try:
1858 sock = socket.create_connection((url.hostname, port))
1859 sock.send('%s %s %s\r\n' % (
1860 self.command, path, self.protocol_version))
1861 for header in self.headers.headers:
1862 header = header.strip()
1863 if (header.lower().startswith('connection') or
1864 header.lower().startswith('proxy')):
1865 continue
1866 sock.send('%s\r\n' % header)
1867 sock.send('\r\n')
1868 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001869 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001870 self.send_response(500)
1871 self.end_headers()
1872 finally:
1873 if sock is not None:
1874 sock.close()
1875
1876 def do_CONNECT(self):
1877 try:
1878 pos = self.path.rfind(':')
1879 host = self.path[:pos]
1880 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001881 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001882 self.send_response(400)
1883 self.end_headers()
1884
1885 try:
1886 sock = socket.create_connection((host, port))
1887 self.send_response(200, 'Connection established')
1888 self.end_headers()
1889 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001890 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001891 self.send_response(500)
1892 self.end_headers()
1893 finally:
1894 sock.close()
1895
1896 def do_GET(self):
1897 self._do_common_method()
1898
1899 def do_HEAD(self):
1900 self._do_common_method()
1901
1902
mattm@chromium.org830a3712012-11-07 23:00:07 +00001903class ServerRunner(testserver_base.TestServerRunner):
1904 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001905
mattm@chromium.org830a3712012-11-07 23:00:07 +00001906 def __init__(self):
1907 super(ServerRunner, self).__init__()
1908 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001909
mattm@chromium.org830a3712012-11-07 23:00:07 +00001910 def __make_data_dir(self):
1911 if self.options.data_dir:
1912 if not os.path.isdir(self.options.data_dir):
1913 raise testserver_base.OptionError('specified data dir not found: ' +
1914 self.options.data_dir + ' exiting...')
1915 my_data_dir = self.options.data_dir
1916 else:
1917 # Create the default path to our data dir, relative to the exe dir.
1918 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1919 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001920
mattm@chromium.org830a3712012-11-07 23:00:07 +00001921 #TODO(ibrar): Must use Find* funtion defined in google\tools
1922 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001923
mattm@chromium.org830a3712012-11-07 23:00:07 +00001924 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001925
mattm@chromium.org830a3712012-11-07 23:00:07 +00001926 def create_server(self, server_data):
1927 port = self.options.port
1928 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001929
mattm@chromium.org830a3712012-11-07 23:00:07 +00001930 if self.options.server_type == SERVER_HTTP:
1931 if self.options.https:
1932 pem_cert_and_key = None
1933 if self.options.cert_and_key_file:
1934 if not os.path.isfile(self.options.cert_and_key_file):
1935 raise testserver_base.OptionError(
1936 'specified server cert file not found: ' +
1937 self.options.cert_and_key_file + ' exiting...')
1938 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001939 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001940 # generate a new certificate and run an OCSP server for it.
1941 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001942 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001943 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001944
mattm@chromium.org830a3712012-11-07 23:00:07 +00001945 ocsp_der = None
1946 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001947
mattm@chromium.org830a3712012-11-07 23:00:07 +00001948 if self.options.ocsp == 'ok':
1949 ocsp_state = minica.OCSP_STATE_GOOD
1950 elif self.options.ocsp == 'revoked':
1951 ocsp_state = minica.OCSP_STATE_REVOKED
1952 elif self.options.ocsp == 'invalid':
1953 ocsp_state = minica.OCSP_STATE_INVALID
1954 elif self.options.ocsp == 'unauthorized':
1955 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1956 elif self.options.ocsp == 'unknown':
1957 ocsp_state = minica.OCSP_STATE_UNKNOWN
1958 else:
1959 raise testserver_base.OptionError('unknown OCSP status: ' +
1960 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001961
mattm@chromium.org830a3712012-11-07 23:00:07 +00001962 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1963 subject = "127.0.0.1",
1964 ocsp_url = ("http://%s:%d/ocsp" %
1965 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00001966 ocsp_state = ocsp_state,
1967 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001968
1969 self.__ocsp_server.ocsp_response = ocsp_der
1970
1971 for ca_cert in self.options.ssl_client_ca:
1972 if not os.path.isfile(ca_cert):
1973 raise testserver_base.OptionError(
1974 'specified trusted client CA file not found: ' + ca_cert +
1975 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001976
1977 stapled_ocsp_response = None
1978 if self.__ocsp_server and self.options.staple_ocsp_response:
1979 stapled_ocsp_response = self.__ocsp_server.ocsp_response
1980
mattm@chromium.org830a3712012-11-07 23:00:07 +00001981 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1982 self.options.ssl_client_auth,
1983 self.options.ssl_client_ca,
1984 self.options.ssl_bulk_cipher,
1985 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00001986 self.options.tls_intolerant,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001987 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00001988 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001989 self.options.fallback_scsv,
1990 stapled_ocsp_response)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001991 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001992 else:
1993 server = HTTPServer((host, port), TestPageHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001994 print 'HTTP server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001995
1996 server.data_dir = self.__make_data_dir()
1997 server.file_root_url = self.options.file_root_url
1998 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001999 elif self.options.server_type == SERVER_WEBSOCKET:
2000 # Launch pywebsocket via WebSocketServer.
2001 logger = logging.getLogger()
2002 logger.addHandler(logging.StreamHandler())
2003 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2004 # is required to work correctly. It should be fixed from pywebsocket side.
2005 os.chdir(self.__make_data_dir())
2006 websocket_options = WebSocketOptions(host, port, '.')
2007 if self.options.cert_and_key_file:
2008 websocket_options.use_tls = True
2009 websocket_options.private_key = self.options.cert_and_key_file
2010 websocket_options.certificate = self.options.cert_and_key_file
2011 if self.options.ssl_client_auth:
2012 websocket_options.tls_client_auth = True
2013 if len(self.options.ssl_client_ca) != 1:
2014 raise testserver_base.OptionError(
2015 'one trusted client CA file should be specified')
2016 if not os.path.isfile(self.options.ssl_client_ca[0]):
2017 raise testserver_base.OptionError(
2018 'specified trusted client CA file not found: ' +
2019 self.options.ssl_client_ca[0] + ' exiting...')
2020 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2021 server = WebSocketServer(websocket_options)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002022 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002023 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002024 elif self.options.server_type == SERVER_TCP_ECHO:
2025 # Used for generating the key (randomly) that encodes the "echo request"
2026 # message.
2027 random.seed()
2028 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002029 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002030 server_data['port'] = server.server_port
2031 elif self.options.server_type == SERVER_UDP_ECHO:
2032 # Used for generating the key (randomly) that encodes the "echo request"
2033 # message.
2034 random.seed()
2035 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002036 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002037 server_data['port'] = server.server_port
2038 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2039 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002040 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002041 server_data['port'] = server.server_port
2042 elif self.options.server_type == SERVER_FTP:
2043 my_data_dir = self.__make_data_dir()
2044
2045 # Instantiate a dummy authorizer for managing 'virtual' users
2046 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2047
2048 # Define a new user having full r/w permissions and a read-only
2049 # anonymous user
2050 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2051
2052 authorizer.add_anonymous(my_data_dir)
2053
2054 # Instantiate FTP handler class
2055 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2056 ftp_handler.authorizer = authorizer
2057
2058 # Define a customized banner (string returned when client connects)
2059 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2060 pyftpdlib.ftpserver.__ver__)
2061
2062 # Instantiate FTP server class and listen to address:port
2063 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2064 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002065 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002066 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002067 raise testserver_base.OptionError('unknown server type' +
2068 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002069
mattm@chromium.org830a3712012-11-07 23:00:07 +00002070 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002071
mattm@chromium.org830a3712012-11-07 23:00:07 +00002072 def run_server(self):
2073 if self.__ocsp_server:
2074 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002075
mattm@chromium.org830a3712012-11-07 23:00:07 +00002076 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002077
mattm@chromium.org830a3712012-11-07 23:00:07 +00002078 if self.__ocsp_server:
2079 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002080
mattm@chromium.org830a3712012-11-07 23:00:07 +00002081 def add_options(self):
2082 testserver_base.TestServerRunner.add_options(self)
2083 self.option_parser.add_option('-f', '--ftp', action='store_const',
2084 const=SERVER_FTP, default=SERVER_HTTP,
2085 dest='server_type',
2086 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002087 self.option_parser.add_option('--tcp-echo', action='store_const',
2088 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2089 dest='server_type',
2090 help='start up a tcp echo server.')
2091 self.option_parser.add_option('--udp-echo', action='store_const',
2092 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2093 dest='server_type',
2094 help='start up a udp echo server.')
2095 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2096 const=SERVER_BASIC_AUTH_PROXY,
2097 default=SERVER_HTTP, dest='server_type',
2098 help='start up a proxy server which requires '
2099 'basic authentication.')
2100 self.option_parser.add_option('--websocket', action='store_const',
2101 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2102 dest='server_type',
2103 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002104 self.option_parser.add_option('--https', action='store_true',
2105 dest='https', help='Specify that https '
2106 'should be used.')
2107 self.option_parser.add_option('--cert-and-key-file',
2108 dest='cert_and_key_file', help='specify the '
2109 'path to the file containing the certificate '
2110 'and private key for the server in PEM '
2111 'format')
2112 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2113 help='The type of OCSP response generated '
2114 'for the automatically generated '
2115 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002116 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2117 default=0, type=int,
2118 help='If non-zero then the generated '
2119 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002120 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2121 default='0', type='int',
2122 help='If nonzero, certain TLS connections '
2123 'will be aborted in order to test version '
2124 'fallback. 1 means all TLS versions will be '
2125 'aborted. 2 means TLS 1.1 or higher will be '
2126 'aborted. 3 means TLS 1.2 or higher will be '
2127 'aborted.')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002128 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2129 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002130 default='',
2131 help='Base64 encoded SCT list. If set, '
2132 'server will respond with a '
2133 'signed_certificate_timestamp TLS extension '
2134 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002135 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2136 default=False, const=True,
2137 action='store_const',
2138 help='If given, TLS_FALLBACK_SCSV support '
2139 'will be enabled. This causes the server to '
2140 'reject fallback connections from compatible '
2141 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002142 self.option_parser.add_option('--staple-ocsp-response',
2143 dest='staple_ocsp_response',
2144 default=False, action='store_true',
2145 help='If set, server will staple the OCSP '
2146 'response whenever OCSP is on and the client '
2147 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002148 self.option_parser.add_option('--https-record-resume',
2149 dest='record_resume', const=True,
2150 default=False, action='store_const',
2151 help='Record resumption cache events rather '
2152 'than resuming as normal. Allows the use of '
2153 'the /ssl-session-cache request')
2154 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2155 help='Require SSL client auth on every '
2156 'connection.')
2157 self.option_parser.add_option('--ssl-client-ca', action='append',
2158 default=[], help='Specify that the client '
2159 'certificate request should include the CA '
2160 'named in the subject of the DER-encoded '
2161 'certificate contained in the specified '
2162 'file. This option may appear multiple '
2163 'times, indicating multiple CA names should '
2164 'be sent in the request.')
2165 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2166 help='Specify the bulk encryption '
2167 'algorithm(s) that will be accepted by the '
2168 'SSL server. Valid values are "aes256", '
2169 '"aes128", "3des", "rc4". If omitted, all '
2170 'algorithms will be used. This option may '
2171 'appear multiple times, indicating '
2172 'multiple algorithms should be enabled.');
2173 self.option_parser.add_option('--file-root-url', default='/files/',
2174 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002175
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002176
initial.commit94958cf2008-07-26 22:42:52 +00002177if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002178 sys.exit(ServerRunner().main())