blob: f50dd0c8e06c6339449996978ed95565c5a0b71f [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,
szym@chromium.org3be0d452013-12-13 20:27:14 +0000139 fallback_scsv_enabled):
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
agl@chromium.org143daa42012-04-26 18:45:34 +0000153
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000154 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000155 s = open(ca_file).read()
156 x509 = tlslite.api.X509()
157 x509.parse(s)
158 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000159 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
160 if ssl_bulk_ciphers is not None:
161 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000162
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000163 if record_resume_info:
164 # If record_resume_info is true then we'll replace the session cache with
165 # an object that records the lookups and inserts that it sees.
166 self.session_cache = RecordingSSLSessionCache()
167 else:
168 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000169 testserver_base.StoppableHTTPServer.__init__(self,
170 server_address,
171 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000172
173 def handshake(self, tlsConnection):
174 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000175
initial.commit94958cf2008-07-26 22:42:52 +0000176 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000177 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000178 tlsConnection.handshakeServer(certChain=self.cert_chain,
179 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000180 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000181 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000182 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000183 reqCAs=self.ssl_client_cas,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000184 tlsIntolerant=self.tls_intolerant,
185 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000186 self.signed_cert_timestamps,
szym@chromium.org3be0d452013-12-13 20:27:14 +0000187 fallbackSCSV=self.fallback_scsv_enabled)
initial.commit94958cf2008-07-26 22:42:52 +0000188 tlsConnection.ignoreAbruptClose = True
189 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000190 except tlslite.api.TLSAbruptCloseError:
191 # Ignore abrupt close.
192 return True
initial.commit94958cf2008-07-26 22:42:52 +0000193 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000194 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000195 return False
196
akalin@chromium.org154bb132010-11-12 02:20:27 +0000197
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000198class FTPServer(testserver_base.ClientRestrictingServerMixIn,
199 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000200 """This is a specialization of FTPServer that adds client verification."""
201
202 pass
203
204
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000205class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
206 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000207 """A TCP echo server that echoes back what it has received."""
208
209 def server_bind(self):
210 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000211
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000212 SocketServer.TCPServer.server_bind(self)
213 host, port = self.socket.getsockname()[:2]
214 self.server_name = socket.getfqdn(host)
215 self.server_port = port
216
217 def serve_forever(self):
218 self.stop = False
219 self.nonce_time = None
220 while not self.stop:
221 self.handle_request()
222 self.socket.close()
223
224
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000225class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
226 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000227 """A UDP echo server that echoes back what it has received."""
228
229 def server_bind(self):
230 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000231
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000232 SocketServer.UDPServer.server_bind(self)
233 host, port = self.socket.getsockname()[:2]
234 self.server_name = socket.getfqdn(host)
235 self.server_port = port
236
237 def serve_forever(self):
238 self.stop = False
239 self.nonce_time = None
240 while not self.stop:
241 self.handle_request()
242 self.socket.close()
243
244
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000245class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000246 # Class variables to allow for persistence state between page handler
247 # invocations
248 rst_limits = {}
249 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000250
251 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000252 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000253 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000254 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000255 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000256 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000257 self.NoCacheMaxAgeTimeHandler,
258 self.NoCacheTimeHandler,
259 self.CacheTimeHandler,
260 self.CacheExpiresHandler,
261 self.CacheProxyRevalidateHandler,
262 self.CachePrivateHandler,
263 self.CachePublicHandler,
264 self.CacheSMaxAgeHandler,
265 self.CacheMustRevalidateHandler,
266 self.CacheMustRevalidateMaxAgeHandler,
267 self.CacheNoStoreHandler,
268 self.CacheNoStoreMaxAgeHandler,
269 self.CacheNoTransformHandler,
270 self.DownloadHandler,
271 self.DownloadFinishHandler,
272 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000273 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000274 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000275 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000276 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000277 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000278 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000279 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000280 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000281 self.AuthBasicHandler,
282 self.AuthDigestHandler,
283 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000284 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000285 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000286 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000287 self.ServerRedirectHandler,
288 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000289 self.MultipartHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000290 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000291 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000292 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000293 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000294 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000295 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000296 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000297 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000298 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000299 self.PostOnlyFileHandler,
300 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000301 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000302 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000303 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000304 head_handlers = [
305 self.FileHandler,
306 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000307
maruel@google.come250a9b2009-03-10 17:39:46 +0000308 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000309 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000310 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000311 'gif': 'image/gif',
312 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000313 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000314 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000315 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000316 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000317 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000318 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000319 }
initial.commit94958cf2008-07-26 22:42:52 +0000320 self._default_mime_type = 'text/html'
321
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000322 testserver_base.BasePageHandler.__init__(self, request, client_address,
323 socket_server, connect_handlers,
324 get_handlers, head_handlers,
325 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000326
initial.commit94958cf2008-07-26 22:42:52 +0000327 def GetMIMETypeFromName(self, file_name):
328 """Returns the mime type for the specified file_name. So far it only looks
329 at the file extension."""
330
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000331 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000332 if len(extension) == 0:
333 # no extension.
334 return self._default_mime_type
335
ericroman@google.comc17ca532009-05-07 03:51:05 +0000336 # extension starts with a dot, so we need to remove it
337 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000338
initial.commit94958cf2008-07-26 22:42:52 +0000339 def NoCacheMaxAgeTimeHandler(self):
340 """This request handler yields a page with the title set to the current
341 system time, and no caching requested."""
342
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000343 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000344 return False
345
346 self.send_response(200)
347 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000348 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000349 self.end_headers()
350
maruel@google.come250a9b2009-03-10 17:39:46 +0000351 self.wfile.write('<html><head><title>%s</title></head></html>' %
352 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000353
354 return True
355
356 def NoCacheTimeHandler(self):
357 """This request handler yields a page with the title set to the current
358 system time, and no caching requested."""
359
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000360 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000361 return False
362
363 self.send_response(200)
364 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000365 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000366 self.end_headers()
367
maruel@google.come250a9b2009-03-10 17:39:46 +0000368 self.wfile.write('<html><head><title>%s</title></head></html>' %
369 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000370
371 return True
372
373 def CacheTimeHandler(self):
374 """This request handler yields a page with the title set to the current
375 system time, and allows caching for one minute."""
376
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000377 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000378 return False
379
380 self.send_response(200)
381 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000382 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000383 self.end_headers()
384
maruel@google.come250a9b2009-03-10 17:39:46 +0000385 self.wfile.write('<html><head><title>%s</title></head></html>' %
386 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000387
388 return True
389
390 def CacheExpiresHandler(self):
391 """This request handler yields a page with the title set to the current
392 system time, and set the page to expire on 1 Jan 2099."""
393
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000394 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000395 return False
396
397 self.send_response(200)
398 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000399 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000400 self.end_headers()
401
maruel@google.come250a9b2009-03-10 17:39:46 +0000402 self.wfile.write('<html><head><title>%s</title></head></html>' %
403 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000404
405 return True
406
407 def CacheProxyRevalidateHandler(self):
408 """This request handler yields a page with the title set to the current
409 system time, and allows caching for 60 seconds"""
410
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000411 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000412 return False
413
414 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000415 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000416 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
417 self.end_headers()
418
maruel@google.come250a9b2009-03-10 17:39:46 +0000419 self.wfile.write('<html><head><title>%s</title></head></html>' %
420 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000421
422 return True
423
424 def CachePrivateHandler(self):
425 """This request handler yields a page with the title set to the current
426 system time, and allows caching for 5 seconds."""
427
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000428 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000429 return False
430
431 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000432 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000433 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000434 self.end_headers()
435
maruel@google.come250a9b2009-03-10 17:39:46 +0000436 self.wfile.write('<html><head><title>%s</title></head></html>' %
437 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000438
439 return True
440
441 def CachePublicHandler(self):
442 """This request handler yields a page with the title set to the current
443 system time, and allows caching for 5 seconds."""
444
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000445 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000446 return False
447
448 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000449 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000450 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000451 self.end_headers()
452
maruel@google.come250a9b2009-03-10 17:39:46 +0000453 self.wfile.write('<html><head><title>%s</title></head></html>' %
454 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000455
456 return True
457
458 def CacheSMaxAgeHandler(self):
459 """This request handler yields a page with the title set to the current
460 system time, and does not allow for caching."""
461
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000462 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000463 return False
464
465 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000466 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000467 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
468 self.end_headers()
469
maruel@google.come250a9b2009-03-10 17:39:46 +0000470 self.wfile.write('<html><head><title>%s</title></head></html>' %
471 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000472
473 return True
474
475 def CacheMustRevalidateHandler(self):
476 """This request handler yields a page with the title set to the current
477 system time, and does not allow caching."""
478
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000479 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000480 return False
481
482 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000483 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000484 self.send_header('Cache-Control', 'must-revalidate')
485 self.end_headers()
486
maruel@google.come250a9b2009-03-10 17:39:46 +0000487 self.wfile.write('<html><head><title>%s</title></head></html>' %
488 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000489
490 return True
491
492 def CacheMustRevalidateMaxAgeHandler(self):
493 """This request handler yields a page with the title set to the current
494 system time, and does not allow caching event though max-age of 60
495 seconds is specified."""
496
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000497 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000498 return False
499
500 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000501 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000502 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
503 self.end_headers()
504
maruel@google.come250a9b2009-03-10 17:39:46 +0000505 self.wfile.write('<html><head><title>%s</title></head></html>' %
506 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000507
508 return True
509
initial.commit94958cf2008-07-26 22:42:52 +0000510 def CacheNoStoreHandler(self):
511 """This request handler yields a page with the title set to the current
512 system time, and does not allow the page to be stored."""
513
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000514 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000515 return False
516
517 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000518 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000519 self.send_header('Cache-Control', 'no-store')
520 self.end_headers()
521
maruel@google.come250a9b2009-03-10 17:39:46 +0000522 self.wfile.write('<html><head><title>%s</title></head></html>' %
523 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000524
525 return True
526
527 def CacheNoStoreMaxAgeHandler(self):
528 """This request handler yields a page with the title set to the current
529 system time, and does not allow the page to be stored even though max-age
530 of 60 seconds is specified."""
531
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000532 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000533 return False
534
535 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000536 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000537 self.send_header('Cache-Control', 'max-age=60, no-store')
538 self.end_headers()
539
maruel@google.come250a9b2009-03-10 17:39:46 +0000540 self.wfile.write('<html><head><title>%s</title></head></html>' %
541 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000542
543 return True
544
545
546 def CacheNoTransformHandler(self):
547 """This request handler yields a page with the title set to the current
548 system time, and does not allow the content to transformed during
549 user-agent caching"""
550
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000551 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000552 return False
553
554 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000555 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000556 self.send_header('Cache-Control', 'no-transform')
557 self.end_headers()
558
maruel@google.come250a9b2009-03-10 17:39:46 +0000559 self.wfile.write('<html><head><title>%s</title></head></html>' %
560 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000561
562 return True
563
564 def EchoHeader(self):
565 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000566
ananta@chromium.org219b2062009-10-23 16:09:41 +0000567 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000568
ananta@chromium.org56812d02011-04-07 17:52:05 +0000569 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000570 """This function echoes back the value of a specific request header while
571 allowing caching for 16 hours."""
572
ananta@chromium.org56812d02011-04-07 17:52:05 +0000573 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000574
575 def EchoHeaderHelper(self, echo_header):
576 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000577
ananta@chromium.org219b2062009-10-23 16:09:41 +0000578 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000579 return False
580
581 query_char = self.path.find('?')
582 if query_char != -1:
583 header_name = self.path[query_char+1:]
584
585 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000586 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000587 if echo_header == '/echoheadercache':
588 self.send_header('Cache-control', 'max-age=60000')
589 else:
590 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000591 # insert a vary header to properly indicate that the cachability of this
592 # request is subject to value of the request header being echoed.
593 if len(header_name) > 0:
594 self.send_header('Vary', header_name)
595 self.end_headers()
596
597 if len(header_name) > 0:
598 self.wfile.write(self.headers.getheader(header_name))
599
600 return True
601
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000602 def ReadRequestBody(self):
603 """This function reads the body of the current HTTP request, handling
604 both plain and chunked transfer encoded requests."""
605
606 if self.headers.getheader('transfer-encoding') != 'chunked':
607 length = int(self.headers.getheader('content-length'))
608 return self.rfile.read(length)
609
610 # Read the request body as chunks.
611 body = ""
612 while True:
613 line = self.rfile.readline()
614 length = int(line, 16)
615 if length == 0:
616 self.rfile.readline()
617 break
618 body += self.rfile.read(length)
619 self.rfile.read(2)
620 return body
621
initial.commit94958cf2008-07-26 22:42:52 +0000622 def EchoHandler(self):
623 """This handler just echoes back the payload of the request, for testing
624 form submission."""
625
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000626 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000627 return False
628
629 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000630 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000631 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000632 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000633 return True
634
635 def EchoTitleHandler(self):
636 """This handler is like Echo, but sets the page title to the request."""
637
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000638 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000639 return False
640
641 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000642 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000643 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000644 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000645 self.wfile.write('<html><head><title>')
646 self.wfile.write(request)
647 self.wfile.write('</title></head></html>')
648 return True
649
650 def EchoAllHandler(self):
651 """This handler yields a (more) human-readable page listing information
652 about the request header & contents."""
653
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000654 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000655 return False
656
657 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000658 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000659 self.end_headers()
660 self.wfile.write('<html><head><style>'
661 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
662 '</style></head><body>'
663 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000664 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000665 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000666
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000667 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000668 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000669 params = cgi.parse_qs(qs, keep_blank_values=1)
670
671 for param in params:
672 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000673
674 self.wfile.write('</pre>')
675
676 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
677
678 self.wfile.write('</body></html>')
679 return True
680
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000681 def EchoMultipartPostHandler(self):
682 """This handler echoes received multipart post data as json format."""
683
684 if not (self._ShouldHandleRequest("/echomultipartpost") or
685 self._ShouldHandleRequest("/searchbyimage")):
686 return False
687
688 content_type, parameters = cgi.parse_header(
689 self.headers.getheader('content-type'))
690 if content_type == 'multipart/form-data':
691 post_multipart = cgi.parse_multipart(self.rfile, parameters)
692 elif content_type == 'application/x-www-form-urlencoded':
693 raise Exception('POST by application/x-www-form-urlencoded is '
694 'not implemented.')
695 else:
696 post_multipart = {}
697
698 # Since the data can be binary, we encode them by base64.
699 post_multipart_base64_encoded = {}
700 for field, values in post_multipart.items():
701 post_multipart_base64_encoded[field] = [base64.b64encode(value)
702 for value in values]
703
704 result = {'POST_multipart' : post_multipart_base64_encoded}
705
706 self.send_response(200)
707 self.send_header("Content-type", "text/plain")
708 self.end_headers()
709 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
710 return True
711
initial.commit94958cf2008-07-26 22:42:52 +0000712 def DownloadHandler(self):
713 """This handler sends a downloadable file with or without reporting
714 the size (6K)."""
715
716 if self.path.startswith("/download-unknown-size"):
717 send_length = False
718 elif self.path.startswith("/download-known-size"):
719 send_length = True
720 else:
721 return False
722
723 #
724 # The test which uses this functionality is attempting to send
725 # small chunks of data to the client. Use a fairly large buffer
726 # so that we'll fill chrome's IO buffer enough to force it to
727 # actually write the data.
728 # See also the comments in the client-side of this test in
729 # download_uitest.cc
730 #
731 size_chunk1 = 35*1024
732 size_chunk2 = 10*1024
733
734 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000735 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000736 self.send_header('Cache-Control', 'max-age=0')
737 if send_length:
738 self.send_header('Content-Length', size_chunk1 + size_chunk2)
739 self.end_headers()
740
741 # First chunk of data:
742 self.wfile.write("*" * size_chunk1)
743 self.wfile.flush()
744
745 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000746 self.server.wait_for_download = True
747 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000748 self.server.handle_request()
749
750 # Second chunk of data:
751 self.wfile.write("*" * size_chunk2)
752 return True
753
754 def DownloadFinishHandler(self):
755 """This handler just tells the server to finish the current download."""
756
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000757 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000758 return False
759
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000760 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000761 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000762 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000763 self.send_header('Cache-Control', 'max-age=0')
764 self.end_headers()
765 return True
766
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000767 def _ReplaceFileData(self, data, query_parameters):
768 """Replaces matching substrings in a file.
769
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000770 If the 'replace_text' URL query parameter is present, it is expected to be
771 of the form old_text:new_text, which indicates that any old_text strings in
772 the file are replaced with new_text. Multiple 'replace_text' parameters may
773 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000774
775 If the parameters are not present, |data| is returned.
776 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000777
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000778 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000779 replace_text_values = query_dict.get('replace_text', [])
780 for replace_text_value in replace_text_values:
781 replace_text_args = replace_text_value.split(':')
782 if len(replace_text_args) != 2:
783 raise ValueError(
784 'replace_text must be of form old_text:new_text. Actual value: %s' %
785 replace_text_value)
786 old_text_b64, new_text_b64 = replace_text_args
787 old_text = base64.urlsafe_b64decode(old_text_b64)
788 new_text = base64.urlsafe_b64decode(new_text_b64)
789 data = data.replace(old_text, new_text)
790 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000791
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000792 def ZipFileHandler(self):
793 """This handler sends the contents of the requested file in compressed form.
794 Can pass in a parameter that specifies that the content length be
795 C - the compressed size (OK),
796 U - the uncompressed size (Non-standard, but handled),
797 S - less than compressed (OK because we keep going),
798 M - larger than compressed but less than uncompressed (an error),
799 L - larger than uncompressed (an error)
800 Example: compressedfiles/Picture_1.doc?C
801 """
802
803 prefix = "/compressedfiles/"
804 if not self.path.startswith(prefix):
805 return False
806
807 # Consume a request body if present.
808 if self.command == 'POST' or self.command == 'PUT' :
809 self.ReadRequestBody()
810
811 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
812
813 if not query in ('C', 'U', 'S', 'M', 'L'):
814 return False
815
816 sub_path = url_path[len(prefix):]
817 entries = sub_path.split('/')
818 file_path = os.path.join(self.server.data_dir, *entries)
819 if os.path.isdir(file_path):
820 file_path = os.path.join(file_path, 'index.html')
821
822 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000823 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000824 self.send_error(404)
825 return True
826
827 f = open(file_path, "rb")
828 data = f.read()
829 uncompressed_len = len(data)
830 f.close()
831
832 # Compress the data.
833 data = zlib.compress(data)
834 compressed_len = len(data)
835
836 content_length = compressed_len
837 if query == 'U':
838 content_length = uncompressed_len
839 elif query == 'S':
840 content_length = compressed_len / 2
841 elif query == 'M':
842 content_length = (compressed_len + uncompressed_len) / 2
843 elif query == 'L':
844 content_length = compressed_len + uncompressed_len
845
846 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000847 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000848 self.send_header('Content-encoding', 'deflate')
849 self.send_header('Connection', 'close')
850 self.send_header('Content-Length', content_length)
851 self.send_header('ETag', '\'' + file_path + '\'')
852 self.end_headers()
853
854 self.wfile.write(data)
855
856 return True
857
initial.commit94958cf2008-07-26 22:42:52 +0000858 def FileHandler(self):
859 """This handler sends the contents of the requested file. Wow, it's like
860 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000861
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000862 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000863 if not self.path.startswith(prefix):
864 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000865 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000866
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000867 def PostOnlyFileHandler(self):
868 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000869
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000870 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000871 if not self.path.startswith(prefix):
872 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000873 return self._FileHandlerHelper(prefix)
874
875 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000876 request_body = ''
877 if self.command == 'POST' or self.command == 'PUT':
878 # Consume a request body if present.
879 request_body = self.ReadRequestBody()
880
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000881 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000882 query_dict = cgi.parse_qs(query)
883
884 expected_body = query_dict.get('expected_body', [])
885 if expected_body and request_body not in expected_body:
886 self.send_response(404)
887 self.end_headers()
888 self.wfile.write('')
889 return True
890
891 expected_headers = query_dict.get('expected_headers', [])
892 for expected_header in expected_headers:
893 header_name, expected_value = expected_header.split(':')
894 if self.headers.getheader(header_name) != expected_value:
895 self.send_response(404)
896 self.end_headers()
897 self.wfile.write('')
898 return True
899
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000900 sub_path = url_path[len(prefix):]
901 entries = sub_path.split('/')
902 file_path = os.path.join(self.server.data_dir, *entries)
903 if os.path.isdir(file_path):
904 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000905
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000906 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000907 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000908 self.send_error(404)
909 return True
910
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000911 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000912 data = f.read()
913 f.close()
914
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000915 data = self._ReplaceFileData(data, query)
916
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000917 old_protocol_version = self.protocol_version
918
initial.commit94958cf2008-07-26 22:42:52 +0000919 # If file.mock-http-headers exists, it contains the headers we
920 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000921 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000922 if os.path.isfile(headers_path):
923 f = open(headers_path, "r")
924
925 # "HTTP/1.1 200 OK"
926 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000927 http_major, http_minor, status_code = re.findall(
928 'HTTP/(\d+).(\d+) (\d+)', response)[0]
929 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000930 self.send_response(int(status_code))
931
932 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000933 header_values = re.findall('(\S+):\s*(.*)', line)
934 if len(header_values) > 0:
935 # "name: value"
936 name, value = header_values[0]
937 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000938 f.close()
939 else:
940 # Could be more generic once we support mime-type sniffing, but for
941 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000942
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000943 range_header = self.headers.get('Range')
944 if range_header and range_header.startswith('bytes='):
945 # Note this doesn't handle all valid byte range_header values (i.e.
946 # left open ended ones), just enough for what we needed so far.
947 range_header = range_header[6:].split('-')
948 start = int(range_header[0])
949 if range_header[1]:
950 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000951 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000952 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000953
954 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000955 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
956 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000957 self.send_header('Content-Range', content_range)
958 data = data[start: end + 1]
959 else:
960 self.send_response(200)
961
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000962 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000963 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000964 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000965 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000966 self.end_headers()
967
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000968 if (self.command != 'HEAD'):
969 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000970
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000971 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000972 return True
973
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000974 def SetCookieHandler(self):
975 """This handler just sets a cookie, for testing cookie handling."""
976
977 if not self._ShouldHandleRequest("/set-cookie"):
978 return False
979
980 query_char = self.path.find('?')
981 if query_char != -1:
982 cookie_values = self.path[query_char + 1:].split('&')
983 else:
984 cookie_values = ("",)
985 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000986 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000987 for cookie_value in cookie_values:
988 self.send_header('Set-Cookie', '%s' % cookie_value)
989 self.end_headers()
990 for cookie_value in cookie_values:
991 self.wfile.write('%s' % cookie_value)
992 return True
993
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000994 def SetManyCookiesHandler(self):
995 """This handler just sets a given number of cookies, for testing handling
996 of large numbers of cookies."""
997
998 if not self._ShouldHandleRequest("/set-many-cookies"):
999 return False
1000
1001 query_char = self.path.find('?')
1002 if query_char != -1:
1003 num_cookies = int(self.path[query_char + 1:])
1004 else:
1005 num_cookies = 0
1006 self.send_response(200)
1007 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001008 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001009 self.send_header('Set-Cookie', 'a=')
1010 self.end_headers()
1011 self.wfile.write('%d cookies were sent' % num_cookies)
1012 return True
1013
mattm@chromium.org983fc462012-06-30 00:52:08 +00001014 def ExpectAndSetCookieHandler(self):
1015 """Expects some cookies to be sent, and if they are, sets more cookies.
1016
1017 The expect parameter specifies a required cookie. May be specified multiple
1018 times.
1019 The set parameter specifies a cookie to set if all required cookies are
1020 preset. May be specified multiple times.
1021 The data parameter specifies the response body data to be returned."""
1022
1023 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1024 return False
1025
1026 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1027 query_dict = cgi.parse_qs(query)
1028 cookies = set()
1029 if 'Cookie' in self.headers:
1030 cookie_header = self.headers.getheader('Cookie')
1031 cookies.update([s.strip() for s in cookie_header.split(';')])
1032 got_all_expected_cookies = True
1033 for expected_cookie in query_dict.get('expect', []):
1034 if expected_cookie not in cookies:
1035 got_all_expected_cookies = False
1036 self.send_response(200)
1037 self.send_header('Content-Type', 'text/html')
1038 if got_all_expected_cookies:
1039 for cookie_value in query_dict.get('set', []):
1040 self.send_header('Set-Cookie', '%s' % cookie_value)
1041 self.end_headers()
1042 for data_value in query_dict.get('data', []):
1043 self.wfile.write(data_value)
1044 return True
1045
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001046 def SetHeaderHandler(self):
1047 """This handler sets a response header. Parameters are in the
1048 key%3A%20value&key2%3A%20value2 format."""
1049
1050 if not self._ShouldHandleRequest("/set-header"):
1051 return False
1052
1053 query_char = self.path.find('?')
1054 if query_char != -1:
1055 headers_values = self.path[query_char + 1:].split('&')
1056 else:
1057 headers_values = ("",)
1058 self.send_response(200)
1059 self.send_header('Content-Type', 'text/html')
1060 for header_value in headers_values:
1061 header_value = urllib.unquote(header_value)
1062 (key, value) = header_value.split(': ', 1)
1063 self.send_header(key, value)
1064 self.end_headers()
1065 for header_value in headers_values:
1066 self.wfile.write('%s' % header_value)
1067 return True
1068
initial.commit94958cf2008-07-26 22:42:52 +00001069 def AuthBasicHandler(self):
1070 """This handler tests 'Basic' authentication. It just sends a page with
1071 title 'user/pass' if you succeed."""
1072
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001073 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001074 return False
1075
1076 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001077 expected_password = 'secret'
1078 realm = 'testrealm'
1079 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001080
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001081 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1082 query_params = cgi.parse_qs(query, True)
1083 if 'set-cookie-if-challenged' in query_params:
1084 set_cookie_if_challenged = True
1085 if 'password' in query_params:
1086 expected_password = query_params['password'][0]
1087 if 'realm' in query_params:
1088 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001089
initial.commit94958cf2008-07-26 22:42:52 +00001090 auth = self.headers.getheader('authorization')
1091 try:
1092 if not auth:
1093 raise Exception('no auth')
1094 b64str = re.findall(r'Basic (\S+)', auth)[0]
1095 userpass = base64.b64decode(b64str)
1096 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001097 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001098 raise Exception('wrong password')
1099 except Exception, e:
1100 # Authentication failed.
1101 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001102 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001103 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001104 if set_cookie_if_challenged:
1105 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001106 self.end_headers()
1107 self.wfile.write('<html><head>')
1108 self.wfile.write('<title>Denied: %s</title>' % e)
1109 self.wfile.write('</head><body>')
1110 self.wfile.write('auth=%s<p>' % auth)
1111 self.wfile.write('b64str=%s<p>' % b64str)
1112 self.wfile.write('username: %s<p>' % username)
1113 self.wfile.write('userpass: %s<p>' % userpass)
1114 self.wfile.write('password: %s<p>' % password)
1115 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1116 self.wfile.write('</body></html>')
1117 return True
1118
1119 # Authentication successful. (Return a cachable response to allow for
1120 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001121 old_protocol_version = self.protocol_version
1122 self.protocol_version = "HTTP/1.1"
1123
initial.commit94958cf2008-07-26 22:42:52 +00001124 if_none_match = self.headers.getheader('if-none-match')
1125 if if_none_match == "abc":
1126 self.send_response(304)
1127 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001128 elif url_path.endswith(".gif"):
1129 # Using chrome/test/data/google/logo.gif as the test image
1130 test_image_path = ['google', 'logo.gif']
1131 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1132 if not os.path.isfile(gif_path):
1133 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001134 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001135 return True
1136
1137 f = open(gif_path, "rb")
1138 data = f.read()
1139 f.close()
1140
1141 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001142 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001143 self.send_header('Cache-control', 'max-age=60000')
1144 self.send_header('Etag', 'abc')
1145 self.end_headers()
1146 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001147 else:
1148 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001149 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001150 self.send_header('Cache-control', 'max-age=60000')
1151 self.send_header('Etag', 'abc')
1152 self.end_headers()
1153 self.wfile.write('<html><head>')
1154 self.wfile.write('<title>%s/%s</title>' % (username, password))
1155 self.wfile.write('</head><body>')
1156 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001157 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001158 self.wfile.write('</body></html>')
1159
rvargas@google.com54453b72011-05-19 01:11:11 +00001160 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001161 return True
1162
tonyg@chromium.org75054202010-03-31 22:06:10 +00001163 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001164 """Returns a nonce that's stable per request path for the server's lifetime.
1165 This is a fake implementation. A real implementation would only use a given
1166 nonce a single time (hence the name n-once). However, for the purposes of
1167 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001168
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001169 Args:
1170 force_reset: Iff set, the nonce will be changed. Useful for testing the
1171 "stale" response.
1172 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001173
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001174 if force_reset or not self.server.nonce_time:
1175 self.server.nonce_time = time.time()
1176 return hashlib.md5('privatekey%s%d' %
1177 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001178
1179 def AuthDigestHandler(self):
1180 """This handler tests 'Digest' authentication.
1181
1182 It just sends a page with title 'user/pass' if you succeed.
1183
1184 A stale response is sent iff "stale" is present in the request path.
1185 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001186
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001187 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001188 return False
1189
tonyg@chromium.org75054202010-03-31 22:06:10 +00001190 stale = 'stale' in self.path
1191 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001192 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001193 password = 'secret'
1194 realm = 'testrealm'
1195
1196 auth = self.headers.getheader('authorization')
1197 pairs = {}
1198 try:
1199 if not auth:
1200 raise Exception('no auth')
1201 if not auth.startswith('Digest'):
1202 raise Exception('not digest')
1203 # Pull out all the name="value" pairs as a dictionary.
1204 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1205
1206 # Make sure it's all valid.
1207 if pairs['nonce'] != nonce:
1208 raise Exception('wrong nonce')
1209 if pairs['opaque'] != opaque:
1210 raise Exception('wrong opaque')
1211
1212 # Check the 'response' value and make sure it matches our magic hash.
1213 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001214 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001215 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001216 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001217 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001218 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001219 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1220 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001221 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001222
1223 if pairs['response'] != response:
1224 raise Exception('wrong password')
1225 except Exception, e:
1226 # Authentication failed.
1227 self.send_response(401)
1228 hdr = ('Digest '
1229 'realm="%s", '
1230 'domain="/", '
1231 'qop="auth", '
1232 'algorithm=MD5, '
1233 'nonce="%s", '
1234 'opaque="%s"') % (realm, nonce, opaque)
1235 if stale:
1236 hdr += ', stale="TRUE"'
1237 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001238 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001239 self.end_headers()
1240 self.wfile.write('<html><head>')
1241 self.wfile.write('<title>Denied: %s</title>' % e)
1242 self.wfile.write('</head><body>')
1243 self.wfile.write('auth=%s<p>' % auth)
1244 self.wfile.write('pairs=%s<p>' % pairs)
1245 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1246 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1247 self.wfile.write('</body></html>')
1248 return True
1249
1250 # Authentication successful.
1251 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001252 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001253 self.end_headers()
1254 self.wfile.write('<html><head>')
1255 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1256 self.wfile.write('</head><body>')
1257 self.wfile.write('auth=%s<p>' % auth)
1258 self.wfile.write('pairs=%s<p>' % pairs)
1259 self.wfile.write('</body></html>')
1260
1261 return True
1262
1263 def SlowServerHandler(self):
1264 """Wait for the user suggested time before responding. The syntax is
1265 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001266
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001267 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001268 return False
1269 query_char = self.path.find('?')
1270 wait_sec = 1.0
1271 if query_char >= 0:
1272 try:
1273 wait_sec = int(self.path[query_char + 1:])
1274 except ValueError:
1275 pass
1276 time.sleep(wait_sec)
1277 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001278 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001279 self.end_headers()
1280 self.wfile.write("waited %d seconds" % wait_sec)
1281 return True
1282
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001283 def ChunkedServerHandler(self):
1284 """Send chunked response. Allows to specify chunks parameters:
1285 - waitBeforeHeaders - ms to wait before sending headers
1286 - waitBetweenChunks - ms to wait between chunks
1287 - chunkSize - size of each chunk in bytes
1288 - chunksNumber - number of chunks
1289 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1290 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001291
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001292 if not self._ShouldHandleRequest("/chunked"):
1293 return False
1294 query_char = self.path.find('?')
1295 chunkedSettings = {'waitBeforeHeaders' : 0,
1296 'waitBetweenChunks' : 0,
1297 'chunkSize' : 5,
1298 'chunksNumber' : 5}
1299 if query_char >= 0:
1300 params = self.path[query_char + 1:].split('&')
1301 for param in params:
1302 keyValue = param.split('=')
1303 if len(keyValue) == 2:
1304 try:
1305 chunkedSettings[keyValue[0]] = int(keyValue[1])
1306 except ValueError:
1307 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001308 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001309 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1310 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001311 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001312 self.send_header('Connection', 'close')
1313 self.send_header('Transfer-Encoding', 'chunked')
1314 self.end_headers()
1315 # Chunked encoding: sending all chunks, then final zero-length chunk and
1316 # then final CRLF.
1317 for i in range(0, chunkedSettings['chunksNumber']):
1318 if i > 0:
1319 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1320 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001321 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001322 self.sendChunkHelp('')
1323 return True
1324
initial.commit94958cf2008-07-26 22:42:52 +00001325 def ContentTypeHandler(self):
1326 """Returns a string of html with the given content type. E.g.,
1327 /contenttype?text/css returns an html file with the Content-Type
1328 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001329
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001330 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001331 return False
1332 query_char = self.path.find('?')
1333 content_type = self.path[query_char + 1:].strip()
1334 if not content_type:
1335 content_type = 'text/html'
1336 self.send_response(200)
1337 self.send_header('Content-Type', content_type)
1338 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001339 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001340 return True
1341
creis@google.com2f4f6a42011-03-25 19:44:19 +00001342 def NoContentHandler(self):
1343 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001344
creis@google.com2f4f6a42011-03-25 19:44:19 +00001345 if not self._ShouldHandleRequest("/nocontent"):
1346 return False
1347 self.send_response(204)
1348 self.end_headers()
1349 return True
1350
initial.commit94958cf2008-07-26 22:42:52 +00001351 def ServerRedirectHandler(self):
1352 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001353 '/server-redirect?http://foo.bar/asdf' to redirect to
1354 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001355
1356 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001357 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001358 return False
1359
1360 query_char = self.path.find('?')
1361 if query_char < 0 or len(self.path) <= query_char + 1:
1362 self.sendRedirectHelp(test_name)
1363 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001364 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001365
1366 self.send_response(301) # moved permanently
1367 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001368 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001369 self.end_headers()
1370 self.wfile.write('<html><head>')
1371 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1372
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001373 return True
initial.commit94958cf2008-07-26 22:42:52 +00001374
1375 def ClientRedirectHandler(self):
1376 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001377 '/client-redirect?http://foo.bar/asdf' to redirect to
1378 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001379
1380 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001381 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001382 return False
1383
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001384 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001385 if query_char < 0 or len(self.path) <= query_char + 1:
1386 self.sendRedirectHelp(test_name)
1387 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001388 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001389
1390 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001391 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001392 self.end_headers()
1393 self.wfile.write('<html><head>')
1394 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1395 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1396
1397 return True
1398
tony@chromium.org03266982010-03-05 03:18:42 +00001399 def MultipartHandler(self):
1400 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001401
tony@chromium.org4cb88302011-09-27 22:13:49 +00001402 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001403 if not self._ShouldHandleRequest(test_name):
1404 return False
1405
1406 num_frames = 10
1407 bound = '12345'
1408 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001409 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001410 'multipart/x-mixed-replace;boundary=' + bound)
1411 self.end_headers()
1412
1413 for i in xrange(num_frames):
1414 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001415 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001416 self.wfile.write('<title>page ' + str(i) + '</title>')
1417 self.wfile.write('page ' + str(i))
1418
1419 self.wfile.write('--' + bound + '--')
1420 return True
1421
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001422 def GetSSLSessionCacheHandler(self):
1423 """Send a reply containing a log of the session cache operations."""
1424
1425 if not self._ShouldHandleRequest('/ssl-session-cache'):
1426 return False
1427
1428 self.send_response(200)
1429 self.send_header('Content-Type', 'text/plain')
1430 self.end_headers()
1431 try:
1432 for (action, sessionID) in self.server.session_cache.log:
1433 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001434 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001435 self.wfile.write('Pass --https-record-resume in order to use' +
1436 ' this request')
1437 return True
1438
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001439 def SSLManySmallRecords(self):
1440 """Sends a reply consisting of a variety of small writes. These will be
1441 translated into a series of small SSL records when used over an HTTPS
1442 server."""
1443
1444 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1445 return False
1446
1447 self.send_response(200)
1448 self.send_header('Content-Type', 'text/plain')
1449 self.end_headers()
1450
1451 # Write ~26K of data, in 1350 byte chunks
1452 for i in xrange(20):
1453 self.wfile.write('*' * 1350)
1454 self.wfile.flush()
1455 return True
1456
agl@chromium.org04700be2013-03-02 18:40:41 +00001457 def GetChannelID(self):
1458 """Send a reply containing the hashed ChannelID that the client provided."""
1459
1460 if not self._ShouldHandleRequest('/channel-id'):
1461 return False
1462
1463 self.send_response(200)
1464 self.send_header('Content-Type', 'text/plain')
1465 self.end_headers()
1466 channel_id = self.server.tlsConnection.channel_id.tostring()
1467 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1468 return True
1469
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001470 def CloseSocketHandler(self):
1471 """Closes the socket without sending anything."""
1472
1473 if not self._ShouldHandleRequest('/close-socket'):
1474 return False
1475
1476 self.wfile.close()
1477 return True
1478
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001479 def RangeResetHandler(self):
1480 """Send data broken up by connection resets every N (default 4K) bytes.
1481 Support range requests. If the data requested doesn't straddle a reset
1482 boundary, it will all be sent. Used for testing resuming downloads."""
1483
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001484 def DataForRange(start, end):
1485 """Data to be provided for a particular range of bytes."""
1486 # Offset and scale to avoid too obvious (and hence potentially
1487 # collidable) data.
1488 return ''.join([chr(y % 256)
1489 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1490
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001491 if not self._ShouldHandleRequest('/rangereset'):
1492 return False
1493
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001494 # HTTP/1.1 is required for ETag and range support.
1495 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001496 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1497
1498 # Defaults
1499 size = 8000
1500 # Note that the rst is sent just before sending the rst_boundary byte.
1501 rst_boundary = 4000
1502 respond_to_range = True
1503 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001504 rst_limit = -1
1505 token = 'DEFAULT'
1506 fail_precondition = 0
1507 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001508
1509 # Parse the query
1510 qdict = urlparse.parse_qs(query, True)
1511 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001512 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001513 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001514 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001515 if 'token' in qdict:
1516 # Identifying token for stateful tests.
1517 token = qdict['token'][0]
1518 if 'rst_limit' in qdict:
1519 # Max number of rsts for a given token.
1520 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001521 if 'bounce_range' in qdict:
1522 respond_to_range = False
1523 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001524 # Note that hold_for_signal will not work with null range requests;
1525 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001526 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001527 if 'no_verifiers' in qdict:
1528 send_verifiers = False
1529 if 'fail_precondition' in qdict:
1530 fail_precondition = int(qdict['fail_precondition'][0])
1531
1532 # Record already set information, or set it.
1533 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1534 if rst_limit != 0:
1535 TestPageHandler.rst_limits[token] -= 1
1536 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1537 token, fail_precondition)
1538 if fail_precondition != 0:
1539 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001540
1541 first_byte = 0
1542 last_byte = size - 1
1543
1544 # Does that define what we want to return, or do we need to apply
1545 # a range?
1546 range_response = False
1547 range_header = self.headers.getheader('range')
1548 if range_header and respond_to_range:
1549 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1550 if mo.group(1):
1551 first_byte = int(mo.group(1))
1552 if mo.group(2):
1553 last_byte = int(mo.group(2))
1554 if last_byte > size - 1:
1555 last_byte = size - 1
1556 range_response = True
1557 if last_byte < first_byte:
1558 return False
1559
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001560 if (fail_precondition and
1561 (self.headers.getheader('If-Modified-Since') or
1562 self.headers.getheader('If-Match'))):
1563 self.send_response(412)
1564 self.end_headers()
1565 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001566
1567 if range_response:
1568 self.send_response(206)
1569 self.send_header('Content-Range',
1570 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1571 else:
1572 self.send_response(200)
1573 self.send_header('Content-Type', 'application/octet-stream')
1574 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001575 if send_verifiers:
1576 self.send_header('Etag', '"XYZZY"')
1577 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001578 self.end_headers()
1579
1580 if hold_for_signal:
1581 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1582 # a single byte, the self.server.handle_request() below hangs
1583 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001584 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001585 first_byte = first_byte + 1
1586 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001587 self.server.wait_for_download = True
1588 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001589 self.server.handle_request()
1590
1591 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001592 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001593 # No RST has been requested in this range, so we don't need to
1594 # do anything fancy; just write the data and let the python
1595 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001596 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001597 self.wfile.flush()
1598 return True
1599
1600 # We're resetting the connection part way in; go to the RST
1601 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001602 # Because socket semantics do not guarantee that all the data will be
1603 # sent when using the linger semantics to hard close a socket,
1604 # we send the data and then wait for our peer to release us
1605 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001606 data = DataForRange(first_byte, possible_rst)
1607 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001608 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001609 self.server.wait_for_download = True
1610 while self.server.wait_for_download:
1611 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001612 l_onoff = 1 # Linger is active.
1613 l_linger = 0 # Seconds to linger for.
1614 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1615 struct.pack('ii', l_onoff, l_linger))
1616
1617 # Close all duplicates of the underlying socket to force the RST.
1618 self.wfile.close()
1619 self.rfile.close()
1620 self.connection.close()
1621
1622 return True
1623
initial.commit94958cf2008-07-26 22:42:52 +00001624 def DefaultResponseHandler(self):
1625 """This is the catch-all response handler for requests that aren't handled
1626 by one of the special handlers above.
1627 Note that we specify the content-length as without it the https connection
1628 is not closed properly (and the browser keeps expecting data)."""
1629
1630 contents = "Default response given for path: " + self.path
1631 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001632 self.send_header('Content-Type', 'text/html')
1633 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001634 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001635 if (self.command != 'HEAD'):
1636 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001637 return True
1638
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001639 def RedirectConnectHandler(self):
1640 """Sends a redirect to the CONNECT request for www.redirect.com. This
1641 response is not specified by the RFC, so the browser should not follow
1642 the redirect."""
1643
1644 if (self.path.find("www.redirect.com") < 0):
1645 return False
1646
1647 dest = "http://www.destination.com/foo.js"
1648
1649 self.send_response(302) # moved temporarily
1650 self.send_header('Location', dest)
1651 self.send_header('Connection', 'close')
1652 self.end_headers()
1653 return True
1654
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001655 def ServerAuthConnectHandler(self):
1656 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1657 response doesn't make sense because the proxy server cannot request
1658 server authentication."""
1659
1660 if (self.path.find("www.server-auth.com") < 0):
1661 return False
1662
1663 challenge = 'Basic realm="WallyWorld"'
1664
1665 self.send_response(401) # unauthorized
1666 self.send_header('WWW-Authenticate', challenge)
1667 self.send_header('Connection', 'close')
1668 self.end_headers()
1669 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001670
1671 def DefaultConnectResponseHandler(self):
1672 """This is the catch-all response handler for CONNECT requests that aren't
1673 handled by one of the special handlers above. Real Web servers respond
1674 with 400 to CONNECT requests."""
1675
1676 contents = "Your client has issued a malformed or illegal request."
1677 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001678 self.send_header('Content-Type', 'text/html')
1679 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001680 self.end_headers()
1681 self.wfile.write(contents)
1682 return True
1683
initial.commit94958cf2008-07-26 22:42:52 +00001684 # called by the redirect handling function when there is no parameter
1685 def sendRedirectHelp(self, redirect_name):
1686 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001687 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001688 self.end_headers()
1689 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1690 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1691 self.wfile.write('</body></html>')
1692
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001693 # called by chunked handling function
1694 def sendChunkHelp(self, chunk):
1695 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1696 self.wfile.write('%X\r\n' % len(chunk))
1697 self.wfile.write(chunk)
1698 self.wfile.write('\r\n')
1699
akalin@chromium.org154bb132010-11-12 02:20:27 +00001700
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001701class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001702 def __init__(self, request, client_address, socket_server):
1703 handlers = [self.OCSPResponse]
1704 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001705 testserver_base.BasePageHandler.__init__(self, request, client_address,
1706 socket_server, [], handlers, [],
1707 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001708
1709 def OCSPResponse(self):
1710 self.send_response(200)
1711 self.send_header('Content-Type', 'application/ocsp-response')
1712 self.send_header('Content-Length', str(len(self.ocsp_response)))
1713 self.end_headers()
1714
1715 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001716
mattm@chromium.org830a3712012-11-07 23:00:07 +00001717
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001718class TCPEchoHandler(SocketServer.BaseRequestHandler):
1719 """The RequestHandler class for TCP echo server.
1720
1721 It is instantiated once per connection to the server, and overrides the
1722 handle() method to implement communication to the client.
1723 """
1724
1725 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001726 """Handles the request from the client and constructs a response."""
1727
1728 data = self.request.recv(65536).strip()
1729 # Verify the "echo request" message received from the client. Send back
1730 # "echo response" message if "echo request" message is valid.
1731 try:
1732 return_data = echo_message.GetEchoResponseData(data)
1733 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001734 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001735 except ValueError:
1736 return
1737
1738 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001739
1740
1741class UDPEchoHandler(SocketServer.BaseRequestHandler):
1742 """The RequestHandler class for UDP echo server.
1743
1744 It is instantiated once per connection to the server, and overrides the
1745 handle() method to implement communication to the client.
1746 """
1747
1748 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001749 """Handles the request from the client and constructs a response."""
1750
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001751 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001752 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001753 # Verify the "echo request" message received from the client. Send back
1754 # "echo response" message if "echo request" message is valid.
1755 try:
1756 return_data = echo_message.GetEchoResponseData(data)
1757 if not return_data:
1758 return
1759 except ValueError:
1760 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001761 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001762
1763
bashi@chromium.org33233532012-09-08 17:37:24 +00001764class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1765 """A request handler that behaves as a proxy server which requires
1766 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1767 """
1768
1769 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1770
1771 def parse_request(self):
1772 """Overrides parse_request to check credential."""
1773
1774 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1775 return False
1776
1777 auth = self.headers.getheader('Proxy-Authorization')
1778 if auth != self._AUTH_CREDENTIAL:
1779 self.send_response(407)
1780 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1781 self.end_headers()
1782 return False
1783
1784 return True
1785
1786 def _start_read_write(self, sock):
1787 sock.setblocking(0)
1788 self.request.setblocking(0)
1789 rlist = [self.request, sock]
1790 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001791 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001792 if errors:
1793 self.send_response(500)
1794 self.end_headers()
1795 return
1796 for s in ready_sockets:
1797 received = s.recv(1024)
1798 if len(received) == 0:
1799 return
1800 if s == self.request:
1801 other = sock
1802 else:
1803 other = self.request
1804 other.send(received)
1805
1806 def _do_common_method(self):
1807 url = urlparse.urlparse(self.path)
1808 port = url.port
1809 if not port:
1810 if url.scheme == 'http':
1811 port = 80
1812 elif url.scheme == 'https':
1813 port = 443
1814 if not url.hostname or not port:
1815 self.send_response(400)
1816 self.end_headers()
1817 return
1818
1819 if len(url.path) == 0:
1820 path = '/'
1821 else:
1822 path = url.path
1823 if len(url.query) > 0:
1824 path = '%s?%s' % (url.path, url.query)
1825
1826 sock = None
1827 try:
1828 sock = socket.create_connection((url.hostname, port))
1829 sock.send('%s %s %s\r\n' % (
1830 self.command, path, self.protocol_version))
1831 for header in self.headers.headers:
1832 header = header.strip()
1833 if (header.lower().startswith('connection') or
1834 header.lower().startswith('proxy')):
1835 continue
1836 sock.send('%s\r\n' % header)
1837 sock.send('\r\n')
1838 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001839 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001840 self.send_response(500)
1841 self.end_headers()
1842 finally:
1843 if sock is not None:
1844 sock.close()
1845
1846 def do_CONNECT(self):
1847 try:
1848 pos = self.path.rfind(':')
1849 host = self.path[:pos]
1850 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001851 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001852 self.send_response(400)
1853 self.end_headers()
1854
1855 try:
1856 sock = socket.create_connection((host, port))
1857 self.send_response(200, 'Connection established')
1858 self.end_headers()
1859 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001860 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001861 self.send_response(500)
1862 self.end_headers()
1863 finally:
1864 sock.close()
1865
1866 def do_GET(self):
1867 self._do_common_method()
1868
1869 def do_HEAD(self):
1870 self._do_common_method()
1871
1872
mattm@chromium.org830a3712012-11-07 23:00:07 +00001873class ServerRunner(testserver_base.TestServerRunner):
1874 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001875
mattm@chromium.org830a3712012-11-07 23:00:07 +00001876 def __init__(self):
1877 super(ServerRunner, self).__init__()
1878 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001879
mattm@chromium.org830a3712012-11-07 23:00:07 +00001880 def __make_data_dir(self):
1881 if self.options.data_dir:
1882 if not os.path.isdir(self.options.data_dir):
1883 raise testserver_base.OptionError('specified data dir not found: ' +
1884 self.options.data_dir + ' exiting...')
1885 my_data_dir = self.options.data_dir
1886 else:
1887 # Create the default path to our data dir, relative to the exe dir.
1888 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1889 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001890
mattm@chromium.org830a3712012-11-07 23:00:07 +00001891 #TODO(ibrar): Must use Find* funtion defined in google\tools
1892 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001893
mattm@chromium.org830a3712012-11-07 23:00:07 +00001894 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001895
mattm@chromium.org830a3712012-11-07 23:00:07 +00001896 def create_server(self, server_data):
1897 port = self.options.port
1898 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001899
mattm@chromium.org830a3712012-11-07 23:00:07 +00001900 if self.options.server_type == SERVER_HTTP:
1901 if self.options.https:
1902 pem_cert_and_key = None
1903 if self.options.cert_and_key_file:
1904 if not os.path.isfile(self.options.cert_and_key_file):
1905 raise testserver_base.OptionError(
1906 'specified server cert file not found: ' +
1907 self.options.cert_and_key_file + ' exiting...')
1908 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001909 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001910 # generate a new certificate and run an OCSP server for it.
1911 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001912 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001913 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001914
mattm@chromium.org830a3712012-11-07 23:00:07 +00001915 ocsp_der = None
1916 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001917
mattm@chromium.org830a3712012-11-07 23:00:07 +00001918 if self.options.ocsp == 'ok':
1919 ocsp_state = minica.OCSP_STATE_GOOD
1920 elif self.options.ocsp == 'revoked':
1921 ocsp_state = minica.OCSP_STATE_REVOKED
1922 elif self.options.ocsp == 'invalid':
1923 ocsp_state = minica.OCSP_STATE_INVALID
1924 elif self.options.ocsp == 'unauthorized':
1925 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1926 elif self.options.ocsp == 'unknown':
1927 ocsp_state = minica.OCSP_STATE_UNKNOWN
1928 else:
1929 raise testserver_base.OptionError('unknown OCSP status: ' +
1930 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001931
mattm@chromium.org830a3712012-11-07 23:00:07 +00001932 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1933 subject = "127.0.0.1",
1934 ocsp_url = ("http://%s:%d/ocsp" %
1935 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00001936 ocsp_state = ocsp_state,
1937 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001938
1939 self.__ocsp_server.ocsp_response = ocsp_der
1940
1941 for ca_cert in self.options.ssl_client_ca:
1942 if not os.path.isfile(ca_cert):
1943 raise testserver_base.OptionError(
1944 'specified trusted client CA file not found: ' + ca_cert +
1945 ' exiting...')
1946 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1947 self.options.ssl_client_auth,
1948 self.options.ssl_client_ca,
1949 self.options.ssl_bulk_cipher,
1950 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00001951 self.options.tls_intolerant,
szym@chromium.org3be0d452013-12-13 20:27:14 +00001952 self.options.signed_cert_timestamps.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00001953 "base64"),
szym@chromium.org3be0d452013-12-13 20:27:14 +00001954 self.options.fallback_scsv)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001955 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001956 else:
1957 server = HTTPServer((host, port), TestPageHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001958 print 'HTTP server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001959
1960 server.data_dir = self.__make_data_dir()
1961 server.file_root_url = self.options.file_root_url
1962 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001963 elif self.options.server_type == SERVER_WEBSOCKET:
1964 # Launch pywebsocket via WebSocketServer.
1965 logger = logging.getLogger()
1966 logger.addHandler(logging.StreamHandler())
1967 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1968 # is required to work correctly. It should be fixed from pywebsocket side.
1969 os.chdir(self.__make_data_dir())
1970 websocket_options = WebSocketOptions(host, port, '.')
1971 if self.options.cert_and_key_file:
1972 websocket_options.use_tls = True
1973 websocket_options.private_key = self.options.cert_and_key_file
1974 websocket_options.certificate = self.options.cert_and_key_file
1975 if self.options.ssl_client_auth:
1976 websocket_options.tls_client_auth = True
1977 if len(self.options.ssl_client_ca) != 1:
1978 raise testserver_base.OptionError(
1979 'one trusted client CA file should be specified')
1980 if not os.path.isfile(self.options.ssl_client_ca[0]):
1981 raise testserver_base.OptionError(
1982 'specified trusted client CA file not found: ' +
1983 self.options.ssl_client_ca[0] + ' exiting...')
1984 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
1985 server = WebSocketServer(websocket_options)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001986 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001987 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001988 elif self.options.server_type == SERVER_TCP_ECHO:
1989 # Used for generating the key (randomly) that encodes the "echo request"
1990 # message.
1991 random.seed()
1992 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001993 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001994 server_data['port'] = server.server_port
1995 elif self.options.server_type == SERVER_UDP_ECHO:
1996 # Used for generating the key (randomly) that encodes the "echo request"
1997 # message.
1998 random.seed()
1999 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002000 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002001 server_data['port'] = server.server_port
2002 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2003 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002004 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002005 server_data['port'] = server.server_port
2006 elif self.options.server_type == SERVER_FTP:
2007 my_data_dir = self.__make_data_dir()
2008
2009 # Instantiate a dummy authorizer for managing 'virtual' users
2010 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2011
2012 # Define a new user having full r/w permissions and a read-only
2013 # anonymous user
2014 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2015
2016 authorizer.add_anonymous(my_data_dir)
2017
2018 # Instantiate FTP handler class
2019 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2020 ftp_handler.authorizer = authorizer
2021
2022 # Define a customized banner (string returned when client connects)
2023 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2024 pyftpdlib.ftpserver.__ver__)
2025
2026 # Instantiate FTP server class and listen to address:port
2027 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2028 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002029 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002030 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002031 raise testserver_base.OptionError('unknown server type' +
2032 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002033
mattm@chromium.org830a3712012-11-07 23:00:07 +00002034 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002035
mattm@chromium.org830a3712012-11-07 23:00:07 +00002036 def run_server(self):
2037 if self.__ocsp_server:
2038 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002039
mattm@chromium.org830a3712012-11-07 23:00:07 +00002040 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002041
mattm@chromium.org830a3712012-11-07 23:00:07 +00002042 if self.__ocsp_server:
2043 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002044
mattm@chromium.org830a3712012-11-07 23:00:07 +00002045 def add_options(self):
2046 testserver_base.TestServerRunner.add_options(self)
2047 self.option_parser.add_option('-f', '--ftp', action='store_const',
2048 const=SERVER_FTP, default=SERVER_HTTP,
2049 dest='server_type',
2050 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002051 self.option_parser.add_option('--tcp-echo', action='store_const',
2052 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2053 dest='server_type',
2054 help='start up a tcp echo server.')
2055 self.option_parser.add_option('--udp-echo', action='store_const',
2056 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2057 dest='server_type',
2058 help='start up a udp echo server.')
2059 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2060 const=SERVER_BASIC_AUTH_PROXY,
2061 default=SERVER_HTTP, dest='server_type',
2062 help='start up a proxy server which requires '
2063 'basic authentication.')
2064 self.option_parser.add_option('--websocket', action='store_const',
2065 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2066 dest='server_type',
2067 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002068 self.option_parser.add_option('--https', action='store_true',
2069 dest='https', help='Specify that https '
2070 'should be used.')
2071 self.option_parser.add_option('--cert-and-key-file',
2072 dest='cert_and_key_file', help='specify the '
2073 'path to the file containing the certificate '
2074 'and private key for the server in PEM '
2075 'format')
2076 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2077 help='The type of OCSP response generated '
2078 'for the automatically generated '
2079 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002080 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2081 default=0, type=int,
2082 help='If non-zero then the generated '
2083 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002084 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2085 default='0', type='int',
2086 help='If nonzero, certain TLS connections '
2087 'will be aborted in order to test version '
2088 'fallback. 1 means all TLS versions will be '
2089 'aborted. 2 means TLS 1.1 or higher will be '
2090 'aborted. 3 means TLS 1.2 or higher will be '
2091 'aborted.')
szym@chromium.org3be0d452013-12-13 20:27:14 +00002092 self.option_parser.add_option('--signed-cert-timestamps',
2093 dest='signed_cert_timestamps',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002094 default='',
2095 help='Base64 encoded SCT list. If set, '
2096 'server will respond with a '
2097 'signed_certificate_timestamp TLS extension '
2098 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002099 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2100 default=False, const=True,
2101 action='store_const',
2102 help='If given, TLS_FALLBACK_SCSV support '
2103 'will be enabled. This causes the server to '
2104 'reject fallback connections from compatible '
2105 'clients (e.g. Chrome).')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002106 self.option_parser.add_option('--https-record-resume',
2107 dest='record_resume', const=True,
2108 default=False, action='store_const',
2109 help='Record resumption cache events rather '
2110 'than resuming as normal. Allows the use of '
2111 'the /ssl-session-cache request')
2112 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2113 help='Require SSL client auth on every '
2114 'connection.')
2115 self.option_parser.add_option('--ssl-client-ca', action='append',
2116 default=[], help='Specify that the client '
2117 'certificate request should include the CA '
2118 'named in the subject of the DER-encoded '
2119 'certificate contained in the specified '
2120 'file. This option may appear multiple '
2121 'times, indicating multiple CA names should '
2122 'be sent in the request.')
2123 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2124 help='Specify the bulk encryption '
2125 'algorithm(s) that will be accepted by the '
2126 'SSL server. Valid values are "aes256", '
2127 '"aes128", "3des", "rc4". If omitted, all '
2128 'algorithms will be used. This option may '
2129 'appear multiple times, indicating '
2130 'multiple algorithms should be enabled.');
2131 self.option_parser.add_option('--file-root-url', default='/files/',
2132 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002133
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002134
initial.commit94958cf2008-07-26 22:42:52 +00002135if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002136 sys.exit(ServerRunner().main())