blob: 5d961a8c5fb9f53125aa8b9ba1b265cdc7ce9cbb [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
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000030import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000031import sys
32import threading
initial.commit94958cf2008-07-26 22:42:52 +000033import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000034import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000035import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000036import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000037
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000038BASE_DIR = os.path.dirname(os.path.abspath(__file__))
39ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(BASE_DIR)))
40
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000041import echo_message
mattm@chromium.org830a3712012-11-07 23:00:07 +000042import testserver_base
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000043
44# Append at the end of sys.path, it's fine to use the system library.
45sys.path.append(os.path.join(ROOT_DIR, 'third_party', 'pyftpdlib', 'src'))
46sys.path.append(os.path.join(ROOT_DIR, 'third_party', 'tlslite'))
47import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000048import tlslite
49import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000050
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000051# Insert at the beginning of the path, we want this to be used
52# unconditionally.
53sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'pywebsocket', 'src'))
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000054from mod_pywebsocket.standalone import WebSocketServer
davidben@chromium.org06fcf202010-09-22 18:15:23 +000055
maruel@chromium.org756cf982009-03-05 12:46:38 +000056SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000057SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000058SERVER_TCP_ECHO = 2
59SERVER_UDP_ECHO = 3
60SERVER_BASIC_AUTH_PROXY = 4
61SERVER_WEBSOCKET = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000062
63# Default request queue size for WebSocketServer.
64_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000065
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000066class WebSocketOptions:
67 """Holds options for WebSocketServer."""
68
69 def __init__(self, host, port, data_dir):
70 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
71 self.server_host = host
72 self.port = port
73 self.websock_handlers = data_dir
74 self.scan_dir = None
75 self.allow_handlers_outside_root_dir = False
76 self.websock_handlers_map_file = None
77 self.cgi_directories = []
78 self.is_executable_method = None
79 self.allow_draft75 = False
80 self.strict = True
81
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000082 self.use_tls = False
83 self.private_key = None
84 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000085 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000086 self.tls_client_ca = None
87 self.use_basic_auth = False
88
mattm@chromium.org830a3712012-11-07 23:00:07 +000089
agl@chromium.orgf9e66792011-12-12 22:22:19 +000090class RecordingSSLSessionCache(object):
91 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
92 lookups and inserts in order to test session cache behaviours."""
93
94 def __init__(self):
95 self.log = []
96
97 def __getitem__(self, sessionID):
98 self.log.append(('lookup', sessionID))
99 raise KeyError()
100
101 def __setitem__(self, sessionID, session):
102 self.log.append(('insert', sessionID))
103
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000104
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000105class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
106 testserver_base.BrokenPipeHandlerMixIn,
107 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000108 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000109 verification."""
110
111 pass
112
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000113class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
114 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000115 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000116 """This is a specialization of HTTPServer that serves an
117 OCSP response"""
118
119 def serve_forever_on_thread(self):
120 self.thread = threading.Thread(target = self.serve_forever,
121 name = "OCSPServerThread")
122 self.thread.start()
123
124 def stop_serving(self):
125 self.shutdown()
126 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000127
mattm@chromium.org830a3712012-11-07 23:00:07 +0000128
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000129class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000130 testserver_base.ClientRestrictingServerMixIn,
131 testserver_base.BrokenPipeHandlerMixIn,
132 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000133 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000134 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000135
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000136 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000137 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000138 record_resume_info, tls_intolerant, signed_cert_timestamps,
ekasper@google.com5db96062013-12-13 19:57:48 +0000139 fallback_scsv_enabled, ocsp_response):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000140 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000141 # Force using only python implementation - otherwise behavior is different
142 # depending on whether m2crypto Python module is present (error is thrown
143 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
144 # the hood.
145 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
146 private=True,
147 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000148 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000149 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000150 self.tls_intolerant = tls_intolerant
ekasper@google.com24aa8222013-11-28 13:43:26 +0000151 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000152 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com5db96062013-12-13 19:57:48 +0000153 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000154
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000155 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000156 s = open(ca_file).read()
157 x509 = tlslite.api.X509()
158 x509.parse(s)
159 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000160 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
161 if ssl_bulk_ciphers is not None:
162 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000163
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000164 if record_resume_info:
165 # If record_resume_info is true then we'll replace the session cache with
166 # an object that records the lookups and inserts that it sees.
167 self.session_cache = RecordingSSLSessionCache()
168 else:
169 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000170 testserver_base.StoppableHTTPServer.__init__(self,
171 server_address,
172 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000173
174 def handshake(self, tlsConnection):
175 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000176
initial.commit94958cf2008-07-26 22:42:52 +0000177 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000178 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000179 tlsConnection.handshakeServer(certChain=self.cert_chain,
180 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000181 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000182 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000183 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000184 reqCAs=self.ssl_client_cas,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000185 tlsIntolerant=self.tls_intolerant,
186 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000187 self.signed_cert_timestamps,
ekasper@google.com5db96062013-12-13 19:57:48 +0000188 fallbackSCSV=self.fallback_scsv_enabled,
189 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000190 tlsConnection.ignoreAbruptClose = True
191 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000192 except tlslite.api.TLSAbruptCloseError:
193 # Ignore abrupt close.
194 return True
initial.commit94958cf2008-07-26 22:42:52 +0000195 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000196 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000197 return False
198
akalin@chromium.org154bb132010-11-12 02:20:27 +0000199
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000200class FTPServer(testserver_base.ClientRestrictingServerMixIn,
201 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000202 """This is a specialization of FTPServer that adds client verification."""
203
204 pass
205
206
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000207class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
208 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000209 """A TCP echo server that echoes back what it has received."""
210
211 def server_bind(self):
212 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000213
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000214 SocketServer.TCPServer.server_bind(self)
215 host, port = self.socket.getsockname()[:2]
216 self.server_name = socket.getfqdn(host)
217 self.server_port = port
218
219 def serve_forever(self):
220 self.stop = False
221 self.nonce_time = None
222 while not self.stop:
223 self.handle_request()
224 self.socket.close()
225
226
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000227class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
228 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000229 """A UDP echo server that echoes back what it has received."""
230
231 def server_bind(self):
232 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000233
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000234 SocketServer.UDPServer.server_bind(self)
235 host, port = self.socket.getsockname()[:2]
236 self.server_name = socket.getfqdn(host)
237 self.server_port = port
238
239 def serve_forever(self):
240 self.stop = False
241 self.nonce_time = None
242 while not self.stop:
243 self.handle_request()
244 self.socket.close()
245
246
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000247class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000248 # Class variables to allow for persistence state between page handler
249 # invocations
250 rst_limits = {}
251 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000252
253 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000254 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000255 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000256 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000257 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000258 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000259 self.NoCacheMaxAgeTimeHandler,
260 self.NoCacheTimeHandler,
261 self.CacheTimeHandler,
262 self.CacheExpiresHandler,
263 self.CacheProxyRevalidateHandler,
264 self.CachePrivateHandler,
265 self.CachePublicHandler,
266 self.CacheSMaxAgeHandler,
267 self.CacheMustRevalidateHandler,
268 self.CacheMustRevalidateMaxAgeHandler,
269 self.CacheNoStoreHandler,
270 self.CacheNoStoreMaxAgeHandler,
271 self.CacheNoTransformHandler,
272 self.DownloadHandler,
273 self.DownloadFinishHandler,
274 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000275 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000276 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000277 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000278 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000279 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000280 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000281 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000282 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000283 self.AuthBasicHandler,
284 self.AuthDigestHandler,
285 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000286 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000287 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000288 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000289 self.ServerRedirectHandler,
290 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000291 self.MultipartHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000292 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000293 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000294 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000295 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000296 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000297 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000298 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000299 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000300 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000301 self.PostOnlyFileHandler,
302 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000303 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000304 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000305 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000306 head_handlers = [
307 self.FileHandler,
308 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000309
maruel@google.come250a9b2009-03-10 17:39:46 +0000310 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000311 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000312 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000313 'gif': 'image/gif',
314 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000315 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000316 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000317 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000318 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000319 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000320 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000321 }
initial.commit94958cf2008-07-26 22:42:52 +0000322 self._default_mime_type = 'text/html'
323
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000324 testserver_base.BasePageHandler.__init__(self, request, client_address,
325 socket_server, connect_handlers,
326 get_handlers, head_handlers,
327 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000328
initial.commit94958cf2008-07-26 22:42:52 +0000329 def GetMIMETypeFromName(self, file_name):
330 """Returns the mime type for the specified file_name. So far it only looks
331 at the file extension."""
332
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000333 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000334 if len(extension) == 0:
335 # no extension.
336 return self._default_mime_type
337
ericroman@google.comc17ca532009-05-07 03:51:05 +0000338 # extension starts with a dot, so we need to remove it
339 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000340
initial.commit94958cf2008-07-26 22:42:52 +0000341 def NoCacheMaxAgeTimeHandler(self):
342 """This request handler yields a page with the title set to the current
343 system time, and no caching requested."""
344
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000345 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000346 return False
347
348 self.send_response(200)
349 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000350 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000351 self.end_headers()
352
maruel@google.come250a9b2009-03-10 17:39:46 +0000353 self.wfile.write('<html><head><title>%s</title></head></html>' %
354 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000355
356 return True
357
358 def NoCacheTimeHandler(self):
359 """This request handler yields a page with the title set to the current
360 system time, and no caching requested."""
361
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000362 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000363 return False
364
365 self.send_response(200)
366 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000367 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000368 self.end_headers()
369
maruel@google.come250a9b2009-03-10 17:39:46 +0000370 self.wfile.write('<html><head><title>%s</title></head></html>' %
371 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000372
373 return True
374
375 def CacheTimeHandler(self):
376 """This request handler yields a page with the title set to the current
377 system time, and allows caching for one minute."""
378
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000379 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000380 return False
381
382 self.send_response(200)
383 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000384 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000385 self.end_headers()
386
maruel@google.come250a9b2009-03-10 17:39:46 +0000387 self.wfile.write('<html><head><title>%s</title></head></html>' %
388 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000389
390 return True
391
392 def CacheExpiresHandler(self):
393 """This request handler yields a page with the title set to the current
394 system time, and set the page to expire on 1 Jan 2099."""
395
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000396 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000397 return False
398
399 self.send_response(200)
400 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000401 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000402 self.end_headers()
403
maruel@google.come250a9b2009-03-10 17:39:46 +0000404 self.wfile.write('<html><head><title>%s</title></head></html>' %
405 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000406
407 return True
408
409 def CacheProxyRevalidateHandler(self):
410 """This request handler yields a page with the title set to the current
411 system time, and allows caching for 60 seconds"""
412
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000413 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000414 return False
415
416 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000417 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000418 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
419 self.end_headers()
420
maruel@google.come250a9b2009-03-10 17:39:46 +0000421 self.wfile.write('<html><head><title>%s</title></head></html>' %
422 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000423
424 return True
425
426 def CachePrivateHandler(self):
427 """This request handler yields a page with the title set to the current
428 system time, and allows caching for 5 seconds."""
429
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000430 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000431 return False
432
433 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000434 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000435 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000436 self.end_headers()
437
maruel@google.come250a9b2009-03-10 17:39:46 +0000438 self.wfile.write('<html><head><title>%s</title></head></html>' %
439 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000440
441 return True
442
443 def CachePublicHandler(self):
444 """This request handler yields a page with the title set to the current
445 system time, and allows caching for 5 seconds."""
446
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000447 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000448 return False
449
450 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000451 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000452 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000453 self.end_headers()
454
maruel@google.come250a9b2009-03-10 17:39:46 +0000455 self.wfile.write('<html><head><title>%s</title></head></html>' %
456 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000457
458 return True
459
460 def CacheSMaxAgeHandler(self):
461 """This request handler yields a page with the title set to the current
462 system time, and does not allow for caching."""
463
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000464 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000465 return False
466
467 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000468 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000469 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
470 self.end_headers()
471
maruel@google.come250a9b2009-03-10 17:39:46 +0000472 self.wfile.write('<html><head><title>%s</title></head></html>' %
473 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000474
475 return True
476
477 def CacheMustRevalidateHandler(self):
478 """This request handler yields a page with the title set to the current
479 system time, and does not allow caching."""
480
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000481 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000482 return False
483
484 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000485 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000486 self.send_header('Cache-Control', 'must-revalidate')
487 self.end_headers()
488
maruel@google.come250a9b2009-03-10 17:39:46 +0000489 self.wfile.write('<html><head><title>%s</title></head></html>' %
490 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000491
492 return True
493
494 def CacheMustRevalidateMaxAgeHandler(self):
495 """This request handler yields a page with the title set to the current
496 system time, and does not allow caching event though max-age of 60
497 seconds is specified."""
498
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000499 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000500 return False
501
502 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000503 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000504 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
505 self.end_headers()
506
maruel@google.come250a9b2009-03-10 17:39:46 +0000507 self.wfile.write('<html><head><title>%s</title></head></html>' %
508 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000509
510 return True
511
initial.commit94958cf2008-07-26 22:42:52 +0000512 def CacheNoStoreHandler(self):
513 """This request handler yields a page with the title set to the current
514 system time, and does not allow the page to be stored."""
515
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000516 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000517 return False
518
519 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000520 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000521 self.send_header('Cache-Control', 'no-store')
522 self.end_headers()
523
maruel@google.come250a9b2009-03-10 17:39:46 +0000524 self.wfile.write('<html><head><title>%s</title></head></html>' %
525 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000526
527 return True
528
529 def CacheNoStoreMaxAgeHandler(self):
530 """This request handler yields a page with the title set to the current
531 system time, and does not allow the page to be stored even though max-age
532 of 60 seconds is specified."""
533
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000534 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000535 return False
536
537 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000538 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000539 self.send_header('Cache-Control', 'max-age=60, no-store')
540 self.end_headers()
541
maruel@google.come250a9b2009-03-10 17:39:46 +0000542 self.wfile.write('<html><head><title>%s</title></head></html>' %
543 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000544
545 return True
546
547
548 def CacheNoTransformHandler(self):
549 """This request handler yields a page with the title set to the current
550 system time, and does not allow the content to transformed during
551 user-agent caching"""
552
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000553 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000554 return False
555
556 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000557 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000558 self.send_header('Cache-Control', 'no-transform')
559 self.end_headers()
560
maruel@google.come250a9b2009-03-10 17:39:46 +0000561 self.wfile.write('<html><head><title>%s</title></head></html>' %
562 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000563
564 return True
565
566 def EchoHeader(self):
567 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000568
ananta@chromium.org219b2062009-10-23 16:09:41 +0000569 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000570
ananta@chromium.org56812d02011-04-07 17:52:05 +0000571 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000572 """This function echoes back the value of a specific request header while
573 allowing caching for 16 hours."""
574
ananta@chromium.org56812d02011-04-07 17:52:05 +0000575 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000576
577 def EchoHeaderHelper(self, echo_header):
578 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000579
ananta@chromium.org219b2062009-10-23 16:09:41 +0000580 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000581 return False
582
583 query_char = self.path.find('?')
584 if query_char != -1:
585 header_name = self.path[query_char+1:]
586
587 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000588 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000589 if echo_header == '/echoheadercache':
590 self.send_header('Cache-control', 'max-age=60000')
591 else:
592 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000593 # insert a vary header to properly indicate that the cachability of this
594 # request is subject to value of the request header being echoed.
595 if len(header_name) > 0:
596 self.send_header('Vary', header_name)
597 self.end_headers()
598
599 if len(header_name) > 0:
600 self.wfile.write(self.headers.getheader(header_name))
601
602 return True
603
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000604 def ReadRequestBody(self):
605 """This function reads the body of the current HTTP request, handling
606 both plain and chunked transfer encoded requests."""
607
608 if self.headers.getheader('transfer-encoding') != 'chunked':
609 length = int(self.headers.getheader('content-length'))
610 return self.rfile.read(length)
611
612 # Read the request body as chunks.
613 body = ""
614 while True:
615 line = self.rfile.readline()
616 length = int(line, 16)
617 if length == 0:
618 self.rfile.readline()
619 break
620 body += self.rfile.read(length)
621 self.rfile.read(2)
622 return body
623
initial.commit94958cf2008-07-26 22:42:52 +0000624 def EchoHandler(self):
625 """This handler just echoes back the payload of the request, for testing
626 form submission."""
627
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000628 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000629 return False
630
631 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000632 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000633 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000634 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000635 return True
636
637 def EchoTitleHandler(self):
638 """This handler is like Echo, but sets the page title to the request."""
639
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000640 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000641 return False
642
643 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000644 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000645 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000646 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000647 self.wfile.write('<html><head><title>')
648 self.wfile.write(request)
649 self.wfile.write('</title></head></html>')
650 return True
651
652 def EchoAllHandler(self):
653 """This handler yields a (more) human-readable page listing information
654 about the request header & contents."""
655
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000656 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000657 return False
658
659 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000660 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000661 self.end_headers()
662 self.wfile.write('<html><head><style>'
663 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
664 '</style></head><body>'
665 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000666 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000667 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000668
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000669 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000670 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000671 params = cgi.parse_qs(qs, keep_blank_values=1)
672
673 for param in params:
674 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000675
676 self.wfile.write('</pre>')
677
678 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
679
680 self.wfile.write('</body></html>')
681 return True
682
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000683 def EchoMultipartPostHandler(self):
684 """This handler echoes received multipart post data as json format."""
685
686 if not (self._ShouldHandleRequest("/echomultipartpost") or
687 self._ShouldHandleRequest("/searchbyimage")):
688 return False
689
690 content_type, parameters = cgi.parse_header(
691 self.headers.getheader('content-type'))
692 if content_type == 'multipart/form-data':
693 post_multipart = cgi.parse_multipart(self.rfile, parameters)
694 elif content_type == 'application/x-www-form-urlencoded':
695 raise Exception('POST by application/x-www-form-urlencoded is '
696 'not implemented.')
697 else:
698 post_multipart = {}
699
700 # Since the data can be binary, we encode them by base64.
701 post_multipart_base64_encoded = {}
702 for field, values in post_multipart.items():
703 post_multipart_base64_encoded[field] = [base64.b64encode(value)
704 for value in values]
705
706 result = {'POST_multipart' : post_multipart_base64_encoded}
707
708 self.send_response(200)
709 self.send_header("Content-type", "text/plain")
710 self.end_headers()
711 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
712 return True
713
initial.commit94958cf2008-07-26 22:42:52 +0000714 def DownloadHandler(self):
715 """This handler sends a downloadable file with or without reporting
716 the size (6K)."""
717
718 if self.path.startswith("/download-unknown-size"):
719 send_length = False
720 elif self.path.startswith("/download-known-size"):
721 send_length = True
722 else:
723 return False
724
725 #
726 # The test which uses this functionality is attempting to send
727 # small chunks of data to the client. Use a fairly large buffer
728 # so that we'll fill chrome's IO buffer enough to force it to
729 # actually write the data.
730 # See also the comments in the client-side of this test in
731 # download_uitest.cc
732 #
733 size_chunk1 = 35*1024
734 size_chunk2 = 10*1024
735
736 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000737 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000738 self.send_header('Cache-Control', 'max-age=0')
739 if send_length:
740 self.send_header('Content-Length', size_chunk1 + size_chunk2)
741 self.end_headers()
742
743 # First chunk of data:
744 self.wfile.write("*" * size_chunk1)
745 self.wfile.flush()
746
747 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000748 self.server.wait_for_download = True
749 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000750 self.server.handle_request()
751
752 # Second chunk of data:
753 self.wfile.write("*" * size_chunk2)
754 return True
755
756 def DownloadFinishHandler(self):
757 """This handler just tells the server to finish the current download."""
758
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000759 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000760 return False
761
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000762 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000763 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000764 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000765 self.send_header('Cache-Control', 'max-age=0')
766 self.end_headers()
767 return True
768
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000769 def _ReplaceFileData(self, data, query_parameters):
770 """Replaces matching substrings in a file.
771
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000772 If the 'replace_text' URL query parameter is present, it is expected to be
773 of the form old_text:new_text, which indicates that any old_text strings in
774 the file are replaced with new_text. Multiple 'replace_text' parameters may
775 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000776
777 If the parameters are not present, |data| is returned.
778 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000779
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000780 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000781 replace_text_values = query_dict.get('replace_text', [])
782 for replace_text_value in replace_text_values:
783 replace_text_args = replace_text_value.split(':')
784 if len(replace_text_args) != 2:
785 raise ValueError(
786 'replace_text must be of form old_text:new_text. Actual value: %s' %
787 replace_text_value)
788 old_text_b64, new_text_b64 = replace_text_args
789 old_text = base64.urlsafe_b64decode(old_text_b64)
790 new_text = base64.urlsafe_b64decode(new_text_b64)
791 data = data.replace(old_text, new_text)
792 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000793
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000794 def ZipFileHandler(self):
795 """This handler sends the contents of the requested file in compressed form.
796 Can pass in a parameter that specifies that the content length be
797 C - the compressed size (OK),
798 U - the uncompressed size (Non-standard, but handled),
799 S - less than compressed (OK because we keep going),
800 M - larger than compressed but less than uncompressed (an error),
801 L - larger than uncompressed (an error)
802 Example: compressedfiles/Picture_1.doc?C
803 """
804
805 prefix = "/compressedfiles/"
806 if not self.path.startswith(prefix):
807 return False
808
809 # Consume a request body if present.
810 if self.command == 'POST' or self.command == 'PUT' :
811 self.ReadRequestBody()
812
813 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
814
815 if not query in ('C', 'U', 'S', 'M', 'L'):
816 return False
817
818 sub_path = url_path[len(prefix):]
819 entries = sub_path.split('/')
820 file_path = os.path.join(self.server.data_dir, *entries)
821 if os.path.isdir(file_path):
822 file_path = os.path.join(file_path, 'index.html')
823
824 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000825 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000826 self.send_error(404)
827 return True
828
829 f = open(file_path, "rb")
830 data = f.read()
831 uncompressed_len = len(data)
832 f.close()
833
834 # Compress the data.
835 data = zlib.compress(data)
836 compressed_len = len(data)
837
838 content_length = compressed_len
839 if query == 'U':
840 content_length = uncompressed_len
841 elif query == 'S':
842 content_length = compressed_len / 2
843 elif query == 'M':
844 content_length = (compressed_len + uncompressed_len) / 2
845 elif query == 'L':
846 content_length = compressed_len + uncompressed_len
847
848 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000849 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000850 self.send_header('Content-encoding', 'deflate')
851 self.send_header('Connection', 'close')
852 self.send_header('Content-Length', content_length)
853 self.send_header('ETag', '\'' + file_path + '\'')
854 self.end_headers()
855
856 self.wfile.write(data)
857
858 return True
859
initial.commit94958cf2008-07-26 22:42:52 +0000860 def FileHandler(self):
861 """This handler sends the contents of the requested file. Wow, it's like
862 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000863
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000864 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000865 if not self.path.startswith(prefix):
866 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000867 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000868
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000869 def PostOnlyFileHandler(self):
870 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000871
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000872 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000873 if not self.path.startswith(prefix):
874 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000875 return self._FileHandlerHelper(prefix)
876
877 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000878 request_body = ''
879 if self.command == 'POST' or self.command == 'PUT':
880 # Consume a request body if present.
881 request_body = self.ReadRequestBody()
882
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000883 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000884 query_dict = cgi.parse_qs(query)
885
886 expected_body = query_dict.get('expected_body', [])
887 if expected_body and request_body not in expected_body:
888 self.send_response(404)
889 self.end_headers()
890 self.wfile.write('')
891 return True
892
893 expected_headers = query_dict.get('expected_headers', [])
894 for expected_header in expected_headers:
895 header_name, expected_value = expected_header.split(':')
896 if self.headers.getheader(header_name) != expected_value:
897 self.send_response(404)
898 self.end_headers()
899 self.wfile.write('')
900 return True
901
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000902 sub_path = url_path[len(prefix):]
903 entries = sub_path.split('/')
904 file_path = os.path.join(self.server.data_dir, *entries)
905 if os.path.isdir(file_path):
906 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000907
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000908 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000909 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000910 self.send_error(404)
911 return True
912
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000913 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000914 data = f.read()
915 f.close()
916
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000917 data = self._ReplaceFileData(data, query)
918
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000919 old_protocol_version = self.protocol_version
920
initial.commit94958cf2008-07-26 22:42:52 +0000921 # If file.mock-http-headers exists, it contains the headers we
922 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000923 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000924 if os.path.isfile(headers_path):
925 f = open(headers_path, "r")
926
927 # "HTTP/1.1 200 OK"
928 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000929 http_major, http_minor, status_code = re.findall(
930 'HTTP/(\d+).(\d+) (\d+)', response)[0]
931 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000932 self.send_response(int(status_code))
933
934 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000935 header_values = re.findall('(\S+):\s*(.*)', line)
936 if len(header_values) > 0:
937 # "name: value"
938 name, value = header_values[0]
939 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000940 f.close()
941 else:
942 # Could be more generic once we support mime-type sniffing, but for
943 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000944
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000945 range_header = self.headers.get('Range')
946 if range_header and range_header.startswith('bytes='):
947 # Note this doesn't handle all valid byte range_header values (i.e.
948 # left open ended ones), just enough for what we needed so far.
949 range_header = range_header[6:].split('-')
950 start = int(range_header[0])
951 if range_header[1]:
952 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000953 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000954 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000955
956 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000957 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
958 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000959 self.send_header('Content-Range', content_range)
960 data = data[start: end + 1]
961 else:
962 self.send_response(200)
963
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000964 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000965 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000966 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000967 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000968 self.end_headers()
969
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000970 if (self.command != 'HEAD'):
971 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000972
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000973 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000974 return True
975
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000976 def SetCookieHandler(self):
977 """This handler just sets a cookie, for testing cookie handling."""
978
979 if not self._ShouldHandleRequest("/set-cookie"):
980 return False
981
982 query_char = self.path.find('?')
983 if query_char != -1:
984 cookie_values = self.path[query_char + 1:].split('&')
985 else:
986 cookie_values = ("",)
987 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000988 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000989 for cookie_value in cookie_values:
990 self.send_header('Set-Cookie', '%s' % cookie_value)
991 self.end_headers()
992 for cookie_value in cookie_values:
993 self.wfile.write('%s' % cookie_value)
994 return True
995
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000996 def SetManyCookiesHandler(self):
997 """This handler just sets a given number of cookies, for testing handling
998 of large numbers of cookies."""
999
1000 if not self._ShouldHandleRequest("/set-many-cookies"):
1001 return False
1002
1003 query_char = self.path.find('?')
1004 if query_char != -1:
1005 num_cookies = int(self.path[query_char + 1:])
1006 else:
1007 num_cookies = 0
1008 self.send_response(200)
1009 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001010 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001011 self.send_header('Set-Cookie', 'a=')
1012 self.end_headers()
1013 self.wfile.write('%d cookies were sent' % num_cookies)
1014 return True
1015
mattm@chromium.org983fc462012-06-30 00:52:08 +00001016 def ExpectAndSetCookieHandler(self):
1017 """Expects some cookies to be sent, and if they are, sets more cookies.
1018
1019 The expect parameter specifies a required cookie. May be specified multiple
1020 times.
1021 The set parameter specifies a cookie to set if all required cookies are
1022 preset. May be specified multiple times.
1023 The data parameter specifies the response body data to be returned."""
1024
1025 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1026 return False
1027
1028 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1029 query_dict = cgi.parse_qs(query)
1030 cookies = set()
1031 if 'Cookie' in self.headers:
1032 cookie_header = self.headers.getheader('Cookie')
1033 cookies.update([s.strip() for s in cookie_header.split(';')])
1034 got_all_expected_cookies = True
1035 for expected_cookie in query_dict.get('expect', []):
1036 if expected_cookie not in cookies:
1037 got_all_expected_cookies = False
1038 self.send_response(200)
1039 self.send_header('Content-Type', 'text/html')
1040 if got_all_expected_cookies:
1041 for cookie_value in query_dict.get('set', []):
1042 self.send_header('Set-Cookie', '%s' % cookie_value)
1043 self.end_headers()
1044 for data_value in query_dict.get('data', []):
1045 self.wfile.write(data_value)
1046 return True
1047
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001048 def SetHeaderHandler(self):
1049 """This handler sets a response header. Parameters are in the
1050 key%3A%20value&key2%3A%20value2 format."""
1051
1052 if not self._ShouldHandleRequest("/set-header"):
1053 return False
1054
1055 query_char = self.path.find('?')
1056 if query_char != -1:
1057 headers_values = self.path[query_char + 1:].split('&')
1058 else:
1059 headers_values = ("",)
1060 self.send_response(200)
1061 self.send_header('Content-Type', 'text/html')
1062 for header_value in headers_values:
1063 header_value = urllib.unquote(header_value)
1064 (key, value) = header_value.split(': ', 1)
1065 self.send_header(key, value)
1066 self.end_headers()
1067 for header_value in headers_values:
1068 self.wfile.write('%s' % header_value)
1069 return True
1070
initial.commit94958cf2008-07-26 22:42:52 +00001071 def AuthBasicHandler(self):
1072 """This handler tests 'Basic' authentication. It just sends a page with
1073 title 'user/pass' if you succeed."""
1074
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001075 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001076 return False
1077
1078 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001079 expected_password = 'secret'
1080 realm = 'testrealm'
1081 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001082
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001083 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1084 query_params = cgi.parse_qs(query, True)
1085 if 'set-cookie-if-challenged' in query_params:
1086 set_cookie_if_challenged = True
1087 if 'password' in query_params:
1088 expected_password = query_params['password'][0]
1089 if 'realm' in query_params:
1090 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001091
initial.commit94958cf2008-07-26 22:42:52 +00001092 auth = self.headers.getheader('authorization')
1093 try:
1094 if not auth:
1095 raise Exception('no auth')
1096 b64str = re.findall(r'Basic (\S+)', auth)[0]
1097 userpass = base64.b64decode(b64str)
1098 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001099 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001100 raise Exception('wrong password')
1101 except Exception, e:
1102 # Authentication failed.
1103 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001104 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001105 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001106 if set_cookie_if_challenged:
1107 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001108 self.end_headers()
1109 self.wfile.write('<html><head>')
1110 self.wfile.write('<title>Denied: %s</title>' % e)
1111 self.wfile.write('</head><body>')
1112 self.wfile.write('auth=%s<p>' % auth)
1113 self.wfile.write('b64str=%s<p>' % b64str)
1114 self.wfile.write('username: %s<p>' % username)
1115 self.wfile.write('userpass: %s<p>' % userpass)
1116 self.wfile.write('password: %s<p>' % password)
1117 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1118 self.wfile.write('</body></html>')
1119 return True
1120
1121 # Authentication successful. (Return a cachable response to allow for
1122 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001123 old_protocol_version = self.protocol_version
1124 self.protocol_version = "HTTP/1.1"
1125
initial.commit94958cf2008-07-26 22:42:52 +00001126 if_none_match = self.headers.getheader('if-none-match')
1127 if if_none_match == "abc":
1128 self.send_response(304)
1129 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001130 elif url_path.endswith(".gif"):
1131 # Using chrome/test/data/google/logo.gif as the test image
1132 test_image_path = ['google', 'logo.gif']
1133 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1134 if not os.path.isfile(gif_path):
1135 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001136 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001137 return True
1138
1139 f = open(gif_path, "rb")
1140 data = f.read()
1141 f.close()
1142
1143 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001144 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001145 self.send_header('Cache-control', 'max-age=60000')
1146 self.send_header('Etag', 'abc')
1147 self.end_headers()
1148 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001149 else:
1150 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001151 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001152 self.send_header('Cache-control', 'max-age=60000')
1153 self.send_header('Etag', 'abc')
1154 self.end_headers()
1155 self.wfile.write('<html><head>')
1156 self.wfile.write('<title>%s/%s</title>' % (username, password))
1157 self.wfile.write('</head><body>')
1158 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001159 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001160 self.wfile.write('</body></html>')
1161
rvargas@google.com54453b72011-05-19 01:11:11 +00001162 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001163 return True
1164
tonyg@chromium.org75054202010-03-31 22:06:10 +00001165 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001166 """Returns a nonce that's stable per request path for the server's lifetime.
1167 This is a fake implementation. A real implementation would only use a given
1168 nonce a single time (hence the name n-once). However, for the purposes of
1169 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001170
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001171 Args:
1172 force_reset: Iff set, the nonce will be changed. Useful for testing the
1173 "stale" response.
1174 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001175
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001176 if force_reset or not self.server.nonce_time:
1177 self.server.nonce_time = time.time()
1178 return hashlib.md5('privatekey%s%d' %
1179 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001180
1181 def AuthDigestHandler(self):
1182 """This handler tests 'Digest' authentication.
1183
1184 It just sends a page with title 'user/pass' if you succeed.
1185
1186 A stale response is sent iff "stale" is present in the request path.
1187 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001188
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001189 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001190 return False
1191
tonyg@chromium.org75054202010-03-31 22:06:10 +00001192 stale = 'stale' in self.path
1193 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001194 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001195 password = 'secret'
1196 realm = 'testrealm'
1197
1198 auth = self.headers.getheader('authorization')
1199 pairs = {}
1200 try:
1201 if not auth:
1202 raise Exception('no auth')
1203 if not auth.startswith('Digest'):
1204 raise Exception('not digest')
1205 # Pull out all the name="value" pairs as a dictionary.
1206 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1207
1208 # Make sure it's all valid.
1209 if pairs['nonce'] != nonce:
1210 raise Exception('wrong nonce')
1211 if pairs['opaque'] != opaque:
1212 raise Exception('wrong opaque')
1213
1214 # Check the 'response' value and make sure it matches our magic hash.
1215 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001216 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001217 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001218 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001219 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001220 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001221 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1222 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001223 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001224
1225 if pairs['response'] != response:
1226 raise Exception('wrong password')
1227 except Exception, e:
1228 # Authentication failed.
1229 self.send_response(401)
1230 hdr = ('Digest '
1231 'realm="%s", '
1232 'domain="/", '
1233 'qop="auth", '
1234 'algorithm=MD5, '
1235 'nonce="%s", '
1236 'opaque="%s"') % (realm, nonce, opaque)
1237 if stale:
1238 hdr += ', stale="TRUE"'
1239 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001240 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001241 self.end_headers()
1242 self.wfile.write('<html><head>')
1243 self.wfile.write('<title>Denied: %s</title>' % e)
1244 self.wfile.write('</head><body>')
1245 self.wfile.write('auth=%s<p>' % auth)
1246 self.wfile.write('pairs=%s<p>' % pairs)
1247 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1248 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1249 self.wfile.write('</body></html>')
1250 return True
1251
1252 # Authentication successful.
1253 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001254 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001255 self.end_headers()
1256 self.wfile.write('<html><head>')
1257 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1258 self.wfile.write('</head><body>')
1259 self.wfile.write('auth=%s<p>' % auth)
1260 self.wfile.write('pairs=%s<p>' % pairs)
1261 self.wfile.write('</body></html>')
1262
1263 return True
1264
1265 def SlowServerHandler(self):
1266 """Wait for the user suggested time before responding. The syntax is
1267 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001268
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001269 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001270 return False
1271 query_char = self.path.find('?')
1272 wait_sec = 1.0
1273 if query_char >= 0:
1274 try:
1275 wait_sec = int(self.path[query_char + 1:])
1276 except ValueError:
1277 pass
1278 time.sleep(wait_sec)
1279 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001280 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001281 self.end_headers()
1282 self.wfile.write("waited %d seconds" % wait_sec)
1283 return True
1284
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001285 def ChunkedServerHandler(self):
1286 """Send chunked response. Allows to specify chunks parameters:
1287 - waitBeforeHeaders - ms to wait before sending headers
1288 - waitBetweenChunks - ms to wait between chunks
1289 - chunkSize - size of each chunk in bytes
1290 - chunksNumber - number of chunks
1291 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1292 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001293
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001294 if not self._ShouldHandleRequest("/chunked"):
1295 return False
1296 query_char = self.path.find('?')
1297 chunkedSettings = {'waitBeforeHeaders' : 0,
1298 'waitBetweenChunks' : 0,
1299 'chunkSize' : 5,
1300 'chunksNumber' : 5}
1301 if query_char >= 0:
1302 params = self.path[query_char + 1:].split('&')
1303 for param in params:
1304 keyValue = param.split('=')
1305 if len(keyValue) == 2:
1306 try:
1307 chunkedSettings[keyValue[0]] = int(keyValue[1])
1308 except ValueError:
1309 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001310 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001311 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1312 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001313 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001314 self.send_header('Connection', 'close')
1315 self.send_header('Transfer-Encoding', 'chunked')
1316 self.end_headers()
1317 # Chunked encoding: sending all chunks, then final zero-length chunk and
1318 # then final CRLF.
1319 for i in range(0, chunkedSettings['chunksNumber']):
1320 if i > 0:
1321 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1322 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001323 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001324 self.sendChunkHelp('')
1325 return True
1326
initial.commit94958cf2008-07-26 22:42:52 +00001327 def ContentTypeHandler(self):
1328 """Returns a string of html with the given content type. E.g.,
1329 /contenttype?text/css returns an html file with the Content-Type
1330 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001331
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001332 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001333 return False
1334 query_char = self.path.find('?')
1335 content_type = self.path[query_char + 1:].strip()
1336 if not content_type:
1337 content_type = 'text/html'
1338 self.send_response(200)
1339 self.send_header('Content-Type', content_type)
1340 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001341 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001342 return True
1343
creis@google.com2f4f6a42011-03-25 19:44:19 +00001344 def NoContentHandler(self):
1345 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001346
creis@google.com2f4f6a42011-03-25 19:44:19 +00001347 if not self._ShouldHandleRequest("/nocontent"):
1348 return False
1349 self.send_response(204)
1350 self.end_headers()
1351 return True
1352
initial.commit94958cf2008-07-26 22:42:52 +00001353 def ServerRedirectHandler(self):
1354 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001355 '/server-redirect?http://foo.bar/asdf' to redirect to
1356 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001357
1358 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001359 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001360 return False
1361
1362 query_char = self.path.find('?')
1363 if query_char < 0 or len(self.path) <= query_char + 1:
1364 self.sendRedirectHelp(test_name)
1365 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001366 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001367
1368 self.send_response(301) # moved permanently
1369 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001370 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001371 self.end_headers()
1372 self.wfile.write('<html><head>')
1373 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1374
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001375 return True
initial.commit94958cf2008-07-26 22:42:52 +00001376
1377 def ClientRedirectHandler(self):
1378 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001379 '/client-redirect?http://foo.bar/asdf' to redirect to
1380 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001381
1382 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001383 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001384 return False
1385
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001386 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001387 if query_char < 0 or len(self.path) <= query_char + 1:
1388 self.sendRedirectHelp(test_name)
1389 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001390 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001391
1392 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001393 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001394 self.end_headers()
1395 self.wfile.write('<html><head>')
1396 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1397 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1398
1399 return True
1400
tony@chromium.org03266982010-03-05 03:18:42 +00001401 def MultipartHandler(self):
1402 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001403
tony@chromium.org4cb88302011-09-27 22:13:49 +00001404 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001405 if not self._ShouldHandleRequest(test_name):
1406 return False
1407
1408 num_frames = 10
1409 bound = '12345'
1410 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001411 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001412 'multipart/x-mixed-replace;boundary=' + bound)
1413 self.end_headers()
1414
1415 for i in xrange(num_frames):
1416 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001417 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001418 self.wfile.write('<title>page ' + str(i) + '</title>')
1419 self.wfile.write('page ' + str(i))
1420
1421 self.wfile.write('--' + bound + '--')
1422 return True
1423
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001424 def GetSSLSessionCacheHandler(self):
1425 """Send a reply containing a log of the session cache operations."""
1426
1427 if not self._ShouldHandleRequest('/ssl-session-cache'):
1428 return False
1429
1430 self.send_response(200)
1431 self.send_header('Content-Type', 'text/plain')
1432 self.end_headers()
1433 try:
1434 for (action, sessionID) in self.server.session_cache.log:
1435 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001436 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001437 self.wfile.write('Pass --https-record-resume in order to use' +
1438 ' this request')
1439 return True
1440
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001441 def SSLManySmallRecords(self):
1442 """Sends a reply consisting of a variety of small writes. These will be
1443 translated into a series of small SSL records when used over an HTTPS
1444 server."""
1445
1446 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1447 return False
1448
1449 self.send_response(200)
1450 self.send_header('Content-Type', 'text/plain')
1451 self.end_headers()
1452
1453 # Write ~26K of data, in 1350 byte chunks
1454 for i in xrange(20):
1455 self.wfile.write('*' * 1350)
1456 self.wfile.flush()
1457 return True
1458
agl@chromium.org04700be2013-03-02 18:40:41 +00001459 def GetChannelID(self):
1460 """Send a reply containing the hashed ChannelID that the client provided."""
1461
1462 if not self._ShouldHandleRequest('/channel-id'):
1463 return False
1464
1465 self.send_response(200)
1466 self.send_header('Content-Type', 'text/plain')
1467 self.end_headers()
1468 channel_id = self.server.tlsConnection.channel_id.tostring()
1469 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1470 return True
1471
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001472 def CloseSocketHandler(self):
1473 """Closes the socket without sending anything."""
1474
1475 if not self._ShouldHandleRequest('/close-socket'):
1476 return False
1477
1478 self.wfile.close()
1479 return True
1480
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001481 def RangeResetHandler(self):
1482 """Send data broken up by connection resets every N (default 4K) bytes.
1483 Support range requests. If the data requested doesn't straddle a reset
1484 boundary, it will all be sent. Used for testing resuming downloads."""
1485
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001486 def DataForRange(start, end):
1487 """Data to be provided for a particular range of bytes."""
1488 # Offset and scale to avoid too obvious (and hence potentially
1489 # collidable) data.
1490 return ''.join([chr(y % 256)
1491 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1492
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001493 if not self._ShouldHandleRequest('/rangereset'):
1494 return False
1495
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001496 # HTTP/1.1 is required for ETag and range support.
1497 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001498 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1499
1500 # Defaults
1501 size = 8000
1502 # Note that the rst is sent just before sending the rst_boundary byte.
1503 rst_boundary = 4000
1504 respond_to_range = True
1505 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001506 rst_limit = -1
1507 token = 'DEFAULT'
1508 fail_precondition = 0
1509 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001510
1511 # Parse the query
1512 qdict = urlparse.parse_qs(query, True)
1513 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001514 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001515 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001516 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001517 if 'token' in qdict:
1518 # Identifying token for stateful tests.
1519 token = qdict['token'][0]
1520 if 'rst_limit' in qdict:
1521 # Max number of rsts for a given token.
1522 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001523 if 'bounce_range' in qdict:
1524 respond_to_range = False
1525 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001526 # Note that hold_for_signal will not work with null range requests;
1527 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001528 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001529 if 'no_verifiers' in qdict:
1530 send_verifiers = False
1531 if 'fail_precondition' in qdict:
1532 fail_precondition = int(qdict['fail_precondition'][0])
1533
1534 # Record already set information, or set it.
1535 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1536 if rst_limit != 0:
1537 TestPageHandler.rst_limits[token] -= 1
1538 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1539 token, fail_precondition)
1540 if fail_precondition != 0:
1541 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001542
1543 first_byte = 0
1544 last_byte = size - 1
1545
1546 # Does that define what we want to return, or do we need to apply
1547 # a range?
1548 range_response = False
1549 range_header = self.headers.getheader('range')
1550 if range_header and respond_to_range:
1551 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1552 if mo.group(1):
1553 first_byte = int(mo.group(1))
1554 if mo.group(2):
1555 last_byte = int(mo.group(2))
1556 if last_byte > size - 1:
1557 last_byte = size - 1
1558 range_response = True
1559 if last_byte < first_byte:
1560 return False
1561
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001562 if (fail_precondition and
1563 (self.headers.getheader('If-Modified-Since') or
1564 self.headers.getheader('If-Match'))):
1565 self.send_response(412)
1566 self.end_headers()
1567 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001568
1569 if range_response:
1570 self.send_response(206)
1571 self.send_header('Content-Range',
1572 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1573 else:
1574 self.send_response(200)
1575 self.send_header('Content-Type', 'application/octet-stream')
1576 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001577 if send_verifiers:
1578 self.send_header('Etag', '"XYZZY"')
1579 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001580 self.end_headers()
1581
1582 if hold_for_signal:
1583 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1584 # a single byte, the self.server.handle_request() below hangs
1585 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001586 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001587 first_byte = first_byte + 1
1588 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001589 self.server.wait_for_download = True
1590 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001591 self.server.handle_request()
1592
1593 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001594 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001595 # No RST has been requested in this range, so we don't need to
1596 # do anything fancy; just write the data and let the python
1597 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001598 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001599 self.wfile.flush()
1600 return True
1601
1602 # We're resetting the connection part way in; go to the RST
1603 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001604 # Because socket semantics do not guarantee that all the data will be
1605 # sent when using the linger semantics to hard close a socket,
1606 # we send the data and then wait for our peer to release us
1607 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001608 data = DataForRange(first_byte, possible_rst)
1609 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001610 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001611 self.server.wait_for_download = True
1612 while self.server.wait_for_download:
1613 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001614 l_onoff = 1 # Linger is active.
1615 l_linger = 0 # Seconds to linger for.
1616 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1617 struct.pack('ii', l_onoff, l_linger))
1618
1619 # Close all duplicates of the underlying socket to force the RST.
1620 self.wfile.close()
1621 self.rfile.close()
1622 self.connection.close()
1623
1624 return True
1625
initial.commit94958cf2008-07-26 22:42:52 +00001626 def DefaultResponseHandler(self):
1627 """This is the catch-all response handler for requests that aren't handled
1628 by one of the special handlers above.
1629 Note that we specify the content-length as without it the https connection
1630 is not closed properly (and the browser keeps expecting data)."""
1631
1632 contents = "Default response given for path: " + self.path
1633 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001634 self.send_header('Content-Type', 'text/html')
1635 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001636 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001637 if (self.command != 'HEAD'):
1638 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001639 return True
1640
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001641 def RedirectConnectHandler(self):
1642 """Sends a redirect to the CONNECT request for www.redirect.com. This
1643 response is not specified by the RFC, so the browser should not follow
1644 the redirect."""
1645
1646 if (self.path.find("www.redirect.com") < 0):
1647 return False
1648
1649 dest = "http://www.destination.com/foo.js"
1650
1651 self.send_response(302) # moved temporarily
1652 self.send_header('Location', dest)
1653 self.send_header('Connection', 'close')
1654 self.end_headers()
1655 return True
1656
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001657 def ServerAuthConnectHandler(self):
1658 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1659 response doesn't make sense because the proxy server cannot request
1660 server authentication."""
1661
1662 if (self.path.find("www.server-auth.com") < 0):
1663 return False
1664
1665 challenge = 'Basic realm="WallyWorld"'
1666
1667 self.send_response(401) # unauthorized
1668 self.send_header('WWW-Authenticate', challenge)
1669 self.send_header('Connection', 'close')
1670 self.end_headers()
1671 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001672
1673 def DefaultConnectResponseHandler(self):
1674 """This is the catch-all response handler for CONNECT requests that aren't
1675 handled by one of the special handlers above. Real Web servers respond
1676 with 400 to CONNECT requests."""
1677
1678 contents = "Your client has issued a malformed or illegal request."
1679 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001680 self.send_header('Content-Type', 'text/html')
1681 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001682 self.end_headers()
1683 self.wfile.write(contents)
1684 return True
1685
initial.commit94958cf2008-07-26 22:42:52 +00001686 # called by the redirect handling function when there is no parameter
1687 def sendRedirectHelp(self, redirect_name):
1688 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001689 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001690 self.end_headers()
1691 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1692 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1693 self.wfile.write('</body></html>')
1694
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001695 # called by chunked handling function
1696 def sendChunkHelp(self, chunk):
1697 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1698 self.wfile.write('%X\r\n' % len(chunk))
1699 self.wfile.write(chunk)
1700 self.wfile.write('\r\n')
1701
akalin@chromium.org154bb132010-11-12 02:20:27 +00001702
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001703class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001704 def __init__(self, request, client_address, socket_server):
1705 handlers = [self.OCSPResponse]
1706 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001707 testserver_base.BasePageHandler.__init__(self, request, client_address,
1708 socket_server, [], handlers, [],
1709 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001710
1711 def OCSPResponse(self):
1712 self.send_response(200)
1713 self.send_header('Content-Type', 'application/ocsp-response')
1714 self.send_header('Content-Length', str(len(self.ocsp_response)))
1715 self.end_headers()
1716
1717 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001718
mattm@chromium.org830a3712012-11-07 23:00:07 +00001719
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001720class TCPEchoHandler(SocketServer.BaseRequestHandler):
1721 """The RequestHandler class for TCP echo server.
1722
1723 It is instantiated once per connection to the server, and overrides the
1724 handle() method to implement communication to the client.
1725 """
1726
1727 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001728 """Handles the request from the client and constructs a response."""
1729
1730 data = self.request.recv(65536).strip()
1731 # Verify the "echo request" message received from the client. Send back
1732 # "echo response" message if "echo request" message is valid.
1733 try:
1734 return_data = echo_message.GetEchoResponseData(data)
1735 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001736 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001737 except ValueError:
1738 return
1739
1740 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001741
1742
1743class UDPEchoHandler(SocketServer.BaseRequestHandler):
1744 """The RequestHandler class for UDP echo server.
1745
1746 It is instantiated once per connection to the server, and overrides the
1747 handle() method to implement communication to the client.
1748 """
1749
1750 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001751 """Handles the request from the client and constructs a response."""
1752
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001753 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001754 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001755 # Verify the "echo request" message received from the client. Send back
1756 # "echo response" message if "echo request" message is valid.
1757 try:
1758 return_data = echo_message.GetEchoResponseData(data)
1759 if not return_data:
1760 return
1761 except ValueError:
1762 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001763 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001764
1765
bashi@chromium.org33233532012-09-08 17:37:24 +00001766class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1767 """A request handler that behaves as a proxy server which requires
1768 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1769 """
1770
1771 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1772
1773 def parse_request(self):
1774 """Overrides parse_request to check credential."""
1775
1776 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1777 return False
1778
1779 auth = self.headers.getheader('Proxy-Authorization')
1780 if auth != self._AUTH_CREDENTIAL:
1781 self.send_response(407)
1782 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1783 self.end_headers()
1784 return False
1785
1786 return True
1787
1788 def _start_read_write(self, sock):
1789 sock.setblocking(0)
1790 self.request.setblocking(0)
1791 rlist = [self.request, sock]
1792 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001793 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001794 if errors:
1795 self.send_response(500)
1796 self.end_headers()
1797 return
1798 for s in ready_sockets:
1799 received = s.recv(1024)
1800 if len(received) == 0:
1801 return
1802 if s == self.request:
1803 other = sock
1804 else:
1805 other = self.request
1806 other.send(received)
1807
1808 def _do_common_method(self):
1809 url = urlparse.urlparse(self.path)
1810 port = url.port
1811 if not port:
1812 if url.scheme == 'http':
1813 port = 80
1814 elif url.scheme == 'https':
1815 port = 443
1816 if not url.hostname or not port:
1817 self.send_response(400)
1818 self.end_headers()
1819 return
1820
1821 if len(url.path) == 0:
1822 path = '/'
1823 else:
1824 path = url.path
1825 if len(url.query) > 0:
1826 path = '%s?%s' % (url.path, url.query)
1827
1828 sock = None
1829 try:
1830 sock = socket.create_connection((url.hostname, port))
1831 sock.send('%s %s %s\r\n' % (
1832 self.command, path, self.protocol_version))
1833 for header in self.headers.headers:
1834 header = header.strip()
1835 if (header.lower().startswith('connection') or
1836 header.lower().startswith('proxy')):
1837 continue
1838 sock.send('%s\r\n' % header)
1839 sock.send('\r\n')
1840 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001841 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001842 self.send_response(500)
1843 self.end_headers()
1844 finally:
1845 if sock is not None:
1846 sock.close()
1847
1848 def do_CONNECT(self):
1849 try:
1850 pos = self.path.rfind(':')
1851 host = self.path[:pos]
1852 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001853 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001854 self.send_response(400)
1855 self.end_headers()
1856
1857 try:
1858 sock = socket.create_connection((host, port))
1859 self.send_response(200, 'Connection established')
1860 self.end_headers()
1861 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001862 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001863 self.send_response(500)
1864 self.end_headers()
1865 finally:
1866 sock.close()
1867
1868 def do_GET(self):
1869 self._do_common_method()
1870
1871 def do_HEAD(self):
1872 self._do_common_method()
1873
1874
mattm@chromium.org830a3712012-11-07 23:00:07 +00001875class ServerRunner(testserver_base.TestServerRunner):
1876 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001877
mattm@chromium.org830a3712012-11-07 23:00:07 +00001878 def __init__(self):
1879 super(ServerRunner, self).__init__()
1880 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001881
mattm@chromium.org830a3712012-11-07 23:00:07 +00001882 def __make_data_dir(self):
1883 if self.options.data_dir:
1884 if not os.path.isdir(self.options.data_dir):
1885 raise testserver_base.OptionError('specified data dir not found: ' +
1886 self.options.data_dir + ' exiting...')
1887 my_data_dir = self.options.data_dir
1888 else:
1889 # Create the default path to our data dir, relative to the exe dir.
1890 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1891 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001892
mattm@chromium.org830a3712012-11-07 23:00:07 +00001893 #TODO(ibrar): Must use Find* funtion defined in google\tools
1894 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001895
mattm@chromium.org830a3712012-11-07 23:00:07 +00001896 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001897
mattm@chromium.org830a3712012-11-07 23:00:07 +00001898 def create_server(self, server_data):
1899 port = self.options.port
1900 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001901
mattm@chromium.org830a3712012-11-07 23:00:07 +00001902 if self.options.server_type == SERVER_HTTP:
1903 if self.options.https:
1904 pem_cert_and_key = None
1905 if self.options.cert_and_key_file:
1906 if not os.path.isfile(self.options.cert_and_key_file):
1907 raise testserver_base.OptionError(
1908 'specified server cert file not found: ' +
1909 self.options.cert_and_key_file + ' exiting...')
1910 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001911 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001912 # generate a new certificate and run an OCSP server for it.
1913 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001914 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001915 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001916
mattm@chromium.org830a3712012-11-07 23:00:07 +00001917 ocsp_der = None
1918 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001919
mattm@chromium.org830a3712012-11-07 23:00:07 +00001920 if self.options.ocsp == 'ok':
1921 ocsp_state = minica.OCSP_STATE_GOOD
1922 elif self.options.ocsp == 'revoked':
1923 ocsp_state = minica.OCSP_STATE_REVOKED
1924 elif self.options.ocsp == 'invalid':
1925 ocsp_state = minica.OCSP_STATE_INVALID
1926 elif self.options.ocsp == 'unauthorized':
1927 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1928 elif self.options.ocsp == 'unknown':
1929 ocsp_state = minica.OCSP_STATE_UNKNOWN
1930 else:
1931 raise testserver_base.OptionError('unknown OCSP status: ' +
1932 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001933
mattm@chromium.org830a3712012-11-07 23:00:07 +00001934 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1935 subject = "127.0.0.1",
1936 ocsp_url = ("http://%s:%d/ocsp" %
1937 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00001938 ocsp_state = ocsp_state,
1939 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001940
1941 self.__ocsp_server.ocsp_response = ocsp_der
1942
1943 for ca_cert in self.options.ssl_client_ca:
1944 if not os.path.isfile(ca_cert):
1945 raise testserver_base.OptionError(
1946 'specified trusted client CA file not found: ' + ca_cert +
1947 ' exiting...')
ekasper@google.com5db96062013-12-13 19:57:48 +00001948
1949 stapled_ocsp_response = None
1950 if self.__ocsp_server and self.options.staple_ocsp_response:
1951 stapled_ocsp_response = self.__ocsp_server.ocsp_response
1952
mattm@chromium.org830a3712012-11-07 23:00:07 +00001953 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1954 self.options.ssl_client_auth,
1955 self.options.ssl_client_ca,
1956 self.options.ssl_bulk_cipher,
1957 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00001958 self.options.tls_intolerant,
ekasper@google.com5db96062013-12-13 19:57:48 +00001959 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00001960 "base64"),
ekasper@google.com5db96062013-12-13 19:57:48 +00001961 self.options.fallback_scsv,
1962 stapled_ocsp_response)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001963 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001964 else:
1965 server = HTTPServer((host, port), TestPageHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001966 print 'HTTP server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001967
1968 server.data_dir = self.__make_data_dir()
1969 server.file_root_url = self.options.file_root_url
1970 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001971 elif self.options.server_type == SERVER_WEBSOCKET:
1972 # Launch pywebsocket via WebSocketServer.
1973 logger = logging.getLogger()
1974 logger.addHandler(logging.StreamHandler())
1975 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1976 # is required to work correctly. It should be fixed from pywebsocket side.
1977 os.chdir(self.__make_data_dir())
1978 websocket_options = WebSocketOptions(host, port, '.')
1979 if self.options.cert_and_key_file:
1980 websocket_options.use_tls = True
1981 websocket_options.private_key = self.options.cert_and_key_file
1982 websocket_options.certificate = self.options.cert_and_key_file
1983 if self.options.ssl_client_auth:
1984 websocket_options.tls_client_auth = True
1985 if len(self.options.ssl_client_ca) != 1:
1986 raise testserver_base.OptionError(
1987 'one trusted client CA file should be specified')
1988 if not os.path.isfile(self.options.ssl_client_ca[0]):
1989 raise testserver_base.OptionError(
1990 'specified trusted client CA file not found: ' +
1991 self.options.ssl_client_ca[0] + ' exiting...')
1992 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
1993 server = WebSocketServer(websocket_options)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001994 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001995 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001996 elif self.options.server_type == SERVER_TCP_ECHO:
1997 # Used for generating the key (randomly) that encodes the "echo request"
1998 # message.
1999 random.seed()
2000 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002001 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002002 server_data['port'] = server.server_port
2003 elif self.options.server_type == SERVER_UDP_ECHO:
2004 # Used for generating the key (randomly) that encodes the "echo request"
2005 # message.
2006 random.seed()
2007 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002008 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002009 server_data['port'] = server.server_port
2010 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2011 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002012 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002013 server_data['port'] = server.server_port
2014 elif self.options.server_type == SERVER_FTP:
2015 my_data_dir = self.__make_data_dir()
2016
2017 # Instantiate a dummy authorizer for managing 'virtual' users
2018 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2019
2020 # Define a new user having full r/w permissions and a read-only
2021 # anonymous user
2022 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2023
2024 authorizer.add_anonymous(my_data_dir)
2025
2026 # Instantiate FTP handler class
2027 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2028 ftp_handler.authorizer = authorizer
2029
2030 # Define a customized banner (string returned when client connects)
2031 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2032 pyftpdlib.ftpserver.__ver__)
2033
2034 # Instantiate FTP server class and listen to address:port
2035 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2036 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002037 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002038 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002039 raise testserver_base.OptionError('unknown server type' +
2040 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002041
mattm@chromium.org830a3712012-11-07 23:00:07 +00002042 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002043
mattm@chromium.org830a3712012-11-07 23:00:07 +00002044 def run_server(self):
2045 if self.__ocsp_server:
2046 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002047
mattm@chromium.org830a3712012-11-07 23:00:07 +00002048 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002049
mattm@chromium.org830a3712012-11-07 23:00:07 +00002050 if self.__ocsp_server:
2051 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002052
mattm@chromium.org830a3712012-11-07 23:00:07 +00002053 def add_options(self):
2054 testserver_base.TestServerRunner.add_options(self)
2055 self.option_parser.add_option('-f', '--ftp', action='store_const',
2056 const=SERVER_FTP, default=SERVER_HTTP,
2057 dest='server_type',
2058 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002059 self.option_parser.add_option('--tcp-echo', action='store_const',
2060 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2061 dest='server_type',
2062 help='start up a tcp echo server.')
2063 self.option_parser.add_option('--udp-echo', action='store_const',
2064 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2065 dest='server_type',
2066 help='start up a udp echo server.')
2067 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2068 const=SERVER_BASIC_AUTH_PROXY,
2069 default=SERVER_HTTP, dest='server_type',
2070 help='start up a proxy server which requires '
2071 'basic authentication.')
2072 self.option_parser.add_option('--websocket', action='store_const',
2073 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2074 dest='server_type',
2075 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002076 self.option_parser.add_option('--https', action='store_true',
2077 dest='https', help='Specify that https '
2078 'should be used.')
2079 self.option_parser.add_option('--cert-and-key-file',
2080 dest='cert_and_key_file', help='specify the '
2081 'path to the file containing the certificate '
2082 'and private key for the server in PEM '
2083 'format')
2084 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2085 help='The type of OCSP response generated '
2086 'for the automatically generated '
2087 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002088 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2089 default=0, type=int,
2090 help='If non-zero then the generated '
2091 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002092 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2093 default='0', type='int',
2094 help='If nonzero, certain TLS connections '
2095 'will be aborted in order to test version '
2096 'fallback. 1 means all TLS versions will be '
2097 'aborted. 2 means TLS 1.1 or higher will be '
2098 'aborted. 3 means TLS 1.2 or higher will be '
2099 'aborted.')
ekasper@google.com5db96062013-12-13 19:57:48 +00002100 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2101 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002102 default='',
2103 help='Base64 encoded SCT list. If set, '
2104 'server will respond with a '
2105 'signed_certificate_timestamp TLS extension '
2106 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002107 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2108 default=False, const=True,
2109 action='store_const',
2110 help='If given, TLS_FALLBACK_SCSV support '
2111 'will be enabled. This causes the server to '
2112 'reject fallback connections from compatible '
2113 'clients (e.g. Chrome).')
ekasper@google.com5db96062013-12-13 19:57:48 +00002114 self.option_parser.add_option('--staple-ocsp-response',
2115 dest='staple_ocsp_response',
2116 default=False, action='store_true',
2117 help='If set, server will staple the OCSP '
2118 'response whenever OCSP is on and the client '
2119 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002120 self.option_parser.add_option('--https-record-resume',
2121 dest='record_resume', const=True,
2122 default=False, action='store_const',
2123 help='Record resumption cache events rather '
2124 'than resuming as normal. Allows the use of '
2125 'the /ssl-session-cache request')
2126 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2127 help='Require SSL client auth on every '
2128 'connection.')
2129 self.option_parser.add_option('--ssl-client-ca', action='append',
2130 default=[], help='Specify that the client '
2131 'certificate request should include the CA '
2132 'named in the subject of the DER-encoded '
2133 'certificate contained in the specified '
2134 'file. This option may appear multiple '
2135 'times, indicating multiple CA names should '
2136 'be sent in the request.')
2137 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2138 help='Specify the bulk encryption '
2139 'algorithm(s) that will be accepted by the '
2140 'SSL server. Valid values are "aes256", '
2141 '"aes128", "3des", "rc4". If omitted, all '
2142 'algorithms will be used. This option may '
2143 'appear multiple times, indicating '
2144 'multiple algorithms should be enabled.');
2145 self.option_parser.add_option('--file-root-url', default='/files/',
2146 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002147
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002148
initial.commit94958cf2008-07-26 22:42:52 +00002149if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002150 sys.exit(ServerRunner().main())