blob: 27f0f66c0bd6d63c378d53a4f688e57f58383871 [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
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000024import random
initial.commit94958cf2008-07-26 22:42:52 +000025import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000026import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000027import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000028import SocketServer
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000029import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000030import sys
31import threading
initial.commit94958cf2008-07-26 22:42:52 +000032import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000033import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000034import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000035import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000036
phajdan.jr@chromium.orgbeaf5e82013-03-18 17:36:12 +000037# TODO(phajdan.jr): Remove after debugging http://crbug.com/96594 .
38sys.stdout.write('testserver.py right after std imports!\n')
39sys.stdout.flush()
40
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000041import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000042import pyftpdlib.ftpserver
mattm@chromium.org830a3712012-11-07 23:00:07 +000043import testserver_base
initial.commit94958cf2008-07-26 22:42:52 +000044import tlslite
45import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000046
mattm@chromium.org830a3712012-11-07 23:00:07 +000047BASE_DIR = os.path.dirname(os.path.abspath(__file__))
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000048sys.path.insert(
49 0, os.path.join(BASE_DIR, '..', '..', '..', 'third_party/pywebsocket/src'))
50from mod_pywebsocket.standalone import WebSocketServer
davidben@chromium.org06fcf202010-09-22 18:15:23 +000051
maruel@chromium.org756cf982009-03-05 12:46:38 +000052SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000053SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000054SERVER_TCP_ECHO = 2
55SERVER_UDP_ECHO = 3
56SERVER_BASIC_AUTH_PROXY = 4
57SERVER_WEBSOCKET = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000058
59# Default request queue size for WebSocketServer.
60_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000061
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000062class WebSocketOptions:
63 """Holds options for WebSocketServer."""
64
65 def __init__(self, host, port, data_dir):
66 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
67 self.server_host = host
68 self.port = port
69 self.websock_handlers = data_dir
70 self.scan_dir = None
71 self.allow_handlers_outside_root_dir = False
72 self.websock_handlers_map_file = None
73 self.cgi_directories = []
74 self.is_executable_method = None
75 self.allow_draft75 = False
76 self.strict = True
77
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000078 self.use_tls = False
79 self.private_key = None
80 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000081 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000082 self.tls_client_ca = None
83 self.use_basic_auth = False
84
mattm@chromium.org830a3712012-11-07 23:00:07 +000085
agl@chromium.orgf9e66792011-12-12 22:22:19 +000086class RecordingSSLSessionCache(object):
87 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
88 lookups and inserts in order to test session cache behaviours."""
89
90 def __init__(self):
91 self.log = []
92
93 def __getitem__(self, sessionID):
94 self.log.append(('lookup', sessionID))
95 raise KeyError()
96
97 def __setitem__(self, sessionID, session):
98 self.log.append(('insert', sessionID))
99
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000100
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000101class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
102 testserver_base.BrokenPipeHandlerMixIn,
103 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000104 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000105 verification."""
106
107 pass
108
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000109class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
110 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000111 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000112 """This is a specialization of HTTPServer that serves an
113 OCSP response"""
114
115 def serve_forever_on_thread(self):
116 self.thread = threading.Thread(target = self.serve_forever,
117 name = "OCSPServerThread")
118 self.thread.start()
119
120 def stop_serving(self):
121 self.shutdown()
122 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000123
mattm@chromium.org830a3712012-11-07 23:00:07 +0000124
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000125class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000126 testserver_base.ClientRestrictingServerMixIn,
127 testserver_base.BrokenPipeHandlerMixIn,
128 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000129 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000130 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000131
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000132 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000133 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000134 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000135 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
136 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000137 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000138 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000139 self.tls_intolerant = tls_intolerant
140
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000141 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000142 s = open(ca_file).read()
143 x509 = tlslite.api.X509()
144 x509.parse(s)
145 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000146 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
147 if ssl_bulk_ciphers is not None:
148 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000149
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000150 if record_resume_info:
151 # If record_resume_info is true then we'll replace the session cache with
152 # an object that records the lookups and inserts that it sees.
153 self.session_cache = RecordingSSLSessionCache()
154 else:
155 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000156 testserver_base.StoppableHTTPServer.__init__(self,
157 server_address,
158 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000159
160 def handshake(self, tlsConnection):
161 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000162
initial.commit94958cf2008-07-26 22:42:52 +0000163 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000164 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000165 tlsConnection.handshakeServer(certChain=self.cert_chain,
166 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000167 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000168 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000169 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000170 reqCAs=self.ssl_client_cas,
171 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000172 tlsConnection.ignoreAbruptClose = True
173 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000174 except tlslite.api.TLSAbruptCloseError:
175 # Ignore abrupt close.
176 return True
initial.commit94958cf2008-07-26 22:42:52 +0000177 except tlslite.api.TLSError, error:
phajdan.jr@chromium.orgbeaf5e82013-03-18 17:36:12 +0000178 sys.stdout.write("Handshake failure: %s" % str(error))
179 sys.stdout.flush()
initial.commit94958cf2008-07-26 22:42:52 +0000180 return False
181
akalin@chromium.org154bb132010-11-12 02:20:27 +0000182
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000183class FTPServer(testserver_base.ClientRestrictingServerMixIn,
184 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000185 """This is a specialization of FTPServer that adds client verification."""
186
187 pass
188
189
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000190class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
191 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000192 """A TCP echo server that echoes back what it has received."""
193
194 def server_bind(self):
195 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000196
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000197 SocketServer.TCPServer.server_bind(self)
198 host, port = self.socket.getsockname()[:2]
199 self.server_name = socket.getfqdn(host)
200 self.server_port = port
201
202 def serve_forever(self):
203 self.stop = False
204 self.nonce_time = None
205 while not self.stop:
206 self.handle_request()
207 self.socket.close()
208
209
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000210class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
211 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000212 """A UDP echo server that echoes back what it has received."""
213
214 def server_bind(self):
215 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000216
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000217 SocketServer.UDPServer.server_bind(self)
218 host, port = self.socket.getsockname()[:2]
219 self.server_name = socket.getfqdn(host)
220 self.server_port = port
221
222 def serve_forever(self):
223 self.stop = False
224 self.nonce_time = None
225 while not self.stop:
226 self.handle_request()
227 self.socket.close()
228
229
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000230class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000231 # Class variables to allow for persistence state between page handler
232 # invocations
233 rst_limits = {}
234 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000235
236 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000237 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000238 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000239 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000240 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000241 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000242 self.NoCacheMaxAgeTimeHandler,
243 self.NoCacheTimeHandler,
244 self.CacheTimeHandler,
245 self.CacheExpiresHandler,
246 self.CacheProxyRevalidateHandler,
247 self.CachePrivateHandler,
248 self.CachePublicHandler,
249 self.CacheSMaxAgeHandler,
250 self.CacheMustRevalidateHandler,
251 self.CacheMustRevalidateMaxAgeHandler,
252 self.CacheNoStoreHandler,
253 self.CacheNoStoreMaxAgeHandler,
254 self.CacheNoTransformHandler,
255 self.DownloadHandler,
256 self.DownloadFinishHandler,
257 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000258 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000259 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000260 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000261 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000262 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000263 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000264 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000265 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000266 self.AuthBasicHandler,
267 self.AuthDigestHandler,
268 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000269 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000270 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000271 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000272 self.ServerRedirectHandler,
273 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000274 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000275 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000276 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000277 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000278 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000279 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000280 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000281 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000282 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000283 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000284 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000285 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000286 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000287 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000288 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000289 head_handlers = [
290 self.FileHandler,
291 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000292
maruel@google.come250a9b2009-03-10 17:39:46 +0000293 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000294 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000295 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000296 'gif': 'image/gif',
297 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000298 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000299 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000300 'pdf' : 'application/pdf',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000301 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000302 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000303 }
initial.commit94958cf2008-07-26 22:42:52 +0000304 self._default_mime_type = 'text/html'
305
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000306 testserver_base.BasePageHandler.__init__(self, request, client_address,
307 socket_server, connect_handlers,
308 get_handlers, head_handlers,
309 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000310
initial.commit94958cf2008-07-26 22:42:52 +0000311 def GetMIMETypeFromName(self, file_name):
312 """Returns the mime type for the specified file_name. So far it only looks
313 at the file extension."""
314
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000315 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000316 if len(extension) == 0:
317 # no extension.
318 return self._default_mime_type
319
ericroman@google.comc17ca532009-05-07 03:51:05 +0000320 # extension starts with a dot, so we need to remove it
321 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000322
initial.commit94958cf2008-07-26 22:42:52 +0000323 def NoCacheMaxAgeTimeHandler(self):
324 """This request handler yields a page with the title set to the current
325 system time, and no caching requested."""
326
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000327 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000328 return False
329
330 self.send_response(200)
331 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000332 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000333 self.end_headers()
334
maruel@google.come250a9b2009-03-10 17:39:46 +0000335 self.wfile.write('<html><head><title>%s</title></head></html>' %
336 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000337
338 return True
339
340 def NoCacheTimeHandler(self):
341 """This request handler yields a page with the title set to the current
342 system time, and no caching requested."""
343
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000344 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000345 return False
346
347 self.send_response(200)
348 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000349 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000350 self.end_headers()
351
maruel@google.come250a9b2009-03-10 17:39:46 +0000352 self.wfile.write('<html><head><title>%s</title></head></html>' %
353 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000354
355 return True
356
357 def CacheTimeHandler(self):
358 """This request handler yields a page with the title set to the current
359 system time, and allows caching for one minute."""
360
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000361 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000362 return False
363
364 self.send_response(200)
365 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000366 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000367 self.end_headers()
368
maruel@google.come250a9b2009-03-10 17:39:46 +0000369 self.wfile.write('<html><head><title>%s</title></head></html>' %
370 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000371
372 return True
373
374 def CacheExpiresHandler(self):
375 """This request handler yields a page with the title set to the current
376 system time, and set the page to expire on 1 Jan 2099."""
377
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000378 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000379 return False
380
381 self.send_response(200)
382 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000383 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000384 self.end_headers()
385
maruel@google.come250a9b2009-03-10 17:39:46 +0000386 self.wfile.write('<html><head><title>%s</title></head></html>' %
387 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000388
389 return True
390
391 def CacheProxyRevalidateHandler(self):
392 """This request handler yields a page with the title set to the current
393 system time, and allows caching for 60 seconds"""
394
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000395 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000396 return False
397
398 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000399 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000400 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
401 self.end_headers()
402
maruel@google.come250a9b2009-03-10 17:39:46 +0000403 self.wfile.write('<html><head><title>%s</title></head></html>' %
404 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000405
406 return True
407
408 def CachePrivateHandler(self):
409 """This request handler yields a page with the title set to the current
410 system time, and allows caching for 5 seconds."""
411
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000412 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000413 return False
414
415 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000416 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000417 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000418 self.end_headers()
419
maruel@google.come250a9b2009-03-10 17:39:46 +0000420 self.wfile.write('<html><head><title>%s</title></head></html>' %
421 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000422
423 return True
424
425 def CachePublicHandler(self):
426 """This request handler yields a page with the title set to the current
427 system time, and allows caching for 5 seconds."""
428
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000429 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000430 return False
431
432 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000433 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000434 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000435 self.end_headers()
436
maruel@google.come250a9b2009-03-10 17:39:46 +0000437 self.wfile.write('<html><head><title>%s</title></head></html>' %
438 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000439
440 return True
441
442 def CacheSMaxAgeHandler(self):
443 """This request handler yields a page with the title set to the current
444 system time, and does not allow for caching."""
445
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000446 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000447 return False
448
449 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000450 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000451 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
452 self.end_headers()
453
maruel@google.come250a9b2009-03-10 17:39:46 +0000454 self.wfile.write('<html><head><title>%s</title></head></html>' %
455 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000456
457 return True
458
459 def CacheMustRevalidateHandler(self):
460 """This request handler yields a page with the title set to the current
461 system time, and does not allow caching."""
462
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000463 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000464 return False
465
466 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000467 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000468 self.send_header('Cache-Control', 'must-revalidate')
469 self.end_headers()
470
maruel@google.come250a9b2009-03-10 17:39:46 +0000471 self.wfile.write('<html><head><title>%s</title></head></html>' %
472 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000473
474 return True
475
476 def CacheMustRevalidateMaxAgeHandler(self):
477 """This request handler yields a page with the title set to the current
478 system time, and does not allow caching event though max-age of 60
479 seconds is specified."""
480
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000481 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000482 return False
483
484 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000485 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000486 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
487 self.end_headers()
488
maruel@google.come250a9b2009-03-10 17:39:46 +0000489 self.wfile.write('<html><head><title>%s</title></head></html>' %
490 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000491
492 return True
493
initial.commit94958cf2008-07-26 22:42:52 +0000494 def CacheNoStoreHandler(self):
495 """This request handler yields a page with the title set to the current
496 system time, and does not allow the page to be stored."""
497
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000498 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000499 return False
500
501 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000502 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000503 self.send_header('Cache-Control', 'no-store')
504 self.end_headers()
505
maruel@google.come250a9b2009-03-10 17:39:46 +0000506 self.wfile.write('<html><head><title>%s</title></head></html>' %
507 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000508
509 return True
510
511 def CacheNoStoreMaxAgeHandler(self):
512 """This request handler yields a page with the title set to the current
513 system time, and does not allow the page to be stored even though max-age
514 of 60 seconds is specified."""
515
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000516 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000517 return False
518
519 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000520 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000521 self.send_header('Cache-Control', 'max-age=60, no-store')
522 self.end_headers()
523
maruel@google.come250a9b2009-03-10 17:39:46 +0000524 self.wfile.write('<html><head><title>%s</title></head></html>' %
525 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000526
527 return True
528
529
530 def CacheNoTransformHandler(self):
531 """This request handler yields a page with the title set to the current
532 system time, and does not allow the content to transformed during
533 user-agent caching"""
534
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000535 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000536 return False
537
538 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000539 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000540 self.send_header('Cache-Control', 'no-transform')
541 self.end_headers()
542
maruel@google.come250a9b2009-03-10 17:39:46 +0000543 self.wfile.write('<html><head><title>%s</title></head></html>' %
544 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000545
546 return True
547
548 def EchoHeader(self):
549 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000550
ananta@chromium.org219b2062009-10-23 16:09:41 +0000551 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000552
ananta@chromium.org56812d02011-04-07 17:52:05 +0000553 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000554 """This function echoes back the value of a specific request header while
555 allowing caching for 16 hours."""
556
ananta@chromium.org56812d02011-04-07 17:52:05 +0000557 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000558
559 def EchoHeaderHelper(self, echo_header):
560 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000561
ananta@chromium.org219b2062009-10-23 16:09:41 +0000562 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000563 return False
564
565 query_char = self.path.find('?')
566 if query_char != -1:
567 header_name = self.path[query_char+1:]
568
569 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000570 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000571 if echo_header == '/echoheadercache':
572 self.send_header('Cache-control', 'max-age=60000')
573 else:
574 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000575 # insert a vary header to properly indicate that the cachability of this
576 # request is subject to value of the request header being echoed.
577 if len(header_name) > 0:
578 self.send_header('Vary', header_name)
579 self.end_headers()
580
581 if len(header_name) > 0:
582 self.wfile.write(self.headers.getheader(header_name))
583
584 return True
585
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000586 def ReadRequestBody(self):
587 """This function reads the body of the current HTTP request, handling
588 both plain and chunked transfer encoded requests."""
589
590 if self.headers.getheader('transfer-encoding') != 'chunked':
591 length = int(self.headers.getheader('content-length'))
592 return self.rfile.read(length)
593
594 # Read the request body as chunks.
595 body = ""
596 while True:
597 line = self.rfile.readline()
598 length = int(line, 16)
599 if length == 0:
600 self.rfile.readline()
601 break
602 body += self.rfile.read(length)
603 self.rfile.read(2)
604 return body
605
initial.commit94958cf2008-07-26 22:42:52 +0000606 def EchoHandler(self):
607 """This handler just echoes back the payload of the request, for testing
608 form submission."""
609
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000610 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000611 return False
612
613 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000614 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000615 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000616 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000617 return True
618
619 def EchoTitleHandler(self):
620 """This handler is like Echo, but sets the page title to the request."""
621
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000622 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000623 return False
624
625 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000626 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000627 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000628 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000629 self.wfile.write('<html><head><title>')
630 self.wfile.write(request)
631 self.wfile.write('</title></head></html>')
632 return True
633
634 def EchoAllHandler(self):
635 """This handler yields a (more) human-readable page listing information
636 about the request header & contents."""
637
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000638 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000639 return False
640
641 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000642 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000643 self.end_headers()
644 self.wfile.write('<html><head><style>'
645 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
646 '</style></head><body>'
647 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000648 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000649 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000650
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000651 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000652 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000653 params = cgi.parse_qs(qs, keep_blank_values=1)
654
655 for param in params:
656 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000657
658 self.wfile.write('</pre>')
659
660 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
661
662 self.wfile.write('</body></html>')
663 return True
664
665 def DownloadHandler(self):
666 """This handler sends a downloadable file with or without reporting
667 the size (6K)."""
668
669 if self.path.startswith("/download-unknown-size"):
670 send_length = False
671 elif self.path.startswith("/download-known-size"):
672 send_length = True
673 else:
674 return False
675
676 #
677 # The test which uses this functionality is attempting to send
678 # small chunks of data to the client. Use a fairly large buffer
679 # so that we'll fill chrome's IO buffer enough to force it to
680 # actually write the data.
681 # See also the comments in the client-side of this test in
682 # download_uitest.cc
683 #
684 size_chunk1 = 35*1024
685 size_chunk2 = 10*1024
686
687 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000688 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000689 self.send_header('Cache-Control', 'max-age=0')
690 if send_length:
691 self.send_header('Content-Length', size_chunk1 + size_chunk2)
692 self.end_headers()
693
694 # First chunk of data:
695 self.wfile.write("*" * size_chunk1)
696 self.wfile.flush()
697
698 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000699 self.server.wait_for_download = True
700 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000701 self.server.handle_request()
702
703 # Second chunk of data:
704 self.wfile.write("*" * size_chunk2)
705 return True
706
707 def DownloadFinishHandler(self):
708 """This handler just tells the server to finish the current download."""
709
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000710 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000711 return False
712
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000713 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000714 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000715 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000716 self.send_header('Cache-Control', 'max-age=0')
717 self.end_headers()
718 return True
719
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000720 def _ReplaceFileData(self, data, query_parameters):
721 """Replaces matching substrings in a file.
722
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000723 If the 'replace_text' URL query parameter is present, it is expected to be
724 of the form old_text:new_text, which indicates that any old_text strings in
725 the file are replaced with new_text. Multiple 'replace_text' parameters may
726 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000727
728 If the parameters are not present, |data| is returned.
729 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000730
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000731 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000732 replace_text_values = query_dict.get('replace_text', [])
733 for replace_text_value in replace_text_values:
734 replace_text_args = replace_text_value.split(':')
735 if len(replace_text_args) != 2:
736 raise ValueError(
737 'replace_text must be of form old_text:new_text. Actual value: %s' %
738 replace_text_value)
739 old_text_b64, new_text_b64 = replace_text_args
740 old_text = base64.urlsafe_b64decode(old_text_b64)
741 new_text = base64.urlsafe_b64decode(new_text_b64)
742 data = data.replace(old_text, new_text)
743 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000744
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000745 def ZipFileHandler(self):
746 """This handler sends the contents of the requested file in compressed form.
747 Can pass in a parameter that specifies that the content length be
748 C - the compressed size (OK),
749 U - the uncompressed size (Non-standard, but handled),
750 S - less than compressed (OK because we keep going),
751 M - larger than compressed but less than uncompressed (an error),
752 L - larger than uncompressed (an error)
753 Example: compressedfiles/Picture_1.doc?C
754 """
755
756 prefix = "/compressedfiles/"
757 if not self.path.startswith(prefix):
758 return False
759
760 # Consume a request body if present.
761 if self.command == 'POST' or self.command == 'PUT' :
762 self.ReadRequestBody()
763
764 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
765
766 if not query in ('C', 'U', 'S', 'M', 'L'):
767 return False
768
769 sub_path = url_path[len(prefix):]
770 entries = sub_path.split('/')
771 file_path = os.path.join(self.server.data_dir, *entries)
772 if os.path.isdir(file_path):
773 file_path = os.path.join(file_path, 'index.html')
774
775 if not os.path.isfile(file_path):
phajdan.jr@chromium.orgbeaf5e82013-03-18 17:36:12 +0000776 sys.stdout.write("File not found " + sub_path +
777 " full path:" + file_path + "\n")
778 sys.stdout.flush()
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000779 self.send_error(404)
780 return True
781
782 f = open(file_path, "rb")
783 data = f.read()
784 uncompressed_len = len(data)
785 f.close()
786
787 # Compress the data.
788 data = zlib.compress(data)
789 compressed_len = len(data)
790
791 content_length = compressed_len
792 if query == 'U':
793 content_length = uncompressed_len
794 elif query == 'S':
795 content_length = compressed_len / 2
796 elif query == 'M':
797 content_length = (compressed_len + uncompressed_len) / 2
798 elif query == 'L':
799 content_length = compressed_len + uncompressed_len
800
801 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000802 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000803 self.send_header('Content-encoding', 'deflate')
804 self.send_header('Connection', 'close')
805 self.send_header('Content-Length', content_length)
806 self.send_header('ETag', '\'' + file_path + '\'')
807 self.end_headers()
808
809 self.wfile.write(data)
810
811 return True
812
initial.commit94958cf2008-07-26 22:42:52 +0000813 def FileHandler(self):
814 """This handler sends the contents of the requested file. Wow, it's like
815 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000816
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000817 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000818 if not self.path.startswith(prefix):
819 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000820 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000821
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000822 def PostOnlyFileHandler(self):
823 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000824
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000825 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000826 if not self.path.startswith(prefix):
827 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000828 return self._FileHandlerHelper(prefix)
829
830 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000831 request_body = ''
832 if self.command == 'POST' or self.command == 'PUT':
833 # Consume a request body if present.
834 request_body = self.ReadRequestBody()
835
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000836 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000837 query_dict = cgi.parse_qs(query)
838
839 expected_body = query_dict.get('expected_body', [])
840 if expected_body and request_body not in expected_body:
841 self.send_response(404)
842 self.end_headers()
843 self.wfile.write('')
844 return True
845
846 expected_headers = query_dict.get('expected_headers', [])
847 for expected_header in expected_headers:
848 header_name, expected_value = expected_header.split(':')
849 if self.headers.getheader(header_name) != expected_value:
850 self.send_response(404)
851 self.end_headers()
852 self.wfile.write('')
853 return True
854
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000855 sub_path = url_path[len(prefix):]
856 entries = sub_path.split('/')
857 file_path = os.path.join(self.server.data_dir, *entries)
858 if os.path.isdir(file_path):
859 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000860
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000861 if not os.path.isfile(file_path):
phajdan.jr@chromium.orgbeaf5e82013-03-18 17:36:12 +0000862 sys.stdout.write("File not found " + sub_path +
863 " full path:" + file_path + "\n")
864 sys.stdout.flush()
initial.commit94958cf2008-07-26 22:42:52 +0000865 self.send_error(404)
866 return True
867
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000868 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000869 data = f.read()
870 f.close()
871
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000872 data = self._ReplaceFileData(data, query)
873
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000874 old_protocol_version = self.protocol_version
875
initial.commit94958cf2008-07-26 22:42:52 +0000876 # If file.mock-http-headers exists, it contains the headers we
877 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000878 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000879 if os.path.isfile(headers_path):
880 f = open(headers_path, "r")
881
882 # "HTTP/1.1 200 OK"
883 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000884 http_major, http_minor, status_code = re.findall(
885 'HTTP/(\d+).(\d+) (\d+)', response)[0]
886 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000887 self.send_response(int(status_code))
888
889 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000890 header_values = re.findall('(\S+):\s*(.*)', line)
891 if len(header_values) > 0:
892 # "name: value"
893 name, value = header_values[0]
894 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000895 f.close()
896 else:
897 # Could be more generic once we support mime-type sniffing, but for
898 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000899
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000900 range_header = self.headers.get('Range')
901 if range_header and range_header.startswith('bytes='):
902 # Note this doesn't handle all valid byte range_header values (i.e.
903 # left open ended ones), just enough for what we needed so far.
904 range_header = range_header[6:].split('-')
905 start = int(range_header[0])
906 if range_header[1]:
907 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000908 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000909 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000910
911 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000912 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
913 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000914 self.send_header('Content-Range', content_range)
915 data = data[start: end + 1]
916 else:
917 self.send_response(200)
918
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000919 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000920 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000921 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000922 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000923 self.end_headers()
924
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000925 if (self.command != 'HEAD'):
926 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000927
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000928 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000929 return True
930
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000931 def SetCookieHandler(self):
932 """This handler just sets a cookie, for testing cookie handling."""
933
934 if not self._ShouldHandleRequest("/set-cookie"):
935 return False
936
937 query_char = self.path.find('?')
938 if query_char != -1:
939 cookie_values = self.path[query_char + 1:].split('&')
940 else:
941 cookie_values = ("",)
942 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000943 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000944 for cookie_value in cookie_values:
945 self.send_header('Set-Cookie', '%s' % cookie_value)
946 self.end_headers()
947 for cookie_value in cookie_values:
948 self.wfile.write('%s' % cookie_value)
949 return True
950
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000951 def SetManyCookiesHandler(self):
952 """This handler just sets a given number of cookies, for testing handling
953 of large numbers of cookies."""
954
955 if not self._ShouldHandleRequest("/set-many-cookies"):
956 return False
957
958 query_char = self.path.find('?')
959 if query_char != -1:
960 num_cookies = int(self.path[query_char + 1:])
961 else:
962 num_cookies = 0
963 self.send_response(200)
964 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000965 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000966 self.send_header('Set-Cookie', 'a=')
967 self.end_headers()
968 self.wfile.write('%d cookies were sent' % num_cookies)
969 return True
970
mattm@chromium.org983fc462012-06-30 00:52:08 +0000971 def ExpectAndSetCookieHandler(self):
972 """Expects some cookies to be sent, and if they are, sets more cookies.
973
974 The expect parameter specifies a required cookie. May be specified multiple
975 times.
976 The set parameter specifies a cookie to set if all required cookies are
977 preset. May be specified multiple times.
978 The data parameter specifies the response body data to be returned."""
979
980 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
981 return False
982
983 _, _, _, _, query, _ = urlparse.urlparse(self.path)
984 query_dict = cgi.parse_qs(query)
985 cookies = set()
986 if 'Cookie' in self.headers:
987 cookie_header = self.headers.getheader('Cookie')
988 cookies.update([s.strip() for s in cookie_header.split(';')])
989 got_all_expected_cookies = True
990 for expected_cookie in query_dict.get('expect', []):
991 if expected_cookie not in cookies:
992 got_all_expected_cookies = False
993 self.send_response(200)
994 self.send_header('Content-Type', 'text/html')
995 if got_all_expected_cookies:
996 for cookie_value in query_dict.get('set', []):
997 self.send_header('Set-Cookie', '%s' % cookie_value)
998 self.end_headers()
999 for data_value in query_dict.get('data', []):
1000 self.wfile.write(data_value)
1001 return True
1002
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001003 def SetHeaderHandler(self):
1004 """This handler sets a response header. Parameters are in the
1005 key%3A%20value&key2%3A%20value2 format."""
1006
1007 if not self._ShouldHandleRequest("/set-header"):
1008 return False
1009
1010 query_char = self.path.find('?')
1011 if query_char != -1:
1012 headers_values = self.path[query_char + 1:].split('&')
1013 else:
1014 headers_values = ("",)
1015 self.send_response(200)
1016 self.send_header('Content-Type', 'text/html')
1017 for header_value in headers_values:
1018 header_value = urllib.unquote(header_value)
1019 (key, value) = header_value.split(': ', 1)
1020 self.send_header(key, value)
1021 self.end_headers()
1022 for header_value in headers_values:
1023 self.wfile.write('%s' % header_value)
1024 return True
1025
initial.commit94958cf2008-07-26 22:42:52 +00001026 def AuthBasicHandler(self):
1027 """This handler tests 'Basic' authentication. It just sends a page with
1028 title 'user/pass' if you succeed."""
1029
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001030 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001031 return False
1032
1033 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001034 expected_password = 'secret'
1035 realm = 'testrealm'
1036 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001037
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001038 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1039 query_params = cgi.parse_qs(query, True)
1040 if 'set-cookie-if-challenged' in query_params:
1041 set_cookie_if_challenged = True
1042 if 'password' in query_params:
1043 expected_password = query_params['password'][0]
1044 if 'realm' in query_params:
1045 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001046
initial.commit94958cf2008-07-26 22:42:52 +00001047 auth = self.headers.getheader('authorization')
1048 try:
1049 if not auth:
1050 raise Exception('no auth')
1051 b64str = re.findall(r'Basic (\S+)', auth)[0]
1052 userpass = base64.b64decode(b64str)
1053 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001054 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001055 raise Exception('wrong password')
1056 except Exception, e:
1057 # Authentication failed.
1058 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001059 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001060 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001061 if set_cookie_if_challenged:
1062 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001063 self.end_headers()
1064 self.wfile.write('<html><head>')
1065 self.wfile.write('<title>Denied: %s</title>' % e)
1066 self.wfile.write('</head><body>')
1067 self.wfile.write('auth=%s<p>' % auth)
1068 self.wfile.write('b64str=%s<p>' % b64str)
1069 self.wfile.write('username: %s<p>' % username)
1070 self.wfile.write('userpass: %s<p>' % userpass)
1071 self.wfile.write('password: %s<p>' % password)
1072 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1073 self.wfile.write('</body></html>')
1074 return True
1075
1076 # Authentication successful. (Return a cachable response to allow for
1077 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001078 old_protocol_version = self.protocol_version
1079 self.protocol_version = "HTTP/1.1"
1080
initial.commit94958cf2008-07-26 22:42:52 +00001081 if_none_match = self.headers.getheader('if-none-match')
1082 if if_none_match == "abc":
1083 self.send_response(304)
1084 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001085 elif url_path.endswith(".gif"):
1086 # Using chrome/test/data/google/logo.gif as the test image
1087 test_image_path = ['google', 'logo.gif']
1088 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1089 if not os.path.isfile(gif_path):
1090 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001091 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001092 return True
1093
1094 f = open(gif_path, "rb")
1095 data = f.read()
1096 f.close()
1097
1098 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001099 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001100 self.send_header('Cache-control', 'max-age=60000')
1101 self.send_header('Etag', 'abc')
1102 self.end_headers()
1103 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001104 else:
1105 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001106 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001107 self.send_header('Cache-control', 'max-age=60000')
1108 self.send_header('Etag', 'abc')
1109 self.end_headers()
1110 self.wfile.write('<html><head>')
1111 self.wfile.write('<title>%s/%s</title>' % (username, password))
1112 self.wfile.write('</head><body>')
1113 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001114 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001115 self.wfile.write('</body></html>')
1116
rvargas@google.com54453b72011-05-19 01:11:11 +00001117 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001118 return True
1119
tonyg@chromium.org75054202010-03-31 22:06:10 +00001120 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001121 """Returns a nonce that's stable per request path for the server's lifetime.
1122 This is a fake implementation. A real implementation would only use a given
1123 nonce a single time (hence the name n-once). However, for the purposes of
1124 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001125
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001126 Args:
1127 force_reset: Iff set, the nonce will be changed. Useful for testing the
1128 "stale" response.
1129 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001130
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001131 if force_reset or not self.server.nonce_time:
1132 self.server.nonce_time = time.time()
1133 return hashlib.md5('privatekey%s%d' %
1134 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001135
1136 def AuthDigestHandler(self):
1137 """This handler tests 'Digest' authentication.
1138
1139 It just sends a page with title 'user/pass' if you succeed.
1140
1141 A stale response is sent iff "stale" is present in the request path.
1142 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001143
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001144 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001145 return False
1146
tonyg@chromium.org75054202010-03-31 22:06:10 +00001147 stale = 'stale' in self.path
1148 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001149 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001150 password = 'secret'
1151 realm = 'testrealm'
1152
1153 auth = self.headers.getheader('authorization')
1154 pairs = {}
1155 try:
1156 if not auth:
1157 raise Exception('no auth')
1158 if not auth.startswith('Digest'):
1159 raise Exception('not digest')
1160 # Pull out all the name="value" pairs as a dictionary.
1161 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1162
1163 # Make sure it's all valid.
1164 if pairs['nonce'] != nonce:
1165 raise Exception('wrong nonce')
1166 if pairs['opaque'] != opaque:
1167 raise Exception('wrong opaque')
1168
1169 # Check the 'response' value and make sure it matches our magic hash.
1170 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001171 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001172 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001173 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001174 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001175 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001176 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1177 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001178 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001179
1180 if pairs['response'] != response:
1181 raise Exception('wrong password')
1182 except Exception, e:
1183 # Authentication failed.
1184 self.send_response(401)
1185 hdr = ('Digest '
1186 'realm="%s", '
1187 'domain="/", '
1188 'qop="auth", '
1189 'algorithm=MD5, '
1190 'nonce="%s", '
1191 'opaque="%s"') % (realm, nonce, opaque)
1192 if stale:
1193 hdr += ', stale="TRUE"'
1194 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001195 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001196 self.end_headers()
1197 self.wfile.write('<html><head>')
1198 self.wfile.write('<title>Denied: %s</title>' % e)
1199 self.wfile.write('</head><body>')
1200 self.wfile.write('auth=%s<p>' % auth)
1201 self.wfile.write('pairs=%s<p>' % pairs)
1202 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1203 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1204 self.wfile.write('</body></html>')
1205 return True
1206
1207 # Authentication successful.
1208 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001209 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001210 self.end_headers()
1211 self.wfile.write('<html><head>')
1212 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1213 self.wfile.write('</head><body>')
1214 self.wfile.write('auth=%s<p>' % auth)
1215 self.wfile.write('pairs=%s<p>' % pairs)
1216 self.wfile.write('</body></html>')
1217
1218 return True
1219
1220 def SlowServerHandler(self):
1221 """Wait for the user suggested time before responding. The syntax is
1222 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001223
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001224 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001225 return False
1226 query_char = self.path.find('?')
1227 wait_sec = 1.0
1228 if query_char >= 0:
1229 try:
1230 wait_sec = int(self.path[query_char + 1:])
1231 except ValueError:
1232 pass
1233 time.sleep(wait_sec)
1234 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001235 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001236 self.end_headers()
1237 self.wfile.write("waited %d seconds" % wait_sec)
1238 return True
1239
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001240 def ChunkedServerHandler(self):
1241 """Send chunked response. Allows to specify chunks parameters:
1242 - waitBeforeHeaders - ms to wait before sending headers
1243 - waitBetweenChunks - ms to wait between chunks
1244 - chunkSize - size of each chunk in bytes
1245 - chunksNumber - number of chunks
1246 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1247 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001248
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001249 if not self._ShouldHandleRequest("/chunked"):
1250 return False
1251 query_char = self.path.find('?')
1252 chunkedSettings = {'waitBeforeHeaders' : 0,
1253 'waitBetweenChunks' : 0,
1254 'chunkSize' : 5,
1255 'chunksNumber' : 5}
1256 if query_char >= 0:
1257 params = self.path[query_char + 1:].split('&')
1258 for param in params:
1259 keyValue = param.split('=')
1260 if len(keyValue) == 2:
1261 try:
1262 chunkedSettings[keyValue[0]] = int(keyValue[1])
1263 except ValueError:
1264 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001265 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001266 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1267 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001268 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001269 self.send_header('Connection', 'close')
1270 self.send_header('Transfer-Encoding', 'chunked')
1271 self.end_headers()
1272 # Chunked encoding: sending all chunks, then final zero-length chunk and
1273 # then final CRLF.
1274 for i in range(0, chunkedSettings['chunksNumber']):
1275 if i > 0:
1276 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1277 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001278 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001279 self.sendChunkHelp('')
1280 return True
1281
initial.commit94958cf2008-07-26 22:42:52 +00001282 def ContentTypeHandler(self):
1283 """Returns a string of html with the given content type. E.g.,
1284 /contenttype?text/css returns an html file with the Content-Type
1285 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001286
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001287 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001288 return False
1289 query_char = self.path.find('?')
1290 content_type = self.path[query_char + 1:].strip()
1291 if not content_type:
1292 content_type = 'text/html'
1293 self.send_response(200)
1294 self.send_header('Content-Type', content_type)
1295 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001296 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001297 return True
1298
creis@google.com2f4f6a42011-03-25 19:44:19 +00001299 def NoContentHandler(self):
1300 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001301
creis@google.com2f4f6a42011-03-25 19:44:19 +00001302 if not self._ShouldHandleRequest("/nocontent"):
1303 return False
1304 self.send_response(204)
1305 self.end_headers()
1306 return True
1307
initial.commit94958cf2008-07-26 22:42:52 +00001308 def ServerRedirectHandler(self):
1309 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001310 '/server-redirect?http://foo.bar/asdf' to redirect to
1311 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001312
1313 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001314 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001315 return False
1316
1317 query_char = self.path.find('?')
1318 if query_char < 0 or len(self.path) <= query_char + 1:
1319 self.sendRedirectHelp(test_name)
1320 return True
1321 dest = self.path[query_char + 1:]
1322
1323 self.send_response(301) # moved permanently
1324 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001325 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001326 self.end_headers()
1327 self.wfile.write('<html><head>')
1328 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1329
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001330 return True
initial.commit94958cf2008-07-26 22:42:52 +00001331
1332 def ClientRedirectHandler(self):
1333 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001334 '/client-redirect?http://foo.bar/asdf' to redirect to
1335 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001336
1337 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001338 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001339 return False
1340
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001341 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001342 if query_char < 0 or len(self.path) <= query_char + 1:
1343 self.sendRedirectHelp(test_name)
1344 return True
1345 dest = self.path[query_char + 1:]
1346
1347 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001348 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001349 self.end_headers()
1350 self.wfile.write('<html><head>')
1351 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1352 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1353
1354 return True
1355
tony@chromium.org03266982010-03-05 03:18:42 +00001356 def MultipartHandler(self):
1357 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001358
tony@chromium.org4cb88302011-09-27 22:13:49 +00001359 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001360 if not self._ShouldHandleRequest(test_name):
1361 return False
1362
1363 num_frames = 10
1364 bound = '12345'
1365 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001366 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001367 'multipart/x-mixed-replace;boundary=' + bound)
1368 self.end_headers()
1369
1370 for i in xrange(num_frames):
1371 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001372 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001373 self.wfile.write('<title>page ' + str(i) + '</title>')
1374 self.wfile.write('page ' + str(i))
1375
1376 self.wfile.write('--' + bound + '--')
1377 return True
1378
tony@chromium.org4cb88302011-09-27 22:13:49 +00001379 def MultipartSlowHandler(self):
1380 """Send a multipart response (3 text/html pages) with a slight delay
1381 between each page. This is similar to how some pages show status using
1382 multipart."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001383
tony@chromium.org4cb88302011-09-27 22:13:49 +00001384 test_name = '/multipart-slow'
1385 if not self._ShouldHandleRequest(test_name):
1386 return False
1387
1388 num_frames = 3
1389 bound = '12345'
1390 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001391 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001392 'multipart/x-mixed-replace;boundary=' + bound)
1393 self.end_headers()
1394
1395 for i in xrange(num_frames):
1396 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001397 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001398 time.sleep(0.25)
1399 if i == 2:
1400 self.wfile.write('<title>PASS</title>')
1401 else:
1402 self.wfile.write('<title>page ' + str(i) + '</title>')
1403 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1404
1405 self.wfile.write('--' + bound + '--')
1406 return True
1407
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001408 def GetSSLSessionCacheHandler(self):
1409 """Send a reply containing a log of the session cache operations."""
1410
1411 if not self._ShouldHandleRequest('/ssl-session-cache'):
1412 return False
1413
1414 self.send_response(200)
1415 self.send_header('Content-Type', 'text/plain')
1416 self.end_headers()
1417 try:
1418 for (action, sessionID) in self.server.session_cache.log:
1419 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001420 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001421 self.wfile.write('Pass --https-record-resume in order to use' +
1422 ' this request')
1423 return True
1424
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001425 def SSLManySmallRecords(self):
1426 """Sends a reply consisting of a variety of small writes. These will be
1427 translated into a series of small SSL records when used over an HTTPS
1428 server."""
1429
1430 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1431 return False
1432
1433 self.send_response(200)
1434 self.send_header('Content-Type', 'text/plain')
1435 self.end_headers()
1436
1437 # Write ~26K of data, in 1350 byte chunks
1438 for i in xrange(20):
1439 self.wfile.write('*' * 1350)
1440 self.wfile.flush()
1441 return True
1442
agl@chromium.org04700be2013-03-02 18:40:41 +00001443 def GetChannelID(self):
1444 """Send a reply containing the hashed ChannelID that the client provided."""
1445
1446 if not self._ShouldHandleRequest('/channel-id'):
1447 return False
1448
1449 self.send_response(200)
1450 self.send_header('Content-Type', 'text/plain')
1451 self.end_headers()
1452 channel_id = self.server.tlsConnection.channel_id.tostring()
1453 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1454 return True
1455
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001456 def CloseSocketHandler(self):
1457 """Closes the socket without sending anything."""
1458
1459 if not self._ShouldHandleRequest('/close-socket'):
1460 return False
1461
1462 self.wfile.close()
1463 return True
1464
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001465 def RangeResetHandler(self):
1466 """Send data broken up by connection resets every N (default 4K) bytes.
1467 Support range requests. If the data requested doesn't straddle a reset
1468 boundary, it will all be sent. Used for testing resuming downloads."""
1469
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001470 def DataForRange(start, end):
1471 """Data to be provided for a particular range of bytes."""
1472 # Offset and scale to avoid too obvious (and hence potentially
1473 # collidable) data.
1474 return ''.join([chr(y % 256)
1475 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1476
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001477 if not self._ShouldHandleRequest('/rangereset'):
1478 return False
1479
1480 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1481
1482 # Defaults
1483 size = 8000
1484 # Note that the rst is sent just before sending the rst_boundary byte.
1485 rst_boundary = 4000
1486 respond_to_range = True
1487 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001488 rst_limit = -1
1489 token = 'DEFAULT'
1490 fail_precondition = 0
1491 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001492
1493 # Parse the query
1494 qdict = urlparse.parse_qs(query, True)
1495 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001496 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001497 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001498 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001499 if 'token' in qdict:
1500 # Identifying token for stateful tests.
1501 token = qdict['token'][0]
1502 if 'rst_limit' in qdict:
1503 # Max number of rsts for a given token.
1504 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001505 if 'bounce_range' in qdict:
1506 respond_to_range = False
1507 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001508 # Note that hold_for_signal will not work with null range requests;
1509 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001510 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001511 if 'no_verifiers' in qdict:
1512 send_verifiers = False
1513 if 'fail_precondition' in qdict:
1514 fail_precondition = int(qdict['fail_precondition'][0])
1515
1516 # Record already set information, or set it.
1517 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1518 if rst_limit != 0:
1519 TestPageHandler.rst_limits[token] -= 1
1520 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1521 token, fail_precondition)
1522 if fail_precondition != 0:
1523 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001524
1525 first_byte = 0
1526 last_byte = size - 1
1527
1528 # Does that define what we want to return, or do we need to apply
1529 # a range?
1530 range_response = False
1531 range_header = self.headers.getheader('range')
1532 if range_header and respond_to_range:
1533 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1534 if mo.group(1):
1535 first_byte = int(mo.group(1))
1536 if mo.group(2):
1537 last_byte = int(mo.group(2))
1538 if last_byte > size - 1:
1539 last_byte = size - 1
1540 range_response = True
1541 if last_byte < first_byte:
1542 return False
1543
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001544 if (fail_precondition and
1545 (self.headers.getheader('If-Modified-Since') or
1546 self.headers.getheader('If-Match'))):
1547 self.send_response(412)
1548 self.end_headers()
1549 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001550
1551 if range_response:
1552 self.send_response(206)
1553 self.send_header('Content-Range',
1554 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1555 else:
1556 self.send_response(200)
1557 self.send_header('Content-Type', 'application/octet-stream')
1558 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001559 if send_verifiers:
1560 self.send_header('Etag', '"XYZZY"')
1561 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001562 self.end_headers()
1563
1564 if hold_for_signal:
1565 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1566 # a single byte, the self.server.handle_request() below hangs
1567 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001568 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001569 first_byte = first_byte + 1
1570 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001571 self.server.wait_for_download = True
1572 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001573 self.server.handle_request()
1574
1575 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001576 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001577 # No RST has been requested in this range, so we don't need to
1578 # do anything fancy; just write the data and let the python
1579 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001580 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001581 self.wfile.flush()
1582 return True
1583
1584 # We're resetting the connection part way in; go to the RST
1585 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001586 # Because socket semantics do not guarantee that all the data will be
1587 # sent when using the linger semantics to hard close a socket,
1588 # we send the data and then wait for our peer to release us
1589 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001590 data = DataForRange(first_byte, possible_rst)
1591 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001592 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001593 self.server.wait_for_download = True
1594 while self.server.wait_for_download:
1595 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001596 l_onoff = 1 # Linger is active.
1597 l_linger = 0 # Seconds to linger for.
1598 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1599 struct.pack('ii', l_onoff, l_linger))
1600
1601 # Close all duplicates of the underlying socket to force the RST.
1602 self.wfile.close()
1603 self.rfile.close()
1604 self.connection.close()
1605
1606 return True
1607
initial.commit94958cf2008-07-26 22:42:52 +00001608 def DefaultResponseHandler(self):
1609 """This is the catch-all response handler for requests that aren't handled
1610 by one of the special handlers above.
1611 Note that we specify the content-length as without it the https connection
1612 is not closed properly (and the browser keeps expecting data)."""
1613
1614 contents = "Default response given for path: " + self.path
1615 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001616 self.send_header('Content-Type', 'text/html')
1617 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001618 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001619 if (self.command != 'HEAD'):
1620 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001621 return True
1622
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001623 def RedirectConnectHandler(self):
1624 """Sends a redirect to the CONNECT request for www.redirect.com. This
1625 response is not specified by the RFC, so the browser should not follow
1626 the redirect."""
1627
1628 if (self.path.find("www.redirect.com") < 0):
1629 return False
1630
1631 dest = "http://www.destination.com/foo.js"
1632
1633 self.send_response(302) # moved temporarily
1634 self.send_header('Location', dest)
1635 self.send_header('Connection', 'close')
1636 self.end_headers()
1637 return True
1638
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001639 def ServerAuthConnectHandler(self):
1640 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1641 response doesn't make sense because the proxy server cannot request
1642 server authentication."""
1643
1644 if (self.path.find("www.server-auth.com") < 0):
1645 return False
1646
1647 challenge = 'Basic realm="WallyWorld"'
1648
1649 self.send_response(401) # unauthorized
1650 self.send_header('WWW-Authenticate', challenge)
1651 self.send_header('Connection', 'close')
1652 self.end_headers()
1653 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001654
1655 def DefaultConnectResponseHandler(self):
1656 """This is the catch-all response handler for CONNECT requests that aren't
1657 handled by one of the special handlers above. Real Web servers respond
1658 with 400 to CONNECT requests."""
1659
1660 contents = "Your client has issued a malformed or illegal request."
1661 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001662 self.send_header('Content-Type', 'text/html')
1663 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001664 self.end_headers()
1665 self.wfile.write(contents)
1666 return True
1667
initial.commit94958cf2008-07-26 22:42:52 +00001668 # called by the redirect handling function when there is no parameter
1669 def sendRedirectHelp(self, redirect_name):
1670 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001671 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001672 self.end_headers()
1673 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1674 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1675 self.wfile.write('</body></html>')
1676
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001677 # called by chunked handling function
1678 def sendChunkHelp(self, chunk):
1679 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1680 self.wfile.write('%X\r\n' % len(chunk))
1681 self.wfile.write(chunk)
1682 self.wfile.write('\r\n')
1683
akalin@chromium.org154bb132010-11-12 02:20:27 +00001684
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001685class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001686 def __init__(self, request, client_address, socket_server):
1687 handlers = [self.OCSPResponse]
1688 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001689 testserver_base.BasePageHandler.__init__(self, request, client_address,
1690 socket_server, [], handlers, [],
1691 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001692
1693 def OCSPResponse(self):
1694 self.send_response(200)
1695 self.send_header('Content-Type', 'application/ocsp-response')
1696 self.send_header('Content-Length', str(len(self.ocsp_response)))
1697 self.end_headers()
1698
1699 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001700
mattm@chromium.org830a3712012-11-07 23:00:07 +00001701
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001702class TCPEchoHandler(SocketServer.BaseRequestHandler):
1703 """The RequestHandler class for TCP echo server.
1704
1705 It is instantiated once per connection to the server, and overrides the
1706 handle() method to implement communication to the client.
1707 """
1708
1709 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001710 """Handles the request from the client and constructs a response."""
1711
1712 data = self.request.recv(65536).strip()
1713 # Verify the "echo request" message received from the client. Send back
1714 # "echo response" message if "echo request" message is valid.
1715 try:
1716 return_data = echo_message.GetEchoResponseData(data)
1717 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001718 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001719 except ValueError:
1720 return
1721
1722 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001723
1724
1725class UDPEchoHandler(SocketServer.BaseRequestHandler):
1726 """The RequestHandler class for UDP echo server.
1727
1728 It is instantiated once per connection to the server, and overrides the
1729 handle() method to implement communication to the client.
1730 """
1731
1732 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001733 """Handles the request from the client and constructs a response."""
1734
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001735 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001736 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001737 # Verify the "echo request" message received from the client. Send back
1738 # "echo response" message if "echo request" message is valid.
1739 try:
1740 return_data = echo_message.GetEchoResponseData(data)
1741 if not return_data:
1742 return
1743 except ValueError:
1744 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001745 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001746
1747
bashi@chromium.org33233532012-09-08 17:37:24 +00001748class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1749 """A request handler that behaves as a proxy server which requires
1750 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1751 """
1752
1753 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1754
1755 def parse_request(self):
1756 """Overrides parse_request to check credential."""
1757
1758 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1759 return False
1760
1761 auth = self.headers.getheader('Proxy-Authorization')
1762 if auth != self._AUTH_CREDENTIAL:
1763 self.send_response(407)
1764 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1765 self.end_headers()
1766 return False
1767
1768 return True
1769
1770 def _start_read_write(self, sock):
1771 sock.setblocking(0)
1772 self.request.setblocking(0)
1773 rlist = [self.request, sock]
1774 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001775 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001776 if errors:
1777 self.send_response(500)
1778 self.end_headers()
1779 return
1780 for s in ready_sockets:
1781 received = s.recv(1024)
1782 if len(received) == 0:
1783 return
1784 if s == self.request:
1785 other = sock
1786 else:
1787 other = self.request
1788 other.send(received)
1789
1790 def _do_common_method(self):
1791 url = urlparse.urlparse(self.path)
1792 port = url.port
1793 if not port:
1794 if url.scheme == 'http':
1795 port = 80
1796 elif url.scheme == 'https':
1797 port = 443
1798 if not url.hostname or not port:
1799 self.send_response(400)
1800 self.end_headers()
1801 return
1802
1803 if len(url.path) == 0:
1804 path = '/'
1805 else:
1806 path = url.path
1807 if len(url.query) > 0:
1808 path = '%s?%s' % (url.path, url.query)
1809
1810 sock = None
1811 try:
1812 sock = socket.create_connection((url.hostname, port))
1813 sock.send('%s %s %s\r\n' % (
1814 self.command, path, self.protocol_version))
1815 for header in self.headers.headers:
1816 header = header.strip()
1817 if (header.lower().startswith('connection') or
1818 header.lower().startswith('proxy')):
1819 continue
1820 sock.send('%s\r\n' % header)
1821 sock.send('\r\n')
1822 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001823 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001824 self.send_response(500)
1825 self.end_headers()
1826 finally:
1827 if sock is not None:
1828 sock.close()
1829
1830 def do_CONNECT(self):
1831 try:
1832 pos = self.path.rfind(':')
1833 host = self.path[:pos]
1834 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001835 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001836 self.send_response(400)
1837 self.end_headers()
1838
1839 try:
1840 sock = socket.create_connection((host, port))
1841 self.send_response(200, 'Connection established')
1842 self.end_headers()
1843 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001844 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001845 self.send_response(500)
1846 self.end_headers()
1847 finally:
1848 sock.close()
1849
1850 def do_GET(self):
1851 self._do_common_method()
1852
1853 def do_HEAD(self):
1854 self._do_common_method()
1855
1856
mattm@chromium.org830a3712012-11-07 23:00:07 +00001857class ServerRunner(testserver_base.TestServerRunner):
1858 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001859
mattm@chromium.org830a3712012-11-07 23:00:07 +00001860 def __init__(self):
1861 super(ServerRunner, self).__init__()
1862 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001863
mattm@chromium.org830a3712012-11-07 23:00:07 +00001864 def __make_data_dir(self):
1865 if self.options.data_dir:
1866 if not os.path.isdir(self.options.data_dir):
1867 raise testserver_base.OptionError('specified data dir not found: ' +
1868 self.options.data_dir + ' exiting...')
1869 my_data_dir = self.options.data_dir
1870 else:
1871 # Create the default path to our data dir, relative to the exe dir.
1872 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1873 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001874
mattm@chromium.org830a3712012-11-07 23:00:07 +00001875 #TODO(ibrar): Must use Find* funtion defined in google\tools
1876 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001877
mattm@chromium.org830a3712012-11-07 23:00:07 +00001878 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001879
mattm@chromium.org830a3712012-11-07 23:00:07 +00001880 def create_server(self, server_data):
1881 port = self.options.port
1882 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001883
mattm@chromium.org830a3712012-11-07 23:00:07 +00001884 if self.options.server_type == SERVER_HTTP:
1885 if self.options.https:
1886 pem_cert_and_key = None
1887 if self.options.cert_and_key_file:
1888 if not os.path.isfile(self.options.cert_and_key_file):
1889 raise testserver_base.OptionError(
1890 'specified server cert file not found: ' +
1891 self.options.cert_and_key_file + ' exiting...')
1892 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001893 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001894 # generate a new certificate and run an OCSP server for it.
1895 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
phajdan.jr@chromium.orgbeaf5e82013-03-18 17:36:12 +00001896 sys.stdout.write('OCSP server started on %s:%d...\n' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001897 (host, self.__ocsp_server.server_port))
phajdan.jr@chromium.orgbeaf5e82013-03-18 17:36:12 +00001898 sys.stdout.flush()
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001899
mattm@chromium.org830a3712012-11-07 23:00:07 +00001900 ocsp_der = None
1901 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001902
mattm@chromium.org830a3712012-11-07 23:00:07 +00001903 if self.options.ocsp == 'ok':
1904 ocsp_state = minica.OCSP_STATE_GOOD
1905 elif self.options.ocsp == 'revoked':
1906 ocsp_state = minica.OCSP_STATE_REVOKED
1907 elif self.options.ocsp == 'invalid':
1908 ocsp_state = minica.OCSP_STATE_INVALID
1909 elif self.options.ocsp == 'unauthorized':
1910 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1911 elif self.options.ocsp == 'unknown':
1912 ocsp_state = minica.OCSP_STATE_UNKNOWN
1913 else:
1914 raise testserver_base.OptionError('unknown OCSP status: ' +
1915 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001916
mattm@chromium.org830a3712012-11-07 23:00:07 +00001917 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1918 subject = "127.0.0.1",
1919 ocsp_url = ("http://%s:%d/ocsp" %
1920 (host, self.__ocsp_server.server_port)),
1921 ocsp_state = ocsp_state)
1922
1923 self.__ocsp_server.ocsp_response = ocsp_der
1924
1925 for ca_cert in self.options.ssl_client_ca:
1926 if not os.path.isfile(ca_cert):
1927 raise testserver_base.OptionError(
1928 'specified trusted client CA file not found: ' + ca_cert +
1929 ' exiting...')
1930 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1931 self.options.ssl_client_auth,
1932 self.options.ssl_client_ca,
1933 self.options.ssl_bulk_cipher,
1934 self.options.record_resume,
1935 self.options.tls_intolerant)
phajdan.jr@chromium.orgbeaf5e82013-03-18 17:36:12 +00001936 sys.stdout.write('HTTPS server started on %s:%d...\n' %
1937 (host, server.server_port))
1938 sys.stdout.flush()
mattm@chromium.org830a3712012-11-07 23:00:07 +00001939 else:
1940 server = HTTPServer((host, port), TestPageHandler)
phajdan.jr@chromium.orgbeaf5e82013-03-18 17:36:12 +00001941 sys.stdout.write('HTTP server started on %s:%d...\n' %
1942 (host, server.server_port))
1943 sys.stdout.flush()
mattm@chromium.org830a3712012-11-07 23:00:07 +00001944
1945 server.data_dir = self.__make_data_dir()
1946 server.file_root_url = self.options.file_root_url
1947 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001948 elif self.options.server_type == SERVER_WEBSOCKET:
1949 # Launch pywebsocket via WebSocketServer.
1950 logger = logging.getLogger()
1951 logger.addHandler(logging.StreamHandler())
1952 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1953 # is required to work correctly. It should be fixed from pywebsocket side.
1954 os.chdir(self.__make_data_dir())
1955 websocket_options = WebSocketOptions(host, port, '.')
1956 if self.options.cert_and_key_file:
1957 websocket_options.use_tls = True
1958 websocket_options.private_key = self.options.cert_and_key_file
1959 websocket_options.certificate = self.options.cert_and_key_file
1960 if self.options.ssl_client_auth:
1961 websocket_options.tls_client_auth = True
1962 if len(self.options.ssl_client_ca) != 1:
1963 raise testserver_base.OptionError(
1964 'one trusted client CA file should be specified')
1965 if not os.path.isfile(self.options.ssl_client_ca[0]):
1966 raise testserver_base.OptionError(
1967 'specified trusted client CA file not found: ' +
1968 self.options.ssl_client_ca[0] + ' exiting...')
1969 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
1970 server = WebSocketServer(websocket_options)
phajdan.jr@chromium.orgbeaf5e82013-03-18 17:36:12 +00001971 sys.stdout.write('WebSocket server started on %s:%d...\n'
1972 % (host, server.server_port))
1973 sys.stdout.flush()
mattm@chromium.org830a3712012-11-07 23:00:07 +00001974 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001975 elif self.options.server_type == SERVER_TCP_ECHO:
1976 # Used for generating the key (randomly) that encodes the "echo request"
1977 # message.
1978 random.seed()
1979 server = TCPEchoServer((host, port), TCPEchoHandler)
phajdan.jr@chromium.orgbeaf5e82013-03-18 17:36:12 +00001980 sys.stdout.write('Echo TCP server started on port %d...\n'
1981 % server.server_port)
1982 sys.stdout.flush()
mattm@chromium.org830a3712012-11-07 23:00:07 +00001983 server_data['port'] = server.server_port
1984 elif self.options.server_type == SERVER_UDP_ECHO:
1985 # Used for generating the key (randomly) that encodes the "echo request"
1986 # message.
1987 random.seed()
1988 server = UDPEchoServer((host, port), UDPEchoHandler)
phajdan.jr@chromium.orgbeaf5e82013-03-18 17:36:12 +00001989 sys.stdout.write('Echo UDP server started on port %d...\n' %
1990 server.server_port)
1991 sys.stdout.flush()
mattm@chromium.org830a3712012-11-07 23:00:07 +00001992 server_data['port'] = server.server_port
1993 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
1994 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
phajdan.jr@chromium.orgbeaf5e82013-03-18 17:36:12 +00001995 sys.stdout.write('BasicAuthProxy server started on port %d...\n' %
1996 server.server_port)
1997 sys.stdout.flush()
mattm@chromium.org830a3712012-11-07 23:00:07 +00001998 server_data['port'] = server.server_port
1999 elif self.options.server_type == SERVER_FTP:
2000 my_data_dir = self.__make_data_dir()
2001
2002 # Instantiate a dummy authorizer for managing 'virtual' users
2003 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2004
2005 # Define a new user having full r/w permissions and a read-only
2006 # anonymous user
2007 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2008
2009 authorizer.add_anonymous(my_data_dir)
2010
2011 # Instantiate FTP handler class
2012 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2013 ftp_handler.authorizer = authorizer
2014
2015 # Define a customized banner (string returned when client connects)
2016 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2017 pyftpdlib.ftpserver.__ver__)
2018
2019 # Instantiate FTP server class and listen to address:port
2020 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2021 server_data['port'] = server.socket.getsockname()[1]
phajdan.jr@chromium.orgbeaf5e82013-03-18 17:36:12 +00002022 sys.stdout.write('FTP server started on port %d...\n' %
2023 server_data['port'])
2024 sys.stdout.flush()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002025 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002026 raise testserver_base.OptionError('unknown server type' +
2027 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002028
mattm@chromium.org830a3712012-11-07 23:00:07 +00002029 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002030
mattm@chromium.org830a3712012-11-07 23:00:07 +00002031 def run_server(self):
2032 if self.__ocsp_server:
2033 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002034
mattm@chromium.org830a3712012-11-07 23:00:07 +00002035 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002036
mattm@chromium.org830a3712012-11-07 23:00:07 +00002037 if self.__ocsp_server:
2038 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002039
mattm@chromium.org830a3712012-11-07 23:00:07 +00002040 def add_options(self):
2041 testserver_base.TestServerRunner.add_options(self)
2042 self.option_parser.add_option('-f', '--ftp', action='store_const',
2043 const=SERVER_FTP, default=SERVER_HTTP,
2044 dest='server_type',
2045 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002046 self.option_parser.add_option('--tcp-echo', action='store_const',
2047 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2048 dest='server_type',
2049 help='start up a tcp echo server.')
2050 self.option_parser.add_option('--udp-echo', action='store_const',
2051 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2052 dest='server_type',
2053 help='start up a udp echo server.')
2054 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2055 const=SERVER_BASIC_AUTH_PROXY,
2056 default=SERVER_HTTP, dest='server_type',
2057 help='start up a proxy server which requires '
2058 'basic authentication.')
2059 self.option_parser.add_option('--websocket', action='store_const',
2060 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2061 dest='server_type',
2062 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002063 self.option_parser.add_option('--https', action='store_true',
2064 dest='https', help='Specify that https '
2065 'should be used.')
2066 self.option_parser.add_option('--cert-and-key-file',
2067 dest='cert_and_key_file', help='specify the '
2068 'path to the file containing the certificate '
2069 'and private key for the server in PEM '
2070 'format')
2071 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2072 help='The type of OCSP response generated '
2073 'for the automatically generated '
2074 'certificate. One of [ok,revoked,invalid]')
2075 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2076 default='0', type='int',
2077 help='If nonzero, certain TLS connections '
2078 'will be aborted in order to test version '
2079 'fallback. 1 means all TLS versions will be '
2080 'aborted. 2 means TLS 1.1 or higher will be '
2081 'aborted. 3 means TLS 1.2 or higher will be '
2082 'aborted.')
2083 self.option_parser.add_option('--https-record-resume',
2084 dest='record_resume', const=True,
2085 default=False, action='store_const',
2086 help='Record resumption cache events rather '
2087 'than resuming as normal. Allows the use of '
2088 'the /ssl-session-cache request')
2089 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2090 help='Require SSL client auth on every '
2091 'connection.')
2092 self.option_parser.add_option('--ssl-client-ca', action='append',
2093 default=[], help='Specify that the client '
2094 'certificate request should include the CA '
2095 'named in the subject of the DER-encoded '
2096 'certificate contained in the specified '
2097 'file. This option may appear multiple '
2098 'times, indicating multiple CA names should '
2099 'be sent in the request.')
2100 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2101 help='Specify the bulk encryption '
2102 'algorithm(s) that will be accepted by the '
2103 'SSL server. Valid values are "aes256", '
2104 '"aes128", "3des", "rc4". If omitted, all '
2105 'algorithms will be used. This option may '
2106 'appear multiple times, indicating '
2107 'multiple algorithms should be enabled.');
2108 self.option_parser.add_option('--file-root-url', default='/files/',
2109 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002110
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002111
initial.commit94958cf2008-07-26 22:42:52 +00002112if __name__ == '__main__':
phajdan.jr@chromium.orgbeaf5e82013-03-18 17:36:12 +00002113 # TODO(phajdan.jr): Remove after debugging http://crbug.com/96594 .
2114 sys.stdout.write('testserver.py running!\n')
2115 sys.stdout.flush()
2116
mattm@chromium.org830a3712012-11-07 23:00:07 +00002117 sys.exit(ServerRunner().main())