blob: 44312a08895a7c068b4509cc733ed3fc294efa76 [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00002# Copyright 2013 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00006"""This is a simple HTTP/FTP/TCP/UDP/BASIC_AUTH_PROXY/WEBSOCKET server used for
7testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00008
9It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000010By default, it listens on an ephemeral port and sends the port number back to
11the originating process over a pipe. The originating process can specify an
12explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000013It can use https if you specify the flag --https=CERT where CERT is the path
14to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000015"""
16
17import base64
18import BaseHTTPServer
19import cgi
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000020import hashlib
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000021import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000022import minica
initial.commit94958cf2008-07-26 22:42:52 +000023import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000024import random
initial.commit94958cf2008-07-26 22:42:52 +000025import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000026import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000027import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000028import SocketServer
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000029import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000030import sys
31import threading
initial.commit94958cf2008-07-26 22:42:52 +000032import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000033import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000034import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000035import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000036
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000037import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000038import pyftpdlib.ftpserver
mattm@chromium.org830a3712012-11-07 23:00:07 +000039import testserver_base
initial.commit94958cf2008-07-26 22:42:52 +000040import tlslite
41import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000042
mattm@chromium.org830a3712012-11-07 23:00:07 +000043BASE_DIR = os.path.dirname(os.path.abspath(__file__))
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000044sys.path.insert(
45 0, os.path.join(BASE_DIR, '..', '..', '..', 'third_party/pywebsocket/src'))
46from mod_pywebsocket.standalone import WebSocketServer
davidben@chromium.org06fcf202010-09-22 18:15:23 +000047
maruel@chromium.org756cf982009-03-05 12:46:38 +000048SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000049SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000050SERVER_TCP_ECHO = 2
51SERVER_UDP_ECHO = 3
52SERVER_BASIC_AUTH_PROXY = 4
53SERVER_WEBSOCKET = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000054
55# Default request queue size for WebSocketServer.
56_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000057
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000058class WebSocketOptions:
59 """Holds options for WebSocketServer."""
60
61 def __init__(self, host, port, data_dir):
62 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
63 self.server_host = host
64 self.port = port
65 self.websock_handlers = data_dir
66 self.scan_dir = None
67 self.allow_handlers_outside_root_dir = False
68 self.websock_handlers_map_file = None
69 self.cgi_directories = []
70 self.is_executable_method = None
71 self.allow_draft75 = False
72 self.strict = True
73
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000074 self.use_tls = False
75 self.private_key = None
76 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000077 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000078 self.tls_client_ca = None
79 self.use_basic_auth = False
80
mattm@chromium.org830a3712012-11-07 23:00:07 +000081
agl@chromium.orgf9e66792011-12-12 22:22:19 +000082class RecordingSSLSessionCache(object):
83 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
84 lookups and inserts in order to test session cache behaviours."""
85
86 def __init__(self):
87 self.log = []
88
89 def __getitem__(self, sessionID):
90 self.log.append(('lookup', sessionID))
91 raise KeyError()
92
93 def __setitem__(self, sessionID, session):
94 self.log.append(('insert', sessionID))
95
erikwright@chromium.org847ef282012-02-22 16:41:10 +000096
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000097class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
98 testserver_base.BrokenPipeHandlerMixIn,
99 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000100 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000101 verification."""
102
103 pass
104
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000105class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
106 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000107 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000108 """This is a specialization of HTTPServer that serves an
109 OCSP response"""
110
111 def serve_forever_on_thread(self):
112 self.thread = threading.Thread(target = self.serve_forever,
113 name = "OCSPServerThread")
114 self.thread.start()
115
116 def stop_serving(self):
117 self.shutdown()
118 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000119
mattm@chromium.org830a3712012-11-07 23:00:07 +0000120
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000121class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000122 testserver_base.ClientRestrictingServerMixIn,
123 testserver_base.BrokenPipeHandlerMixIn,
124 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000125 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000126 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000127
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000128 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000129 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000130 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000131 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
132 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000133 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000134 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000135 self.tls_intolerant = tls_intolerant
136
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000137 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000138 s = open(ca_file).read()
139 x509 = tlslite.api.X509()
140 x509.parse(s)
141 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000142 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
143 if ssl_bulk_ciphers is not None:
144 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000145
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000146 if record_resume_info:
147 # If record_resume_info is true then we'll replace the session cache with
148 # an object that records the lookups and inserts that it sees.
149 self.session_cache = RecordingSSLSessionCache()
150 else:
151 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000152 testserver_base.StoppableHTTPServer.__init__(self,
153 server_address,
154 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000155
156 def handshake(self, tlsConnection):
157 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000158
initial.commit94958cf2008-07-26 22:42:52 +0000159 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000160 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000161 tlsConnection.handshakeServer(certChain=self.cert_chain,
162 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000163 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000164 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000165 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000166 reqCAs=self.ssl_client_cas,
167 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000168 tlsConnection.ignoreAbruptClose = True
169 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000170 except tlslite.api.TLSAbruptCloseError:
171 # Ignore abrupt close.
172 return True
initial.commit94958cf2008-07-26 22:42:52 +0000173 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000174 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000175 return False
176
akalin@chromium.org154bb132010-11-12 02:20:27 +0000177
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000178class FTPServer(testserver_base.ClientRestrictingServerMixIn,
179 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000180 """This is a specialization of FTPServer that adds client verification."""
181
182 pass
183
184
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000185class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
186 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000187 """A TCP echo server that echoes back what it has received."""
188
189 def server_bind(self):
190 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000191
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000192 SocketServer.TCPServer.server_bind(self)
193 host, port = self.socket.getsockname()[:2]
194 self.server_name = socket.getfqdn(host)
195 self.server_port = port
196
197 def serve_forever(self):
198 self.stop = False
199 self.nonce_time = None
200 while not self.stop:
201 self.handle_request()
202 self.socket.close()
203
204
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000205class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
206 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000207 """A UDP echo server that echoes back what it has received."""
208
209 def server_bind(self):
210 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000211
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000212 SocketServer.UDPServer.server_bind(self)
213 host, port = self.socket.getsockname()[:2]
214 self.server_name = socket.getfqdn(host)
215 self.server_port = port
216
217 def serve_forever(self):
218 self.stop = False
219 self.nonce_time = None
220 while not self.stop:
221 self.handle_request()
222 self.socket.close()
223
224
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000225class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000226 # Class variables to allow for persistence state between page handler
227 # invocations
228 rst_limits = {}
229 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000230
231 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000232 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000233 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000234 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000235 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000236 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000237 self.NoCacheMaxAgeTimeHandler,
238 self.NoCacheTimeHandler,
239 self.CacheTimeHandler,
240 self.CacheExpiresHandler,
241 self.CacheProxyRevalidateHandler,
242 self.CachePrivateHandler,
243 self.CachePublicHandler,
244 self.CacheSMaxAgeHandler,
245 self.CacheMustRevalidateHandler,
246 self.CacheMustRevalidateMaxAgeHandler,
247 self.CacheNoStoreHandler,
248 self.CacheNoStoreMaxAgeHandler,
249 self.CacheNoTransformHandler,
250 self.DownloadHandler,
251 self.DownloadFinishHandler,
252 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000253 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000254 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000255 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000256 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000257 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000258 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000259 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000260 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000261 self.AuthBasicHandler,
262 self.AuthDigestHandler,
263 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000264 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000265 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000266 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000267 self.ServerRedirectHandler,
268 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000269 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000270 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000271 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000272 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000273 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000274 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000275 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000276 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000277 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000278 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000279 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000280 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000281 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000282 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000283 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000284 head_handlers = [
285 self.FileHandler,
286 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000287
maruel@google.come250a9b2009-03-10 17:39:46 +0000288 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000289 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000290 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000291 'gif': 'image/gif',
292 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000293 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000294 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000295 'pdf' : 'application/pdf',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000296 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000297 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000298 }
initial.commit94958cf2008-07-26 22:42:52 +0000299 self._default_mime_type = 'text/html'
300
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000301 testserver_base.BasePageHandler.__init__(self, request, client_address,
302 socket_server, connect_handlers,
303 get_handlers, head_handlers,
304 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000305
initial.commit94958cf2008-07-26 22:42:52 +0000306 def GetMIMETypeFromName(self, file_name):
307 """Returns the mime type for the specified file_name. So far it only looks
308 at the file extension."""
309
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000310 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000311 if len(extension) == 0:
312 # no extension.
313 return self._default_mime_type
314
ericroman@google.comc17ca532009-05-07 03:51:05 +0000315 # extension starts with a dot, so we need to remove it
316 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000317
initial.commit94958cf2008-07-26 22:42:52 +0000318 def NoCacheMaxAgeTimeHandler(self):
319 """This request handler yields a page with the title set to the current
320 system time, and no caching requested."""
321
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000322 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000323 return False
324
325 self.send_response(200)
326 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000327 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000328 self.end_headers()
329
maruel@google.come250a9b2009-03-10 17:39:46 +0000330 self.wfile.write('<html><head><title>%s</title></head></html>' %
331 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000332
333 return True
334
335 def NoCacheTimeHandler(self):
336 """This request handler yields a page with the title set to the current
337 system time, and no caching requested."""
338
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000339 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000340 return False
341
342 self.send_response(200)
343 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000344 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000345 self.end_headers()
346
maruel@google.come250a9b2009-03-10 17:39:46 +0000347 self.wfile.write('<html><head><title>%s</title></head></html>' %
348 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000349
350 return True
351
352 def CacheTimeHandler(self):
353 """This request handler yields a page with the title set to the current
354 system time, and allows caching for one minute."""
355
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000356 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000357 return False
358
359 self.send_response(200)
360 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000361 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000362 self.end_headers()
363
maruel@google.come250a9b2009-03-10 17:39:46 +0000364 self.wfile.write('<html><head><title>%s</title></head></html>' %
365 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000366
367 return True
368
369 def CacheExpiresHandler(self):
370 """This request handler yields a page with the title set to the current
371 system time, and set the page to expire on 1 Jan 2099."""
372
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000373 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000374 return False
375
376 self.send_response(200)
377 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000378 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000379 self.end_headers()
380
maruel@google.come250a9b2009-03-10 17:39:46 +0000381 self.wfile.write('<html><head><title>%s</title></head></html>' %
382 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000383
384 return True
385
386 def CacheProxyRevalidateHandler(self):
387 """This request handler yields a page with the title set to the current
388 system time, and allows caching for 60 seconds"""
389
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000390 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000391 return False
392
393 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000394 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000395 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
396 self.end_headers()
397
maruel@google.come250a9b2009-03-10 17:39:46 +0000398 self.wfile.write('<html><head><title>%s</title></head></html>' %
399 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000400
401 return True
402
403 def CachePrivateHandler(self):
404 """This request handler yields a page with the title set to the current
405 system time, and allows caching for 5 seconds."""
406
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000407 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000408 return False
409
410 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000411 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000412 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000413 self.end_headers()
414
maruel@google.come250a9b2009-03-10 17:39:46 +0000415 self.wfile.write('<html><head><title>%s</title></head></html>' %
416 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000417
418 return True
419
420 def CachePublicHandler(self):
421 """This request handler yields a page with the title set to the current
422 system time, and allows caching for 5 seconds."""
423
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000424 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000425 return False
426
427 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000428 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000429 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000430 self.end_headers()
431
maruel@google.come250a9b2009-03-10 17:39:46 +0000432 self.wfile.write('<html><head><title>%s</title></head></html>' %
433 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000434
435 return True
436
437 def CacheSMaxAgeHandler(self):
438 """This request handler yields a page with the title set to the current
439 system time, and does not allow for caching."""
440
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000441 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000442 return False
443
444 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000445 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000446 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
447 self.end_headers()
448
maruel@google.come250a9b2009-03-10 17:39:46 +0000449 self.wfile.write('<html><head><title>%s</title></head></html>' %
450 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000451
452 return True
453
454 def CacheMustRevalidateHandler(self):
455 """This request handler yields a page with the title set to the current
456 system time, and does not allow caching."""
457
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000458 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000459 return False
460
461 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000462 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000463 self.send_header('Cache-Control', 'must-revalidate')
464 self.end_headers()
465
maruel@google.come250a9b2009-03-10 17:39:46 +0000466 self.wfile.write('<html><head><title>%s</title></head></html>' %
467 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000468
469 return True
470
471 def CacheMustRevalidateMaxAgeHandler(self):
472 """This request handler yields a page with the title set to the current
473 system time, and does not allow caching event though max-age of 60
474 seconds is specified."""
475
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000476 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000477 return False
478
479 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000480 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000481 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
482 self.end_headers()
483
maruel@google.come250a9b2009-03-10 17:39:46 +0000484 self.wfile.write('<html><head><title>%s</title></head></html>' %
485 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000486
487 return True
488
initial.commit94958cf2008-07-26 22:42:52 +0000489 def CacheNoStoreHandler(self):
490 """This request handler yields a page with the title set to the current
491 system time, and does not allow the page to be stored."""
492
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000493 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000494 return False
495
496 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000497 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000498 self.send_header('Cache-Control', 'no-store')
499 self.end_headers()
500
maruel@google.come250a9b2009-03-10 17:39:46 +0000501 self.wfile.write('<html><head><title>%s</title></head></html>' %
502 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000503
504 return True
505
506 def CacheNoStoreMaxAgeHandler(self):
507 """This request handler yields a page with the title set to the current
508 system time, and does not allow the page to be stored even though max-age
509 of 60 seconds is specified."""
510
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000511 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000512 return False
513
514 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000515 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000516 self.send_header('Cache-Control', 'max-age=60, no-store')
517 self.end_headers()
518
maruel@google.come250a9b2009-03-10 17:39:46 +0000519 self.wfile.write('<html><head><title>%s</title></head></html>' %
520 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000521
522 return True
523
524
525 def CacheNoTransformHandler(self):
526 """This request handler yields a page with the title set to the current
527 system time, and does not allow the content to transformed during
528 user-agent caching"""
529
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000530 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000531 return False
532
533 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000534 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000535 self.send_header('Cache-Control', 'no-transform')
536 self.end_headers()
537
maruel@google.come250a9b2009-03-10 17:39:46 +0000538 self.wfile.write('<html><head><title>%s</title></head></html>' %
539 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000540
541 return True
542
543 def EchoHeader(self):
544 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000545
ananta@chromium.org219b2062009-10-23 16:09:41 +0000546 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000547
ananta@chromium.org56812d02011-04-07 17:52:05 +0000548 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000549 """This function echoes back the value of a specific request header while
550 allowing caching for 16 hours."""
551
ananta@chromium.org56812d02011-04-07 17:52:05 +0000552 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000553
554 def EchoHeaderHelper(self, echo_header):
555 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000556
ananta@chromium.org219b2062009-10-23 16:09:41 +0000557 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000558 return False
559
560 query_char = self.path.find('?')
561 if query_char != -1:
562 header_name = self.path[query_char+1:]
563
564 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000565 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000566 if echo_header == '/echoheadercache':
567 self.send_header('Cache-control', 'max-age=60000')
568 else:
569 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000570 # insert a vary header to properly indicate that the cachability of this
571 # request is subject to value of the request header being echoed.
572 if len(header_name) > 0:
573 self.send_header('Vary', header_name)
574 self.end_headers()
575
576 if len(header_name) > 0:
577 self.wfile.write(self.headers.getheader(header_name))
578
579 return True
580
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000581 def ReadRequestBody(self):
582 """This function reads the body of the current HTTP request, handling
583 both plain and chunked transfer encoded requests."""
584
585 if self.headers.getheader('transfer-encoding') != 'chunked':
586 length = int(self.headers.getheader('content-length'))
587 return self.rfile.read(length)
588
589 # Read the request body as chunks.
590 body = ""
591 while True:
592 line = self.rfile.readline()
593 length = int(line, 16)
594 if length == 0:
595 self.rfile.readline()
596 break
597 body += self.rfile.read(length)
598 self.rfile.read(2)
599 return body
600
initial.commit94958cf2008-07-26 22:42:52 +0000601 def EchoHandler(self):
602 """This handler just echoes back the payload of the request, for testing
603 form submission."""
604
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000605 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000606 return False
607
608 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000609 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000610 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000611 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000612 return True
613
614 def EchoTitleHandler(self):
615 """This handler is like Echo, but sets the page title to the request."""
616
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000617 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000618 return False
619
620 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000621 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000622 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000623 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000624 self.wfile.write('<html><head><title>')
625 self.wfile.write(request)
626 self.wfile.write('</title></head></html>')
627 return True
628
629 def EchoAllHandler(self):
630 """This handler yields a (more) human-readable page listing information
631 about the request header & contents."""
632
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000633 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000634 return False
635
636 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000637 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000638 self.end_headers()
639 self.wfile.write('<html><head><style>'
640 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
641 '</style></head><body>'
642 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000643 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000644 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000645
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000646 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000647 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000648 params = cgi.parse_qs(qs, keep_blank_values=1)
649
650 for param in params:
651 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000652
653 self.wfile.write('</pre>')
654
655 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
656
657 self.wfile.write('</body></html>')
658 return True
659
660 def DownloadHandler(self):
661 """This handler sends a downloadable file with or without reporting
662 the size (6K)."""
663
664 if self.path.startswith("/download-unknown-size"):
665 send_length = False
666 elif self.path.startswith("/download-known-size"):
667 send_length = True
668 else:
669 return False
670
671 #
672 # The test which uses this functionality is attempting to send
673 # small chunks of data to the client. Use a fairly large buffer
674 # so that we'll fill chrome's IO buffer enough to force it to
675 # actually write the data.
676 # See also the comments in the client-side of this test in
677 # download_uitest.cc
678 #
679 size_chunk1 = 35*1024
680 size_chunk2 = 10*1024
681
682 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000683 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000684 self.send_header('Cache-Control', 'max-age=0')
685 if send_length:
686 self.send_header('Content-Length', size_chunk1 + size_chunk2)
687 self.end_headers()
688
689 # First chunk of data:
690 self.wfile.write("*" * size_chunk1)
691 self.wfile.flush()
692
693 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000694 self.server.wait_for_download = True
695 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000696 self.server.handle_request()
697
698 # Second chunk of data:
699 self.wfile.write("*" * size_chunk2)
700 return True
701
702 def DownloadFinishHandler(self):
703 """This handler just tells the server to finish the current download."""
704
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000705 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000706 return False
707
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000708 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000709 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000710 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000711 self.send_header('Cache-Control', 'max-age=0')
712 self.end_headers()
713 return True
714
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000715 def _ReplaceFileData(self, data, query_parameters):
716 """Replaces matching substrings in a file.
717
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000718 If the 'replace_text' URL query parameter is present, it is expected to be
719 of the form old_text:new_text, which indicates that any old_text strings in
720 the file are replaced with new_text. Multiple 'replace_text' parameters may
721 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000722
723 If the parameters are not present, |data| is returned.
724 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000725
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000726 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000727 replace_text_values = query_dict.get('replace_text', [])
728 for replace_text_value in replace_text_values:
729 replace_text_args = replace_text_value.split(':')
730 if len(replace_text_args) != 2:
731 raise ValueError(
732 'replace_text must be of form old_text:new_text. Actual value: %s' %
733 replace_text_value)
734 old_text_b64, new_text_b64 = replace_text_args
735 old_text = base64.urlsafe_b64decode(old_text_b64)
736 new_text = base64.urlsafe_b64decode(new_text_b64)
737 data = data.replace(old_text, new_text)
738 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000739
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000740 def ZipFileHandler(self):
741 """This handler sends the contents of the requested file in compressed form.
742 Can pass in a parameter that specifies that the content length be
743 C - the compressed size (OK),
744 U - the uncompressed size (Non-standard, but handled),
745 S - less than compressed (OK because we keep going),
746 M - larger than compressed but less than uncompressed (an error),
747 L - larger than uncompressed (an error)
748 Example: compressedfiles/Picture_1.doc?C
749 """
750
751 prefix = "/compressedfiles/"
752 if not self.path.startswith(prefix):
753 return False
754
755 # Consume a request body if present.
756 if self.command == 'POST' or self.command == 'PUT' :
757 self.ReadRequestBody()
758
759 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
760
761 if not query in ('C', 'U', 'S', 'M', 'L'):
762 return False
763
764 sub_path = url_path[len(prefix):]
765 entries = sub_path.split('/')
766 file_path = os.path.join(self.server.data_dir, *entries)
767 if os.path.isdir(file_path):
768 file_path = os.path.join(file_path, 'index.html')
769
770 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000771 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000772 self.send_error(404)
773 return True
774
775 f = open(file_path, "rb")
776 data = f.read()
777 uncompressed_len = len(data)
778 f.close()
779
780 # Compress the data.
781 data = zlib.compress(data)
782 compressed_len = len(data)
783
784 content_length = compressed_len
785 if query == 'U':
786 content_length = uncompressed_len
787 elif query == 'S':
788 content_length = compressed_len / 2
789 elif query == 'M':
790 content_length = (compressed_len + uncompressed_len) / 2
791 elif query == 'L':
792 content_length = compressed_len + uncompressed_len
793
794 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000795 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000796 self.send_header('Content-encoding', 'deflate')
797 self.send_header('Connection', 'close')
798 self.send_header('Content-Length', content_length)
799 self.send_header('ETag', '\'' + file_path + '\'')
800 self.end_headers()
801
802 self.wfile.write(data)
803
804 return True
805
initial.commit94958cf2008-07-26 22:42:52 +0000806 def FileHandler(self):
807 """This handler sends the contents of the requested file. Wow, it's like
808 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000809
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000810 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000811 if not self.path.startswith(prefix):
812 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000813 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000814
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000815 def PostOnlyFileHandler(self):
816 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000817
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000818 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000819 if not self.path.startswith(prefix):
820 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000821 return self._FileHandlerHelper(prefix)
822
823 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000824 request_body = ''
825 if self.command == 'POST' or self.command == 'PUT':
826 # Consume a request body if present.
827 request_body = self.ReadRequestBody()
828
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000829 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000830 query_dict = cgi.parse_qs(query)
831
832 expected_body = query_dict.get('expected_body', [])
833 if expected_body and request_body not in expected_body:
834 self.send_response(404)
835 self.end_headers()
836 self.wfile.write('')
837 return True
838
839 expected_headers = query_dict.get('expected_headers', [])
840 for expected_header in expected_headers:
841 header_name, expected_value = expected_header.split(':')
842 if self.headers.getheader(header_name) != expected_value:
843 self.send_response(404)
844 self.end_headers()
845 self.wfile.write('')
846 return True
847
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000848 sub_path = url_path[len(prefix):]
849 entries = sub_path.split('/')
850 file_path = os.path.join(self.server.data_dir, *entries)
851 if os.path.isdir(file_path):
852 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000853
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000854 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000855 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000856 self.send_error(404)
857 return True
858
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000859 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000860 data = f.read()
861 f.close()
862
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000863 data = self._ReplaceFileData(data, query)
864
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000865 old_protocol_version = self.protocol_version
866
initial.commit94958cf2008-07-26 22:42:52 +0000867 # If file.mock-http-headers exists, it contains the headers we
868 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000869 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000870 if os.path.isfile(headers_path):
871 f = open(headers_path, "r")
872
873 # "HTTP/1.1 200 OK"
874 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000875 http_major, http_minor, status_code = re.findall(
876 'HTTP/(\d+).(\d+) (\d+)', response)[0]
877 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000878 self.send_response(int(status_code))
879
880 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000881 header_values = re.findall('(\S+):\s*(.*)', line)
882 if len(header_values) > 0:
883 # "name: value"
884 name, value = header_values[0]
885 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000886 f.close()
887 else:
888 # Could be more generic once we support mime-type sniffing, but for
889 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000890
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000891 range_header = self.headers.get('Range')
892 if range_header and range_header.startswith('bytes='):
893 # Note this doesn't handle all valid byte range_header values (i.e.
894 # left open ended ones), just enough for what we needed so far.
895 range_header = range_header[6:].split('-')
896 start = int(range_header[0])
897 if range_header[1]:
898 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000899 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000900 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000901
902 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000903 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
904 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000905 self.send_header('Content-Range', content_range)
906 data = data[start: end + 1]
907 else:
908 self.send_response(200)
909
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000910 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000911 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000912 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000913 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000914 self.end_headers()
915
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000916 if (self.command != 'HEAD'):
917 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000918
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000919 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000920 return True
921
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000922 def SetCookieHandler(self):
923 """This handler just sets a cookie, for testing cookie handling."""
924
925 if not self._ShouldHandleRequest("/set-cookie"):
926 return False
927
928 query_char = self.path.find('?')
929 if query_char != -1:
930 cookie_values = self.path[query_char + 1:].split('&')
931 else:
932 cookie_values = ("",)
933 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000934 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000935 for cookie_value in cookie_values:
936 self.send_header('Set-Cookie', '%s' % cookie_value)
937 self.end_headers()
938 for cookie_value in cookie_values:
939 self.wfile.write('%s' % cookie_value)
940 return True
941
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000942 def SetManyCookiesHandler(self):
943 """This handler just sets a given number of cookies, for testing handling
944 of large numbers of cookies."""
945
946 if not self._ShouldHandleRequest("/set-many-cookies"):
947 return False
948
949 query_char = self.path.find('?')
950 if query_char != -1:
951 num_cookies = int(self.path[query_char + 1:])
952 else:
953 num_cookies = 0
954 self.send_response(200)
955 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000956 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000957 self.send_header('Set-Cookie', 'a=')
958 self.end_headers()
959 self.wfile.write('%d cookies were sent' % num_cookies)
960 return True
961
mattm@chromium.org983fc462012-06-30 00:52:08 +0000962 def ExpectAndSetCookieHandler(self):
963 """Expects some cookies to be sent, and if they are, sets more cookies.
964
965 The expect parameter specifies a required cookie. May be specified multiple
966 times.
967 The set parameter specifies a cookie to set if all required cookies are
968 preset. May be specified multiple times.
969 The data parameter specifies the response body data to be returned."""
970
971 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
972 return False
973
974 _, _, _, _, query, _ = urlparse.urlparse(self.path)
975 query_dict = cgi.parse_qs(query)
976 cookies = set()
977 if 'Cookie' in self.headers:
978 cookie_header = self.headers.getheader('Cookie')
979 cookies.update([s.strip() for s in cookie_header.split(';')])
980 got_all_expected_cookies = True
981 for expected_cookie in query_dict.get('expect', []):
982 if expected_cookie not in cookies:
983 got_all_expected_cookies = False
984 self.send_response(200)
985 self.send_header('Content-Type', 'text/html')
986 if got_all_expected_cookies:
987 for cookie_value in query_dict.get('set', []):
988 self.send_header('Set-Cookie', '%s' % cookie_value)
989 self.end_headers()
990 for data_value in query_dict.get('data', []):
991 self.wfile.write(data_value)
992 return True
993
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000994 def SetHeaderHandler(self):
995 """This handler sets a response header. Parameters are in the
996 key%3A%20value&key2%3A%20value2 format."""
997
998 if not self._ShouldHandleRequest("/set-header"):
999 return False
1000
1001 query_char = self.path.find('?')
1002 if query_char != -1:
1003 headers_values = self.path[query_char + 1:].split('&')
1004 else:
1005 headers_values = ("",)
1006 self.send_response(200)
1007 self.send_header('Content-Type', 'text/html')
1008 for header_value in headers_values:
1009 header_value = urllib.unquote(header_value)
1010 (key, value) = header_value.split(': ', 1)
1011 self.send_header(key, value)
1012 self.end_headers()
1013 for header_value in headers_values:
1014 self.wfile.write('%s' % header_value)
1015 return True
1016
initial.commit94958cf2008-07-26 22:42:52 +00001017 def AuthBasicHandler(self):
1018 """This handler tests 'Basic' authentication. It just sends a page with
1019 title 'user/pass' if you succeed."""
1020
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001021 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001022 return False
1023
1024 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001025 expected_password = 'secret'
1026 realm = 'testrealm'
1027 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001028
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001029 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1030 query_params = cgi.parse_qs(query, True)
1031 if 'set-cookie-if-challenged' in query_params:
1032 set_cookie_if_challenged = True
1033 if 'password' in query_params:
1034 expected_password = query_params['password'][0]
1035 if 'realm' in query_params:
1036 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001037
initial.commit94958cf2008-07-26 22:42:52 +00001038 auth = self.headers.getheader('authorization')
1039 try:
1040 if not auth:
1041 raise Exception('no auth')
1042 b64str = re.findall(r'Basic (\S+)', auth)[0]
1043 userpass = base64.b64decode(b64str)
1044 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001045 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001046 raise Exception('wrong password')
1047 except Exception, e:
1048 # Authentication failed.
1049 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001050 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001051 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001052 if set_cookie_if_challenged:
1053 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001054 self.end_headers()
1055 self.wfile.write('<html><head>')
1056 self.wfile.write('<title>Denied: %s</title>' % e)
1057 self.wfile.write('</head><body>')
1058 self.wfile.write('auth=%s<p>' % auth)
1059 self.wfile.write('b64str=%s<p>' % b64str)
1060 self.wfile.write('username: %s<p>' % username)
1061 self.wfile.write('userpass: %s<p>' % userpass)
1062 self.wfile.write('password: %s<p>' % password)
1063 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1064 self.wfile.write('</body></html>')
1065 return True
1066
1067 # Authentication successful. (Return a cachable response to allow for
1068 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001069 old_protocol_version = self.protocol_version
1070 self.protocol_version = "HTTP/1.1"
1071
initial.commit94958cf2008-07-26 22:42:52 +00001072 if_none_match = self.headers.getheader('if-none-match')
1073 if if_none_match == "abc":
1074 self.send_response(304)
1075 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001076 elif url_path.endswith(".gif"):
1077 # Using chrome/test/data/google/logo.gif as the test image
1078 test_image_path = ['google', 'logo.gif']
1079 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1080 if not os.path.isfile(gif_path):
1081 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001082 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001083 return True
1084
1085 f = open(gif_path, "rb")
1086 data = f.read()
1087 f.close()
1088
1089 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001090 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001091 self.send_header('Cache-control', 'max-age=60000')
1092 self.send_header('Etag', 'abc')
1093 self.end_headers()
1094 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001095 else:
1096 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001097 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001098 self.send_header('Cache-control', 'max-age=60000')
1099 self.send_header('Etag', 'abc')
1100 self.end_headers()
1101 self.wfile.write('<html><head>')
1102 self.wfile.write('<title>%s/%s</title>' % (username, password))
1103 self.wfile.write('</head><body>')
1104 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001105 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001106 self.wfile.write('</body></html>')
1107
rvargas@google.com54453b72011-05-19 01:11:11 +00001108 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001109 return True
1110
tonyg@chromium.org75054202010-03-31 22:06:10 +00001111 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001112 """Returns a nonce that's stable per request path for the server's lifetime.
1113 This is a fake implementation. A real implementation would only use a given
1114 nonce a single time (hence the name n-once). However, for the purposes of
1115 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001116
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001117 Args:
1118 force_reset: Iff set, the nonce will be changed. Useful for testing the
1119 "stale" response.
1120 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001121
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001122 if force_reset or not self.server.nonce_time:
1123 self.server.nonce_time = time.time()
1124 return hashlib.md5('privatekey%s%d' %
1125 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001126
1127 def AuthDigestHandler(self):
1128 """This handler tests 'Digest' authentication.
1129
1130 It just sends a page with title 'user/pass' if you succeed.
1131
1132 A stale response is sent iff "stale" is present in the request path.
1133 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001134
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001135 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001136 return False
1137
tonyg@chromium.org75054202010-03-31 22:06:10 +00001138 stale = 'stale' in self.path
1139 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001140 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001141 password = 'secret'
1142 realm = 'testrealm'
1143
1144 auth = self.headers.getheader('authorization')
1145 pairs = {}
1146 try:
1147 if not auth:
1148 raise Exception('no auth')
1149 if not auth.startswith('Digest'):
1150 raise Exception('not digest')
1151 # Pull out all the name="value" pairs as a dictionary.
1152 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1153
1154 # Make sure it's all valid.
1155 if pairs['nonce'] != nonce:
1156 raise Exception('wrong nonce')
1157 if pairs['opaque'] != opaque:
1158 raise Exception('wrong opaque')
1159
1160 # Check the 'response' value and make sure it matches our magic hash.
1161 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001162 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001163 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001164 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001165 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001166 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001167 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1168 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001169 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001170
1171 if pairs['response'] != response:
1172 raise Exception('wrong password')
1173 except Exception, e:
1174 # Authentication failed.
1175 self.send_response(401)
1176 hdr = ('Digest '
1177 'realm="%s", '
1178 'domain="/", '
1179 'qop="auth", '
1180 'algorithm=MD5, '
1181 'nonce="%s", '
1182 'opaque="%s"') % (realm, nonce, opaque)
1183 if stale:
1184 hdr += ', stale="TRUE"'
1185 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001186 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001187 self.end_headers()
1188 self.wfile.write('<html><head>')
1189 self.wfile.write('<title>Denied: %s</title>' % e)
1190 self.wfile.write('</head><body>')
1191 self.wfile.write('auth=%s<p>' % auth)
1192 self.wfile.write('pairs=%s<p>' % pairs)
1193 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1194 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1195 self.wfile.write('</body></html>')
1196 return True
1197
1198 # Authentication successful.
1199 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001200 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001201 self.end_headers()
1202 self.wfile.write('<html><head>')
1203 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1204 self.wfile.write('</head><body>')
1205 self.wfile.write('auth=%s<p>' % auth)
1206 self.wfile.write('pairs=%s<p>' % pairs)
1207 self.wfile.write('</body></html>')
1208
1209 return True
1210
1211 def SlowServerHandler(self):
1212 """Wait for the user suggested time before responding. The syntax is
1213 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001214
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001215 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001216 return False
1217 query_char = self.path.find('?')
1218 wait_sec = 1.0
1219 if query_char >= 0:
1220 try:
1221 wait_sec = int(self.path[query_char + 1:])
1222 except ValueError:
1223 pass
1224 time.sleep(wait_sec)
1225 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001226 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001227 self.end_headers()
1228 self.wfile.write("waited %d seconds" % wait_sec)
1229 return True
1230
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001231 def ChunkedServerHandler(self):
1232 """Send chunked response. Allows to specify chunks parameters:
1233 - waitBeforeHeaders - ms to wait before sending headers
1234 - waitBetweenChunks - ms to wait between chunks
1235 - chunkSize - size of each chunk in bytes
1236 - chunksNumber - number of chunks
1237 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1238 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001239
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001240 if not self._ShouldHandleRequest("/chunked"):
1241 return False
1242 query_char = self.path.find('?')
1243 chunkedSettings = {'waitBeforeHeaders' : 0,
1244 'waitBetweenChunks' : 0,
1245 'chunkSize' : 5,
1246 'chunksNumber' : 5}
1247 if query_char >= 0:
1248 params = self.path[query_char + 1:].split('&')
1249 for param in params:
1250 keyValue = param.split('=')
1251 if len(keyValue) == 2:
1252 try:
1253 chunkedSettings[keyValue[0]] = int(keyValue[1])
1254 except ValueError:
1255 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001256 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001257 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1258 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001259 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001260 self.send_header('Connection', 'close')
1261 self.send_header('Transfer-Encoding', 'chunked')
1262 self.end_headers()
1263 # Chunked encoding: sending all chunks, then final zero-length chunk and
1264 # then final CRLF.
1265 for i in range(0, chunkedSettings['chunksNumber']):
1266 if i > 0:
1267 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1268 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001269 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001270 self.sendChunkHelp('')
1271 return True
1272
initial.commit94958cf2008-07-26 22:42:52 +00001273 def ContentTypeHandler(self):
1274 """Returns a string of html with the given content type. E.g.,
1275 /contenttype?text/css returns an html file with the Content-Type
1276 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001277
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001278 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001279 return False
1280 query_char = self.path.find('?')
1281 content_type = self.path[query_char + 1:].strip()
1282 if not content_type:
1283 content_type = 'text/html'
1284 self.send_response(200)
1285 self.send_header('Content-Type', content_type)
1286 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001287 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001288 return True
1289
creis@google.com2f4f6a42011-03-25 19:44:19 +00001290 def NoContentHandler(self):
1291 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001292
creis@google.com2f4f6a42011-03-25 19:44:19 +00001293 if not self._ShouldHandleRequest("/nocontent"):
1294 return False
1295 self.send_response(204)
1296 self.end_headers()
1297 return True
1298
initial.commit94958cf2008-07-26 22:42:52 +00001299 def ServerRedirectHandler(self):
1300 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001301 '/server-redirect?http://foo.bar/asdf' to redirect to
1302 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001303
1304 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001305 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001306 return False
1307
1308 query_char = self.path.find('?')
1309 if query_char < 0 or len(self.path) <= query_char + 1:
1310 self.sendRedirectHelp(test_name)
1311 return True
1312 dest = self.path[query_char + 1:]
1313
1314 self.send_response(301) # moved permanently
1315 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001316 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001317 self.end_headers()
1318 self.wfile.write('<html><head>')
1319 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1320
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001321 return True
initial.commit94958cf2008-07-26 22:42:52 +00001322
1323 def ClientRedirectHandler(self):
1324 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001325 '/client-redirect?http://foo.bar/asdf' to redirect to
1326 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001327
1328 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001329 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001330 return False
1331
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001332 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001333 if query_char < 0 or len(self.path) <= query_char + 1:
1334 self.sendRedirectHelp(test_name)
1335 return True
1336 dest = self.path[query_char + 1:]
1337
1338 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001339 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001340 self.end_headers()
1341 self.wfile.write('<html><head>')
1342 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1343 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1344
1345 return True
1346
tony@chromium.org03266982010-03-05 03:18:42 +00001347 def MultipartHandler(self):
1348 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001349
tony@chromium.org4cb88302011-09-27 22:13:49 +00001350 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001351 if not self._ShouldHandleRequest(test_name):
1352 return False
1353
1354 num_frames = 10
1355 bound = '12345'
1356 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001357 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001358 'multipart/x-mixed-replace;boundary=' + bound)
1359 self.end_headers()
1360
1361 for i in xrange(num_frames):
1362 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001363 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001364 self.wfile.write('<title>page ' + str(i) + '</title>')
1365 self.wfile.write('page ' + str(i))
1366
1367 self.wfile.write('--' + bound + '--')
1368 return True
1369
tony@chromium.org4cb88302011-09-27 22:13:49 +00001370 def MultipartSlowHandler(self):
1371 """Send a multipart response (3 text/html pages) with a slight delay
1372 between each page. This is similar to how some pages show status using
1373 multipart."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001374
tony@chromium.org4cb88302011-09-27 22:13:49 +00001375 test_name = '/multipart-slow'
1376 if not self._ShouldHandleRequest(test_name):
1377 return False
1378
1379 num_frames = 3
1380 bound = '12345'
1381 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001382 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001383 'multipart/x-mixed-replace;boundary=' + bound)
1384 self.end_headers()
1385
1386 for i in xrange(num_frames):
1387 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001388 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001389 time.sleep(0.25)
1390 if i == 2:
1391 self.wfile.write('<title>PASS</title>')
1392 else:
1393 self.wfile.write('<title>page ' + str(i) + '</title>')
1394 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1395
1396 self.wfile.write('--' + bound + '--')
1397 return True
1398
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001399 def GetSSLSessionCacheHandler(self):
1400 """Send a reply containing a log of the session cache operations."""
1401
1402 if not self._ShouldHandleRequest('/ssl-session-cache'):
1403 return False
1404
1405 self.send_response(200)
1406 self.send_header('Content-Type', 'text/plain')
1407 self.end_headers()
1408 try:
1409 for (action, sessionID) in self.server.session_cache.log:
1410 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001411 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001412 self.wfile.write('Pass --https-record-resume in order to use' +
1413 ' this request')
1414 return True
1415
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001416 def SSLManySmallRecords(self):
1417 """Sends a reply consisting of a variety of small writes. These will be
1418 translated into a series of small SSL records when used over an HTTPS
1419 server."""
1420
1421 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1422 return False
1423
1424 self.send_response(200)
1425 self.send_header('Content-Type', 'text/plain')
1426 self.end_headers()
1427
1428 # Write ~26K of data, in 1350 byte chunks
1429 for i in xrange(20):
1430 self.wfile.write('*' * 1350)
1431 self.wfile.flush()
1432 return True
1433
agl@chromium.org04700be2013-03-02 18:40:41 +00001434 def GetChannelID(self):
1435 """Send a reply containing the hashed ChannelID that the client provided."""
1436
1437 if not self._ShouldHandleRequest('/channel-id'):
1438 return False
1439
1440 self.send_response(200)
1441 self.send_header('Content-Type', 'text/plain')
1442 self.end_headers()
1443 channel_id = self.server.tlsConnection.channel_id.tostring()
1444 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1445 return True
1446
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001447 def CloseSocketHandler(self):
1448 """Closes the socket without sending anything."""
1449
1450 if not self._ShouldHandleRequest('/close-socket'):
1451 return False
1452
1453 self.wfile.close()
1454 return True
1455
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001456 def RangeResetHandler(self):
1457 """Send data broken up by connection resets every N (default 4K) bytes.
1458 Support range requests. If the data requested doesn't straddle a reset
1459 boundary, it will all be sent. Used for testing resuming downloads."""
1460
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001461 def DataForRange(start, end):
1462 """Data to be provided for a particular range of bytes."""
1463 # Offset and scale to avoid too obvious (and hence potentially
1464 # collidable) data.
1465 return ''.join([chr(y % 256)
1466 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1467
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001468 if not self._ShouldHandleRequest('/rangereset'):
1469 return False
1470
1471 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1472
1473 # Defaults
1474 size = 8000
1475 # Note that the rst is sent just before sending the rst_boundary byte.
1476 rst_boundary = 4000
1477 respond_to_range = True
1478 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001479 rst_limit = -1
1480 token = 'DEFAULT'
1481 fail_precondition = 0
1482 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001483
1484 # Parse the query
1485 qdict = urlparse.parse_qs(query, True)
1486 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001487 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001488 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001489 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001490 if 'token' in qdict:
1491 # Identifying token for stateful tests.
1492 token = qdict['token'][0]
1493 if 'rst_limit' in qdict:
1494 # Max number of rsts for a given token.
1495 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001496 if 'bounce_range' in qdict:
1497 respond_to_range = False
1498 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001499 # Note that hold_for_signal will not work with null range requests;
1500 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001501 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001502 if 'no_verifiers' in qdict:
1503 send_verifiers = False
1504 if 'fail_precondition' in qdict:
1505 fail_precondition = int(qdict['fail_precondition'][0])
1506
1507 # Record already set information, or set it.
1508 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1509 if rst_limit != 0:
1510 TestPageHandler.rst_limits[token] -= 1
1511 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1512 token, fail_precondition)
1513 if fail_precondition != 0:
1514 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001515
1516 first_byte = 0
1517 last_byte = size - 1
1518
1519 # Does that define what we want to return, or do we need to apply
1520 # a range?
1521 range_response = False
1522 range_header = self.headers.getheader('range')
1523 if range_header and respond_to_range:
1524 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1525 if mo.group(1):
1526 first_byte = int(mo.group(1))
1527 if mo.group(2):
1528 last_byte = int(mo.group(2))
1529 if last_byte > size - 1:
1530 last_byte = size - 1
1531 range_response = True
1532 if last_byte < first_byte:
1533 return False
1534
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001535 if (fail_precondition and
1536 (self.headers.getheader('If-Modified-Since') or
1537 self.headers.getheader('If-Match'))):
1538 self.send_response(412)
1539 self.end_headers()
1540 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001541
1542 if range_response:
1543 self.send_response(206)
1544 self.send_header('Content-Range',
1545 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1546 else:
1547 self.send_response(200)
1548 self.send_header('Content-Type', 'application/octet-stream')
1549 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001550 if send_verifiers:
1551 self.send_header('Etag', '"XYZZY"')
1552 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001553 self.end_headers()
1554
1555 if hold_for_signal:
1556 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1557 # a single byte, the self.server.handle_request() below hangs
1558 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001559 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001560 first_byte = first_byte + 1
1561 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001562 self.server.wait_for_download = True
1563 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001564 self.server.handle_request()
1565
1566 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001567 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001568 # No RST has been requested in this range, so we don't need to
1569 # do anything fancy; just write the data and let the python
1570 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001571 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001572 self.wfile.flush()
1573 return True
1574
1575 # We're resetting the connection part way in; go to the RST
1576 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001577 # Because socket semantics do not guarantee that all the data will be
1578 # sent when using the linger semantics to hard close a socket,
1579 # we send the data and then wait for our peer to release us
1580 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001581 data = DataForRange(first_byte, possible_rst)
1582 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001583 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001584 self.server.wait_for_download = True
1585 while self.server.wait_for_download:
1586 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001587 l_onoff = 1 # Linger is active.
1588 l_linger = 0 # Seconds to linger for.
1589 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1590 struct.pack('ii', l_onoff, l_linger))
1591
1592 # Close all duplicates of the underlying socket to force the RST.
1593 self.wfile.close()
1594 self.rfile.close()
1595 self.connection.close()
1596
1597 return True
1598
initial.commit94958cf2008-07-26 22:42:52 +00001599 def DefaultResponseHandler(self):
1600 """This is the catch-all response handler for requests that aren't handled
1601 by one of the special handlers above.
1602 Note that we specify the content-length as without it the https connection
1603 is not closed properly (and the browser keeps expecting data)."""
1604
1605 contents = "Default response given for path: " + self.path
1606 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001607 self.send_header('Content-Type', 'text/html')
1608 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001609 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001610 if (self.command != 'HEAD'):
1611 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001612 return True
1613
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001614 def RedirectConnectHandler(self):
1615 """Sends a redirect to the CONNECT request for www.redirect.com. This
1616 response is not specified by the RFC, so the browser should not follow
1617 the redirect."""
1618
1619 if (self.path.find("www.redirect.com") < 0):
1620 return False
1621
1622 dest = "http://www.destination.com/foo.js"
1623
1624 self.send_response(302) # moved temporarily
1625 self.send_header('Location', dest)
1626 self.send_header('Connection', 'close')
1627 self.end_headers()
1628 return True
1629
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001630 def ServerAuthConnectHandler(self):
1631 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1632 response doesn't make sense because the proxy server cannot request
1633 server authentication."""
1634
1635 if (self.path.find("www.server-auth.com") < 0):
1636 return False
1637
1638 challenge = 'Basic realm="WallyWorld"'
1639
1640 self.send_response(401) # unauthorized
1641 self.send_header('WWW-Authenticate', challenge)
1642 self.send_header('Connection', 'close')
1643 self.end_headers()
1644 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001645
1646 def DefaultConnectResponseHandler(self):
1647 """This is the catch-all response handler for CONNECT requests that aren't
1648 handled by one of the special handlers above. Real Web servers respond
1649 with 400 to CONNECT requests."""
1650
1651 contents = "Your client has issued a malformed or illegal request."
1652 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001653 self.send_header('Content-Type', 'text/html')
1654 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001655 self.end_headers()
1656 self.wfile.write(contents)
1657 return True
1658
initial.commit94958cf2008-07-26 22:42:52 +00001659 # called by the redirect handling function when there is no parameter
1660 def sendRedirectHelp(self, redirect_name):
1661 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001662 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001663 self.end_headers()
1664 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1665 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1666 self.wfile.write('</body></html>')
1667
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001668 # called by chunked handling function
1669 def sendChunkHelp(self, chunk):
1670 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1671 self.wfile.write('%X\r\n' % len(chunk))
1672 self.wfile.write(chunk)
1673 self.wfile.write('\r\n')
1674
akalin@chromium.org154bb132010-11-12 02:20:27 +00001675
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001676class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001677 def __init__(self, request, client_address, socket_server):
1678 handlers = [self.OCSPResponse]
1679 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001680 testserver_base.BasePageHandler.__init__(self, request, client_address,
1681 socket_server, [], handlers, [],
1682 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001683
1684 def OCSPResponse(self):
1685 self.send_response(200)
1686 self.send_header('Content-Type', 'application/ocsp-response')
1687 self.send_header('Content-Length', str(len(self.ocsp_response)))
1688 self.end_headers()
1689
1690 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001691
mattm@chromium.org830a3712012-11-07 23:00:07 +00001692
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001693class TCPEchoHandler(SocketServer.BaseRequestHandler):
1694 """The RequestHandler class for TCP echo server.
1695
1696 It is instantiated once per connection to the server, and overrides the
1697 handle() method to implement communication to the client.
1698 """
1699
1700 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001701 """Handles the request from the client and constructs a response."""
1702
1703 data = self.request.recv(65536).strip()
1704 # Verify the "echo request" message received from the client. Send back
1705 # "echo response" message if "echo request" message is valid.
1706 try:
1707 return_data = echo_message.GetEchoResponseData(data)
1708 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001709 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001710 except ValueError:
1711 return
1712
1713 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001714
1715
1716class UDPEchoHandler(SocketServer.BaseRequestHandler):
1717 """The RequestHandler class for UDP echo server.
1718
1719 It is instantiated once per connection to the server, and overrides the
1720 handle() method to implement communication to the client.
1721 """
1722
1723 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001724 """Handles the request from the client and constructs a response."""
1725
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001726 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001727 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001728 # Verify the "echo request" message received from the client. Send back
1729 # "echo response" message if "echo request" message is valid.
1730 try:
1731 return_data = echo_message.GetEchoResponseData(data)
1732 if not return_data:
1733 return
1734 except ValueError:
1735 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001736 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001737
1738
bashi@chromium.org33233532012-09-08 17:37:24 +00001739class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1740 """A request handler that behaves as a proxy server which requires
1741 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1742 """
1743
1744 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1745
1746 def parse_request(self):
1747 """Overrides parse_request to check credential."""
1748
1749 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1750 return False
1751
1752 auth = self.headers.getheader('Proxy-Authorization')
1753 if auth != self._AUTH_CREDENTIAL:
1754 self.send_response(407)
1755 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1756 self.end_headers()
1757 return False
1758
1759 return True
1760
1761 def _start_read_write(self, sock):
1762 sock.setblocking(0)
1763 self.request.setblocking(0)
1764 rlist = [self.request, sock]
1765 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001766 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001767 if errors:
1768 self.send_response(500)
1769 self.end_headers()
1770 return
1771 for s in ready_sockets:
1772 received = s.recv(1024)
1773 if len(received) == 0:
1774 return
1775 if s == self.request:
1776 other = sock
1777 else:
1778 other = self.request
1779 other.send(received)
1780
1781 def _do_common_method(self):
1782 url = urlparse.urlparse(self.path)
1783 port = url.port
1784 if not port:
1785 if url.scheme == 'http':
1786 port = 80
1787 elif url.scheme == 'https':
1788 port = 443
1789 if not url.hostname or not port:
1790 self.send_response(400)
1791 self.end_headers()
1792 return
1793
1794 if len(url.path) == 0:
1795 path = '/'
1796 else:
1797 path = url.path
1798 if len(url.query) > 0:
1799 path = '%s?%s' % (url.path, url.query)
1800
1801 sock = None
1802 try:
1803 sock = socket.create_connection((url.hostname, port))
1804 sock.send('%s %s %s\r\n' % (
1805 self.command, path, self.protocol_version))
1806 for header in self.headers.headers:
1807 header = header.strip()
1808 if (header.lower().startswith('connection') or
1809 header.lower().startswith('proxy')):
1810 continue
1811 sock.send('%s\r\n' % header)
1812 sock.send('\r\n')
1813 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001814 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001815 self.send_response(500)
1816 self.end_headers()
1817 finally:
1818 if sock is not None:
1819 sock.close()
1820
1821 def do_CONNECT(self):
1822 try:
1823 pos = self.path.rfind(':')
1824 host = self.path[:pos]
1825 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001826 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001827 self.send_response(400)
1828 self.end_headers()
1829
1830 try:
1831 sock = socket.create_connection((host, port))
1832 self.send_response(200, 'Connection established')
1833 self.end_headers()
1834 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001835 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001836 self.send_response(500)
1837 self.end_headers()
1838 finally:
1839 sock.close()
1840
1841 def do_GET(self):
1842 self._do_common_method()
1843
1844 def do_HEAD(self):
1845 self._do_common_method()
1846
1847
mattm@chromium.org830a3712012-11-07 23:00:07 +00001848class ServerRunner(testserver_base.TestServerRunner):
1849 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001850
mattm@chromium.org830a3712012-11-07 23:00:07 +00001851 def __init__(self):
1852 super(ServerRunner, self).__init__()
1853 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001854
mattm@chromium.org830a3712012-11-07 23:00:07 +00001855 def __make_data_dir(self):
1856 if self.options.data_dir:
1857 if not os.path.isdir(self.options.data_dir):
1858 raise testserver_base.OptionError('specified data dir not found: ' +
1859 self.options.data_dir + ' exiting...')
1860 my_data_dir = self.options.data_dir
1861 else:
1862 # Create the default path to our data dir, relative to the exe dir.
1863 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1864 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001865
mattm@chromium.org830a3712012-11-07 23:00:07 +00001866 #TODO(ibrar): Must use Find* funtion defined in google\tools
1867 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001868
mattm@chromium.org830a3712012-11-07 23:00:07 +00001869 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001870
mattm@chromium.org830a3712012-11-07 23:00:07 +00001871 def create_server(self, server_data):
1872 port = self.options.port
1873 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001874
mattm@chromium.org830a3712012-11-07 23:00:07 +00001875 if self.options.server_type == SERVER_HTTP:
1876 if self.options.https:
1877 pem_cert_and_key = None
1878 if self.options.cert_and_key_file:
1879 if not os.path.isfile(self.options.cert_and_key_file):
1880 raise testserver_base.OptionError(
1881 'specified server cert file not found: ' +
1882 self.options.cert_and_key_file + ' exiting...')
1883 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001884 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001885 # generate a new certificate and run an OCSP server for it.
1886 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001887 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001888 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001889
mattm@chromium.org830a3712012-11-07 23:00:07 +00001890 ocsp_der = None
1891 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001892
mattm@chromium.org830a3712012-11-07 23:00:07 +00001893 if self.options.ocsp == 'ok':
1894 ocsp_state = minica.OCSP_STATE_GOOD
1895 elif self.options.ocsp == 'revoked':
1896 ocsp_state = minica.OCSP_STATE_REVOKED
1897 elif self.options.ocsp == 'invalid':
1898 ocsp_state = minica.OCSP_STATE_INVALID
1899 elif self.options.ocsp == 'unauthorized':
1900 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1901 elif self.options.ocsp == 'unknown':
1902 ocsp_state = minica.OCSP_STATE_UNKNOWN
1903 else:
1904 raise testserver_base.OptionError('unknown OCSP status: ' +
1905 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001906
mattm@chromium.org830a3712012-11-07 23:00:07 +00001907 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1908 subject = "127.0.0.1",
1909 ocsp_url = ("http://%s:%d/ocsp" %
1910 (host, self.__ocsp_server.server_port)),
1911 ocsp_state = ocsp_state)
1912
1913 self.__ocsp_server.ocsp_response = ocsp_der
1914
1915 for ca_cert in self.options.ssl_client_ca:
1916 if not os.path.isfile(ca_cert):
1917 raise testserver_base.OptionError(
1918 'specified trusted client CA file not found: ' + ca_cert +
1919 ' exiting...')
1920 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1921 self.options.ssl_client_auth,
1922 self.options.ssl_client_ca,
1923 self.options.ssl_bulk_cipher,
1924 self.options.record_resume,
1925 self.options.tls_intolerant)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001926 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001927 else:
1928 server = HTTPServer((host, port), TestPageHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001929 print 'HTTP server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001930
1931 server.data_dir = self.__make_data_dir()
1932 server.file_root_url = self.options.file_root_url
1933 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001934 elif self.options.server_type == SERVER_WEBSOCKET:
1935 # Launch pywebsocket via WebSocketServer.
1936 logger = logging.getLogger()
1937 logger.addHandler(logging.StreamHandler())
1938 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1939 # is required to work correctly. It should be fixed from pywebsocket side.
1940 os.chdir(self.__make_data_dir())
1941 websocket_options = WebSocketOptions(host, port, '.')
1942 if self.options.cert_and_key_file:
1943 websocket_options.use_tls = True
1944 websocket_options.private_key = self.options.cert_and_key_file
1945 websocket_options.certificate = self.options.cert_and_key_file
1946 if self.options.ssl_client_auth:
1947 websocket_options.tls_client_auth = True
1948 if len(self.options.ssl_client_ca) != 1:
1949 raise testserver_base.OptionError(
1950 'one trusted client CA file should be specified')
1951 if not os.path.isfile(self.options.ssl_client_ca[0]):
1952 raise testserver_base.OptionError(
1953 'specified trusted client CA file not found: ' +
1954 self.options.ssl_client_ca[0] + ' exiting...')
1955 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
1956 server = WebSocketServer(websocket_options)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001957 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001958 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001959 elif self.options.server_type == SERVER_TCP_ECHO:
1960 # Used for generating the key (randomly) that encodes the "echo request"
1961 # message.
1962 random.seed()
1963 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001964 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001965 server_data['port'] = server.server_port
1966 elif self.options.server_type == SERVER_UDP_ECHO:
1967 # Used for generating the key (randomly) that encodes the "echo request"
1968 # message.
1969 random.seed()
1970 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001971 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001972 server_data['port'] = server.server_port
1973 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
1974 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001975 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001976 server_data['port'] = server.server_port
1977 elif self.options.server_type == SERVER_FTP:
1978 my_data_dir = self.__make_data_dir()
1979
1980 # Instantiate a dummy authorizer for managing 'virtual' users
1981 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1982
1983 # Define a new user having full r/w permissions and a read-only
1984 # anonymous user
1985 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1986
1987 authorizer.add_anonymous(my_data_dir)
1988
1989 # Instantiate FTP handler class
1990 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1991 ftp_handler.authorizer = authorizer
1992
1993 # Define a customized banner (string returned when client connects)
1994 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1995 pyftpdlib.ftpserver.__ver__)
1996
1997 # Instantiate FTP server class and listen to address:port
1998 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
1999 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002000 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002001 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002002 raise testserver_base.OptionError('unknown server type' +
2003 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002004
mattm@chromium.org830a3712012-11-07 23:00:07 +00002005 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002006
mattm@chromium.org830a3712012-11-07 23:00:07 +00002007 def run_server(self):
2008 if self.__ocsp_server:
2009 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002010
mattm@chromium.org830a3712012-11-07 23:00:07 +00002011 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002012
mattm@chromium.org830a3712012-11-07 23:00:07 +00002013 if self.__ocsp_server:
2014 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002015
mattm@chromium.org830a3712012-11-07 23:00:07 +00002016 def add_options(self):
2017 testserver_base.TestServerRunner.add_options(self)
2018 self.option_parser.add_option('-f', '--ftp', action='store_const',
2019 const=SERVER_FTP, default=SERVER_HTTP,
2020 dest='server_type',
2021 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002022 self.option_parser.add_option('--tcp-echo', action='store_const',
2023 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2024 dest='server_type',
2025 help='start up a tcp echo server.')
2026 self.option_parser.add_option('--udp-echo', action='store_const',
2027 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2028 dest='server_type',
2029 help='start up a udp echo server.')
2030 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2031 const=SERVER_BASIC_AUTH_PROXY,
2032 default=SERVER_HTTP, dest='server_type',
2033 help='start up a proxy server which requires '
2034 'basic authentication.')
2035 self.option_parser.add_option('--websocket', action='store_const',
2036 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2037 dest='server_type',
2038 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002039 self.option_parser.add_option('--https', action='store_true',
2040 dest='https', help='Specify that https '
2041 'should be used.')
2042 self.option_parser.add_option('--cert-and-key-file',
2043 dest='cert_and_key_file', help='specify the '
2044 'path to the file containing the certificate '
2045 'and private key for the server in PEM '
2046 'format')
2047 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2048 help='The type of OCSP response generated '
2049 'for the automatically generated '
2050 'certificate. One of [ok,revoked,invalid]')
2051 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2052 default='0', type='int',
2053 help='If nonzero, certain TLS connections '
2054 'will be aborted in order to test version '
2055 'fallback. 1 means all TLS versions will be '
2056 'aborted. 2 means TLS 1.1 or higher will be '
2057 'aborted. 3 means TLS 1.2 or higher will be '
2058 'aborted.')
2059 self.option_parser.add_option('--https-record-resume',
2060 dest='record_resume', const=True,
2061 default=False, action='store_const',
2062 help='Record resumption cache events rather '
2063 'than resuming as normal. Allows the use of '
2064 'the /ssl-session-cache request')
2065 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2066 help='Require SSL client auth on every '
2067 'connection.')
2068 self.option_parser.add_option('--ssl-client-ca', action='append',
2069 default=[], help='Specify that the client '
2070 'certificate request should include the CA '
2071 'named in the subject of the DER-encoded '
2072 'certificate contained in the specified '
2073 'file. This option may appear multiple '
2074 'times, indicating multiple CA names should '
2075 'be sent in the request.')
2076 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2077 help='Specify the bulk encryption '
2078 'algorithm(s) that will be accepted by the '
2079 'SSL server. Valid values are "aes256", '
2080 '"aes128", "3des", "rc4". If omitted, all '
2081 'algorithms will be used. This option may '
2082 'appear multiple times, indicating '
2083 'multiple algorithms should be enabled.');
2084 self.option_parser.add_option('--file-root-url', default='/files/',
2085 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002086
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002087
initial.commit94958cf2008-07-26 22:42:52 +00002088if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002089 sys.exit(ServerRunner().main())