blob: 8bfdd9d86679b514bc86a3ef632a82f35de95a0d [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
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000037import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000038import pyftpdlib.ftpserver
mattm@chromium.org830a3712012-11-07 23:00:07 +000039import testserver_base
initial.commit94958cf2008-07-26 22:42:52 +000040import tlslite
41import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000042
mattm@chromium.org830a3712012-11-07 23:00:07 +000043BASE_DIR = os.path.dirname(os.path.abspath(__file__))
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000044sys.path.insert(
45 0, os.path.join(BASE_DIR, '..', '..', '..', 'third_party/pywebsocket/src'))
46from mod_pywebsocket.standalone import WebSocketServer
davidben@chromium.org06fcf202010-09-22 18:15:23 +000047
maruel@chromium.org756cf982009-03-05 12:46:38 +000048SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000049SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000050SERVER_TCP_ECHO = 2
51SERVER_UDP_ECHO = 3
52SERVER_BASIC_AUTH_PROXY = 4
53SERVER_WEBSOCKET = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000054
55# Default request queue size for WebSocketServer.
56_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000057
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000058class WebSocketOptions:
59 """Holds options for WebSocketServer."""
60
61 def __init__(self, host, port, data_dir):
62 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
63 self.server_host = host
64 self.port = port
65 self.websock_handlers = data_dir
66 self.scan_dir = None
67 self.allow_handlers_outside_root_dir = False
68 self.websock_handlers_map_file = None
69 self.cgi_directories = []
70 self.is_executable_method = None
71 self.allow_draft75 = False
72 self.strict = True
73
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000074 self.use_tls = False
75 self.private_key = None
76 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000077 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000078 self.tls_client_ca = None
79 self.use_basic_auth = False
80
mattm@chromium.org830a3712012-11-07 23:00:07 +000081
agl@chromium.orgf9e66792011-12-12 22:22:19 +000082class RecordingSSLSessionCache(object):
83 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
84 lookups and inserts in order to test session cache behaviours."""
85
86 def __init__(self):
87 self.log = []
88
89 def __getitem__(self, sessionID):
90 self.log.append(('lookup', sessionID))
91 raise KeyError()
92
93 def __setitem__(self, sessionID, session):
94 self.log.append(('insert', sessionID))
95
erikwright@chromium.org847ef282012-02-22 16:41:10 +000096
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000097class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
98 testserver_base.BrokenPipeHandlerMixIn,
99 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000100 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000101 verification."""
102
103 pass
104
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000105class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
106 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000107 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000108 """This is a specialization of HTTPServer that serves an
109 OCSP response"""
110
111 def serve_forever_on_thread(self):
112 self.thread = threading.Thread(target = self.serve_forever,
113 name = "OCSPServerThread")
114 self.thread.start()
115
116 def stop_serving(self):
117 self.shutdown()
118 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000119
mattm@chromium.org830a3712012-11-07 23:00:07 +0000120
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000121class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000122 testserver_base.ClientRestrictingServerMixIn,
123 testserver_base.BrokenPipeHandlerMixIn,
124 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000125 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000126 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000127
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000128 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000129 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000130 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000131 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
132 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000133 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000134 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000135 self.tls_intolerant = tls_intolerant
136
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000137 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000138 s = open(ca_file).read()
139 x509 = tlslite.api.X509()
140 x509.parse(s)
141 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000142 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
143 if ssl_bulk_ciphers is not None:
144 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000145
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000146 if record_resume_info:
147 # If record_resume_info is true then we'll replace the session cache with
148 # an object that records the lookups and inserts that it sees.
149 self.session_cache = RecordingSSLSessionCache()
150 else:
151 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000152 testserver_base.StoppableHTTPServer.__init__(self,
153 server_address,
154 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000155
156 def handshake(self, tlsConnection):
157 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000158
initial.commit94958cf2008-07-26 22:42:52 +0000159 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000160 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000161 tlsConnection.handshakeServer(certChain=self.cert_chain,
162 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000163 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000164 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000165 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000166 reqCAs=self.ssl_client_cas,
167 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000168 tlsConnection.ignoreAbruptClose = True
169 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000170 except tlslite.api.TLSAbruptCloseError:
171 # Ignore abrupt close.
172 return True
initial.commit94958cf2008-07-26 22:42:52 +0000173 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000174 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000175 return False
176
akalin@chromium.org154bb132010-11-12 02:20:27 +0000177
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000178class FTPServer(testserver_base.ClientRestrictingServerMixIn,
179 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000180 """This is a specialization of FTPServer that adds client verification."""
181
182 pass
183
184
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000185class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
186 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000187 """A TCP echo server that echoes back what it has received."""
188
189 def server_bind(self):
190 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000191
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000192 SocketServer.TCPServer.server_bind(self)
193 host, port = self.socket.getsockname()[:2]
194 self.server_name = socket.getfqdn(host)
195 self.server_port = port
196
197 def serve_forever(self):
198 self.stop = False
199 self.nonce_time = None
200 while not self.stop:
201 self.handle_request()
202 self.socket.close()
203
204
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000205class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
206 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000207 """A UDP echo server that echoes back what it has received."""
208
209 def server_bind(self):
210 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000211
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000212 SocketServer.UDPServer.server_bind(self)
213 host, port = self.socket.getsockname()[:2]
214 self.server_name = socket.getfqdn(host)
215 self.server_port = port
216
217 def serve_forever(self):
218 self.stop = False
219 self.nonce_time = None
220 while not self.stop:
221 self.handle_request()
222 self.socket.close()
223
224
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000225class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000226 # Class variables to allow for persistence state between page handler
227 # invocations
228 rst_limits = {}
229 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000230
231 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000232 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000233 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000234 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000235 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000236 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000237 self.NoCacheMaxAgeTimeHandler,
238 self.NoCacheTimeHandler,
239 self.CacheTimeHandler,
240 self.CacheExpiresHandler,
241 self.CacheProxyRevalidateHandler,
242 self.CachePrivateHandler,
243 self.CachePublicHandler,
244 self.CacheSMaxAgeHandler,
245 self.CacheMustRevalidateHandler,
246 self.CacheMustRevalidateMaxAgeHandler,
247 self.CacheNoStoreHandler,
248 self.CacheNoStoreMaxAgeHandler,
249 self.CacheNoTransformHandler,
250 self.DownloadHandler,
251 self.DownloadFinishHandler,
252 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000253 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000254 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000255 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000256 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000257 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000258 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000259 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000260 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000261 self.AuthBasicHandler,
262 self.AuthDigestHandler,
263 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000264 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000265 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000266 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000267 self.ServerRedirectHandler,
268 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000269 self.MultipartHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000270 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000271 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000272 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000273 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000274 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000275 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000276 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000277 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000278 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000279 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000280 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000281 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000282 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000283 head_handlers = [
284 self.FileHandler,
285 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000286
maruel@google.come250a9b2009-03-10 17:39:46 +0000287 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000288 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000289 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000290 'gif': 'image/gif',
291 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000292 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000293 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000294 'pdf' : 'application/pdf',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000295 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000296 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000297 }
initial.commit94958cf2008-07-26 22:42:52 +0000298 self._default_mime_type = 'text/html'
299
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000300 testserver_base.BasePageHandler.__init__(self, request, client_address,
301 socket_server, connect_handlers,
302 get_handlers, head_handlers,
303 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000304
initial.commit94958cf2008-07-26 22:42:52 +0000305 def GetMIMETypeFromName(self, file_name):
306 """Returns the mime type for the specified file_name. So far it only looks
307 at the file extension."""
308
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000309 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000310 if len(extension) == 0:
311 # no extension.
312 return self._default_mime_type
313
ericroman@google.comc17ca532009-05-07 03:51:05 +0000314 # extension starts with a dot, so we need to remove it
315 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000316
initial.commit94958cf2008-07-26 22:42:52 +0000317 def NoCacheMaxAgeTimeHandler(self):
318 """This request handler yields a page with the title set to the current
319 system time, and no caching requested."""
320
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000321 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000322 return False
323
324 self.send_response(200)
325 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000326 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000327 self.end_headers()
328
maruel@google.come250a9b2009-03-10 17:39:46 +0000329 self.wfile.write('<html><head><title>%s</title></head></html>' %
330 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000331
332 return True
333
334 def NoCacheTimeHandler(self):
335 """This request handler yields a page with the title set to the current
336 system time, and no caching requested."""
337
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000338 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000339 return False
340
341 self.send_response(200)
342 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000343 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000344 self.end_headers()
345
maruel@google.come250a9b2009-03-10 17:39:46 +0000346 self.wfile.write('<html><head><title>%s</title></head></html>' %
347 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000348
349 return True
350
351 def CacheTimeHandler(self):
352 """This request handler yields a page with the title set to the current
353 system time, and allows caching for one minute."""
354
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000355 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000356 return False
357
358 self.send_response(200)
359 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000360 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000361 self.end_headers()
362
maruel@google.come250a9b2009-03-10 17:39:46 +0000363 self.wfile.write('<html><head><title>%s</title></head></html>' %
364 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000365
366 return True
367
368 def CacheExpiresHandler(self):
369 """This request handler yields a page with the title set to the current
370 system time, and set the page to expire on 1 Jan 2099."""
371
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000372 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000373 return False
374
375 self.send_response(200)
376 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000377 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000378 self.end_headers()
379
maruel@google.come250a9b2009-03-10 17:39:46 +0000380 self.wfile.write('<html><head><title>%s</title></head></html>' %
381 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000382
383 return True
384
385 def CacheProxyRevalidateHandler(self):
386 """This request handler yields a page with the title set to the current
387 system time, and allows caching for 60 seconds"""
388
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000389 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000390 return False
391
392 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000393 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000394 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
395 self.end_headers()
396
maruel@google.come250a9b2009-03-10 17:39:46 +0000397 self.wfile.write('<html><head><title>%s</title></head></html>' %
398 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000399
400 return True
401
402 def CachePrivateHandler(self):
403 """This request handler yields a page with the title set to the current
404 system time, and allows caching for 5 seconds."""
405
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000406 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000407 return False
408
409 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000410 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000411 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000412 self.end_headers()
413
maruel@google.come250a9b2009-03-10 17:39:46 +0000414 self.wfile.write('<html><head><title>%s</title></head></html>' %
415 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000416
417 return True
418
419 def CachePublicHandler(self):
420 """This request handler yields a page with the title set to the current
421 system time, and allows caching for 5 seconds."""
422
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000423 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000424 return False
425
426 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000427 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000428 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000429 self.end_headers()
430
maruel@google.come250a9b2009-03-10 17:39:46 +0000431 self.wfile.write('<html><head><title>%s</title></head></html>' %
432 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000433
434 return True
435
436 def CacheSMaxAgeHandler(self):
437 """This request handler yields a page with the title set to the current
438 system time, and does not allow for caching."""
439
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000440 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000441 return False
442
443 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000444 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000445 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
446 self.end_headers()
447
maruel@google.come250a9b2009-03-10 17:39:46 +0000448 self.wfile.write('<html><head><title>%s</title></head></html>' %
449 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000450
451 return True
452
453 def CacheMustRevalidateHandler(self):
454 """This request handler yields a page with the title set to the current
455 system time, and does not allow caching."""
456
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000457 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000458 return False
459
460 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000461 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000462 self.send_header('Cache-Control', 'must-revalidate')
463 self.end_headers()
464
maruel@google.come250a9b2009-03-10 17:39:46 +0000465 self.wfile.write('<html><head><title>%s</title></head></html>' %
466 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000467
468 return True
469
470 def CacheMustRevalidateMaxAgeHandler(self):
471 """This request handler yields a page with the title set to the current
472 system time, and does not allow caching event though max-age of 60
473 seconds is specified."""
474
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000475 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000476 return False
477
478 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000479 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000480 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
481 self.end_headers()
482
maruel@google.come250a9b2009-03-10 17:39:46 +0000483 self.wfile.write('<html><head><title>%s</title></head></html>' %
484 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000485
486 return True
487
initial.commit94958cf2008-07-26 22:42:52 +0000488 def CacheNoStoreHandler(self):
489 """This request handler yields a page with the title set to the current
490 system time, and does not allow the page to be stored."""
491
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000492 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000493 return False
494
495 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000496 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000497 self.send_header('Cache-Control', 'no-store')
498 self.end_headers()
499
maruel@google.come250a9b2009-03-10 17:39:46 +0000500 self.wfile.write('<html><head><title>%s</title></head></html>' %
501 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000502
503 return True
504
505 def CacheNoStoreMaxAgeHandler(self):
506 """This request handler yields a page with the title set to the current
507 system time, and does not allow the page to be stored even though max-age
508 of 60 seconds is specified."""
509
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000510 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000511 return False
512
513 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000514 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000515 self.send_header('Cache-Control', 'max-age=60, no-store')
516 self.end_headers()
517
maruel@google.come250a9b2009-03-10 17:39:46 +0000518 self.wfile.write('<html><head><title>%s</title></head></html>' %
519 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000520
521 return True
522
523
524 def CacheNoTransformHandler(self):
525 """This request handler yields a page with the title set to the current
526 system time, and does not allow the content to transformed during
527 user-agent caching"""
528
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000529 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000530 return False
531
532 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000533 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000534 self.send_header('Cache-Control', 'no-transform')
535 self.end_headers()
536
maruel@google.come250a9b2009-03-10 17:39:46 +0000537 self.wfile.write('<html><head><title>%s</title></head></html>' %
538 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000539
540 return True
541
542 def EchoHeader(self):
543 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000544
ananta@chromium.org219b2062009-10-23 16:09:41 +0000545 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000546
ananta@chromium.org56812d02011-04-07 17:52:05 +0000547 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000548 """This function echoes back the value of a specific request header while
549 allowing caching for 16 hours."""
550
ananta@chromium.org56812d02011-04-07 17:52:05 +0000551 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000552
553 def EchoHeaderHelper(self, echo_header):
554 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000555
ananta@chromium.org219b2062009-10-23 16:09:41 +0000556 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000557 return False
558
559 query_char = self.path.find('?')
560 if query_char != -1:
561 header_name = self.path[query_char+1:]
562
563 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000564 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000565 if echo_header == '/echoheadercache':
566 self.send_header('Cache-control', 'max-age=60000')
567 else:
568 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000569 # insert a vary header to properly indicate that the cachability of this
570 # request is subject to value of the request header being echoed.
571 if len(header_name) > 0:
572 self.send_header('Vary', header_name)
573 self.end_headers()
574
575 if len(header_name) > 0:
576 self.wfile.write(self.headers.getheader(header_name))
577
578 return True
579
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000580 def ReadRequestBody(self):
581 """This function reads the body of the current HTTP request, handling
582 both plain and chunked transfer encoded requests."""
583
584 if self.headers.getheader('transfer-encoding') != 'chunked':
585 length = int(self.headers.getheader('content-length'))
586 return self.rfile.read(length)
587
588 # Read the request body as chunks.
589 body = ""
590 while True:
591 line = self.rfile.readline()
592 length = int(line, 16)
593 if length == 0:
594 self.rfile.readline()
595 break
596 body += self.rfile.read(length)
597 self.rfile.read(2)
598 return body
599
initial.commit94958cf2008-07-26 22:42:52 +0000600 def EchoHandler(self):
601 """This handler just echoes back the payload of the request, for testing
602 form submission."""
603
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000604 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000605 return False
606
607 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000608 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000609 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000610 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000611 return True
612
613 def EchoTitleHandler(self):
614 """This handler is like Echo, but sets the page title to the request."""
615
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000616 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000617 return False
618
619 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000620 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000621 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000622 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000623 self.wfile.write('<html><head><title>')
624 self.wfile.write(request)
625 self.wfile.write('</title></head></html>')
626 return True
627
628 def EchoAllHandler(self):
629 """This handler yields a (more) human-readable page listing information
630 about the request header & contents."""
631
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000632 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000633 return False
634
635 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000636 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000637 self.end_headers()
638 self.wfile.write('<html><head><style>'
639 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
640 '</style></head><body>'
641 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000642 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000643 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000644
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000645 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000646 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000647 params = cgi.parse_qs(qs, keep_blank_values=1)
648
649 for param in params:
650 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000651
652 self.wfile.write('</pre>')
653
654 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
655
656 self.wfile.write('</body></html>')
657 return True
658
659 def DownloadHandler(self):
660 """This handler sends a downloadable file with or without reporting
661 the size (6K)."""
662
663 if self.path.startswith("/download-unknown-size"):
664 send_length = False
665 elif self.path.startswith("/download-known-size"):
666 send_length = True
667 else:
668 return False
669
670 #
671 # The test which uses this functionality is attempting to send
672 # small chunks of data to the client. Use a fairly large buffer
673 # so that we'll fill chrome's IO buffer enough to force it to
674 # actually write the data.
675 # See also the comments in the client-side of this test in
676 # download_uitest.cc
677 #
678 size_chunk1 = 35*1024
679 size_chunk2 = 10*1024
680
681 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000682 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000683 self.send_header('Cache-Control', 'max-age=0')
684 if send_length:
685 self.send_header('Content-Length', size_chunk1 + size_chunk2)
686 self.end_headers()
687
688 # First chunk of data:
689 self.wfile.write("*" * size_chunk1)
690 self.wfile.flush()
691
692 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000693 self.server.wait_for_download = True
694 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000695 self.server.handle_request()
696
697 # Second chunk of data:
698 self.wfile.write("*" * size_chunk2)
699 return True
700
701 def DownloadFinishHandler(self):
702 """This handler just tells the server to finish the current download."""
703
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000704 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000705 return False
706
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000707 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000708 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000709 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000710 self.send_header('Cache-Control', 'max-age=0')
711 self.end_headers()
712 return True
713
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000714 def _ReplaceFileData(self, data, query_parameters):
715 """Replaces matching substrings in a file.
716
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000717 If the 'replace_text' URL query parameter is present, it is expected to be
718 of the form old_text:new_text, which indicates that any old_text strings in
719 the file are replaced with new_text. Multiple 'replace_text' parameters may
720 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000721
722 If the parameters are not present, |data| is returned.
723 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000724
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000725 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000726 replace_text_values = query_dict.get('replace_text', [])
727 for replace_text_value in replace_text_values:
728 replace_text_args = replace_text_value.split(':')
729 if len(replace_text_args) != 2:
730 raise ValueError(
731 'replace_text must be of form old_text:new_text. Actual value: %s' %
732 replace_text_value)
733 old_text_b64, new_text_b64 = replace_text_args
734 old_text = base64.urlsafe_b64decode(old_text_b64)
735 new_text = base64.urlsafe_b64decode(new_text_b64)
736 data = data.replace(old_text, new_text)
737 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000738
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000739 def ZipFileHandler(self):
740 """This handler sends the contents of the requested file in compressed form.
741 Can pass in a parameter that specifies that the content length be
742 C - the compressed size (OK),
743 U - the uncompressed size (Non-standard, but handled),
744 S - less than compressed (OK because we keep going),
745 M - larger than compressed but less than uncompressed (an error),
746 L - larger than uncompressed (an error)
747 Example: compressedfiles/Picture_1.doc?C
748 """
749
750 prefix = "/compressedfiles/"
751 if not self.path.startswith(prefix):
752 return False
753
754 # Consume a request body if present.
755 if self.command == 'POST' or self.command == 'PUT' :
756 self.ReadRequestBody()
757
758 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
759
760 if not query in ('C', 'U', 'S', 'M', 'L'):
761 return False
762
763 sub_path = url_path[len(prefix):]
764 entries = sub_path.split('/')
765 file_path = os.path.join(self.server.data_dir, *entries)
766 if os.path.isdir(file_path):
767 file_path = os.path.join(file_path, 'index.html')
768
769 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000770 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000771 self.send_error(404)
772 return True
773
774 f = open(file_path, "rb")
775 data = f.read()
776 uncompressed_len = len(data)
777 f.close()
778
779 # Compress the data.
780 data = zlib.compress(data)
781 compressed_len = len(data)
782
783 content_length = compressed_len
784 if query == 'U':
785 content_length = uncompressed_len
786 elif query == 'S':
787 content_length = compressed_len / 2
788 elif query == 'M':
789 content_length = (compressed_len + uncompressed_len) / 2
790 elif query == 'L':
791 content_length = compressed_len + uncompressed_len
792
793 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000794 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000795 self.send_header('Content-encoding', 'deflate')
796 self.send_header('Connection', 'close')
797 self.send_header('Content-Length', content_length)
798 self.send_header('ETag', '\'' + file_path + '\'')
799 self.end_headers()
800
801 self.wfile.write(data)
802
803 return True
804
initial.commit94958cf2008-07-26 22:42:52 +0000805 def FileHandler(self):
806 """This handler sends the contents of the requested file. Wow, it's like
807 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000808
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000809 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000810 if not self.path.startswith(prefix):
811 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000812 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000813
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000814 def PostOnlyFileHandler(self):
815 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000816
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000817 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000818 if not self.path.startswith(prefix):
819 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000820 return self._FileHandlerHelper(prefix)
821
822 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000823 request_body = ''
824 if self.command == 'POST' or self.command == 'PUT':
825 # Consume a request body if present.
826 request_body = self.ReadRequestBody()
827
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000828 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000829 query_dict = cgi.parse_qs(query)
830
831 expected_body = query_dict.get('expected_body', [])
832 if expected_body and request_body not in expected_body:
833 self.send_response(404)
834 self.end_headers()
835 self.wfile.write('')
836 return True
837
838 expected_headers = query_dict.get('expected_headers', [])
839 for expected_header in expected_headers:
840 header_name, expected_value = expected_header.split(':')
841 if self.headers.getheader(header_name) != expected_value:
842 self.send_response(404)
843 self.end_headers()
844 self.wfile.write('')
845 return True
846
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000847 sub_path = url_path[len(prefix):]
848 entries = sub_path.split('/')
849 file_path = os.path.join(self.server.data_dir, *entries)
850 if os.path.isdir(file_path):
851 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000852
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000853 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000854 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000855 self.send_error(404)
856 return True
857
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000858 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000859 data = f.read()
860 f.close()
861
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000862 data = self._ReplaceFileData(data, query)
863
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000864 old_protocol_version = self.protocol_version
865
initial.commit94958cf2008-07-26 22:42:52 +0000866 # If file.mock-http-headers exists, it contains the headers we
867 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000868 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000869 if os.path.isfile(headers_path):
870 f = open(headers_path, "r")
871
872 # "HTTP/1.1 200 OK"
873 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000874 http_major, http_minor, status_code = re.findall(
875 'HTTP/(\d+).(\d+) (\d+)', response)[0]
876 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000877 self.send_response(int(status_code))
878
879 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000880 header_values = re.findall('(\S+):\s*(.*)', line)
881 if len(header_values) > 0:
882 # "name: value"
883 name, value = header_values[0]
884 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000885 f.close()
886 else:
887 # Could be more generic once we support mime-type sniffing, but for
888 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000889
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000890 range_header = self.headers.get('Range')
891 if range_header and range_header.startswith('bytes='):
892 # Note this doesn't handle all valid byte range_header values (i.e.
893 # left open ended ones), just enough for what we needed so far.
894 range_header = range_header[6:].split('-')
895 start = int(range_header[0])
896 if range_header[1]:
897 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000898 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000899 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000900
901 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000902 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
903 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000904 self.send_header('Content-Range', content_range)
905 data = data[start: end + 1]
906 else:
907 self.send_response(200)
908
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000909 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000910 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000911 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000912 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000913 self.end_headers()
914
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000915 if (self.command != 'HEAD'):
916 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000917
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000918 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000919 return True
920
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000921 def SetCookieHandler(self):
922 """This handler just sets a cookie, for testing cookie handling."""
923
924 if not self._ShouldHandleRequest("/set-cookie"):
925 return False
926
927 query_char = self.path.find('?')
928 if query_char != -1:
929 cookie_values = self.path[query_char + 1:].split('&')
930 else:
931 cookie_values = ("",)
932 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000933 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000934 for cookie_value in cookie_values:
935 self.send_header('Set-Cookie', '%s' % cookie_value)
936 self.end_headers()
937 for cookie_value in cookie_values:
938 self.wfile.write('%s' % cookie_value)
939 return True
940
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000941 def SetManyCookiesHandler(self):
942 """This handler just sets a given number of cookies, for testing handling
943 of large numbers of cookies."""
944
945 if not self._ShouldHandleRequest("/set-many-cookies"):
946 return False
947
948 query_char = self.path.find('?')
949 if query_char != -1:
950 num_cookies = int(self.path[query_char + 1:])
951 else:
952 num_cookies = 0
953 self.send_response(200)
954 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000955 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000956 self.send_header('Set-Cookie', 'a=')
957 self.end_headers()
958 self.wfile.write('%d cookies were sent' % num_cookies)
959 return True
960
mattm@chromium.org983fc462012-06-30 00:52:08 +0000961 def ExpectAndSetCookieHandler(self):
962 """Expects some cookies to be sent, and if they are, sets more cookies.
963
964 The expect parameter specifies a required cookie. May be specified multiple
965 times.
966 The set parameter specifies a cookie to set if all required cookies are
967 preset. May be specified multiple times.
968 The data parameter specifies the response body data to be returned."""
969
970 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
971 return False
972
973 _, _, _, _, query, _ = urlparse.urlparse(self.path)
974 query_dict = cgi.parse_qs(query)
975 cookies = set()
976 if 'Cookie' in self.headers:
977 cookie_header = self.headers.getheader('Cookie')
978 cookies.update([s.strip() for s in cookie_header.split(';')])
979 got_all_expected_cookies = True
980 for expected_cookie in query_dict.get('expect', []):
981 if expected_cookie not in cookies:
982 got_all_expected_cookies = False
983 self.send_response(200)
984 self.send_header('Content-Type', 'text/html')
985 if got_all_expected_cookies:
986 for cookie_value in query_dict.get('set', []):
987 self.send_header('Set-Cookie', '%s' % cookie_value)
988 self.end_headers()
989 for data_value in query_dict.get('data', []):
990 self.wfile.write(data_value)
991 return True
992
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000993 def SetHeaderHandler(self):
994 """This handler sets a response header. Parameters are in the
995 key%3A%20value&key2%3A%20value2 format."""
996
997 if not self._ShouldHandleRequest("/set-header"):
998 return False
999
1000 query_char = self.path.find('?')
1001 if query_char != -1:
1002 headers_values = self.path[query_char + 1:].split('&')
1003 else:
1004 headers_values = ("",)
1005 self.send_response(200)
1006 self.send_header('Content-Type', 'text/html')
1007 for header_value in headers_values:
1008 header_value = urllib.unquote(header_value)
1009 (key, value) = header_value.split(': ', 1)
1010 self.send_header(key, value)
1011 self.end_headers()
1012 for header_value in headers_values:
1013 self.wfile.write('%s' % header_value)
1014 return True
1015
initial.commit94958cf2008-07-26 22:42:52 +00001016 def AuthBasicHandler(self):
1017 """This handler tests 'Basic' authentication. It just sends a page with
1018 title 'user/pass' if you succeed."""
1019
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001020 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001021 return False
1022
1023 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001024 expected_password = 'secret'
1025 realm = 'testrealm'
1026 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001027
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001028 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1029 query_params = cgi.parse_qs(query, True)
1030 if 'set-cookie-if-challenged' in query_params:
1031 set_cookie_if_challenged = True
1032 if 'password' in query_params:
1033 expected_password = query_params['password'][0]
1034 if 'realm' in query_params:
1035 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001036
initial.commit94958cf2008-07-26 22:42:52 +00001037 auth = self.headers.getheader('authorization')
1038 try:
1039 if not auth:
1040 raise Exception('no auth')
1041 b64str = re.findall(r'Basic (\S+)', auth)[0]
1042 userpass = base64.b64decode(b64str)
1043 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001044 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001045 raise Exception('wrong password')
1046 except Exception, e:
1047 # Authentication failed.
1048 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001049 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001050 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001051 if set_cookie_if_challenged:
1052 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001053 self.end_headers()
1054 self.wfile.write('<html><head>')
1055 self.wfile.write('<title>Denied: %s</title>' % e)
1056 self.wfile.write('</head><body>')
1057 self.wfile.write('auth=%s<p>' % auth)
1058 self.wfile.write('b64str=%s<p>' % b64str)
1059 self.wfile.write('username: %s<p>' % username)
1060 self.wfile.write('userpass: %s<p>' % userpass)
1061 self.wfile.write('password: %s<p>' % password)
1062 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1063 self.wfile.write('</body></html>')
1064 return True
1065
1066 # Authentication successful. (Return a cachable response to allow for
1067 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001068 old_protocol_version = self.protocol_version
1069 self.protocol_version = "HTTP/1.1"
1070
initial.commit94958cf2008-07-26 22:42:52 +00001071 if_none_match = self.headers.getheader('if-none-match')
1072 if if_none_match == "abc":
1073 self.send_response(304)
1074 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001075 elif url_path.endswith(".gif"):
1076 # Using chrome/test/data/google/logo.gif as the test image
1077 test_image_path = ['google', 'logo.gif']
1078 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1079 if not os.path.isfile(gif_path):
1080 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001081 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001082 return True
1083
1084 f = open(gif_path, "rb")
1085 data = f.read()
1086 f.close()
1087
1088 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001089 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001090 self.send_header('Cache-control', 'max-age=60000')
1091 self.send_header('Etag', 'abc')
1092 self.end_headers()
1093 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001094 else:
1095 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001096 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001097 self.send_header('Cache-control', 'max-age=60000')
1098 self.send_header('Etag', 'abc')
1099 self.end_headers()
1100 self.wfile.write('<html><head>')
1101 self.wfile.write('<title>%s/%s</title>' % (username, password))
1102 self.wfile.write('</head><body>')
1103 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001104 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001105 self.wfile.write('</body></html>')
1106
rvargas@google.com54453b72011-05-19 01:11:11 +00001107 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001108 return True
1109
tonyg@chromium.org75054202010-03-31 22:06:10 +00001110 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001111 """Returns a nonce that's stable per request path for the server's lifetime.
1112 This is a fake implementation. A real implementation would only use a given
1113 nonce a single time (hence the name n-once). However, for the purposes of
1114 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001115
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001116 Args:
1117 force_reset: Iff set, the nonce will be changed. Useful for testing the
1118 "stale" response.
1119 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001120
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001121 if force_reset or not self.server.nonce_time:
1122 self.server.nonce_time = time.time()
1123 return hashlib.md5('privatekey%s%d' %
1124 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001125
1126 def AuthDigestHandler(self):
1127 """This handler tests 'Digest' authentication.
1128
1129 It just sends a page with title 'user/pass' if you succeed.
1130
1131 A stale response is sent iff "stale" is present in the request path.
1132 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001133
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001134 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001135 return False
1136
tonyg@chromium.org75054202010-03-31 22:06:10 +00001137 stale = 'stale' in self.path
1138 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001139 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001140 password = 'secret'
1141 realm = 'testrealm'
1142
1143 auth = self.headers.getheader('authorization')
1144 pairs = {}
1145 try:
1146 if not auth:
1147 raise Exception('no auth')
1148 if not auth.startswith('Digest'):
1149 raise Exception('not digest')
1150 # Pull out all the name="value" pairs as a dictionary.
1151 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1152
1153 # Make sure it's all valid.
1154 if pairs['nonce'] != nonce:
1155 raise Exception('wrong nonce')
1156 if pairs['opaque'] != opaque:
1157 raise Exception('wrong opaque')
1158
1159 # Check the 'response' value and make sure it matches our magic hash.
1160 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001161 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001162 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001163 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001164 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001165 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001166 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1167 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001168 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001169
1170 if pairs['response'] != response:
1171 raise Exception('wrong password')
1172 except Exception, e:
1173 # Authentication failed.
1174 self.send_response(401)
1175 hdr = ('Digest '
1176 'realm="%s", '
1177 'domain="/", '
1178 'qop="auth", '
1179 'algorithm=MD5, '
1180 'nonce="%s", '
1181 'opaque="%s"') % (realm, nonce, opaque)
1182 if stale:
1183 hdr += ', stale="TRUE"'
1184 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001185 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001186 self.end_headers()
1187 self.wfile.write('<html><head>')
1188 self.wfile.write('<title>Denied: %s</title>' % e)
1189 self.wfile.write('</head><body>')
1190 self.wfile.write('auth=%s<p>' % auth)
1191 self.wfile.write('pairs=%s<p>' % pairs)
1192 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1193 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1194 self.wfile.write('</body></html>')
1195 return True
1196
1197 # Authentication successful.
1198 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001199 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001200 self.end_headers()
1201 self.wfile.write('<html><head>')
1202 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1203 self.wfile.write('</head><body>')
1204 self.wfile.write('auth=%s<p>' % auth)
1205 self.wfile.write('pairs=%s<p>' % pairs)
1206 self.wfile.write('</body></html>')
1207
1208 return True
1209
1210 def SlowServerHandler(self):
1211 """Wait for the user suggested time before responding. The syntax is
1212 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001213
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001214 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001215 return False
1216 query_char = self.path.find('?')
1217 wait_sec = 1.0
1218 if query_char >= 0:
1219 try:
1220 wait_sec = int(self.path[query_char + 1:])
1221 except ValueError:
1222 pass
1223 time.sleep(wait_sec)
1224 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001225 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001226 self.end_headers()
1227 self.wfile.write("waited %d seconds" % wait_sec)
1228 return True
1229
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001230 def ChunkedServerHandler(self):
1231 """Send chunked response. Allows to specify chunks parameters:
1232 - waitBeforeHeaders - ms to wait before sending headers
1233 - waitBetweenChunks - ms to wait between chunks
1234 - chunkSize - size of each chunk in bytes
1235 - chunksNumber - number of chunks
1236 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1237 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001238
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001239 if not self._ShouldHandleRequest("/chunked"):
1240 return False
1241 query_char = self.path.find('?')
1242 chunkedSettings = {'waitBeforeHeaders' : 0,
1243 'waitBetweenChunks' : 0,
1244 'chunkSize' : 5,
1245 'chunksNumber' : 5}
1246 if query_char >= 0:
1247 params = self.path[query_char + 1:].split('&')
1248 for param in params:
1249 keyValue = param.split('=')
1250 if len(keyValue) == 2:
1251 try:
1252 chunkedSettings[keyValue[0]] = int(keyValue[1])
1253 except ValueError:
1254 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001255 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001256 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1257 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001258 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001259 self.send_header('Connection', 'close')
1260 self.send_header('Transfer-Encoding', 'chunked')
1261 self.end_headers()
1262 # Chunked encoding: sending all chunks, then final zero-length chunk and
1263 # then final CRLF.
1264 for i in range(0, chunkedSettings['chunksNumber']):
1265 if i > 0:
1266 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1267 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001268 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001269 self.sendChunkHelp('')
1270 return True
1271
initial.commit94958cf2008-07-26 22:42:52 +00001272 def ContentTypeHandler(self):
1273 """Returns a string of html with the given content type. E.g.,
1274 /contenttype?text/css returns an html file with the Content-Type
1275 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001276
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001277 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001278 return False
1279 query_char = self.path.find('?')
1280 content_type = self.path[query_char + 1:].strip()
1281 if not content_type:
1282 content_type = 'text/html'
1283 self.send_response(200)
1284 self.send_header('Content-Type', content_type)
1285 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001286 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001287 return True
1288
creis@google.com2f4f6a42011-03-25 19:44:19 +00001289 def NoContentHandler(self):
1290 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001291
creis@google.com2f4f6a42011-03-25 19:44:19 +00001292 if not self._ShouldHandleRequest("/nocontent"):
1293 return False
1294 self.send_response(204)
1295 self.end_headers()
1296 return True
1297
initial.commit94958cf2008-07-26 22:42:52 +00001298 def ServerRedirectHandler(self):
1299 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001300 '/server-redirect?http://foo.bar/asdf' to redirect to
1301 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001302
1303 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001304 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001305 return False
1306
1307 query_char = self.path.find('?')
1308 if query_char < 0 or len(self.path) <= query_char + 1:
1309 self.sendRedirectHelp(test_name)
1310 return True
1311 dest = self.path[query_char + 1:]
1312
1313 self.send_response(301) # moved permanently
1314 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001315 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001316 self.end_headers()
1317 self.wfile.write('<html><head>')
1318 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1319
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001320 return True
initial.commit94958cf2008-07-26 22:42:52 +00001321
1322 def ClientRedirectHandler(self):
1323 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001324 '/client-redirect?http://foo.bar/asdf' to redirect to
1325 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001326
1327 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001328 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001329 return False
1330
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001331 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001332 if query_char < 0 or len(self.path) <= query_char + 1:
1333 self.sendRedirectHelp(test_name)
1334 return True
1335 dest = self.path[query_char + 1:]
1336
1337 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001338 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001339 self.end_headers()
1340 self.wfile.write('<html><head>')
1341 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1342 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1343
1344 return True
1345
tony@chromium.org03266982010-03-05 03:18:42 +00001346 def MultipartHandler(self):
1347 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001348
tony@chromium.org4cb88302011-09-27 22:13:49 +00001349 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001350 if not self._ShouldHandleRequest(test_name):
1351 return False
1352
1353 num_frames = 10
1354 bound = '12345'
1355 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001356 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001357 'multipart/x-mixed-replace;boundary=' + bound)
1358 self.end_headers()
1359
1360 for i in xrange(num_frames):
1361 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001362 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001363 self.wfile.write('<title>page ' + str(i) + '</title>')
1364 self.wfile.write('page ' + str(i))
1365
1366 self.wfile.write('--' + bound + '--')
1367 return True
1368
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001369 def GetSSLSessionCacheHandler(self):
1370 """Send a reply containing a log of the session cache operations."""
1371
1372 if not self._ShouldHandleRequest('/ssl-session-cache'):
1373 return False
1374
1375 self.send_response(200)
1376 self.send_header('Content-Type', 'text/plain')
1377 self.end_headers()
1378 try:
1379 for (action, sessionID) in self.server.session_cache.log:
1380 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001381 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001382 self.wfile.write('Pass --https-record-resume in order to use' +
1383 ' this request')
1384 return True
1385
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001386 def SSLManySmallRecords(self):
1387 """Sends a reply consisting of a variety of small writes. These will be
1388 translated into a series of small SSL records when used over an HTTPS
1389 server."""
1390
1391 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1392 return False
1393
1394 self.send_response(200)
1395 self.send_header('Content-Type', 'text/plain')
1396 self.end_headers()
1397
1398 # Write ~26K of data, in 1350 byte chunks
1399 for i in xrange(20):
1400 self.wfile.write('*' * 1350)
1401 self.wfile.flush()
1402 return True
1403
agl@chromium.org04700be2013-03-02 18:40:41 +00001404 def GetChannelID(self):
1405 """Send a reply containing the hashed ChannelID that the client provided."""
1406
1407 if not self._ShouldHandleRequest('/channel-id'):
1408 return False
1409
1410 self.send_response(200)
1411 self.send_header('Content-Type', 'text/plain')
1412 self.end_headers()
1413 channel_id = self.server.tlsConnection.channel_id.tostring()
1414 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1415 return True
1416
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001417 def CloseSocketHandler(self):
1418 """Closes the socket without sending anything."""
1419
1420 if not self._ShouldHandleRequest('/close-socket'):
1421 return False
1422
1423 self.wfile.close()
1424 return True
1425
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001426 def RangeResetHandler(self):
1427 """Send data broken up by connection resets every N (default 4K) bytes.
1428 Support range requests. If the data requested doesn't straddle a reset
1429 boundary, it will all be sent. Used for testing resuming downloads."""
1430
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001431 def DataForRange(start, end):
1432 """Data to be provided for a particular range of bytes."""
1433 # Offset and scale to avoid too obvious (and hence potentially
1434 # collidable) data.
1435 return ''.join([chr(y % 256)
1436 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1437
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001438 if not self._ShouldHandleRequest('/rangereset'):
1439 return False
1440
1441 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1442
1443 # Defaults
1444 size = 8000
1445 # Note that the rst is sent just before sending the rst_boundary byte.
1446 rst_boundary = 4000
1447 respond_to_range = True
1448 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001449 rst_limit = -1
1450 token = 'DEFAULT'
1451 fail_precondition = 0
1452 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001453
1454 # Parse the query
1455 qdict = urlparse.parse_qs(query, True)
1456 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001457 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001458 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001459 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001460 if 'token' in qdict:
1461 # Identifying token for stateful tests.
1462 token = qdict['token'][0]
1463 if 'rst_limit' in qdict:
1464 # Max number of rsts for a given token.
1465 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001466 if 'bounce_range' in qdict:
1467 respond_to_range = False
1468 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001469 # Note that hold_for_signal will not work with null range requests;
1470 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001471 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001472 if 'no_verifiers' in qdict:
1473 send_verifiers = False
1474 if 'fail_precondition' in qdict:
1475 fail_precondition = int(qdict['fail_precondition'][0])
1476
1477 # Record already set information, or set it.
1478 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1479 if rst_limit != 0:
1480 TestPageHandler.rst_limits[token] -= 1
1481 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1482 token, fail_precondition)
1483 if fail_precondition != 0:
1484 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001485
1486 first_byte = 0
1487 last_byte = size - 1
1488
1489 # Does that define what we want to return, or do we need to apply
1490 # a range?
1491 range_response = False
1492 range_header = self.headers.getheader('range')
1493 if range_header and respond_to_range:
1494 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1495 if mo.group(1):
1496 first_byte = int(mo.group(1))
1497 if mo.group(2):
1498 last_byte = int(mo.group(2))
1499 if last_byte > size - 1:
1500 last_byte = size - 1
1501 range_response = True
1502 if last_byte < first_byte:
1503 return False
1504
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001505 if (fail_precondition and
1506 (self.headers.getheader('If-Modified-Since') or
1507 self.headers.getheader('If-Match'))):
1508 self.send_response(412)
1509 self.end_headers()
1510 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001511
1512 if range_response:
1513 self.send_response(206)
1514 self.send_header('Content-Range',
1515 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1516 else:
1517 self.send_response(200)
1518 self.send_header('Content-Type', 'application/octet-stream')
1519 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001520 if send_verifiers:
1521 self.send_header('Etag', '"XYZZY"')
1522 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001523 self.end_headers()
1524
1525 if hold_for_signal:
1526 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1527 # a single byte, the self.server.handle_request() below hangs
1528 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001529 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001530 first_byte = first_byte + 1
1531 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001532 self.server.wait_for_download = True
1533 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001534 self.server.handle_request()
1535
1536 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001537 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001538 # No RST has been requested in this range, so we don't need to
1539 # do anything fancy; just write the data and let the python
1540 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001541 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001542 self.wfile.flush()
1543 return True
1544
1545 # We're resetting the connection part way in; go to the RST
1546 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001547 # Because socket semantics do not guarantee that all the data will be
1548 # sent when using the linger semantics to hard close a socket,
1549 # we send the data and then wait for our peer to release us
1550 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001551 data = DataForRange(first_byte, possible_rst)
1552 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001553 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001554 self.server.wait_for_download = True
1555 while self.server.wait_for_download:
1556 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001557 l_onoff = 1 # Linger is active.
1558 l_linger = 0 # Seconds to linger for.
1559 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1560 struct.pack('ii', l_onoff, l_linger))
1561
1562 # Close all duplicates of the underlying socket to force the RST.
1563 self.wfile.close()
1564 self.rfile.close()
1565 self.connection.close()
1566
1567 return True
1568
initial.commit94958cf2008-07-26 22:42:52 +00001569 def DefaultResponseHandler(self):
1570 """This is the catch-all response handler for requests that aren't handled
1571 by one of the special handlers above.
1572 Note that we specify the content-length as without it the https connection
1573 is not closed properly (and the browser keeps expecting data)."""
1574
1575 contents = "Default response given for path: " + self.path
1576 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001577 self.send_header('Content-Type', 'text/html')
1578 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001579 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001580 if (self.command != 'HEAD'):
1581 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001582 return True
1583
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001584 def RedirectConnectHandler(self):
1585 """Sends a redirect to the CONNECT request for www.redirect.com. This
1586 response is not specified by the RFC, so the browser should not follow
1587 the redirect."""
1588
1589 if (self.path.find("www.redirect.com") < 0):
1590 return False
1591
1592 dest = "http://www.destination.com/foo.js"
1593
1594 self.send_response(302) # moved temporarily
1595 self.send_header('Location', dest)
1596 self.send_header('Connection', 'close')
1597 self.end_headers()
1598 return True
1599
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001600 def ServerAuthConnectHandler(self):
1601 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1602 response doesn't make sense because the proxy server cannot request
1603 server authentication."""
1604
1605 if (self.path.find("www.server-auth.com") < 0):
1606 return False
1607
1608 challenge = 'Basic realm="WallyWorld"'
1609
1610 self.send_response(401) # unauthorized
1611 self.send_header('WWW-Authenticate', challenge)
1612 self.send_header('Connection', 'close')
1613 self.end_headers()
1614 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001615
1616 def DefaultConnectResponseHandler(self):
1617 """This is the catch-all response handler for CONNECT requests that aren't
1618 handled by one of the special handlers above. Real Web servers respond
1619 with 400 to CONNECT requests."""
1620
1621 contents = "Your client has issued a malformed or illegal request."
1622 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001623 self.send_header('Content-Type', 'text/html')
1624 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001625 self.end_headers()
1626 self.wfile.write(contents)
1627 return True
1628
initial.commit94958cf2008-07-26 22:42:52 +00001629 # called by the redirect handling function when there is no parameter
1630 def sendRedirectHelp(self, redirect_name):
1631 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001632 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001633 self.end_headers()
1634 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1635 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1636 self.wfile.write('</body></html>')
1637
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001638 # called by chunked handling function
1639 def sendChunkHelp(self, chunk):
1640 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1641 self.wfile.write('%X\r\n' % len(chunk))
1642 self.wfile.write(chunk)
1643 self.wfile.write('\r\n')
1644
akalin@chromium.org154bb132010-11-12 02:20:27 +00001645
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001646class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001647 def __init__(self, request, client_address, socket_server):
1648 handlers = [self.OCSPResponse]
1649 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001650 testserver_base.BasePageHandler.__init__(self, request, client_address,
1651 socket_server, [], handlers, [],
1652 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001653
1654 def OCSPResponse(self):
1655 self.send_response(200)
1656 self.send_header('Content-Type', 'application/ocsp-response')
1657 self.send_header('Content-Length', str(len(self.ocsp_response)))
1658 self.end_headers()
1659
1660 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001661
mattm@chromium.org830a3712012-11-07 23:00:07 +00001662
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001663class TCPEchoHandler(SocketServer.BaseRequestHandler):
1664 """The RequestHandler class for TCP echo server.
1665
1666 It is instantiated once per connection to the server, and overrides the
1667 handle() method to implement communication to the client.
1668 """
1669
1670 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001671 """Handles the request from the client and constructs a response."""
1672
1673 data = self.request.recv(65536).strip()
1674 # Verify the "echo request" message received from the client. Send back
1675 # "echo response" message if "echo request" message is valid.
1676 try:
1677 return_data = echo_message.GetEchoResponseData(data)
1678 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001679 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001680 except ValueError:
1681 return
1682
1683 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001684
1685
1686class UDPEchoHandler(SocketServer.BaseRequestHandler):
1687 """The RequestHandler class for UDP echo server.
1688
1689 It is instantiated once per connection to the server, and overrides the
1690 handle() method to implement communication to the client.
1691 """
1692
1693 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001694 """Handles the request from the client and constructs a response."""
1695
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001696 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001697 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001698 # Verify the "echo request" message received from the client. Send back
1699 # "echo response" message if "echo request" message is valid.
1700 try:
1701 return_data = echo_message.GetEchoResponseData(data)
1702 if not return_data:
1703 return
1704 except ValueError:
1705 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001706 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001707
1708
bashi@chromium.org33233532012-09-08 17:37:24 +00001709class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1710 """A request handler that behaves as a proxy server which requires
1711 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1712 """
1713
1714 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1715
1716 def parse_request(self):
1717 """Overrides parse_request to check credential."""
1718
1719 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1720 return False
1721
1722 auth = self.headers.getheader('Proxy-Authorization')
1723 if auth != self._AUTH_CREDENTIAL:
1724 self.send_response(407)
1725 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1726 self.end_headers()
1727 return False
1728
1729 return True
1730
1731 def _start_read_write(self, sock):
1732 sock.setblocking(0)
1733 self.request.setblocking(0)
1734 rlist = [self.request, sock]
1735 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001736 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001737 if errors:
1738 self.send_response(500)
1739 self.end_headers()
1740 return
1741 for s in ready_sockets:
1742 received = s.recv(1024)
1743 if len(received) == 0:
1744 return
1745 if s == self.request:
1746 other = sock
1747 else:
1748 other = self.request
1749 other.send(received)
1750
1751 def _do_common_method(self):
1752 url = urlparse.urlparse(self.path)
1753 port = url.port
1754 if not port:
1755 if url.scheme == 'http':
1756 port = 80
1757 elif url.scheme == 'https':
1758 port = 443
1759 if not url.hostname or not port:
1760 self.send_response(400)
1761 self.end_headers()
1762 return
1763
1764 if len(url.path) == 0:
1765 path = '/'
1766 else:
1767 path = url.path
1768 if len(url.query) > 0:
1769 path = '%s?%s' % (url.path, url.query)
1770
1771 sock = None
1772 try:
1773 sock = socket.create_connection((url.hostname, port))
1774 sock.send('%s %s %s\r\n' % (
1775 self.command, path, self.protocol_version))
1776 for header in self.headers.headers:
1777 header = header.strip()
1778 if (header.lower().startswith('connection') or
1779 header.lower().startswith('proxy')):
1780 continue
1781 sock.send('%s\r\n' % header)
1782 sock.send('\r\n')
1783 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001784 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001785 self.send_response(500)
1786 self.end_headers()
1787 finally:
1788 if sock is not None:
1789 sock.close()
1790
1791 def do_CONNECT(self):
1792 try:
1793 pos = self.path.rfind(':')
1794 host = self.path[:pos]
1795 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001796 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001797 self.send_response(400)
1798 self.end_headers()
1799
1800 try:
1801 sock = socket.create_connection((host, port))
1802 self.send_response(200, 'Connection established')
1803 self.end_headers()
1804 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001805 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001806 self.send_response(500)
1807 self.end_headers()
1808 finally:
1809 sock.close()
1810
1811 def do_GET(self):
1812 self._do_common_method()
1813
1814 def do_HEAD(self):
1815 self._do_common_method()
1816
1817
mattm@chromium.org830a3712012-11-07 23:00:07 +00001818class ServerRunner(testserver_base.TestServerRunner):
1819 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001820
mattm@chromium.org830a3712012-11-07 23:00:07 +00001821 def __init__(self):
1822 super(ServerRunner, self).__init__()
1823 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001824
mattm@chromium.org830a3712012-11-07 23:00:07 +00001825 def __make_data_dir(self):
1826 if self.options.data_dir:
1827 if not os.path.isdir(self.options.data_dir):
1828 raise testserver_base.OptionError('specified data dir not found: ' +
1829 self.options.data_dir + ' exiting...')
1830 my_data_dir = self.options.data_dir
1831 else:
1832 # Create the default path to our data dir, relative to the exe dir.
1833 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1834 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001835
mattm@chromium.org830a3712012-11-07 23:00:07 +00001836 #TODO(ibrar): Must use Find* funtion defined in google\tools
1837 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001838
mattm@chromium.org830a3712012-11-07 23:00:07 +00001839 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001840
mattm@chromium.org830a3712012-11-07 23:00:07 +00001841 def create_server(self, server_data):
1842 port = self.options.port
1843 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001844
mattm@chromium.org830a3712012-11-07 23:00:07 +00001845 if self.options.server_type == SERVER_HTTP:
1846 if self.options.https:
1847 pem_cert_and_key = None
1848 if self.options.cert_and_key_file:
1849 if not os.path.isfile(self.options.cert_and_key_file):
1850 raise testserver_base.OptionError(
1851 'specified server cert file not found: ' +
1852 self.options.cert_and_key_file + ' exiting...')
1853 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001854 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001855 # generate a new certificate and run an OCSP server for it.
1856 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001857 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001858 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001859
mattm@chromium.org830a3712012-11-07 23:00:07 +00001860 ocsp_der = None
1861 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001862
mattm@chromium.org830a3712012-11-07 23:00:07 +00001863 if self.options.ocsp == 'ok':
1864 ocsp_state = minica.OCSP_STATE_GOOD
1865 elif self.options.ocsp == 'revoked':
1866 ocsp_state = minica.OCSP_STATE_REVOKED
1867 elif self.options.ocsp == 'invalid':
1868 ocsp_state = minica.OCSP_STATE_INVALID
1869 elif self.options.ocsp == 'unauthorized':
1870 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1871 elif self.options.ocsp == 'unknown':
1872 ocsp_state = minica.OCSP_STATE_UNKNOWN
1873 else:
1874 raise testserver_base.OptionError('unknown OCSP status: ' +
1875 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001876
mattm@chromium.org830a3712012-11-07 23:00:07 +00001877 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1878 subject = "127.0.0.1",
1879 ocsp_url = ("http://%s:%d/ocsp" %
1880 (host, self.__ocsp_server.server_port)),
1881 ocsp_state = ocsp_state)
1882
1883 self.__ocsp_server.ocsp_response = ocsp_der
1884
1885 for ca_cert in self.options.ssl_client_ca:
1886 if not os.path.isfile(ca_cert):
1887 raise testserver_base.OptionError(
1888 'specified trusted client CA file not found: ' + ca_cert +
1889 ' exiting...')
1890 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1891 self.options.ssl_client_auth,
1892 self.options.ssl_client_ca,
1893 self.options.ssl_bulk_cipher,
1894 self.options.record_resume,
1895 self.options.tls_intolerant)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001896 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001897 else:
1898 server = HTTPServer((host, port), TestPageHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001899 print 'HTTP server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001900
1901 server.data_dir = self.__make_data_dir()
1902 server.file_root_url = self.options.file_root_url
1903 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001904 elif self.options.server_type == SERVER_WEBSOCKET:
1905 # Launch pywebsocket via WebSocketServer.
1906 logger = logging.getLogger()
1907 logger.addHandler(logging.StreamHandler())
1908 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1909 # is required to work correctly. It should be fixed from pywebsocket side.
1910 os.chdir(self.__make_data_dir())
1911 websocket_options = WebSocketOptions(host, port, '.')
1912 if self.options.cert_and_key_file:
1913 websocket_options.use_tls = True
1914 websocket_options.private_key = self.options.cert_and_key_file
1915 websocket_options.certificate = self.options.cert_and_key_file
1916 if self.options.ssl_client_auth:
1917 websocket_options.tls_client_auth = True
1918 if len(self.options.ssl_client_ca) != 1:
1919 raise testserver_base.OptionError(
1920 'one trusted client CA file should be specified')
1921 if not os.path.isfile(self.options.ssl_client_ca[0]):
1922 raise testserver_base.OptionError(
1923 'specified trusted client CA file not found: ' +
1924 self.options.ssl_client_ca[0] + ' exiting...')
1925 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
1926 server = WebSocketServer(websocket_options)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001927 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001928 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001929 elif self.options.server_type == SERVER_TCP_ECHO:
1930 # Used for generating the key (randomly) that encodes the "echo request"
1931 # message.
1932 random.seed()
1933 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001934 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001935 server_data['port'] = server.server_port
1936 elif self.options.server_type == SERVER_UDP_ECHO:
1937 # Used for generating the key (randomly) that encodes the "echo request"
1938 # message.
1939 random.seed()
1940 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001941 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001942 server_data['port'] = server.server_port
1943 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
1944 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001945 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001946 server_data['port'] = server.server_port
1947 elif self.options.server_type == SERVER_FTP:
1948 my_data_dir = self.__make_data_dir()
1949
1950 # Instantiate a dummy authorizer for managing 'virtual' users
1951 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1952
1953 # Define a new user having full r/w permissions and a read-only
1954 # anonymous user
1955 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1956
1957 authorizer.add_anonymous(my_data_dir)
1958
1959 # Instantiate FTP handler class
1960 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1961 ftp_handler.authorizer = authorizer
1962
1963 # Define a customized banner (string returned when client connects)
1964 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1965 pyftpdlib.ftpserver.__ver__)
1966
1967 # Instantiate FTP server class and listen to address:port
1968 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
1969 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001970 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001971 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001972 raise testserver_base.OptionError('unknown server type' +
1973 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00001974
mattm@chromium.org830a3712012-11-07 23:00:07 +00001975 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001976
mattm@chromium.org830a3712012-11-07 23:00:07 +00001977 def run_server(self):
1978 if self.__ocsp_server:
1979 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001980
mattm@chromium.org830a3712012-11-07 23:00:07 +00001981 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001982
mattm@chromium.org830a3712012-11-07 23:00:07 +00001983 if self.__ocsp_server:
1984 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001985
mattm@chromium.org830a3712012-11-07 23:00:07 +00001986 def add_options(self):
1987 testserver_base.TestServerRunner.add_options(self)
1988 self.option_parser.add_option('-f', '--ftp', action='store_const',
1989 const=SERVER_FTP, default=SERVER_HTTP,
1990 dest='server_type',
1991 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001992 self.option_parser.add_option('--tcp-echo', action='store_const',
1993 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1994 dest='server_type',
1995 help='start up a tcp echo server.')
1996 self.option_parser.add_option('--udp-echo', action='store_const',
1997 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
1998 dest='server_type',
1999 help='start up a udp echo server.')
2000 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2001 const=SERVER_BASIC_AUTH_PROXY,
2002 default=SERVER_HTTP, dest='server_type',
2003 help='start up a proxy server which requires '
2004 'basic authentication.')
2005 self.option_parser.add_option('--websocket', action='store_const',
2006 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2007 dest='server_type',
2008 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002009 self.option_parser.add_option('--https', action='store_true',
2010 dest='https', help='Specify that https '
2011 'should be used.')
2012 self.option_parser.add_option('--cert-and-key-file',
2013 dest='cert_and_key_file', help='specify the '
2014 'path to the file containing the certificate '
2015 'and private key for the server in PEM '
2016 'format')
2017 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2018 help='The type of OCSP response generated '
2019 'for the automatically generated '
2020 'certificate. One of [ok,revoked,invalid]')
2021 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2022 default='0', type='int',
2023 help='If nonzero, certain TLS connections '
2024 'will be aborted in order to test version '
2025 'fallback. 1 means all TLS versions will be '
2026 'aborted. 2 means TLS 1.1 or higher will be '
2027 'aborted. 3 means TLS 1.2 or higher will be '
2028 'aborted.')
2029 self.option_parser.add_option('--https-record-resume',
2030 dest='record_resume', const=True,
2031 default=False, action='store_const',
2032 help='Record resumption cache events rather '
2033 'than resuming as normal. Allows the use of '
2034 'the /ssl-session-cache request')
2035 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2036 help='Require SSL client auth on every '
2037 'connection.')
2038 self.option_parser.add_option('--ssl-client-ca', action='append',
2039 default=[], help='Specify that the client '
2040 'certificate request should include the CA '
2041 'named in the subject of the DER-encoded '
2042 'certificate contained in the specified '
2043 'file. This option may appear multiple '
2044 'times, indicating multiple CA names should '
2045 'be sent in the request.')
2046 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2047 help='Specify the bulk encryption '
2048 'algorithm(s) that will be accepted by the '
2049 'SSL server. Valid values are "aes256", '
2050 '"aes128", "3des", "rc4". If omitted, all '
2051 'algorithms will be used. This option may '
2052 'appear multiple times, indicating '
2053 'multiple algorithms should be enabled.');
2054 self.option_parser.add_option('--file-root-url', default='/files/',
2055 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002056
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002057
initial.commit94958cf2008-07-26 22:42:52 +00002058if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002059 sys.exit(ServerRunner().main())