blob: d99c5ff72baa26ce1ec2e95e865fe30ae390a069 [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00002# Copyright 2013 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00006"""This is a simple HTTP/FTP/TCP/UDP/BASIC_AUTH_PROXY/WEBSOCKET server used for
7testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00008
9It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000010By default, it listens on an ephemeral port and sends the port number back to
11the originating process over a pipe. The originating process can specify an
12explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000013It can use https if you specify the flag --https=CERT where CERT is the path
14to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000015"""
16
17import base64
18import BaseHTTPServer
19import cgi
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000020import hashlib
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000021import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000022import minica
initial.commit94958cf2008-07-26 22:42:52 +000023import os
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +000024import json
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000025import random
initial.commit94958cf2008-07-26 22:42:52 +000026import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000028import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000029import SocketServer
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000030import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000031import sys
32import threading
initial.commit94958cf2008-07-26 22:42:52 +000033import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000034import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000035import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000036import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000037
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000038import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000039import pyftpdlib.ftpserver
mattm@chromium.org830a3712012-11-07 23:00:07 +000040import testserver_base
initial.commit94958cf2008-07-26 22:42:52 +000041import tlslite
42import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000043
mattm@chromium.org830a3712012-11-07 23:00:07 +000044BASE_DIR = os.path.dirname(os.path.abspath(__file__))
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000045sys.path.insert(
46 0, os.path.join(BASE_DIR, '..', '..', '..', 'third_party/pywebsocket/src'))
47from mod_pywebsocket.standalone import WebSocketServer
davidben@chromium.org06fcf202010-09-22 18:15:23 +000048
maruel@chromium.org756cf982009-03-05 12:46:38 +000049SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000050SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000051SERVER_TCP_ECHO = 2
52SERVER_UDP_ECHO = 3
53SERVER_BASIC_AUTH_PROXY = 4
54SERVER_WEBSOCKET = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000055
56# Default request queue size for WebSocketServer.
57_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000058
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000059class WebSocketOptions:
60 """Holds options for WebSocketServer."""
61
62 def __init__(self, host, port, data_dir):
63 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
64 self.server_host = host
65 self.port = port
66 self.websock_handlers = data_dir
67 self.scan_dir = None
68 self.allow_handlers_outside_root_dir = False
69 self.websock_handlers_map_file = None
70 self.cgi_directories = []
71 self.is_executable_method = None
72 self.allow_draft75 = False
73 self.strict = True
74
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000075 self.use_tls = False
76 self.private_key = None
77 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000078 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000079 self.tls_client_ca = None
80 self.use_basic_auth = False
81
mattm@chromium.org830a3712012-11-07 23:00:07 +000082
agl@chromium.orgf9e66792011-12-12 22:22:19 +000083class RecordingSSLSessionCache(object):
84 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
85 lookups and inserts in order to test session cache behaviours."""
86
87 def __init__(self):
88 self.log = []
89
90 def __getitem__(self, sessionID):
91 self.log.append(('lookup', sessionID))
92 raise KeyError()
93
94 def __setitem__(self, sessionID, session):
95 self.log.append(('insert', sessionID))
96
erikwright@chromium.org847ef282012-02-22 16:41:10 +000097
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000098class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
99 testserver_base.BrokenPipeHandlerMixIn,
100 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000101 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000102 verification."""
103
104 pass
105
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000106class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
107 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000108 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000109 """This is a specialization of HTTPServer that serves an
110 OCSP response"""
111
112 def serve_forever_on_thread(self):
113 self.thread = threading.Thread(target = self.serve_forever,
114 name = "OCSPServerThread")
115 self.thread.start()
116
117 def stop_serving(self):
118 self.shutdown()
119 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000120
mattm@chromium.org830a3712012-11-07 23:00:07 +0000121
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000122class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000123 testserver_base.ClientRestrictingServerMixIn,
124 testserver_base.BrokenPipeHandlerMixIn,
125 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000126 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000127 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000128
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000129 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000130 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000131 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000132 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000133 # Force using only python implementation - otherwise behavior is different
134 # depending on whether m2crypto Python module is present (error is thrown
135 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
136 # the hood.
137 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
138 private=True,
139 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000140 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000141 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000142 self.tls_intolerant = tls_intolerant
143
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000144 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000145 s = open(ca_file).read()
146 x509 = tlslite.api.X509()
147 x509.parse(s)
148 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000149 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
150 if ssl_bulk_ciphers is not None:
151 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000152
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000153 if record_resume_info:
154 # If record_resume_info is true then we'll replace the session cache with
155 # an object that records the lookups and inserts that it sees.
156 self.session_cache = RecordingSSLSessionCache()
157 else:
158 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000159 testserver_base.StoppableHTTPServer.__init__(self,
160 server_address,
161 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000162
163 def handshake(self, tlsConnection):
164 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000165
initial.commit94958cf2008-07-26 22:42:52 +0000166 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000167 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000168 tlsConnection.handshakeServer(certChain=self.cert_chain,
169 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000170 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000171 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000172 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000173 reqCAs=self.ssl_client_cas,
174 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000175 tlsConnection.ignoreAbruptClose = True
176 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000177 except tlslite.api.TLSAbruptCloseError:
178 # Ignore abrupt close.
179 return True
initial.commit94958cf2008-07-26 22:42:52 +0000180 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000181 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000182 return False
183
akalin@chromium.org154bb132010-11-12 02:20:27 +0000184
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000185class FTPServer(testserver_base.ClientRestrictingServerMixIn,
186 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000187 """This is a specialization of FTPServer that adds client verification."""
188
189 pass
190
191
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000192class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
193 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000194 """A TCP echo server that echoes back what it has received."""
195
196 def server_bind(self):
197 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000198
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000199 SocketServer.TCPServer.server_bind(self)
200 host, port = self.socket.getsockname()[:2]
201 self.server_name = socket.getfqdn(host)
202 self.server_port = port
203
204 def serve_forever(self):
205 self.stop = False
206 self.nonce_time = None
207 while not self.stop:
208 self.handle_request()
209 self.socket.close()
210
211
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000212class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
213 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000214 """A UDP echo server that echoes back what it has received."""
215
216 def server_bind(self):
217 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000218
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000219 SocketServer.UDPServer.server_bind(self)
220 host, port = self.socket.getsockname()[:2]
221 self.server_name = socket.getfqdn(host)
222 self.server_port = port
223
224 def serve_forever(self):
225 self.stop = False
226 self.nonce_time = None
227 while not self.stop:
228 self.handle_request()
229 self.socket.close()
230
231
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000232class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000233 # Class variables to allow for persistence state between page handler
234 # invocations
235 rst_limits = {}
236 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000237
238 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000239 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000240 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000241 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000242 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000243 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000244 self.NoCacheMaxAgeTimeHandler,
245 self.NoCacheTimeHandler,
246 self.CacheTimeHandler,
247 self.CacheExpiresHandler,
248 self.CacheProxyRevalidateHandler,
249 self.CachePrivateHandler,
250 self.CachePublicHandler,
251 self.CacheSMaxAgeHandler,
252 self.CacheMustRevalidateHandler,
253 self.CacheMustRevalidateMaxAgeHandler,
254 self.CacheNoStoreHandler,
255 self.CacheNoStoreMaxAgeHandler,
256 self.CacheNoTransformHandler,
257 self.DownloadHandler,
258 self.DownloadFinishHandler,
259 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000260 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000261 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000262 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000263 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000264 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000265 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000266 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000267 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000268 self.AuthBasicHandler,
269 self.AuthDigestHandler,
270 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000271 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000272 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000273 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000274 self.ServerRedirectHandler,
275 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000276 self.MultipartHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000277 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000278 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000279 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000280 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000281 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000282 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000283 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000284 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000285 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000286 self.PostOnlyFileHandler,
287 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000288 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000289 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000290 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000291 head_handlers = [
292 self.FileHandler,
293 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000294
maruel@google.come250a9b2009-03-10 17:39:46 +0000295 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000296 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000297 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000298 'gif': 'image/gif',
299 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000300 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000301 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000302 'pdf' : 'application/pdf',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000303 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000304 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000305 }
initial.commit94958cf2008-07-26 22:42:52 +0000306 self._default_mime_type = 'text/html'
307
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000308 testserver_base.BasePageHandler.__init__(self, request, client_address,
309 socket_server, connect_handlers,
310 get_handlers, head_handlers,
311 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000312
initial.commit94958cf2008-07-26 22:42:52 +0000313 def GetMIMETypeFromName(self, file_name):
314 """Returns the mime type for the specified file_name. So far it only looks
315 at the file extension."""
316
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000317 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000318 if len(extension) == 0:
319 # no extension.
320 return self._default_mime_type
321
ericroman@google.comc17ca532009-05-07 03:51:05 +0000322 # extension starts with a dot, so we need to remove it
323 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000324
initial.commit94958cf2008-07-26 22:42:52 +0000325 def NoCacheMaxAgeTimeHandler(self):
326 """This request handler yields a page with the title set to the current
327 system time, and no caching requested."""
328
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000329 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000330 return False
331
332 self.send_response(200)
333 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000334 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000335 self.end_headers()
336
maruel@google.come250a9b2009-03-10 17:39:46 +0000337 self.wfile.write('<html><head><title>%s</title></head></html>' %
338 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000339
340 return True
341
342 def NoCacheTimeHandler(self):
343 """This request handler yields a page with the title set to the current
344 system time, and no caching requested."""
345
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000346 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000347 return False
348
349 self.send_response(200)
350 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000351 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000352 self.end_headers()
353
maruel@google.come250a9b2009-03-10 17:39:46 +0000354 self.wfile.write('<html><head><title>%s</title></head></html>' %
355 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000356
357 return True
358
359 def CacheTimeHandler(self):
360 """This request handler yields a page with the title set to the current
361 system time, and allows caching for one minute."""
362
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000363 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000364 return False
365
366 self.send_response(200)
367 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000368 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000369 self.end_headers()
370
maruel@google.come250a9b2009-03-10 17:39:46 +0000371 self.wfile.write('<html><head><title>%s</title></head></html>' %
372 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000373
374 return True
375
376 def CacheExpiresHandler(self):
377 """This request handler yields a page with the title set to the current
378 system time, and set the page to expire on 1 Jan 2099."""
379
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000380 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000381 return False
382
383 self.send_response(200)
384 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000385 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000386 self.end_headers()
387
maruel@google.come250a9b2009-03-10 17:39:46 +0000388 self.wfile.write('<html><head><title>%s</title></head></html>' %
389 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000390
391 return True
392
393 def CacheProxyRevalidateHandler(self):
394 """This request handler yields a page with the title set to the current
395 system time, and allows caching for 60 seconds"""
396
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000397 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000398 return False
399
400 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000401 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000402 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
403 self.end_headers()
404
maruel@google.come250a9b2009-03-10 17:39:46 +0000405 self.wfile.write('<html><head><title>%s</title></head></html>' %
406 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000407
408 return True
409
410 def CachePrivateHandler(self):
411 """This request handler yields a page with the title set to the current
412 system time, and allows caching for 5 seconds."""
413
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000414 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000415 return False
416
417 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000418 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000419 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000420 self.end_headers()
421
maruel@google.come250a9b2009-03-10 17:39:46 +0000422 self.wfile.write('<html><head><title>%s</title></head></html>' %
423 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000424
425 return True
426
427 def CachePublicHandler(self):
428 """This request handler yields a page with the title set to the current
429 system time, and allows caching for 5 seconds."""
430
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000431 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000432 return False
433
434 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000435 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000436 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000437 self.end_headers()
438
maruel@google.come250a9b2009-03-10 17:39:46 +0000439 self.wfile.write('<html><head><title>%s</title></head></html>' %
440 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000441
442 return True
443
444 def CacheSMaxAgeHandler(self):
445 """This request handler yields a page with the title set to the current
446 system time, and does not allow for caching."""
447
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000448 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000449 return False
450
451 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000452 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000453 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
454 self.end_headers()
455
maruel@google.come250a9b2009-03-10 17:39:46 +0000456 self.wfile.write('<html><head><title>%s</title></head></html>' %
457 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000458
459 return True
460
461 def CacheMustRevalidateHandler(self):
462 """This request handler yields a page with the title set to the current
463 system time, and does not allow caching."""
464
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000465 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000466 return False
467
468 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000469 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000470 self.send_header('Cache-Control', 'must-revalidate')
471 self.end_headers()
472
maruel@google.come250a9b2009-03-10 17:39:46 +0000473 self.wfile.write('<html><head><title>%s</title></head></html>' %
474 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000475
476 return True
477
478 def CacheMustRevalidateMaxAgeHandler(self):
479 """This request handler yields a page with the title set to the current
480 system time, and does not allow caching event though max-age of 60
481 seconds is specified."""
482
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000483 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000484 return False
485
486 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000487 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000488 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
489 self.end_headers()
490
maruel@google.come250a9b2009-03-10 17:39:46 +0000491 self.wfile.write('<html><head><title>%s</title></head></html>' %
492 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000493
494 return True
495
initial.commit94958cf2008-07-26 22:42:52 +0000496 def CacheNoStoreHandler(self):
497 """This request handler yields a page with the title set to the current
498 system time, and does not allow the page to be stored."""
499
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000500 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000501 return False
502
503 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000504 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000505 self.send_header('Cache-Control', 'no-store')
506 self.end_headers()
507
maruel@google.come250a9b2009-03-10 17:39:46 +0000508 self.wfile.write('<html><head><title>%s</title></head></html>' %
509 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000510
511 return True
512
513 def CacheNoStoreMaxAgeHandler(self):
514 """This request handler yields a page with the title set to the current
515 system time, and does not allow the page to be stored even though max-age
516 of 60 seconds is specified."""
517
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000518 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000519 return False
520
521 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000522 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000523 self.send_header('Cache-Control', 'max-age=60, no-store')
524 self.end_headers()
525
maruel@google.come250a9b2009-03-10 17:39:46 +0000526 self.wfile.write('<html><head><title>%s</title></head></html>' %
527 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000528
529 return True
530
531
532 def CacheNoTransformHandler(self):
533 """This request handler yields a page with the title set to the current
534 system time, and does not allow the content to transformed during
535 user-agent caching"""
536
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000537 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000538 return False
539
540 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000541 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000542 self.send_header('Cache-Control', 'no-transform')
543 self.end_headers()
544
maruel@google.come250a9b2009-03-10 17:39:46 +0000545 self.wfile.write('<html><head><title>%s</title></head></html>' %
546 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000547
548 return True
549
550 def EchoHeader(self):
551 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000552
ananta@chromium.org219b2062009-10-23 16:09:41 +0000553 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000554
ananta@chromium.org56812d02011-04-07 17:52:05 +0000555 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000556 """This function echoes back the value of a specific request header while
557 allowing caching for 16 hours."""
558
ananta@chromium.org56812d02011-04-07 17:52:05 +0000559 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000560
561 def EchoHeaderHelper(self, echo_header):
562 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000563
ananta@chromium.org219b2062009-10-23 16:09:41 +0000564 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000565 return False
566
567 query_char = self.path.find('?')
568 if query_char != -1:
569 header_name = self.path[query_char+1:]
570
571 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000572 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000573 if echo_header == '/echoheadercache':
574 self.send_header('Cache-control', 'max-age=60000')
575 else:
576 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000577 # insert a vary header to properly indicate that the cachability of this
578 # request is subject to value of the request header being echoed.
579 if len(header_name) > 0:
580 self.send_header('Vary', header_name)
581 self.end_headers()
582
583 if len(header_name) > 0:
584 self.wfile.write(self.headers.getheader(header_name))
585
586 return True
587
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000588 def ReadRequestBody(self):
589 """This function reads the body of the current HTTP request, handling
590 both plain and chunked transfer encoded requests."""
591
592 if self.headers.getheader('transfer-encoding') != 'chunked':
593 length = int(self.headers.getheader('content-length'))
594 return self.rfile.read(length)
595
596 # Read the request body as chunks.
597 body = ""
598 while True:
599 line = self.rfile.readline()
600 length = int(line, 16)
601 if length == 0:
602 self.rfile.readline()
603 break
604 body += self.rfile.read(length)
605 self.rfile.read(2)
606 return body
607
initial.commit94958cf2008-07-26 22:42:52 +0000608 def EchoHandler(self):
609 """This handler just echoes back the payload of the request, for testing
610 form submission."""
611
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000612 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000613 return False
614
615 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000616 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000617 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000618 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000619 return True
620
621 def EchoTitleHandler(self):
622 """This handler is like Echo, but sets the page title to the request."""
623
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000624 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000625 return False
626
627 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000628 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000629 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000630 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000631 self.wfile.write('<html><head><title>')
632 self.wfile.write(request)
633 self.wfile.write('</title></head></html>')
634 return True
635
636 def EchoAllHandler(self):
637 """This handler yields a (more) human-readable page listing information
638 about the request header & contents."""
639
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000640 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000641 return False
642
643 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000644 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000645 self.end_headers()
646 self.wfile.write('<html><head><style>'
647 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
648 '</style></head><body>'
649 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000650 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000651 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000652
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000653 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000654 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000655 params = cgi.parse_qs(qs, keep_blank_values=1)
656
657 for param in params:
658 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000659
660 self.wfile.write('</pre>')
661
662 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
663
664 self.wfile.write('</body></html>')
665 return True
666
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000667 def EchoMultipartPostHandler(self):
668 """This handler echoes received multipart post data as json format."""
669
670 if not (self._ShouldHandleRequest("/echomultipartpost") or
671 self._ShouldHandleRequest("/searchbyimage")):
672 return False
673
674 content_type, parameters = cgi.parse_header(
675 self.headers.getheader('content-type'))
676 if content_type == 'multipart/form-data':
677 post_multipart = cgi.parse_multipart(self.rfile, parameters)
678 elif content_type == 'application/x-www-form-urlencoded':
679 raise Exception('POST by application/x-www-form-urlencoded is '
680 'not implemented.')
681 else:
682 post_multipart = {}
683
684 # Since the data can be binary, we encode them by base64.
685 post_multipart_base64_encoded = {}
686 for field, values in post_multipart.items():
687 post_multipart_base64_encoded[field] = [base64.b64encode(value)
688 for value in values]
689
690 result = {'POST_multipart' : post_multipart_base64_encoded}
691
692 self.send_response(200)
693 self.send_header("Content-type", "text/plain")
694 self.end_headers()
695 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
696 return True
697
initial.commit94958cf2008-07-26 22:42:52 +0000698 def DownloadHandler(self):
699 """This handler sends a downloadable file with or without reporting
700 the size (6K)."""
701
702 if self.path.startswith("/download-unknown-size"):
703 send_length = False
704 elif self.path.startswith("/download-known-size"):
705 send_length = True
706 else:
707 return False
708
709 #
710 # The test which uses this functionality is attempting to send
711 # small chunks of data to the client. Use a fairly large buffer
712 # so that we'll fill chrome's IO buffer enough to force it to
713 # actually write the data.
714 # See also the comments in the client-side of this test in
715 # download_uitest.cc
716 #
717 size_chunk1 = 35*1024
718 size_chunk2 = 10*1024
719
720 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000721 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000722 self.send_header('Cache-Control', 'max-age=0')
723 if send_length:
724 self.send_header('Content-Length', size_chunk1 + size_chunk2)
725 self.end_headers()
726
727 # First chunk of data:
728 self.wfile.write("*" * size_chunk1)
729 self.wfile.flush()
730
731 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000732 self.server.wait_for_download = True
733 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000734 self.server.handle_request()
735
736 # Second chunk of data:
737 self.wfile.write("*" * size_chunk2)
738 return True
739
740 def DownloadFinishHandler(self):
741 """This handler just tells the server to finish the current download."""
742
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000743 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000744 return False
745
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000746 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000747 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000748 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000749 self.send_header('Cache-Control', 'max-age=0')
750 self.end_headers()
751 return True
752
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000753 def _ReplaceFileData(self, data, query_parameters):
754 """Replaces matching substrings in a file.
755
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000756 If the 'replace_text' URL query parameter is present, it is expected to be
757 of the form old_text:new_text, which indicates that any old_text strings in
758 the file are replaced with new_text. Multiple 'replace_text' parameters may
759 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000760
761 If the parameters are not present, |data| is returned.
762 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000763
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000764 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000765 replace_text_values = query_dict.get('replace_text', [])
766 for replace_text_value in replace_text_values:
767 replace_text_args = replace_text_value.split(':')
768 if len(replace_text_args) != 2:
769 raise ValueError(
770 'replace_text must be of form old_text:new_text. Actual value: %s' %
771 replace_text_value)
772 old_text_b64, new_text_b64 = replace_text_args
773 old_text = base64.urlsafe_b64decode(old_text_b64)
774 new_text = base64.urlsafe_b64decode(new_text_b64)
775 data = data.replace(old_text, new_text)
776 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000777
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000778 def ZipFileHandler(self):
779 """This handler sends the contents of the requested file in compressed form.
780 Can pass in a parameter that specifies that the content length be
781 C - the compressed size (OK),
782 U - the uncompressed size (Non-standard, but handled),
783 S - less than compressed (OK because we keep going),
784 M - larger than compressed but less than uncompressed (an error),
785 L - larger than uncompressed (an error)
786 Example: compressedfiles/Picture_1.doc?C
787 """
788
789 prefix = "/compressedfiles/"
790 if not self.path.startswith(prefix):
791 return False
792
793 # Consume a request body if present.
794 if self.command == 'POST' or self.command == 'PUT' :
795 self.ReadRequestBody()
796
797 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
798
799 if not query in ('C', 'U', 'S', 'M', 'L'):
800 return False
801
802 sub_path = url_path[len(prefix):]
803 entries = sub_path.split('/')
804 file_path = os.path.join(self.server.data_dir, *entries)
805 if os.path.isdir(file_path):
806 file_path = os.path.join(file_path, 'index.html')
807
808 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000809 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000810 self.send_error(404)
811 return True
812
813 f = open(file_path, "rb")
814 data = f.read()
815 uncompressed_len = len(data)
816 f.close()
817
818 # Compress the data.
819 data = zlib.compress(data)
820 compressed_len = len(data)
821
822 content_length = compressed_len
823 if query == 'U':
824 content_length = uncompressed_len
825 elif query == 'S':
826 content_length = compressed_len / 2
827 elif query == 'M':
828 content_length = (compressed_len + uncompressed_len) / 2
829 elif query == 'L':
830 content_length = compressed_len + uncompressed_len
831
832 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000833 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000834 self.send_header('Content-encoding', 'deflate')
835 self.send_header('Connection', 'close')
836 self.send_header('Content-Length', content_length)
837 self.send_header('ETag', '\'' + file_path + '\'')
838 self.end_headers()
839
840 self.wfile.write(data)
841
842 return True
843
initial.commit94958cf2008-07-26 22:42:52 +0000844 def FileHandler(self):
845 """This handler sends the contents of the requested file. Wow, it's like
846 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000847
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000848 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000849 if not self.path.startswith(prefix):
850 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000851 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000852
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000853 def PostOnlyFileHandler(self):
854 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000855
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000856 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000857 if not self.path.startswith(prefix):
858 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000859 return self._FileHandlerHelper(prefix)
860
861 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000862 request_body = ''
863 if self.command == 'POST' or self.command == 'PUT':
864 # Consume a request body if present.
865 request_body = self.ReadRequestBody()
866
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000867 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000868 query_dict = cgi.parse_qs(query)
869
870 expected_body = query_dict.get('expected_body', [])
871 if expected_body and request_body not in expected_body:
872 self.send_response(404)
873 self.end_headers()
874 self.wfile.write('')
875 return True
876
877 expected_headers = query_dict.get('expected_headers', [])
878 for expected_header in expected_headers:
879 header_name, expected_value = expected_header.split(':')
880 if self.headers.getheader(header_name) != expected_value:
881 self.send_response(404)
882 self.end_headers()
883 self.wfile.write('')
884 return True
885
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000886 sub_path = url_path[len(prefix):]
887 entries = sub_path.split('/')
888 file_path = os.path.join(self.server.data_dir, *entries)
889 if os.path.isdir(file_path):
890 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000891
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000892 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000893 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000894 self.send_error(404)
895 return True
896
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000897 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000898 data = f.read()
899 f.close()
900
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000901 data = self._ReplaceFileData(data, query)
902
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000903 old_protocol_version = self.protocol_version
904
initial.commit94958cf2008-07-26 22:42:52 +0000905 # If file.mock-http-headers exists, it contains the headers we
906 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000907 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000908 if os.path.isfile(headers_path):
909 f = open(headers_path, "r")
910
911 # "HTTP/1.1 200 OK"
912 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000913 http_major, http_minor, status_code = re.findall(
914 'HTTP/(\d+).(\d+) (\d+)', response)[0]
915 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000916 self.send_response(int(status_code))
917
918 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000919 header_values = re.findall('(\S+):\s*(.*)', line)
920 if len(header_values) > 0:
921 # "name: value"
922 name, value = header_values[0]
923 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000924 f.close()
925 else:
926 # Could be more generic once we support mime-type sniffing, but for
927 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000928
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000929 range_header = self.headers.get('Range')
930 if range_header and range_header.startswith('bytes='):
931 # Note this doesn't handle all valid byte range_header values (i.e.
932 # left open ended ones), just enough for what we needed so far.
933 range_header = range_header[6:].split('-')
934 start = int(range_header[0])
935 if range_header[1]:
936 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000937 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000938 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000939
940 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000941 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
942 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000943 self.send_header('Content-Range', content_range)
944 data = data[start: end + 1]
945 else:
946 self.send_response(200)
947
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000948 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000949 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000950 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000951 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000952 self.end_headers()
953
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000954 if (self.command != 'HEAD'):
955 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000956
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000957 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000958 return True
959
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000960 def SetCookieHandler(self):
961 """This handler just sets a cookie, for testing cookie handling."""
962
963 if not self._ShouldHandleRequest("/set-cookie"):
964 return False
965
966 query_char = self.path.find('?')
967 if query_char != -1:
968 cookie_values = self.path[query_char + 1:].split('&')
969 else:
970 cookie_values = ("",)
971 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000972 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000973 for cookie_value in cookie_values:
974 self.send_header('Set-Cookie', '%s' % cookie_value)
975 self.end_headers()
976 for cookie_value in cookie_values:
977 self.wfile.write('%s' % cookie_value)
978 return True
979
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000980 def SetManyCookiesHandler(self):
981 """This handler just sets a given number of cookies, for testing handling
982 of large numbers of cookies."""
983
984 if not self._ShouldHandleRequest("/set-many-cookies"):
985 return False
986
987 query_char = self.path.find('?')
988 if query_char != -1:
989 num_cookies = int(self.path[query_char + 1:])
990 else:
991 num_cookies = 0
992 self.send_response(200)
993 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000994 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000995 self.send_header('Set-Cookie', 'a=')
996 self.end_headers()
997 self.wfile.write('%d cookies were sent' % num_cookies)
998 return True
999
mattm@chromium.org983fc462012-06-30 00:52:08 +00001000 def ExpectAndSetCookieHandler(self):
1001 """Expects some cookies to be sent, and if they are, sets more cookies.
1002
1003 The expect parameter specifies a required cookie. May be specified multiple
1004 times.
1005 The set parameter specifies a cookie to set if all required cookies are
1006 preset. May be specified multiple times.
1007 The data parameter specifies the response body data to be returned."""
1008
1009 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1010 return False
1011
1012 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1013 query_dict = cgi.parse_qs(query)
1014 cookies = set()
1015 if 'Cookie' in self.headers:
1016 cookie_header = self.headers.getheader('Cookie')
1017 cookies.update([s.strip() for s in cookie_header.split(';')])
1018 got_all_expected_cookies = True
1019 for expected_cookie in query_dict.get('expect', []):
1020 if expected_cookie not in cookies:
1021 got_all_expected_cookies = False
1022 self.send_response(200)
1023 self.send_header('Content-Type', 'text/html')
1024 if got_all_expected_cookies:
1025 for cookie_value in query_dict.get('set', []):
1026 self.send_header('Set-Cookie', '%s' % cookie_value)
1027 self.end_headers()
1028 for data_value in query_dict.get('data', []):
1029 self.wfile.write(data_value)
1030 return True
1031
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001032 def SetHeaderHandler(self):
1033 """This handler sets a response header. Parameters are in the
1034 key%3A%20value&key2%3A%20value2 format."""
1035
1036 if not self._ShouldHandleRequest("/set-header"):
1037 return False
1038
1039 query_char = self.path.find('?')
1040 if query_char != -1:
1041 headers_values = self.path[query_char + 1:].split('&')
1042 else:
1043 headers_values = ("",)
1044 self.send_response(200)
1045 self.send_header('Content-Type', 'text/html')
1046 for header_value in headers_values:
1047 header_value = urllib.unquote(header_value)
1048 (key, value) = header_value.split(': ', 1)
1049 self.send_header(key, value)
1050 self.end_headers()
1051 for header_value in headers_values:
1052 self.wfile.write('%s' % header_value)
1053 return True
1054
initial.commit94958cf2008-07-26 22:42:52 +00001055 def AuthBasicHandler(self):
1056 """This handler tests 'Basic' authentication. It just sends a page with
1057 title 'user/pass' if you succeed."""
1058
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001059 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001060 return False
1061
1062 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001063 expected_password = 'secret'
1064 realm = 'testrealm'
1065 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001066
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001067 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1068 query_params = cgi.parse_qs(query, True)
1069 if 'set-cookie-if-challenged' in query_params:
1070 set_cookie_if_challenged = True
1071 if 'password' in query_params:
1072 expected_password = query_params['password'][0]
1073 if 'realm' in query_params:
1074 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001075
initial.commit94958cf2008-07-26 22:42:52 +00001076 auth = self.headers.getheader('authorization')
1077 try:
1078 if not auth:
1079 raise Exception('no auth')
1080 b64str = re.findall(r'Basic (\S+)', auth)[0]
1081 userpass = base64.b64decode(b64str)
1082 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001083 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001084 raise Exception('wrong password')
1085 except Exception, e:
1086 # Authentication failed.
1087 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001088 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001089 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001090 if set_cookie_if_challenged:
1091 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001092 self.end_headers()
1093 self.wfile.write('<html><head>')
1094 self.wfile.write('<title>Denied: %s</title>' % e)
1095 self.wfile.write('</head><body>')
1096 self.wfile.write('auth=%s<p>' % auth)
1097 self.wfile.write('b64str=%s<p>' % b64str)
1098 self.wfile.write('username: %s<p>' % username)
1099 self.wfile.write('userpass: %s<p>' % userpass)
1100 self.wfile.write('password: %s<p>' % password)
1101 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1102 self.wfile.write('</body></html>')
1103 return True
1104
1105 # Authentication successful. (Return a cachable response to allow for
1106 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001107 old_protocol_version = self.protocol_version
1108 self.protocol_version = "HTTP/1.1"
1109
initial.commit94958cf2008-07-26 22:42:52 +00001110 if_none_match = self.headers.getheader('if-none-match')
1111 if if_none_match == "abc":
1112 self.send_response(304)
1113 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001114 elif url_path.endswith(".gif"):
1115 # Using chrome/test/data/google/logo.gif as the test image
1116 test_image_path = ['google', 'logo.gif']
1117 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1118 if not os.path.isfile(gif_path):
1119 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001120 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001121 return True
1122
1123 f = open(gif_path, "rb")
1124 data = f.read()
1125 f.close()
1126
1127 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001128 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001129 self.send_header('Cache-control', 'max-age=60000')
1130 self.send_header('Etag', 'abc')
1131 self.end_headers()
1132 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001133 else:
1134 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001135 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001136 self.send_header('Cache-control', 'max-age=60000')
1137 self.send_header('Etag', 'abc')
1138 self.end_headers()
1139 self.wfile.write('<html><head>')
1140 self.wfile.write('<title>%s/%s</title>' % (username, password))
1141 self.wfile.write('</head><body>')
1142 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001143 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001144 self.wfile.write('</body></html>')
1145
rvargas@google.com54453b72011-05-19 01:11:11 +00001146 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001147 return True
1148
tonyg@chromium.org75054202010-03-31 22:06:10 +00001149 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001150 """Returns a nonce that's stable per request path for the server's lifetime.
1151 This is a fake implementation. A real implementation would only use a given
1152 nonce a single time (hence the name n-once). However, for the purposes of
1153 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001154
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001155 Args:
1156 force_reset: Iff set, the nonce will be changed. Useful for testing the
1157 "stale" response.
1158 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001159
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001160 if force_reset or not self.server.nonce_time:
1161 self.server.nonce_time = time.time()
1162 return hashlib.md5('privatekey%s%d' %
1163 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001164
1165 def AuthDigestHandler(self):
1166 """This handler tests 'Digest' authentication.
1167
1168 It just sends a page with title 'user/pass' if you succeed.
1169
1170 A stale response is sent iff "stale" is present in the request path.
1171 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001172
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001173 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001174 return False
1175
tonyg@chromium.org75054202010-03-31 22:06:10 +00001176 stale = 'stale' in self.path
1177 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001178 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001179 password = 'secret'
1180 realm = 'testrealm'
1181
1182 auth = self.headers.getheader('authorization')
1183 pairs = {}
1184 try:
1185 if not auth:
1186 raise Exception('no auth')
1187 if not auth.startswith('Digest'):
1188 raise Exception('not digest')
1189 # Pull out all the name="value" pairs as a dictionary.
1190 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1191
1192 # Make sure it's all valid.
1193 if pairs['nonce'] != nonce:
1194 raise Exception('wrong nonce')
1195 if pairs['opaque'] != opaque:
1196 raise Exception('wrong opaque')
1197
1198 # Check the 'response' value and make sure it matches our magic hash.
1199 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001200 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001201 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001202 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001203 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001204 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001205 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1206 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001207 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001208
1209 if pairs['response'] != response:
1210 raise Exception('wrong password')
1211 except Exception, e:
1212 # Authentication failed.
1213 self.send_response(401)
1214 hdr = ('Digest '
1215 'realm="%s", '
1216 'domain="/", '
1217 'qop="auth", '
1218 'algorithm=MD5, '
1219 'nonce="%s", '
1220 'opaque="%s"') % (realm, nonce, opaque)
1221 if stale:
1222 hdr += ', stale="TRUE"'
1223 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001224 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001225 self.end_headers()
1226 self.wfile.write('<html><head>')
1227 self.wfile.write('<title>Denied: %s</title>' % e)
1228 self.wfile.write('</head><body>')
1229 self.wfile.write('auth=%s<p>' % auth)
1230 self.wfile.write('pairs=%s<p>' % pairs)
1231 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1232 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1233 self.wfile.write('</body></html>')
1234 return True
1235
1236 # Authentication successful.
1237 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001238 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001239 self.end_headers()
1240 self.wfile.write('<html><head>')
1241 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1242 self.wfile.write('</head><body>')
1243 self.wfile.write('auth=%s<p>' % auth)
1244 self.wfile.write('pairs=%s<p>' % pairs)
1245 self.wfile.write('</body></html>')
1246
1247 return True
1248
1249 def SlowServerHandler(self):
1250 """Wait for the user suggested time before responding. The syntax is
1251 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001252
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001253 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001254 return False
1255 query_char = self.path.find('?')
1256 wait_sec = 1.0
1257 if query_char >= 0:
1258 try:
1259 wait_sec = int(self.path[query_char + 1:])
1260 except ValueError:
1261 pass
1262 time.sleep(wait_sec)
1263 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001264 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001265 self.end_headers()
1266 self.wfile.write("waited %d seconds" % wait_sec)
1267 return True
1268
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001269 def ChunkedServerHandler(self):
1270 """Send chunked response. Allows to specify chunks parameters:
1271 - waitBeforeHeaders - ms to wait before sending headers
1272 - waitBetweenChunks - ms to wait between chunks
1273 - chunkSize - size of each chunk in bytes
1274 - chunksNumber - number of chunks
1275 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1276 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001277
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001278 if not self._ShouldHandleRequest("/chunked"):
1279 return False
1280 query_char = self.path.find('?')
1281 chunkedSettings = {'waitBeforeHeaders' : 0,
1282 'waitBetweenChunks' : 0,
1283 'chunkSize' : 5,
1284 'chunksNumber' : 5}
1285 if query_char >= 0:
1286 params = self.path[query_char + 1:].split('&')
1287 for param in params:
1288 keyValue = param.split('=')
1289 if len(keyValue) == 2:
1290 try:
1291 chunkedSettings[keyValue[0]] = int(keyValue[1])
1292 except ValueError:
1293 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001294 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001295 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1296 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001297 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001298 self.send_header('Connection', 'close')
1299 self.send_header('Transfer-Encoding', 'chunked')
1300 self.end_headers()
1301 # Chunked encoding: sending all chunks, then final zero-length chunk and
1302 # then final CRLF.
1303 for i in range(0, chunkedSettings['chunksNumber']):
1304 if i > 0:
1305 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1306 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001307 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001308 self.sendChunkHelp('')
1309 return True
1310
initial.commit94958cf2008-07-26 22:42:52 +00001311 def ContentTypeHandler(self):
1312 """Returns a string of html with the given content type. E.g.,
1313 /contenttype?text/css returns an html file with the Content-Type
1314 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001315
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001316 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001317 return False
1318 query_char = self.path.find('?')
1319 content_type = self.path[query_char + 1:].strip()
1320 if not content_type:
1321 content_type = 'text/html'
1322 self.send_response(200)
1323 self.send_header('Content-Type', content_type)
1324 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001325 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001326 return True
1327
creis@google.com2f4f6a42011-03-25 19:44:19 +00001328 def NoContentHandler(self):
1329 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001330
creis@google.com2f4f6a42011-03-25 19:44:19 +00001331 if not self._ShouldHandleRequest("/nocontent"):
1332 return False
1333 self.send_response(204)
1334 self.end_headers()
1335 return True
1336
initial.commit94958cf2008-07-26 22:42:52 +00001337 def ServerRedirectHandler(self):
1338 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001339 '/server-redirect?http://foo.bar/asdf' to redirect to
1340 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001341
1342 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001343 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001344 return False
1345
1346 query_char = self.path.find('?')
1347 if query_char < 0 or len(self.path) <= query_char + 1:
1348 self.sendRedirectHelp(test_name)
1349 return True
1350 dest = self.path[query_char + 1:]
1351
1352 self.send_response(301) # moved permanently
1353 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001354 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001355 self.end_headers()
1356 self.wfile.write('<html><head>')
1357 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1358
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001359 return True
initial.commit94958cf2008-07-26 22:42:52 +00001360
1361 def ClientRedirectHandler(self):
1362 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001363 '/client-redirect?http://foo.bar/asdf' to redirect to
1364 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001365
1366 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001367 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001368 return False
1369
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001370 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001371 if query_char < 0 or len(self.path) <= query_char + 1:
1372 self.sendRedirectHelp(test_name)
1373 return True
1374 dest = self.path[query_char + 1:]
1375
1376 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001377 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001378 self.end_headers()
1379 self.wfile.write('<html><head>')
1380 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1381 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1382
1383 return True
1384
tony@chromium.org03266982010-03-05 03:18:42 +00001385 def MultipartHandler(self):
1386 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001387
tony@chromium.org4cb88302011-09-27 22:13:49 +00001388 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001389 if not self._ShouldHandleRequest(test_name):
1390 return False
1391
1392 num_frames = 10
1393 bound = '12345'
1394 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001395 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001396 'multipart/x-mixed-replace;boundary=' + bound)
1397 self.end_headers()
1398
1399 for i in xrange(num_frames):
1400 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001401 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001402 self.wfile.write('<title>page ' + str(i) + '</title>')
1403 self.wfile.write('page ' + str(i))
1404
1405 self.wfile.write('--' + bound + '--')
1406 return True
1407
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001408 def GetSSLSessionCacheHandler(self):
1409 """Send a reply containing a log of the session cache operations."""
1410
1411 if not self._ShouldHandleRequest('/ssl-session-cache'):
1412 return False
1413
1414 self.send_response(200)
1415 self.send_header('Content-Type', 'text/plain')
1416 self.end_headers()
1417 try:
1418 for (action, sessionID) in self.server.session_cache.log:
1419 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001420 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001421 self.wfile.write('Pass --https-record-resume in order to use' +
1422 ' this request')
1423 return True
1424
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001425 def SSLManySmallRecords(self):
1426 """Sends a reply consisting of a variety of small writes. These will be
1427 translated into a series of small SSL records when used over an HTTPS
1428 server."""
1429
1430 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1431 return False
1432
1433 self.send_response(200)
1434 self.send_header('Content-Type', 'text/plain')
1435 self.end_headers()
1436
1437 # Write ~26K of data, in 1350 byte chunks
1438 for i in xrange(20):
1439 self.wfile.write('*' * 1350)
1440 self.wfile.flush()
1441 return True
1442
agl@chromium.org04700be2013-03-02 18:40:41 +00001443 def GetChannelID(self):
1444 """Send a reply containing the hashed ChannelID that the client provided."""
1445
1446 if not self._ShouldHandleRequest('/channel-id'):
1447 return False
1448
1449 self.send_response(200)
1450 self.send_header('Content-Type', 'text/plain')
1451 self.end_headers()
1452 channel_id = self.server.tlsConnection.channel_id.tostring()
1453 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1454 return True
1455
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001456 def CloseSocketHandler(self):
1457 """Closes the socket without sending anything."""
1458
1459 if not self._ShouldHandleRequest('/close-socket'):
1460 return False
1461
1462 self.wfile.close()
1463 return True
1464
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001465 def RangeResetHandler(self):
1466 """Send data broken up by connection resets every N (default 4K) bytes.
1467 Support range requests. If the data requested doesn't straddle a reset
1468 boundary, it will all be sent. Used for testing resuming downloads."""
1469
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001470 def DataForRange(start, end):
1471 """Data to be provided for a particular range of bytes."""
1472 # Offset and scale to avoid too obvious (and hence potentially
1473 # collidable) data.
1474 return ''.join([chr(y % 256)
1475 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1476
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001477 if not self._ShouldHandleRequest('/rangereset'):
1478 return False
1479
1480 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1481
1482 # Defaults
1483 size = 8000
1484 # Note that the rst is sent just before sending the rst_boundary byte.
1485 rst_boundary = 4000
1486 respond_to_range = True
1487 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001488 rst_limit = -1
1489 token = 'DEFAULT'
1490 fail_precondition = 0
1491 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001492
1493 # Parse the query
1494 qdict = urlparse.parse_qs(query, True)
1495 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001496 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001497 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001498 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001499 if 'token' in qdict:
1500 # Identifying token for stateful tests.
1501 token = qdict['token'][0]
1502 if 'rst_limit' in qdict:
1503 # Max number of rsts for a given token.
1504 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001505 if 'bounce_range' in qdict:
1506 respond_to_range = False
1507 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001508 # Note that hold_for_signal will not work with null range requests;
1509 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001510 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001511 if 'no_verifiers' in qdict:
1512 send_verifiers = False
1513 if 'fail_precondition' in qdict:
1514 fail_precondition = int(qdict['fail_precondition'][0])
1515
1516 # Record already set information, or set it.
1517 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1518 if rst_limit != 0:
1519 TestPageHandler.rst_limits[token] -= 1
1520 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1521 token, fail_precondition)
1522 if fail_precondition != 0:
1523 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001524
1525 first_byte = 0
1526 last_byte = size - 1
1527
1528 # Does that define what we want to return, or do we need to apply
1529 # a range?
1530 range_response = False
1531 range_header = self.headers.getheader('range')
1532 if range_header and respond_to_range:
1533 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1534 if mo.group(1):
1535 first_byte = int(mo.group(1))
1536 if mo.group(2):
1537 last_byte = int(mo.group(2))
1538 if last_byte > size - 1:
1539 last_byte = size - 1
1540 range_response = True
1541 if last_byte < first_byte:
1542 return False
1543
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001544 if (fail_precondition and
1545 (self.headers.getheader('If-Modified-Since') or
1546 self.headers.getheader('If-Match'))):
1547 self.send_response(412)
1548 self.end_headers()
1549 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001550
1551 if range_response:
1552 self.send_response(206)
1553 self.send_header('Content-Range',
1554 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1555 else:
1556 self.send_response(200)
1557 self.send_header('Content-Type', 'application/octet-stream')
1558 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001559 if send_verifiers:
1560 self.send_header('Etag', '"XYZZY"')
1561 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001562 self.end_headers()
1563
1564 if hold_for_signal:
1565 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1566 # a single byte, the self.server.handle_request() below hangs
1567 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001568 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001569 first_byte = first_byte + 1
1570 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001571 self.server.wait_for_download = True
1572 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001573 self.server.handle_request()
1574
1575 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001576 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001577 # No RST has been requested in this range, so we don't need to
1578 # do anything fancy; just write the data and let the python
1579 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001580 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001581 self.wfile.flush()
1582 return True
1583
1584 # We're resetting the connection part way in; go to the RST
1585 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001586 # Because socket semantics do not guarantee that all the data will be
1587 # sent when using the linger semantics to hard close a socket,
1588 # we send the data and then wait for our peer to release us
1589 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001590 data = DataForRange(first_byte, possible_rst)
1591 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001592 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001593 self.server.wait_for_download = True
1594 while self.server.wait_for_download:
1595 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001596 l_onoff = 1 # Linger is active.
1597 l_linger = 0 # Seconds to linger for.
1598 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1599 struct.pack('ii', l_onoff, l_linger))
1600
1601 # Close all duplicates of the underlying socket to force the RST.
1602 self.wfile.close()
1603 self.rfile.close()
1604 self.connection.close()
1605
1606 return True
1607
initial.commit94958cf2008-07-26 22:42:52 +00001608 def DefaultResponseHandler(self):
1609 """This is the catch-all response handler for requests that aren't handled
1610 by one of the special handlers above.
1611 Note that we specify the content-length as without it the https connection
1612 is not closed properly (and the browser keeps expecting data)."""
1613
1614 contents = "Default response given for path: " + self.path
1615 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001616 self.send_header('Content-Type', 'text/html')
1617 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001618 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001619 if (self.command != 'HEAD'):
1620 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001621 return True
1622
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001623 def RedirectConnectHandler(self):
1624 """Sends a redirect to the CONNECT request for www.redirect.com. This
1625 response is not specified by the RFC, so the browser should not follow
1626 the redirect."""
1627
1628 if (self.path.find("www.redirect.com") < 0):
1629 return False
1630
1631 dest = "http://www.destination.com/foo.js"
1632
1633 self.send_response(302) # moved temporarily
1634 self.send_header('Location', dest)
1635 self.send_header('Connection', 'close')
1636 self.end_headers()
1637 return True
1638
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001639 def ServerAuthConnectHandler(self):
1640 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1641 response doesn't make sense because the proxy server cannot request
1642 server authentication."""
1643
1644 if (self.path.find("www.server-auth.com") < 0):
1645 return False
1646
1647 challenge = 'Basic realm="WallyWorld"'
1648
1649 self.send_response(401) # unauthorized
1650 self.send_header('WWW-Authenticate', challenge)
1651 self.send_header('Connection', 'close')
1652 self.end_headers()
1653 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001654
1655 def DefaultConnectResponseHandler(self):
1656 """This is the catch-all response handler for CONNECT requests that aren't
1657 handled by one of the special handlers above. Real Web servers respond
1658 with 400 to CONNECT requests."""
1659
1660 contents = "Your client has issued a malformed or illegal request."
1661 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001662 self.send_header('Content-Type', 'text/html')
1663 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001664 self.end_headers()
1665 self.wfile.write(contents)
1666 return True
1667
initial.commit94958cf2008-07-26 22:42:52 +00001668 # called by the redirect handling function when there is no parameter
1669 def sendRedirectHelp(self, redirect_name):
1670 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001671 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001672 self.end_headers()
1673 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1674 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1675 self.wfile.write('</body></html>')
1676
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001677 # called by chunked handling function
1678 def sendChunkHelp(self, chunk):
1679 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1680 self.wfile.write('%X\r\n' % len(chunk))
1681 self.wfile.write(chunk)
1682 self.wfile.write('\r\n')
1683
akalin@chromium.org154bb132010-11-12 02:20:27 +00001684
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001685class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001686 def __init__(self, request, client_address, socket_server):
1687 handlers = [self.OCSPResponse]
1688 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001689 testserver_base.BasePageHandler.__init__(self, request, client_address,
1690 socket_server, [], handlers, [],
1691 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001692
1693 def OCSPResponse(self):
1694 self.send_response(200)
1695 self.send_header('Content-Type', 'application/ocsp-response')
1696 self.send_header('Content-Length', str(len(self.ocsp_response)))
1697 self.end_headers()
1698
1699 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001700
mattm@chromium.org830a3712012-11-07 23:00:07 +00001701
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001702class TCPEchoHandler(SocketServer.BaseRequestHandler):
1703 """The RequestHandler class for TCP echo server.
1704
1705 It is instantiated once per connection to the server, and overrides the
1706 handle() method to implement communication to the client.
1707 """
1708
1709 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001710 """Handles the request from the client and constructs a response."""
1711
1712 data = self.request.recv(65536).strip()
1713 # Verify the "echo request" message received from the client. Send back
1714 # "echo response" message if "echo request" message is valid.
1715 try:
1716 return_data = echo_message.GetEchoResponseData(data)
1717 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001718 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001719 except ValueError:
1720 return
1721
1722 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001723
1724
1725class UDPEchoHandler(SocketServer.BaseRequestHandler):
1726 """The RequestHandler class for UDP echo server.
1727
1728 It is instantiated once per connection to the server, and overrides the
1729 handle() method to implement communication to the client.
1730 """
1731
1732 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001733 """Handles the request from the client and constructs a response."""
1734
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001735 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001736 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001737 # Verify the "echo request" message received from the client. Send back
1738 # "echo response" message if "echo request" message is valid.
1739 try:
1740 return_data = echo_message.GetEchoResponseData(data)
1741 if not return_data:
1742 return
1743 except ValueError:
1744 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001745 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001746
1747
bashi@chromium.org33233532012-09-08 17:37:24 +00001748class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1749 """A request handler that behaves as a proxy server which requires
1750 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1751 """
1752
1753 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1754
1755 def parse_request(self):
1756 """Overrides parse_request to check credential."""
1757
1758 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1759 return False
1760
1761 auth = self.headers.getheader('Proxy-Authorization')
1762 if auth != self._AUTH_CREDENTIAL:
1763 self.send_response(407)
1764 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1765 self.end_headers()
1766 return False
1767
1768 return True
1769
1770 def _start_read_write(self, sock):
1771 sock.setblocking(0)
1772 self.request.setblocking(0)
1773 rlist = [self.request, sock]
1774 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001775 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001776 if errors:
1777 self.send_response(500)
1778 self.end_headers()
1779 return
1780 for s in ready_sockets:
1781 received = s.recv(1024)
1782 if len(received) == 0:
1783 return
1784 if s == self.request:
1785 other = sock
1786 else:
1787 other = self.request
1788 other.send(received)
1789
1790 def _do_common_method(self):
1791 url = urlparse.urlparse(self.path)
1792 port = url.port
1793 if not port:
1794 if url.scheme == 'http':
1795 port = 80
1796 elif url.scheme == 'https':
1797 port = 443
1798 if not url.hostname or not port:
1799 self.send_response(400)
1800 self.end_headers()
1801 return
1802
1803 if len(url.path) == 0:
1804 path = '/'
1805 else:
1806 path = url.path
1807 if len(url.query) > 0:
1808 path = '%s?%s' % (url.path, url.query)
1809
1810 sock = None
1811 try:
1812 sock = socket.create_connection((url.hostname, port))
1813 sock.send('%s %s %s\r\n' % (
1814 self.command, path, self.protocol_version))
1815 for header in self.headers.headers:
1816 header = header.strip()
1817 if (header.lower().startswith('connection') or
1818 header.lower().startswith('proxy')):
1819 continue
1820 sock.send('%s\r\n' % header)
1821 sock.send('\r\n')
1822 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001823 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001824 self.send_response(500)
1825 self.end_headers()
1826 finally:
1827 if sock is not None:
1828 sock.close()
1829
1830 def do_CONNECT(self):
1831 try:
1832 pos = self.path.rfind(':')
1833 host = self.path[:pos]
1834 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001835 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001836 self.send_response(400)
1837 self.end_headers()
1838
1839 try:
1840 sock = socket.create_connection((host, port))
1841 self.send_response(200, 'Connection established')
1842 self.end_headers()
1843 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001844 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001845 self.send_response(500)
1846 self.end_headers()
1847 finally:
1848 sock.close()
1849
1850 def do_GET(self):
1851 self._do_common_method()
1852
1853 def do_HEAD(self):
1854 self._do_common_method()
1855
1856
mattm@chromium.org830a3712012-11-07 23:00:07 +00001857class ServerRunner(testserver_base.TestServerRunner):
1858 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001859
mattm@chromium.org830a3712012-11-07 23:00:07 +00001860 def __init__(self):
1861 super(ServerRunner, self).__init__()
1862 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001863
mattm@chromium.org830a3712012-11-07 23:00:07 +00001864 def __make_data_dir(self):
1865 if self.options.data_dir:
1866 if not os.path.isdir(self.options.data_dir):
1867 raise testserver_base.OptionError('specified data dir not found: ' +
1868 self.options.data_dir + ' exiting...')
1869 my_data_dir = self.options.data_dir
1870 else:
1871 # Create the default path to our data dir, relative to the exe dir.
1872 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1873 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001874
mattm@chromium.org830a3712012-11-07 23:00:07 +00001875 #TODO(ibrar): Must use Find* funtion defined in google\tools
1876 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001877
mattm@chromium.org830a3712012-11-07 23:00:07 +00001878 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001879
mattm@chromium.org830a3712012-11-07 23:00:07 +00001880 def create_server(self, server_data):
1881 port = self.options.port
1882 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001883
mattm@chromium.org830a3712012-11-07 23:00:07 +00001884 if self.options.server_type == SERVER_HTTP:
1885 if self.options.https:
1886 pem_cert_and_key = None
1887 if self.options.cert_and_key_file:
1888 if not os.path.isfile(self.options.cert_and_key_file):
1889 raise testserver_base.OptionError(
1890 'specified server cert file not found: ' +
1891 self.options.cert_and_key_file + ' exiting...')
1892 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001893 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001894 # generate a new certificate and run an OCSP server for it.
1895 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001896 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001897 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001898
mattm@chromium.org830a3712012-11-07 23:00:07 +00001899 ocsp_der = None
1900 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001901
mattm@chromium.org830a3712012-11-07 23:00:07 +00001902 if self.options.ocsp == 'ok':
1903 ocsp_state = minica.OCSP_STATE_GOOD
1904 elif self.options.ocsp == 'revoked':
1905 ocsp_state = minica.OCSP_STATE_REVOKED
1906 elif self.options.ocsp == 'invalid':
1907 ocsp_state = minica.OCSP_STATE_INVALID
1908 elif self.options.ocsp == 'unauthorized':
1909 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1910 elif self.options.ocsp == 'unknown':
1911 ocsp_state = minica.OCSP_STATE_UNKNOWN
1912 else:
1913 raise testserver_base.OptionError('unknown OCSP status: ' +
1914 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001915
mattm@chromium.org830a3712012-11-07 23:00:07 +00001916 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1917 subject = "127.0.0.1",
1918 ocsp_url = ("http://%s:%d/ocsp" %
1919 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00001920 ocsp_state = ocsp_state,
1921 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001922
1923 self.__ocsp_server.ocsp_response = ocsp_der
1924
1925 for ca_cert in self.options.ssl_client_ca:
1926 if not os.path.isfile(ca_cert):
1927 raise testserver_base.OptionError(
1928 'specified trusted client CA file not found: ' + ca_cert +
1929 ' exiting...')
1930 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1931 self.options.ssl_client_auth,
1932 self.options.ssl_client_ca,
1933 self.options.ssl_bulk_cipher,
1934 self.options.record_resume,
1935 self.options.tls_intolerant)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001936 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001937 else:
1938 server = HTTPServer((host, port), TestPageHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001939 print 'HTTP server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001940
1941 server.data_dir = self.__make_data_dir()
1942 server.file_root_url = self.options.file_root_url
1943 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001944 elif self.options.server_type == SERVER_WEBSOCKET:
1945 # Launch pywebsocket via WebSocketServer.
1946 logger = logging.getLogger()
1947 logger.addHandler(logging.StreamHandler())
1948 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1949 # is required to work correctly. It should be fixed from pywebsocket side.
1950 os.chdir(self.__make_data_dir())
1951 websocket_options = WebSocketOptions(host, port, '.')
1952 if self.options.cert_and_key_file:
1953 websocket_options.use_tls = True
1954 websocket_options.private_key = self.options.cert_and_key_file
1955 websocket_options.certificate = self.options.cert_and_key_file
1956 if self.options.ssl_client_auth:
1957 websocket_options.tls_client_auth = True
1958 if len(self.options.ssl_client_ca) != 1:
1959 raise testserver_base.OptionError(
1960 'one trusted client CA file should be specified')
1961 if not os.path.isfile(self.options.ssl_client_ca[0]):
1962 raise testserver_base.OptionError(
1963 'specified trusted client CA file not found: ' +
1964 self.options.ssl_client_ca[0] + ' exiting...')
1965 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
1966 server = WebSocketServer(websocket_options)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001967 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001968 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001969 elif self.options.server_type == SERVER_TCP_ECHO:
1970 # Used for generating the key (randomly) that encodes the "echo request"
1971 # message.
1972 random.seed()
1973 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001974 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001975 server_data['port'] = server.server_port
1976 elif self.options.server_type == SERVER_UDP_ECHO:
1977 # Used for generating the key (randomly) that encodes the "echo request"
1978 # message.
1979 random.seed()
1980 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001981 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001982 server_data['port'] = server.server_port
1983 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
1984 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001985 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001986 server_data['port'] = server.server_port
1987 elif self.options.server_type == SERVER_FTP:
1988 my_data_dir = self.__make_data_dir()
1989
1990 # Instantiate a dummy authorizer for managing 'virtual' users
1991 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1992
1993 # Define a new user having full r/w permissions and a read-only
1994 # anonymous user
1995 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1996
1997 authorizer.add_anonymous(my_data_dir)
1998
1999 # Instantiate FTP handler class
2000 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2001 ftp_handler.authorizer = authorizer
2002
2003 # Define a customized banner (string returned when client connects)
2004 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2005 pyftpdlib.ftpserver.__ver__)
2006
2007 # Instantiate FTP server class and listen to address:port
2008 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2009 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002010 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002011 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002012 raise testserver_base.OptionError('unknown server type' +
2013 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002014
mattm@chromium.org830a3712012-11-07 23:00:07 +00002015 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002016
mattm@chromium.org830a3712012-11-07 23:00:07 +00002017 def run_server(self):
2018 if self.__ocsp_server:
2019 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002020
mattm@chromium.org830a3712012-11-07 23:00:07 +00002021 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002022
mattm@chromium.org830a3712012-11-07 23:00:07 +00002023 if self.__ocsp_server:
2024 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002025
mattm@chromium.org830a3712012-11-07 23:00:07 +00002026 def add_options(self):
2027 testserver_base.TestServerRunner.add_options(self)
2028 self.option_parser.add_option('-f', '--ftp', action='store_const',
2029 const=SERVER_FTP, default=SERVER_HTTP,
2030 dest='server_type',
2031 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002032 self.option_parser.add_option('--tcp-echo', action='store_const',
2033 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2034 dest='server_type',
2035 help='start up a tcp echo server.')
2036 self.option_parser.add_option('--udp-echo', action='store_const',
2037 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2038 dest='server_type',
2039 help='start up a udp echo server.')
2040 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2041 const=SERVER_BASIC_AUTH_PROXY,
2042 default=SERVER_HTTP, dest='server_type',
2043 help='start up a proxy server which requires '
2044 'basic authentication.')
2045 self.option_parser.add_option('--websocket', action='store_const',
2046 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2047 dest='server_type',
2048 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002049 self.option_parser.add_option('--https', action='store_true',
2050 dest='https', help='Specify that https '
2051 'should be used.')
2052 self.option_parser.add_option('--cert-and-key-file',
2053 dest='cert_and_key_file', help='specify the '
2054 'path to the file containing the certificate '
2055 'and private key for the server in PEM '
2056 'format')
2057 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2058 help='The type of OCSP response generated '
2059 'for the automatically generated '
2060 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002061 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2062 default=0, type=int,
2063 help='If non-zero then the generated '
2064 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002065 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2066 default='0', type='int',
2067 help='If nonzero, certain TLS connections '
2068 'will be aborted in order to test version '
2069 'fallback. 1 means all TLS versions will be '
2070 'aborted. 2 means TLS 1.1 or higher will be '
2071 'aborted. 3 means TLS 1.2 or higher will be '
2072 'aborted.')
2073 self.option_parser.add_option('--https-record-resume',
2074 dest='record_resume', const=True,
2075 default=False, action='store_const',
2076 help='Record resumption cache events rather '
2077 'than resuming as normal. Allows the use of '
2078 'the /ssl-session-cache request')
2079 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2080 help='Require SSL client auth on every '
2081 'connection.')
2082 self.option_parser.add_option('--ssl-client-ca', action='append',
2083 default=[], help='Specify that the client '
2084 'certificate request should include the CA '
2085 'named in the subject of the DER-encoded '
2086 'certificate contained in the specified '
2087 'file. This option may appear multiple '
2088 'times, indicating multiple CA names should '
2089 'be sent in the request.')
2090 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2091 help='Specify the bulk encryption '
2092 'algorithm(s) that will be accepted by the '
2093 'SSL server. Valid values are "aes256", '
2094 '"aes128", "3des", "rc4". If omitted, all '
2095 'algorithms will be used. This option may '
2096 'appear multiple times, indicating '
2097 'multiple algorithms should be enabled.');
2098 self.option_parser.add_option('--file-root-url', default='/files/',
2099 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002100
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002101
initial.commit94958cf2008-07-26 22:42:52 +00002102if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002103 sys.exit(ServerRunner().main())