blob: 13aafcd538bdfd4775fcf3ba0a8915e193753273 [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00002# Copyright 2013 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00006"""This is a simple HTTP/FTP/TCP/UDP/BASIC_AUTH_PROXY/WEBSOCKET server used for
7testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00008
9It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000010By default, it listens on an ephemeral port and sends the port number back to
11the originating process over a pipe. The originating process can specify an
12explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000013It can use https if you specify the flag --https=CERT where CERT is the path
14to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000015"""
16
17import base64
18import BaseHTTPServer
19import cgi
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000020import hashlib
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000021import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000022import minica
initial.commit94958cf2008-07-26 22:42:52 +000023import os
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +000024import json
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000025import random
initial.commit94958cf2008-07-26 22:42:52 +000026import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000028import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000029import SocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000030import ssl
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000031import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000032import sys
33import threading
initial.commit94958cf2008-07-26 22:42:52 +000034import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000035import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000036import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000037import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000038
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000039BASE_DIR = os.path.dirname(os.path.abspath(__file__))
40ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(BASE_DIR)))
41
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000042import echo_message
mattm@chromium.org830a3712012-11-07 23:00:07 +000043import testserver_base
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000044
45# Append at the end of sys.path, it's fine to use the system library.
46sys.path.append(os.path.join(ROOT_DIR, 'third_party', 'pyftpdlib', 'src'))
47sys.path.append(os.path.join(ROOT_DIR, 'third_party', 'tlslite'))
48import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000049import tlslite
50import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000051
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000052# Insert at the beginning of the path, we want this to be used
53# unconditionally.
54sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'pywebsocket', 'src'))
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000055import mod_pywebsocket.standalone
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000056from mod_pywebsocket.standalone import WebSocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000057# import manually
58mod_pywebsocket.standalone.ssl = ssl
davidben@chromium.org06fcf202010-09-22 18:15:23 +000059
maruel@chromium.org756cf982009-03-05 12:46:38 +000060SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000061SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000062SERVER_TCP_ECHO = 2
63SERVER_UDP_ECHO = 3
64SERVER_BASIC_AUTH_PROXY = 4
65SERVER_WEBSOCKET = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000066
67# Default request queue size for WebSocketServer.
68_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000069
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000070class WebSocketOptions:
71 """Holds options for WebSocketServer."""
72
73 def __init__(self, host, port, data_dir):
74 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
75 self.server_host = host
76 self.port = port
77 self.websock_handlers = data_dir
78 self.scan_dir = None
79 self.allow_handlers_outside_root_dir = False
80 self.websock_handlers_map_file = None
81 self.cgi_directories = []
82 self.is_executable_method = None
83 self.allow_draft75 = False
84 self.strict = True
85
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000086 self.use_tls = False
87 self.private_key = None
88 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000089 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000090 self.tls_client_ca = None
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000091 self.tls_module = 'ssl'
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000092 self.use_basic_auth = False
93
mattm@chromium.org830a3712012-11-07 23:00:07 +000094
agl@chromium.orgf9e66792011-12-12 22:22:19 +000095class RecordingSSLSessionCache(object):
96 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
97 lookups and inserts in order to test session cache behaviours."""
98
99 def __init__(self):
100 self.log = []
101
102 def __getitem__(self, sessionID):
103 self.log.append(('lookup', sessionID))
104 raise KeyError()
105
106 def __setitem__(self, sessionID, session):
107 self.log.append(('insert', sessionID))
108
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000109
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000110class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
111 testserver_base.BrokenPipeHandlerMixIn,
112 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000113 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000114 verification."""
115
116 pass
117
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000118class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
119 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000120 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000121 """This is a specialization of HTTPServer that serves an
122 OCSP response"""
123
124 def serve_forever_on_thread(self):
125 self.thread = threading.Thread(target = self.serve_forever,
126 name = "OCSPServerThread")
127 self.thread.start()
128
129 def stop_serving(self):
130 self.shutdown()
131 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000132
mattm@chromium.org830a3712012-11-07 23:00:07 +0000133
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000134class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000135 testserver_base.ClientRestrictingServerMixIn,
136 testserver_base.BrokenPipeHandlerMixIn,
137 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000138 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000139 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000140
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000141 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000142 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000143 record_resume_info, tls_intolerant, signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000144 fallback_scsv_enabled, ocsp_response):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000145 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000146 # Force using only python implementation - otherwise behavior is different
147 # depending on whether m2crypto Python module is present (error is thrown
148 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
149 # the hood.
150 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
151 private=True,
152 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000153 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000154 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000155 self.tls_intolerant = tls_intolerant
ekasper@google.com24aa8222013-11-28 13:43:26 +0000156 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000157 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000158 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000159
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000160 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000161 s = open(ca_file).read()
162 x509 = tlslite.api.X509()
163 x509.parse(s)
164 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000165 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
166 if ssl_bulk_ciphers is not None:
167 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000168
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000169 if record_resume_info:
170 # If record_resume_info is true then we'll replace the session cache with
171 # an object that records the lookups and inserts that it sees.
172 self.session_cache = RecordingSSLSessionCache()
173 else:
174 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000175 testserver_base.StoppableHTTPServer.__init__(self,
176 server_address,
177 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000178
179 def handshake(self, tlsConnection):
180 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000181
initial.commit94958cf2008-07-26 22:42:52 +0000182 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000183 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000184 tlsConnection.handshakeServer(certChain=self.cert_chain,
185 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000186 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000187 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000188 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000189 reqCAs=self.ssl_client_cas,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000190 tlsIntolerant=self.tls_intolerant,
191 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000192 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000193 fallbackSCSV=self.fallback_scsv_enabled,
194 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000195 tlsConnection.ignoreAbruptClose = True
196 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000197 except tlslite.api.TLSAbruptCloseError:
198 # Ignore abrupt close.
199 return True
initial.commit94958cf2008-07-26 22:42:52 +0000200 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000201 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000202 return False
203
akalin@chromium.org154bb132010-11-12 02:20:27 +0000204
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000205class FTPServer(testserver_base.ClientRestrictingServerMixIn,
206 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000207 """This is a specialization of FTPServer that adds client verification."""
208
209 pass
210
211
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000212class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
213 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000214 """A TCP echo server that echoes back what it has received."""
215
216 def server_bind(self):
217 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000218
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000219 SocketServer.TCPServer.server_bind(self)
220 host, port = self.socket.getsockname()[:2]
221 self.server_name = socket.getfqdn(host)
222 self.server_port = port
223
224 def serve_forever(self):
225 self.stop = False
226 self.nonce_time = None
227 while not self.stop:
228 self.handle_request()
229 self.socket.close()
230
231
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000232class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
233 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000234 """A UDP echo server that echoes back what it has received."""
235
236 def server_bind(self):
237 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000238
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000239 SocketServer.UDPServer.server_bind(self)
240 host, port = self.socket.getsockname()[:2]
241 self.server_name = socket.getfqdn(host)
242 self.server_port = port
243
244 def serve_forever(self):
245 self.stop = False
246 self.nonce_time = None
247 while not self.stop:
248 self.handle_request()
249 self.socket.close()
250
251
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000252class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000253 # Class variables to allow for persistence state between page handler
254 # invocations
255 rst_limits = {}
256 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000257
258 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000259 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000260 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000261 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000262 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000263 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000264 self.NoCacheMaxAgeTimeHandler,
265 self.NoCacheTimeHandler,
266 self.CacheTimeHandler,
267 self.CacheExpiresHandler,
268 self.CacheProxyRevalidateHandler,
269 self.CachePrivateHandler,
270 self.CachePublicHandler,
271 self.CacheSMaxAgeHandler,
272 self.CacheMustRevalidateHandler,
273 self.CacheMustRevalidateMaxAgeHandler,
274 self.CacheNoStoreHandler,
275 self.CacheNoStoreMaxAgeHandler,
276 self.CacheNoTransformHandler,
277 self.DownloadHandler,
278 self.DownloadFinishHandler,
279 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000280 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000281 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000282 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000283 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000284 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000285 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000286 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000287 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000288 self.AuthBasicHandler,
289 self.AuthDigestHandler,
290 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000291 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000292 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000293 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000294 self.ServerRedirectHandler,
295 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000296 self.MultipartHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000297 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000298 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000299 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000300 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000301 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000302 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000303 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000304 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000305 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000306 self.PostOnlyFileHandler,
307 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000308 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000309 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000310 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000311 head_handlers = [
312 self.FileHandler,
313 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000314
maruel@google.come250a9b2009-03-10 17:39:46 +0000315 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000316 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000317 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000318 'gif': 'image/gif',
319 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000320 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000321 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000322 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000323 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000324 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000325 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000326 }
initial.commit94958cf2008-07-26 22:42:52 +0000327 self._default_mime_type = 'text/html'
328
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000329 testserver_base.BasePageHandler.__init__(self, request, client_address,
330 socket_server, connect_handlers,
331 get_handlers, head_handlers,
332 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000333
initial.commit94958cf2008-07-26 22:42:52 +0000334 def GetMIMETypeFromName(self, file_name):
335 """Returns the mime type for the specified file_name. So far it only looks
336 at the file extension."""
337
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000338 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000339 if len(extension) == 0:
340 # no extension.
341 return self._default_mime_type
342
ericroman@google.comc17ca532009-05-07 03:51:05 +0000343 # extension starts with a dot, so we need to remove it
344 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000345
initial.commit94958cf2008-07-26 22:42:52 +0000346 def NoCacheMaxAgeTimeHandler(self):
347 """This request handler yields a page with the title set to the current
348 system time, and no caching requested."""
349
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000350 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000351 return False
352
353 self.send_response(200)
354 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000355 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000356 self.end_headers()
357
maruel@google.come250a9b2009-03-10 17:39:46 +0000358 self.wfile.write('<html><head><title>%s</title></head></html>' %
359 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000360
361 return True
362
363 def NoCacheTimeHandler(self):
364 """This request handler yields a page with the title set to the current
365 system time, and no caching requested."""
366
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000367 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000368 return False
369
370 self.send_response(200)
371 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000372 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000373 self.end_headers()
374
maruel@google.come250a9b2009-03-10 17:39:46 +0000375 self.wfile.write('<html><head><title>%s</title></head></html>' %
376 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000377
378 return True
379
380 def CacheTimeHandler(self):
381 """This request handler yields a page with the title set to the current
382 system time, and allows caching for one minute."""
383
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000384 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000385 return False
386
387 self.send_response(200)
388 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000389 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000390 self.end_headers()
391
maruel@google.come250a9b2009-03-10 17:39:46 +0000392 self.wfile.write('<html><head><title>%s</title></head></html>' %
393 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000394
395 return True
396
397 def CacheExpiresHandler(self):
398 """This request handler yields a page with the title set to the current
399 system time, and set the page to expire on 1 Jan 2099."""
400
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000401 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000402 return False
403
404 self.send_response(200)
405 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000406 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000407 self.end_headers()
408
maruel@google.come250a9b2009-03-10 17:39:46 +0000409 self.wfile.write('<html><head><title>%s</title></head></html>' %
410 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000411
412 return True
413
414 def CacheProxyRevalidateHandler(self):
415 """This request handler yields a page with the title set to the current
416 system time, and allows caching for 60 seconds"""
417
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000418 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000419 return False
420
421 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000422 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000423 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
424 self.end_headers()
425
maruel@google.come250a9b2009-03-10 17:39:46 +0000426 self.wfile.write('<html><head><title>%s</title></head></html>' %
427 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000428
429 return True
430
431 def CachePrivateHandler(self):
432 """This request handler yields a page with the title set to the current
433 system time, and allows caching for 5 seconds."""
434
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000435 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000436 return False
437
438 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000439 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000440 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000441 self.end_headers()
442
maruel@google.come250a9b2009-03-10 17:39:46 +0000443 self.wfile.write('<html><head><title>%s</title></head></html>' %
444 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000445
446 return True
447
448 def CachePublicHandler(self):
449 """This request handler yields a page with the title set to the current
450 system time, and allows caching for 5 seconds."""
451
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000452 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000453 return False
454
455 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000456 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000457 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000458 self.end_headers()
459
maruel@google.come250a9b2009-03-10 17:39:46 +0000460 self.wfile.write('<html><head><title>%s</title></head></html>' %
461 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000462
463 return True
464
465 def CacheSMaxAgeHandler(self):
466 """This request handler yields a page with the title set to the current
467 system time, and does not allow for caching."""
468
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000469 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000470 return False
471
472 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000473 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000474 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
475 self.end_headers()
476
maruel@google.come250a9b2009-03-10 17:39:46 +0000477 self.wfile.write('<html><head><title>%s</title></head></html>' %
478 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000479
480 return True
481
482 def CacheMustRevalidateHandler(self):
483 """This request handler yields a page with the title set to the current
484 system time, and does not allow caching."""
485
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000486 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000487 return False
488
489 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000490 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000491 self.send_header('Cache-Control', 'must-revalidate')
492 self.end_headers()
493
maruel@google.come250a9b2009-03-10 17:39:46 +0000494 self.wfile.write('<html><head><title>%s</title></head></html>' %
495 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000496
497 return True
498
499 def CacheMustRevalidateMaxAgeHandler(self):
500 """This request handler yields a page with the title set to the current
501 system time, and does not allow caching event though max-age of 60
502 seconds is specified."""
503
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000504 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000505 return False
506
507 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000508 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000509 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
510 self.end_headers()
511
maruel@google.come250a9b2009-03-10 17:39:46 +0000512 self.wfile.write('<html><head><title>%s</title></head></html>' %
513 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000514
515 return True
516
initial.commit94958cf2008-07-26 22:42:52 +0000517 def CacheNoStoreHandler(self):
518 """This request handler yields a page with the title set to the current
519 system time, and does not allow the page to be stored."""
520
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000521 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000522 return False
523
524 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000525 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000526 self.send_header('Cache-Control', 'no-store')
527 self.end_headers()
528
maruel@google.come250a9b2009-03-10 17:39:46 +0000529 self.wfile.write('<html><head><title>%s</title></head></html>' %
530 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000531
532 return True
533
534 def CacheNoStoreMaxAgeHandler(self):
535 """This request handler yields a page with the title set to the current
536 system time, and does not allow the page to be stored even though max-age
537 of 60 seconds is specified."""
538
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000539 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000540 return False
541
542 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000543 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000544 self.send_header('Cache-Control', 'max-age=60, no-store')
545 self.end_headers()
546
maruel@google.come250a9b2009-03-10 17:39:46 +0000547 self.wfile.write('<html><head><title>%s</title></head></html>' %
548 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000549
550 return True
551
552
553 def CacheNoTransformHandler(self):
554 """This request handler yields a page with the title set to the current
555 system time, and does not allow the content to transformed during
556 user-agent caching"""
557
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000558 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000559 return False
560
561 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000562 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000563 self.send_header('Cache-Control', 'no-transform')
564 self.end_headers()
565
maruel@google.come250a9b2009-03-10 17:39:46 +0000566 self.wfile.write('<html><head><title>%s</title></head></html>' %
567 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000568
569 return True
570
571 def EchoHeader(self):
572 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000573
ananta@chromium.org219b2062009-10-23 16:09:41 +0000574 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000575
ananta@chromium.org56812d02011-04-07 17:52:05 +0000576 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000577 """This function echoes back the value of a specific request header while
578 allowing caching for 16 hours."""
579
ananta@chromium.org56812d02011-04-07 17:52:05 +0000580 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000581
582 def EchoHeaderHelper(self, echo_header):
583 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000584
ananta@chromium.org219b2062009-10-23 16:09:41 +0000585 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000586 return False
587
588 query_char = self.path.find('?')
589 if query_char != -1:
590 header_name = self.path[query_char+1:]
591
592 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000593 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000594 if echo_header == '/echoheadercache':
595 self.send_header('Cache-control', 'max-age=60000')
596 else:
597 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000598 # insert a vary header to properly indicate that the cachability of this
599 # request is subject to value of the request header being echoed.
600 if len(header_name) > 0:
601 self.send_header('Vary', header_name)
602 self.end_headers()
603
604 if len(header_name) > 0:
605 self.wfile.write(self.headers.getheader(header_name))
606
607 return True
608
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000609 def ReadRequestBody(self):
610 """This function reads the body of the current HTTP request, handling
611 both plain and chunked transfer encoded requests."""
612
613 if self.headers.getheader('transfer-encoding') != 'chunked':
614 length = int(self.headers.getheader('content-length'))
615 return self.rfile.read(length)
616
617 # Read the request body as chunks.
618 body = ""
619 while True:
620 line = self.rfile.readline()
621 length = int(line, 16)
622 if length == 0:
623 self.rfile.readline()
624 break
625 body += self.rfile.read(length)
626 self.rfile.read(2)
627 return body
628
initial.commit94958cf2008-07-26 22:42:52 +0000629 def EchoHandler(self):
630 """This handler just echoes back the payload of the request, for testing
631 form submission."""
632
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000633 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000634 return False
635
636 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000637 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000638 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000639 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000640 return True
641
642 def EchoTitleHandler(self):
643 """This handler is like Echo, but sets the page title to the request."""
644
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000645 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000646 return False
647
648 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000649 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000650 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000651 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000652 self.wfile.write('<html><head><title>')
653 self.wfile.write(request)
654 self.wfile.write('</title></head></html>')
655 return True
656
657 def EchoAllHandler(self):
658 """This handler yields a (more) human-readable page listing information
659 about the request header & contents."""
660
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000661 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000662 return False
663
664 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000665 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000666 self.end_headers()
667 self.wfile.write('<html><head><style>'
668 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
669 '</style></head><body>'
670 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000671 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000672 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000673
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000674 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000675 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000676 params = cgi.parse_qs(qs, keep_blank_values=1)
677
678 for param in params:
679 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000680
681 self.wfile.write('</pre>')
682
683 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
684
685 self.wfile.write('</body></html>')
686 return True
687
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000688 def EchoMultipartPostHandler(self):
689 """This handler echoes received multipart post data as json format."""
690
691 if not (self._ShouldHandleRequest("/echomultipartpost") or
692 self._ShouldHandleRequest("/searchbyimage")):
693 return False
694
695 content_type, parameters = cgi.parse_header(
696 self.headers.getheader('content-type'))
697 if content_type == 'multipart/form-data':
698 post_multipart = cgi.parse_multipart(self.rfile, parameters)
699 elif content_type == 'application/x-www-form-urlencoded':
700 raise Exception('POST by application/x-www-form-urlencoded is '
701 'not implemented.')
702 else:
703 post_multipart = {}
704
705 # Since the data can be binary, we encode them by base64.
706 post_multipart_base64_encoded = {}
707 for field, values in post_multipart.items():
708 post_multipart_base64_encoded[field] = [base64.b64encode(value)
709 for value in values]
710
711 result = {'POST_multipart' : post_multipart_base64_encoded}
712
713 self.send_response(200)
714 self.send_header("Content-type", "text/plain")
715 self.end_headers()
716 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
717 return True
718
initial.commit94958cf2008-07-26 22:42:52 +0000719 def DownloadHandler(self):
720 """This handler sends a downloadable file with or without reporting
721 the size (6K)."""
722
723 if self.path.startswith("/download-unknown-size"):
724 send_length = False
725 elif self.path.startswith("/download-known-size"):
726 send_length = True
727 else:
728 return False
729
730 #
731 # The test which uses this functionality is attempting to send
732 # small chunks of data to the client. Use a fairly large buffer
733 # so that we'll fill chrome's IO buffer enough to force it to
734 # actually write the data.
735 # See also the comments in the client-side of this test in
736 # download_uitest.cc
737 #
738 size_chunk1 = 35*1024
739 size_chunk2 = 10*1024
740
741 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000742 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000743 self.send_header('Cache-Control', 'max-age=0')
744 if send_length:
745 self.send_header('Content-Length', size_chunk1 + size_chunk2)
746 self.end_headers()
747
748 # First chunk of data:
749 self.wfile.write("*" * size_chunk1)
750 self.wfile.flush()
751
752 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000753 self.server.wait_for_download = True
754 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000755 self.server.handle_request()
756
757 # Second chunk of data:
758 self.wfile.write("*" * size_chunk2)
759 return True
760
761 def DownloadFinishHandler(self):
762 """This handler just tells the server to finish the current download."""
763
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000764 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000765 return False
766
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000767 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000768 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000769 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000770 self.send_header('Cache-Control', 'max-age=0')
771 self.end_headers()
772 return True
773
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000774 def _ReplaceFileData(self, data, query_parameters):
775 """Replaces matching substrings in a file.
776
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000777 If the 'replace_text' URL query parameter is present, it is expected to be
778 of the form old_text:new_text, which indicates that any old_text strings in
779 the file are replaced with new_text. Multiple 'replace_text' parameters may
780 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000781
782 If the parameters are not present, |data| is returned.
783 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000784
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000785 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000786 replace_text_values = query_dict.get('replace_text', [])
787 for replace_text_value in replace_text_values:
788 replace_text_args = replace_text_value.split(':')
789 if len(replace_text_args) != 2:
790 raise ValueError(
791 'replace_text must be of form old_text:new_text. Actual value: %s' %
792 replace_text_value)
793 old_text_b64, new_text_b64 = replace_text_args
794 old_text = base64.urlsafe_b64decode(old_text_b64)
795 new_text = base64.urlsafe_b64decode(new_text_b64)
796 data = data.replace(old_text, new_text)
797 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000798
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000799 def ZipFileHandler(self):
800 """This handler sends the contents of the requested file in compressed form.
801 Can pass in a parameter that specifies that the content length be
802 C - the compressed size (OK),
803 U - the uncompressed size (Non-standard, but handled),
804 S - less than compressed (OK because we keep going),
805 M - larger than compressed but less than uncompressed (an error),
806 L - larger than uncompressed (an error)
807 Example: compressedfiles/Picture_1.doc?C
808 """
809
810 prefix = "/compressedfiles/"
811 if not self.path.startswith(prefix):
812 return False
813
814 # Consume a request body if present.
815 if self.command == 'POST' or self.command == 'PUT' :
816 self.ReadRequestBody()
817
818 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
819
820 if not query in ('C', 'U', 'S', 'M', 'L'):
821 return False
822
823 sub_path = url_path[len(prefix):]
824 entries = sub_path.split('/')
825 file_path = os.path.join(self.server.data_dir, *entries)
826 if os.path.isdir(file_path):
827 file_path = os.path.join(file_path, 'index.html')
828
829 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000830 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000831 self.send_error(404)
832 return True
833
834 f = open(file_path, "rb")
835 data = f.read()
836 uncompressed_len = len(data)
837 f.close()
838
839 # Compress the data.
840 data = zlib.compress(data)
841 compressed_len = len(data)
842
843 content_length = compressed_len
844 if query == 'U':
845 content_length = uncompressed_len
846 elif query == 'S':
847 content_length = compressed_len / 2
848 elif query == 'M':
849 content_length = (compressed_len + uncompressed_len) / 2
850 elif query == 'L':
851 content_length = compressed_len + uncompressed_len
852
853 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000854 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000855 self.send_header('Content-encoding', 'deflate')
856 self.send_header('Connection', 'close')
857 self.send_header('Content-Length', content_length)
858 self.send_header('ETag', '\'' + file_path + '\'')
859 self.end_headers()
860
861 self.wfile.write(data)
862
863 return True
864
initial.commit94958cf2008-07-26 22:42:52 +0000865 def FileHandler(self):
866 """This handler sends the contents of the requested file. Wow, it's like
867 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000868
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000869 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000870 if not self.path.startswith(prefix):
871 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000872 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000873
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000874 def PostOnlyFileHandler(self):
875 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000876
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000877 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000878 if not self.path.startswith(prefix):
879 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000880 return self._FileHandlerHelper(prefix)
881
882 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000883 request_body = ''
884 if self.command == 'POST' or self.command == 'PUT':
885 # Consume a request body if present.
886 request_body = self.ReadRequestBody()
887
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000888 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000889 query_dict = cgi.parse_qs(query)
890
891 expected_body = query_dict.get('expected_body', [])
892 if expected_body and request_body not in expected_body:
893 self.send_response(404)
894 self.end_headers()
895 self.wfile.write('')
896 return True
897
898 expected_headers = query_dict.get('expected_headers', [])
899 for expected_header in expected_headers:
900 header_name, expected_value = expected_header.split(':')
901 if self.headers.getheader(header_name) != expected_value:
902 self.send_response(404)
903 self.end_headers()
904 self.wfile.write('')
905 return True
906
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000907 sub_path = url_path[len(prefix):]
908 entries = sub_path.split('/')
909 file_path = os.path.join(self.server.data_dir, *entries)
910 if os.path.isdir(file_path):
911 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000912
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000913 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000914 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000915 self.send_error(404)
916 return True
917
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000918 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000919 data = f.read()
920 f.close()
921
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000922 data = self._ReplaceFileData(data, query)
923
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000924 old_protocol_version = self.protocol_version
925
initial.commit94958cf2008-07-26 22:42:52 +0000926 # If file.mock-http-headers exists, it contains the headers we
927 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000928 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000929 if os.path.isfile(headers_path):
930 f = open(headers_path, "r")
931
932 # "HTTP/1.1 200 OK"
933 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000934 http_major, http_minor, status_code = re.findall(
935 'HTTP/(\d+).(\d+) (\d+)', response)[0]
936 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000937 self.send_response(int(status_code))
938
939 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000940 header_values = re.findall('(\S+):\s*(.*)', line)
941 if len(header_values) > 0:
942 # "name: value"
943 name, value = header_values[0]
944 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000945 f.close()
946 else:
947 # Could be more generic once we support mime-type sniffing, but for
948 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000949
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000950 range_header = self.headers.get('Range')
951 if range_header and range_header.startswith('bytes='):
952 # Note this doesn't handle all valid byte range_header values (i.e.
953 # left open ended ones), just enough for what we needed so far.
954 range_header = range_header[6:].split('-')
955 start = int(range_header[0])
956 if range_header[1]:
957 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000958 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000959 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000960
961 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000962 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
963 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000964 self.send_header('Content-Range', content_range)
965 data = data[start: end + 1]
966 else:
967 self.send_response(200)
968
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000969 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000970 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000971 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000972 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000973 self.end_headers()
974
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000975 if (self.command != 'HEAD'):
976 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000977
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000978 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000979 return True
980
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000981 def SetCookieHandler(self):
982 """This handler just sets a cookie, for testing cookie handling."""
983
984 if not self._ShouldHandleRequest("/set-cookie"):
985 return False
986
987 query_char = self.path.find('?')
988 if query_char != -1:
989 cookie_values = self.path[query_char + 1:].split('&')
990 else:
991 cookie_values = ("",)
992 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000993 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000994 for cookie_value in cookie_values:
995 self.send_header('Set-Cookie', '%s' % cookie_value)
996 self.end_headers()
997 for cookie_value in cookie_values:
998 self.wfile.write('%s' % cookie_value)
999 return True
1000
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001001 def SetManyCookiesHandler(self):
1002 """This handler just sets a given number of cookies, for testing handling
1003 of large numbers of cookies."""
1004
1005 if not self._ShouldHandleRequest("/set-many-cookies"):
1006 return False
1007
1008 query_char = self.path.find('?')
1009 if query_char != -1:
1010 num_cookies = int(self.path[query_char + 1:])
1011 else:
1012 num_cookies = 0
1013 self.send_response(200)
1014 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001015 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001016 self.send_header('Set-Cookie', 'a=')
1017 self.end_headers()
1018 self.wfile.write('%d cookies were sent' % num_cookies)
1019 return True
1020
mattm@chromium.org983fc462012-06-30 00:52:08 +00001021 def ExpectAndSetCookieHandler(self):
1022 """Expects some cookies to be sent, and if they are, sets more cookies.
1023
1024 The expect parameter specifies a required cookie. May be specified multiple
1025 times.
1026 The set parameter specifies a cookie to set if all required cookies are
1027 preset. May be specified multiple times.
1028 The data parameter specifies the response body data to be returned."""
1029
1030 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1031 return False
1032
1033 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1034 query_dict = cgi.parse_qs(query)
1035 cookies = set()
1036 if 'Cookie' in self.headers:
1037 cookie_header = self.headers.getheader('Cookie')
1038 cookies.update([s.strip() for s in cookie_header.split(';')])
1039 got_all_expected_cookies = True
1040 for expected_cookie in query_dict.get('expect', []):
1041 if expected_cookie not in cookies:
1042 got_all_expected_cookies = False
1043 self.send_response(200)
1044 self.send_header('Content-Type', 'text/html')
1045 if got_all_expected_cookies:
1046 for cookie_value in query_dict.get('set', []):
1047 self.send_header('Set-Cookie', '%s' % cookie_value)
1048 self.end_headers()
1049 for data_value in query_dict.get('data', []):
1050 self.wfile.write(data_value)
1051 return True
1052
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001053 def SetHeaderHandler(self):
1054 """This handler sets a response header. Parameters are in the
1055 key%3A%20value&key2%3A%20value2 format."""
1056
1057 if not self._ShouldHandleRequest("/set-header"):
1058 return False
1059
1060 query_char = self.path.find('?')
1061 if query_char != -1:
1062 headers_values = self.path[query_char + 1:].split('&')
1063 else:
1064 headers_values = ("",)
1065 self.send_response(200)
1066 self.send_header('Content-Type', 'text/html')
1067 for header_value in headers_values:
1068 header_value = urllib.unquote(header_value)
1069 (key, value) = header_value.split(': ', 1)
1070 self.send_header(key, value)
1071 self.end_headers()
1072 for header_value in headers_values:
1073 self.wfile.write('%s' % header_value)
1074 return True
1075
initial.commit94958cf2008-07-26 22:42:52 +00001076 def AuthBasicHandler(self):
1077 """This handler tests 'Basic' authentication. It just sends a page with
1078 title 'user/pass' if you succeed."""
1079
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001080 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001081 return False
1082
1083 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001084 expected_password = 'secret'
1085 realm = 'testrealm'
1086 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001087
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001088 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1089 query_params = cgi.parse_qs(query, True)
1090 if 'set-cookie-if-challenged' in query_params:
1091 set_cookie_if_challenged = True
1092 if 'password' in query_params:
1093 expected_password = query_params['password'][0]
1094 if 'realm' in query_params:
1095 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001096
initial.commit94958cf2008-07-26 22:42:52 +00001097 auth = self.headers.getheader('authorization')
1098 try:
1099 if not auth:
1100 raise Exception('no auth')
1101 b64str = re.findall(r'Basic (\S+)', auth)[0]
1102 userpass = base64.b64decode(b64str)
1103 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001104 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001105 raise Exception('wrong password')
1106 except Exception, e:
1107 # Authentication failed.
1108 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001109 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001110 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001111 if set_cookie_if_challenged:
1112 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001113 self.end_headers()
1114 self.wfile.write('<html><head>')
1115 self.wfile.write('<title>Denied: %s</title>' % e)
1116 self.wfile.write('</head><body>')
1117 self.wfile.write('auth=%s<p>' % auth)
1118 self.wfile.write('b64str=%s<p>' % b64str)
1119 self.wfile.write('username: %s<p>' % username)
1120 self.wfile.write('userpass: %s<p>' % userpass)
1121 self.wfile.write('password: %s<p>' % password)
1122 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1123 self.wfile.write('</body></html>')
1124 return True
1125
1126 # Authentication successful. (Return a cachable response to allow for
1127 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001128 old_protocol_version = self.protocol_version
1129 self.protocol_version = "HTTP/1.1"
1130
initial.commit94958cf2008-07-26 22:42:52 +00001131 if_none_match = self.headers.getheader('if-none-match')
1132 if if_none_match == "abc":
1133 self.send_response(304)
1134 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001135 elif url_path.endswith(".gif"):
1136 # Using chrome/test/data/google/logo.gif as the test image
1137 test_image_path = ['google', 'logo.gif']
1138 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1139 if not os.path.isfile(gif_path):
1140 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001141 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001142 return True
1143
1144 f = open(gif_path, "rb")
1145 data = f.read()
1146 f.close()
1147
1148 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001149 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001150 self.send_header('Cache-control', 'max-age=60000')
1151 self.send_header('Etag', 'abc')
1152 self.end_headers()
1153 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001154 else:
1155 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001156 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001157 self.send_header('Cache-control', 'max-age=60000')
1158 self.send_header('Etag', 'abc')
1159 self.end_headers()
1160 self.wfile.write('<html><head>')
1161 self.wfile.write('<title>%s/%s</title>' % (username, password))
1162 self.wfile.write('</head><body>')
1163 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001164 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001165 self.wfile.write('</body></html>')
1166
rvargas@google.com54453b72011-05-19 01:11:11 +00001167 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001168 return True
1169
tonyg@chromium.org75054202010-03-31 22:06:10 +00001170 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001171 """Returns a nonce that's stable per request path for the server's lifetime.
1172 This is a fake implementation. A real implementation would only use a given
1173 nonce a single time (hence the name n-once). However, for the purposes of
1174 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001175
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001176 Args:
1177 force_reset: Iff set, the nonce will be changed. Useful for testing the
1178 "stale" response.
1179 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001180
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001181 if force_reset or not self.server.nonce_time:
1182 self.server.nonce_time = time.time()
1183 return hashlib.md5('privatekey%s%d' %
1184 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001185
1186 def AuthDigestHandler(self):
1187 """This handler tests 'Digest' authentication.
1188
1189 It just sends a page with title 'user/pass' if you succeed.
1190
1191 A stale response is sent iff "stale" is present in the request path.
1192 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001193
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001194 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001195 return False
1196
tonyg@chromium.org75054202010-03-31 22:06:10 +00001197 stale = 'stale' in self.path
1198 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001199 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001200 password = 'secret'
1201 realm = 'testrealm'
1202
1203 auth = self.headers.getheader('authorization')
1204 pairs = {}
1205 try:
1206 if not auth:
1207 raise Exception('no auth')
1208 if not auth.startswith('Digest'):
1209 raise Exception('not digest')
1210 # Pull out all the name="value" pairs as a dictionary.
1211 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1212
1213 # Make sure it's all valid.
1214 if pairs['nonce'] != nonce:
1215 raise Exception('wrong nonce')
1216 if pairs['opaque'] != opaque:
1217 raise Exception('wrong opaque')
1218
1219 # Check the 'response' value and make sure it matches our magic hash.
1220 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001221 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001222 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001223 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001224 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001225 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001226 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1227 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001228 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001229
1230 if pairs['response'] != response:
1231 raise Exception('wrong password')
1232 except Exception, e:
1233 # Authentication failed.
1234 self.send_response(401)
1235 hdr = ('Digest '
1236 'realm="%s", '
1237 'domain="/", '
1238 'qop="auth", '
1239 'algorithm=MD5, '
1240 'nonce="%s", '
1241 'opaque="%s"') % (realm, nonce, opaque)
1242 if stale:
1243 hdr += ', stale="TRUE"'
1244 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001245 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001246 self.end_headers()
1247 self.wfile.write('<html><head>')
1248 self.wfile.write('<title>Denied: %s</title>' % e)
1249 self.wfile.write('</head><body>')
1250 self.wfile.write('auth=%s<p>' % auth)
1251 self.wfile.write('pairs=%s<p>' % pairs)
1252 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1253 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1254 self.wfile.write('</body></html>')
1255 return True
1256
1257 # Authentication successful.
1258 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001259 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001260 self.end_headers()
1261 self.wfile.write('<html><head>')
1262 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1263 self.wfile.write('</head><body>')
1264 self.wfile.write('auth=%s<p>' % auth)
1265 self.wfile.write('pairs=%s<p>' % pairs)
1266 self.wfile.write('</body></html>')
1267
1268 return True
1269
1270 def SlowServerHandler(self):
1271 """Wait for the user suggested time before responding. The syntax is
1272 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001273
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001274 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001275 return False
1276 query_char = self.path.find('?')
1277 wait_sec = 1.0
1278 if query_char >= 0:
1279 try:
1280 wait_sec = int(self.path[query_char + 1:])
1281 except ValueError:
1282 pass
1283 time.sleep(wait_sec)
1284 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001285 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001286 self.end_headers()
1287 self.wfile.write("waited %d seconds" % wait_sec)
1288 return True
1289
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001290 def ChunkedServerHandler(self):
1291 """Send chunked response. Allows to specify chunks parameters:
1292 - waitBeforeHeaders - ms to wait before sending headers
1293 - waitBetweenChunks - ms to wait between chunks
1294 - chunkSize - size of each chunk in bytes
1295 - chunksNumber - number of chunks
1296 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1297 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001298
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001299 if not self._ShouldHandleRequest("/chunked"):
1300 return False
1301 query_char = self.path.find('?')
1302 chunkedSettings = {'waitBeforeHeaders' : 0,
1303 'waitBetweenChunks' : 0,
1304 'chunkSize' : 5,
1305 'chunksNumber' : 5}
1306 if query_char >= 0:
1307 params = self.path[query_char + 1:].split('&')
1308 for param in params:
1309 keyValue = param.split('=')
1310 if len(keyValue) == 2:
1311 try:
1312 chunkedSettings[keyValue[0]] = int(keyValue[1])
1313 except ValueError:
1314 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001315 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001316 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1317 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001318 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001319 self.send_header('Connection', 'close')
1320 self.send_header('Transfer-Encoding', 'chunked')
1321 self.end_headers()
1322 # Chunked encoding: sending all chunks, then final zero-length chunk and
1323 # then final CRLF.
1324 for i in range(0, chunkedSettings['chunksNumber']):
1325 if i > 0:
1326 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1327 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001328 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001329 self.sendChunkHelp('')
1330 return True
1331
initial.commit94958cf2008-07-26 22:42:52 +00001332 def ContentTypeHandler(self):
1333 """Returns a string of html with the given content type. E.g.,
1334 /contenttype?text/css returns an html file with the Content-Type
1335 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001336
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001337 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001338 return False
1339 query_char = self.path.find('?')
1340 content_type = self.path[query_char + 1:].strip()
1341 if not content_type:
1342 content_type = 'text/html'
1343 self.send_response(200)
1344 self.send_header('Content-Type', content_type)
1345 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001346 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001347 return True
1348
creis@google.com2f4f6a42011-03-25 19:44:19 +00001349 def NoContentHandler(self):
1350 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001351
creis@google.com2f4f6a42011-03-25 19:44:19 +00001352 if not self._ShouldHandleRequest("/nocontent"):
1353 return False
1354 self.send_response(204)
1355 self.end_headers()
1356 return True
1357
initial.commit94958cf2008-07-26 22:42:52 +00001358 def ServerRedirectHandler(self):
1359 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001360 '/server-redirect?http://foo.bar/asdf' to redirect to
1361 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001362
1363 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001364 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001365 return False
1366
1367 query_char = self.path.find('?')
1368 if query_char < 0 or len(self.path) <= query_char + 1:
1369 self.sendRedirectHelp(test_name)
1370 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001371 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001372
1373 self.send_response(301) # moved permanently
1374 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001375 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001376 self.end_headers()
1377 self.wfile.write('<html><head>')
1378 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1379
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001380 return True
initial.commit94958cf2008-07-26 22:42:52 +00001381
1382 def ClientRedirectHandler(self):
1383 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001384 '/client-redirect?http://foo.bar/asdf' to redirect to
1385 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001386
1387 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001388 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001389 return False
1390
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001391 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001392 if query_char < 0 or len(self.path) <= query_char + 1:
1393 self.sendRedirectHelp(test_name)
1394 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001395 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001396
1397 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001398 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001399 self.end_headers()
1400 self.wfile.write('<html><head>')
1401 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1402 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1403
1404 return True
1405
tony@chromium.org03266982010-03-05 03:18:42 +00001406 def MultipartHandler(self):
1407 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001408
tony@chromium.org4cb88302011-09-27 22:13:49 +00001409 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001410 if not self._ShouldHandleRequest(test_name):
1411 return False
1412
1413 num_frames = 10
1414 bound = '12345'
1415 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001416 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001417 'multipart/x-mixed-replace;boundary=' + bound)
1418 self.end_headers()
1419
1420 for i in xrange(num_frames):
1421 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001422 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001423 self.wfile.write('<title>page ' + str(i) + '</title>')
1424 self.wfile.write('page ' + str(i))
1425
1426 self.wfile.write('--' + bound + '--')
1427 return True
1428
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001429 def GetSSLSessionCacheHandler(self):
1430 """Send a reply containing a log of the session cache operations."""
1431
1432 if not self._ShouldHandleRequest('/ssl-session-cache'):
1433 return False
1434
1435 self.send_response(200)
1436 self.send_header('Content-Type', 'text/plain')
1437 self.end_headers()
1438 try:
1439 for (action, sessionID) in self.server.session_cache.log:
1440 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001441 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001442 self.wfile.write('Pass --https-record-resume in order to use' +
1443 ' this request')
1444 return True
1445
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001446 def SSLManySmallRecords(self):
1447 """Sends a reply consisting of a variety of small writes. These will be
1448 translated into a series of small SSL records when used over an HTTPS
1449 server."""
1450
1451 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1452 return False
1453
1454 self.send_response(200)
1455 self.send_header('Content-Type', 'text/plain')
1456 self.end_headers()
1457
1458 # Write ~26K of data, in 1350 byte chunks
1459 for i in xrange(20):
1460 self.wfile.write('*' * 1350)
1461 self.wfile.flush()
1462 return True
1463
agl@chromium.org04700be2013-03-02 18:40:41 +00001464 def GetChannelID(self):
1465 """Send a reply containing the hashed ChannelID that the client provided."""
1466
1467 if not self._ShouldHandleRequest('/channel-id'):
1468 return False
1469
1470 self.send_response(200)
1471 self.send_header('Content-Type', 'text/plain')
1472 self.end_headers()
1473 channel_id = self.server.tlsConnection.channel_id.tostring()
1474 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1475 return True
1476
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001477 def CloseSocketHandler(self):
1478 """Closes the socket without sending anything."""
1479
1480 if not self._ShouldHandleRequest('/close-socket'):
1481 return False
1482
1483 self.wfile.close()
1484 return True
1485
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001486 def RangeResetHandler(self):
1487 """Send data broken up by connection resets every N (default 4K) bytes.
1488 Support range requests. If the data requested doesn't straddle a reset
1489 boundary, it will all be sent. Used for testing resuming downloads."""
1490
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001491 def DataForRange(start, end):
1492 """Data to be provided for a particular range of bytes."""
1493 # Offset and scale to avoid too obvious (and hence potentially
1494 # collidable) data.
1495 return ''.join([chr(y % 256)
1496 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1497
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001498 if not self._ShouldHandleRequest('/rangereset'):
1499 return False
1500
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001501 # HTTP/1.1 is required for ETag and range support.
1502 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001503 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1504
1505 # Defaults
1506 size = 8000
1507 # Note that the rst is sent just before sending the rst_boundary byte.
1508 rst_boundary = 4000
1509 respond_to_range = True
1510 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001511 rst_limit = -1
1512 token = 'DEFAULT'
1513 fail_precondition = 0
1514 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001515
1516 # Parse the query
1517 qdict = urlparse.parse_qs(query, True)
1518 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001519 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001520 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001521 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001522 if 'token' in qdict:
1523 # Identifying token for stateful tests.
1524 token = qdict['token'][0]
1525 if 'rst_limit' in qdict:
1526 # Max number of rsts for a given token.
1527 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001528 if 'bounce_range' in qdict:
1529 respond_to_range = False
1530 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001531 # Note that hold_for_signal will not work with null range requests;
1532 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001533 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001534 if 'no_verifiers' in qdict:
1535 send_verifiers = False
1536 if 'fail_precondition' in qdict:
1537 fail_precondition = int(qdict['fail_precondition'][0])
1538
1539 # Record already set information, or set it.
1540 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1541 if rst_limit != 0:
1542 TestPageHandler.rst_limits[token] -= 1
1543 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1544 token, fail_precondition)
1545 if fail_precondition != 0:
1546 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001547
1548 first_byte = 0
1549 last_byte = size - 1
1550
1551 # Does that define what we want to return, or do we need to apply
1552 # a range?
1553 range_response = False
1554 range_header = self.headers.getheader('range')
1555 if range_header and respond_to_range:
1556 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1557 if mo.group(1):
1558 first_byte = int(mo.group(1))
1559 if mo.group(2):
1560 last_byte = int(mo.group(2))
1561 if last_byte > size - 1:
1562 last_byte = size - 1
1563 range_response = True
1564 if last_byte < first_byte:
1565 return False
1566
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001567 if (fail_precondition and
1568 (self.headers.getheader('If-Modified-Since') or
1569 self.headers.getheader('If-Match'))):
1570 self.send_response(412)
1571 self.end_headers()
1572 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001573
1574 if range_response:
1575 self.send_response(206)
1576 self.send_header('Content-Range',
1577 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1578 else:
1579 self.send_response(200)
1580 self.send_header('Content-Type', 'application/octet-stream')
1581 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001582 if send_verifiers:
asanka@chromium.org3ffced92013-12-13 22:20:27 +00001583 # If fail_precondition is non-zero, then the ETag for each request will be
1584 # different.
1585 etag = "%s%d" % (token, fail_precondition)
1586 self.send_header('ETag', etag)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001587 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001588 self.end_headers()
1589
1590 if hold_for_signal:
1591 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1592 # a single byte, the self.server.handle_request() below hangs
1593 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001594 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001595 first_byte = first_byte + 1
1596 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001597 self.server.wait_for_download = True
1598 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001599 self.server.handle_request()
1600
1601 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001602 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001603 # No RST has been requested in this range, so we don't need to
1604 # do anything fancy; just write the data and let the python
1605 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001606 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001607 self.wfile.flush()
1608 return True
1609
1610 # We're resetting the connection part way in; go to the RST
1611 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001612 # Because socket semantics do not guarantee that all the data will be
1613 # sent when using the linger semantics to hard close a socket,
1614 # we send the data and then wait for our peer to release us
1615 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001616 data = DataForRange(first_byte, possible_rst)
1617 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001618 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001619 self.server.wait_for_download = True
1620 while self.server.wait_for_download:
1621 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001622 l_onoff = 1 # Linger is active.
1623 l_linger = 0 # Seconds to linger for.
1624 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1625 struct.pack('ii', l_onoff, l_linger))
1626
1627 # Close all duplicates of the underlying socket to force the RST.
1628 self.wfile.close()
1629 self.rfile.close()
1630 self.connection.close()
1631
1632 return True
1633
initial.commit94958cf2008-07-26 22:42:52 +00001634 def DefaultResponseHandler(self):
1635 """This is the catch-all response handler for requests that aren't handled
1636 by one of the special handlers above.
1637 Note that we specify the content-length as without it the https connection
1638 is not closed properly (and the browser keeps expecting data)."""
1639
1640 contents = "Default response given for path: " + self.path
1641 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001642 self.send_header('Content-Type', 'text/html')
1643 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001644 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001645 if (self.command != 'HEAD'):
1646 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001647 return True
1648
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001649 def RedirectConnectHandler(self):
1650 """Sends a redirect to the CONNECT request for www.redirect.com. This
1651 response is not specified by the RFC, so the browser should not follow
1652 the redirect."""
1653
1654 if (self.path.find("www.redirect.com") < 0):
1655 return False
1656
1657 dest = "http://www.destination.com/foo.js"
1658
1659 self.send_response(302) # moved temporarily
1660 self.send_header('Location', dest)
1661 self.send_header('Connection', 'close')
1662 self.end_headers()
1663 return True
1664
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001665 def ServerAuthConnectHandler(self):
1666 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1667 response doesn't make sense because the proxy server cannot request
1668 server authentication."""
1669
1670 if (self.path.find("www.server-auth.com") < 0):
1671 return False
1672
1673 challenge = 'Basic realm="WallyWorld"'
1674
1675 self.send_response(401) # unauthorized
1676 self.send_header('WWW-Authenticate', challenge)
1677 self.send_header('Connection', 'close')
1678 self.end_headers()
1679 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001680
1681 def DefaultConnectResponseHandler(self):
1682 """This is the catch-all response handler for CONNECT requests that aren't
1683 handled by one of the special handlers above. Real Web servers respond
1684 with 400 to CONNECT requests."""
1685
1686 contents = "Your client has issued a malformed or illegal request."
1687 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001688 self.send_header('Content-Type', 'text/html')
1689 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001690 self.end_headers()
1691 self.wfile.write(contents)
1692 return True
1693
initial.commit94958cf2008-07-26 22:42:52 +00001694 # called by the redirect handling function when there is no parameter
1695 def sendRedirectHelp(self, redirect_name):
1696 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001697 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001698 self.end_headers()
1699 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1700 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1701 self.wfile.write('</body></html>')
1702
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001703 # called by chunked handling function
1704 def sendChunkHelp(self, chunk):
1705 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1706 self.wfile.write('%X\r\n' % len(chunk))
1707 self.wfile.write(chunk)
1708 self.wfile.write('\r\n')
1709
akalin@chromium.org154bb132010-11-12 02:20:27 +00001710
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001711class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001712 def __init__(self, request, client_address, socket_server):
1713 handlers = [self.OCSPResponse]
1714 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001715 testserver_base.BasePageHandler.__init__(self, request, client_address,
1716 socket_server, [], handlers, [],
1717 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001718
1719 def OCSPResponse(self):
1720 self.send_response(200)
1721 self.send_header('Content-Type', 'application/ocsp-response')
1722 self.send_header('Content-Length', str(len(self.ocsp_response)))
1723 self.end_headers()
1724
1725 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001726
mattm@chromium.org830a3712012-11-07 23:00:07 +00001727
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001728class TCPEchoHandler(SocketServer.BaseRequestHandler):
1729 """The RequestHandler class for TCP echo server.
1730
1731 It is instantiated once per connection to the server, and overrides the
1732 handle() method to implement communication to the client.
1733 """
1734
1735 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001736 """Handles the request from the client and constructs a response."""
1737
1738 data = self.request.recv(65536).strip()
1739 # Verify the "echo request" message received from the client. Send back
1740 # "echo response" message if "echo request" message is valid.
1741 try:
1742 return_data = echo_message.GetEchoResponseData(data)
1743 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001744 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001745 except ValueError:
1746 return
1747
1748 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001749
1750
1751class UDPEchoHandler(SocketServer.BaseRequestHandler):
1752 """The RequestHandler class for UDP echo server.
1753
1754 It is instantiated once per connection to the server, and overrides the
1755 handle() method to implement communication to the client.
1756 """
1757
1758 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001759 """Handles the request from the client and constructs a response."""
1760
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001761 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001762 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001763 # Verify the "echo request" message received from the client. Send back
1764 # "echo response" message if "echo request" message is valid.
1765 try:
1766 return_data = echo_message.GetEchoResponseData(data)
1767 if not return_data:
1768 return
1769 except ValueError:
1770 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001771 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001772
1773
bashi@chromium.org33233532012-09-08 17:37:24 +00001774class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1775 """A request handler that behaves as a proxy server which requires
1776 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1777 """
1778
1779 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1780
1781 def parse_request(self):
1782 """Overrides parse_request to check credential."""
1783
1784 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1785 return False
1786
1787 auth = self.headers.getheader('Proxy-Authorization')
1788 if auth != self._AUTH_CREDENTIAL:
1789 self.send_response(407)
1790 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1791 self.end_headers()
1792 return False
1793
1794 return True
1795
1796 def _start_read_write(self, sock):
1797 sock.setblocking(0)
1798 self.request.setblocking(0)
1799 rlist = [self.request, sock]
1800 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001801 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001802 if errors:
1803 self.send_response(500)
1804 self.end_headers()
1805 return
1806 for s in ready_sockets:
1807 received = s.recv(1024)
1808 if len(received) == 0:
1809 return
1810 if s == self.request:
1811 other = sock
1812 else:
1813 other = self.request
1814 other.send(received)
1815
1816 def _do_common_method(self):
1817 url = urlparse.urlparse(self.path)
1818 port = url.port
1819 if not port:
1820 if url.scheme == 'http':
1821 port = 80
1822 elif url.scheme == 'https':
1823 port = 443
1824 if not url.hostname or not port:
1825 self.send_response(400)
1826 self.end_headers()
1827 return
1828
1829 if len(url.path) == 0:
1830 path = '/'
1831 else:
1832 path = url.path
1833 if len(url.query) > 0:
1834 path = '%s?%s' % (url.path, url.query)
1835
1836 sock = None
1837 try:
1838 sock = socket.create_connection((url.hostname, port))
1839 sock.send('%s %s %s\r\n' % (
1840 self.command, path, self.protocol_version))
1841 for header in self.headers.headers:
1842 header = header.strip()
1843 if (header.lower().startswith('connection') or
1844 header.lower().startswith('proxy')):
1845 continue
1846 sock.send('%s\r\n' % header)
1847 sock.send('\r\n')
1848 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001849 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001850 self.send_response(500)
1851 self.end_headers()
1852 finally:
1853 if sock is not None:
1854 sock.close()
1855
1856 def do_CONNECT(self):
1857 try:
1858 pos = self.path.rfind(':')
1859 host = self.path[:pos]
1860 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001861 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001862 self.send_response(400)
1863 self.end_headers()
1864
1865 try:
1866 sock = socket.create_connection((host, port))
1867 self.send_response(200, 'Connection established')
1868 self.end_headers()
1869 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001870 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001871 self.send_response(500)
1872 self.end_headers()
1873 finally:
1874 sock.close()
1875
1876 def do_GET(self):
1877 self._do_common_method()
1878
1879 def do_HEAD(self):
1880 self._do_common_method()
1881
1882
mattm@chromium.org830a3712012-11-07 23:00:07 +00001883class ServerRunner(testserver_base.TestServerRunner):
1884 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001885
mattm@chromium.org830a3712012-11-07 23:00:07 +00001886 def __init__(self):
1887 super(ServerRunner, self).__init__()
1888 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001889
mattm@chromium.org830a3712012-11-07 23:00:07 +00001890 def __make_data_dir(self):
1891 if self.options.data_dir:
1892 if not os.path.isdir(self.options.data_dir):
1893 raise testserver_base.OptionError('specified data dir not found: ' +
1894 self.options.data_dir + ' exiting...')
1895 my_data_dir = self.options.data_dir
1896 else:
1897 # Create the default path to our data dir, relative to the exe dir.
1898 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1899 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001900
mattm@chromium.org830a3712012-11-07 23:00:07 +00001901 #TODO(ibrar): Must use Find* funtion defined in google\tools
1902 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001903
mattm@chromium.org830a3712012-11-07 23:00:07 +00001904 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001905
mattm@chromium.org830a3712012-11-07 23:00:07 +00001906 def create_server(self, server_data):
1907 port = self.options.port
1908 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001909
mattm@chromium.org830a3712012-11-07 23:00:07 +00001910 if self.options.server_type == SERVER_HTTP:
1911 if self.options.https:
1912 pem_cert_and_key = None
1913 if self.options.cert_and_key_file:
1914 if not os.path.isfile(self.options.cert_and_key_file):
1915 raise testserver_base.OptionError(
1916 'specified server cert file not found: ' +
1917 self.options.cert_and_key_file + ' exiting...')
1918 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001919 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001920 # generate a new certificate and run an OCSP server for it.
1921 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001922 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001923 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001924
mattm@chromium.org830a3712012-11-07 23:00:07 +00001925 ocsp_der = None
1926 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001927
mattm@chromium.org830a3712012-11-07 23:00:07 +00001928 if self.options.ocsp == 'ok':
1929 ocsp_state = minica.OCSP_STATE_GOOD
1930 elif self.options.ocsp == 'revoked':
1931 ocsp_state = minica.OCSP_STATE_REVOKED
1932 elif self.options.ocsp == 'invalid':
1933 ocsp_state = minica.OCSP_STATE_INVALID
1934 elif self.options.ocsp == 'unauthorized':
1935 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1936 elif self.options.ocsp == 'unknown':
1937 ocsp_state = minica.OCSP_STATE_UNKNOWN
1938 else:
1939 raise testserver_base.OptionError('unknown OCSP status: ' +
1940 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001941
mattm@chromium.org830a3712012-11-07 23:00:07 +00001942 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1943 subject = "127.0.0.1",
1944 ocsp_url = ("http://%s:%d/ocsp" %
1945 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00001946 ocsp_state = ocsp_state,
1947 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001948
1949 self.__ocsp_server.ocsp_response = ocsp_der
1950
1951 for ca_cert in self.options.ssl_client_ca:
1952 if not os.path.isfile(ca_cert):
1953 raise testserver_base.OptionError(
1954 'specified trusted client CA file not found: ' + ca_cert +
1955 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001956
1957 stapled_ocsp_response = None
1958 if self.__ocsp_server and self.options.staple_ocsp_response:
1959 stapled_ocsp_response = self.__ocsp_server.ocsp_response
1960
mattm@chromium.org830a3712012-11-07 23:00:07 +00001961 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1962 self.options.ssl_client_auth,
1963 self.options.ssl_client_ca,
1964 self.options.ssl_bulk_cipher,
1965 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00001966 self.options.tls_intolerant,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001967 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00001968 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001969 self.options.fallback_scsv,
1970 stapled_ocsp_response)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001971 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001972 else:
1973 server = HTTPServer((host, port), TestPageHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001974 print 'HTTP server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001975
1976 server.data_dir = self.__make_data_dir()
1977 server.file_root_url = self.options.file_root_url
1978 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001979 elif self.options.server_type == SERVER_WEBSOCKET:
1980 # Launch pywebsocket via WebSocketServer.
1981 logger = logging.getLogger()
1982 logger.addHandler(logging.StreamHandler())
1983 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1984 # is required to work correctly. It should be fixed from pywebsocket side.
1985 os.chdir(self.__make_data_dir())
1986 websocket_options = WebSocketOptions(host, port, '.')
1987 if self.options.cert_and_key_file:
1988 websocket_options.use_tls = True
1989 websocket_options.private_key = self.options.cert_and_key_file
1990 websocket_options.certificate = self.options.cert_and_key_file
1991 if self.options.ssl_client_auth:
1992 websocket_options.tls_client_auth = True
1993 if len(self.options.ssl_client_ca) != 1:
1994 raise testserver_base.OptionError(
1995 'one trusted client CA file should be specified')
1996 if not os.path.isfile(self.options.ssl_client_ca[0]):
1997 raise testserver_base.OptionError(
1998 'specified trusted client CA file not found: ' +
1999 self.options.ssl_client_ca[0] + ' exiting...')
2000 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2001 server = WebSocketServer(websocket_options)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002002 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002003 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002004 elif self.options.server_type == SERVER_TCP_ECHO:
2005 # Used for generating the key (randomly) that encodes the "echo request"
2006 # message.
2007 random.seed()
2008 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002009 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002010 server_data['port'] = server.server_port
2011 elif self.options.server_type == SERVER_UDP_ECHO:
2012 # Used for generating the key (randomly) that encodes the "echo request"
2013 # message.
2014 random.seed()
2015 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002016 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002017 server_data['port'] = server.server_port
2018 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2019 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002020 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002021 server_data['port'] = server.server_port
2022 elif self.options.server_type == SERVER_FTP:
2023 my_data_dir = self.__make_data_dir()
2024
2025 # Instantiate a dummy authorizer for managing 'virtual' users
2026 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2027
2028 # Define a new user having full r/w permissions and a read-only
2029 # anonymous user
2030 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2031
2032 authorizer.add_anonymous(my_data_dir)
2033
2034 # Instantiate FTP handler class
2035 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2036 ftp_handler.authorizer = authorizer
2037
2038 # Define a customized banner (string returned when client connects)
2039 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2040 pyftpdlib.ftpserver.__ver__)
2041
2042 # Instantiate FTP server class and listen to address:port
2043 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2044 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002045 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002046 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002047 raise testserver_base.OptionError('unknown server type' +
2048 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002049
mattm@chromium.org830a3712012-11-07 23:00:07 +00002050 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002051
mattm@chromium.org830a3712012-11-07 23:00:07 +00002052 def run_server(self):
2053 if self.__ocsp_server:
2054 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002055
mattm@chromium.org830a3712012-11-07 23:00:07 +00002056 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002057
mattm@chromium.org830a3712012-11-07 23:00:07 +00002058 if self.__ocsp_server:
2059 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002060
mattm@chromium.org830a3712012-11-07 23:00:07 +00002061 def add_options(self):
2062 testserver_base.TestServerRunner.add_options(self)
2063 self.option_parser.add_option('-f', '--ftp', action='store_const',
2064 const=SERVER_FTP, default=SERVER_HTTP,
2065 dest='server_type',
2066 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002067 self.option_parser.add_option('--tcp-echo', action='store_const',
2068 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2069 dest='server_type',
2070 help='start up a tcp echo server.')
2071 self.option_parser.add_option('--udp-echo', action='store_const',
2072 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2073 dest='server_type',
2074 help='start up a udp echo server.')
2075 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2076 const=SERVER_BASIC_AUTH_PROXY,
2077 default=SERVER_HTTP, dest='server_type',
2078 help='start up a proxy server which requires '
2079 'basic authentication.')
2080 self.option_parser.add_option('--websocket', action='store_const',
2081 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2082 dest='server_type',
2083 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002084 self.option_parser.add_option('--https', action='store_true',
2085 dest='https', help='Specify that https '
2086 'should be used.')
2087 self.option_parser.add_option('--cert-and-key-file',
2088 dest='cert_and_key_file', help='specify the '
2089 'path to the file containing the certificate '
2090 'and private key for the server in PEM '
2091 'format')
2092 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2093 help='The type of OCSP response generated '
2094 'for the automatically generated '
2095 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002096 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2097 default=0, type=int,
2098 help='If non-zero then the generated '
2099 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002100 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2101 default='0', type='int',
2102 help='If nonzero, certain TLS connections '
2103 'will be aborted in order to test version '
2104 'fallback. 1 means all TLS versions will be '
2105 'aborted. 2 means TLS 1.1 or higher will be '
2106 'aborted. 3 means TLS 1.2 or higher will be '
2107 'aborted.')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002108 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2109 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002110 default='',
2111 help='Base64 encoded SCT list. If set, '
2112 'server will respond with a '
2113 'signed_certificate_timestamp TLS extension '
2114 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002115 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2116 default=False, const=True,
2117 action='store_const',
2118 help='If given, TLS_FALLBACK_SCSV support '
2119 'will be enabled. This causes the server to '
2120 'reject fallback connections from compatible '
2121 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002122 self.option_parser.add_option('--staple-ocsp-response',
2123 dest='staple_ocsp_response',
2124 default=False, action='store_true',
2125 help='If set, server will staple the OCSP '
2126 'response whenever OCSP is on and the client '
2127 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002128 self.option_parser.add_option('--https-record-resume',
2129 dest='record_resume', const=True,
2130 default=False, action='store_const',
2131 help='Record resumption cache events rather '
2132 'than resuming as normal. Allows the use of '
2133 'the /ssl-session-cache request')
2134 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2135 help='Require SSL client auth on every '
2136 'connection.')
2137 self.option_parser.add_option('--ssl-client-ca', action='append',
2138 default=[], help='Specify that the client '
2139 'certificate request should include the CA '
2140 'named in the subject of the DER-encoded '
2141 'certificate contained in the specified '
2142 'file. This option may appear multiple '
2143 'times, indicating multiple CA names should '
2144 'be sent in the request.')
2145 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2146 help='Specify the bulk encryption '
2147 'algorithm(s) that will be accepted by the '
2148 'SSL server. Valid values are "aes256", '
2149 '"aes128", "3des", "rc4". If omitted, all '
2150 'algorithms will be used. This option may '
2151 'appear multiple times, indicating '
2152 'multiple algorithms should be enabled.');
2153 self.option_parser.add_option('--file-root-url', default='/files/',
2154 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002155
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002156
initial.commit94958cf2008-07-26 22:42:52 +00002157if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002158 sys.exit(ServerRunner().main())