blob: f028497e67c4ba266f3fe9a916a4db7701f303af [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00002# Copyright 2013 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00006"""This is a simple HTTP/FTP/TCP/UDP/BASIC_AUTH_PROXY/WEBSOCKET server used for
7testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00008
9It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000010By default, it listens on an ephemeral port and sends the port number back to
11the originating process over a pipe. The originating process can specify an
12explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000013It can use https if you specify the flag --https=CERT where CERT is the path
14to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000015"""
16
17import base64
18import BaseHTTPServer
19import cgi
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000020import hashlib
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000021import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000022import minica
initial.commit94958cf2008-07-26 22:42:52 +000023import os
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +000024import json
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000025import random
initial.commit94958cf2008-07-26 22:42:52 +000026import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000028import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000029import SocketServer
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000030import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000031import sys
32import threading
initial.commit94958cf2008-07-26 22:42:52 +000033import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000034import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000035import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000036import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000037
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000038import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000039import pyftpdlib.ftpserver
mattm@chromium.org830a3712012-11-07 23:00:07 +000040import testserver_base
initial.commit94958cf2008-07-26 22:42:52 +000041import tlslite
42import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000043
mattm@chromium.org830a3712012-11-07 23:00:07 +000044BASE_DIR = os.path.dirname(os.path.abspath(__file__))
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000045sys.path.insert(
46 0, os.path.join(BASE_DIR, '..', '..', '..', 'third_party/pywebsocket/src'))
47from mod_pywebsocket.standalone import WebSocketServer
davidben@chromium.org06fcf202010-09-22 18:15:23 +000048
maruel@chromium.org756cf982009-03-05 12:46:38 +000049SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000050SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000051SERVER_TCP_ECHO = 2
52SERVER_UDP_ECHO = 3
53SERVER_BASIC_AUTH_PROXY = 4
54SERVER_WEBSOCKET = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000055
56# Default request queue size for WebSocketServer.
57_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000058
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000059class WebSocketOptions:
60 """Holds options for WebSocketServer."""
61
62 def __init__(self, host, port, data_dir):
63 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
64 self.server_host = host
65 self.port = port
66 self.websock_handlers = data_dir
67 self.scan_dir = None
68 self.allow_handlers_outside_root_dir = False
69 self.websock_handlers_map_file = None
70 self.cgi_directories = []
71 self.is_executable_method = None
72 self.allow_draft75 = False
73 self.strict = True
74
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000075 self.use_tls = False
76 self.private_key = None
77 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000078 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000079 self.tls_client_ca = None
80 self.use_basic_auth = False
81
mattm@chromium.org830a3712012-11-07 23:00:07 +000082
agl@chromium.orgf9e66792011-12-12 22:22:19 +000083class RecordingSSLSessionCache(object):
84 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
85 lookups and inserts in order to test session cache behaviours."""
86
87 def __init__(self):
88 self.log = []
89
90 def __getitem__(self, sessionID):
91 self.log.append(('lookup', sessionID))
92 raise KeyError()
93
94 def __setitem__(self, sessionID, session):
95 self.log.append(('insert', sessionID))
96
erikwright@chromium.org847ef282012-02-22 16:41:10 +000097
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000098class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
99 testserver_base.BrokenPipeHandlerMixIn,
100 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000101 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000102 verification."""
103
104 pass
105
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000106class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
107 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000108 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000109 """This is a specialization of HTTPServer that serves an
110 OCSP response"""
111
112 def serve_forever_on_thread(self):
113 self.thread = threading.Thread(target = self.serve_forever,
114 name = "OCSPServerThread")
115 self.thread.start()
116
117 def stop_serving(self):
118 self.shutdown()
119 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000120
mattm@chromium.org830a3712012-11-07 23:00:07 +0000121
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000122class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000123 testserver_base.ClientRestrictingServerMixIn,
124 testserver_base.BrokenPipeHandlerMixIn,
125 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000126 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000127 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000128
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000129 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000130 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000131 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000132 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000133 # Force using only python implementation - otherwise behavior is different
134 # depending on whether m2crypto Python module is present (error is thrown
135 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
136 # the hood.
137 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
138 private=True,
139 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000140 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000141 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000142 self.tls_intolerant = tls_intolerant
143
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000144 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000145 s = open(ca_file).read()
146 x509 = tlslite.api.X509()
147 x509.parse(s)
148 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000149 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
150 if ssl_bulk_ciphers is not None:
151 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000152
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000153 if record_resume_info:
154 # If record_resume_info is true then we'll replace the session cache with
155 # an object that records the lookups and inserts that it sees.
156 self.session_cache = RecordingSSLSessionCache()
157 else:
158 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000159 testserver_base.StoppableHTTPServer.__init__(self,
160 server_address,
161 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000162
163 def handshake(self, tlsConnection):
164 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000165
initial.commit94958cf2008-07-26 22:42:52 +0000166 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000167 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000168 tlsConnection.handshakeServer(certChain=self.cert_chain,
169 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000170 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000171 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000172 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000173 reqCAs=self.ssl_client_cas,
174 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000175 tlsConnection.ignoreAbruptClose = True
176 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000177 except tlslite.api.TLSAbruptCloseError:
178 # Ignore abrupt close.
179 return True
initial.commit94958cf2008-07-26 22:42:52 +0000180 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000181 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000182 return False
183
akalin@chromium.org154bb132010-11-12 02:20:27 +0000184
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000185class FTPServer(testserver_base.ClientRestrictingServerMixIn,
186 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000187 """This is a specialization of FTPServer that adds client verification."""
188
189 pass
190
191
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000192class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
193 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000194 """A TCP echo server that echoes back what it has received."""
195
196 def server_bind(self):
197 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000198
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000199 SocketServer.TCPServer.server_bind(self)
200 host, port = self.socket.getsockname()[:2]
201 self.server_name = socket.getfqdn(host)
202 self.server_port = port
203
204 def serve_forever(self):
205 self.stop = False
206 self.nonce_time = None
207 while not self.stop:
208 self.handle_request()
209 self.socket.close()
210
211
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000212class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
213 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000214 """A UDP echo server that echoes back what it has received."""
215
216 def server_bind(self):
217 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000218
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000219 SocketServer.UDPServer.server_bind(self)
220 host, port = self.socket.getsockname()[:2]
221 self.server_name = socket.getfqdn(host)
222 self.server_port = port
223
224 def serve_forever(self):
225 self.stop = False
226 self.nonce_time = None
227 while not self.stop:
228 self.handle_request()
229 self.socket.close()
230
231
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000232class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000233 # Class variables to allow for persistence state between page handler
234 # invocations
235 rst_limits = {}
236 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000237
238 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000239 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000240 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000241 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000242 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000243 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000244 self.NoCacheMaxAgeTimeHandler,
245 self.NoCacheTimeHandler,
246 self.CacheTimeHandler,
247 self.CacheExpiresHandler,
248 self.CacheProxyRevalidateHandler,
249 self.CachePrivateHandler,
250 self.CachePublicHandler,
251 self.CacheSMaxAgeHandler,
252 self.CacheMustRevalidateHandler,
253 self.CacheMustRevalidateMaxAgeHandler,
254 self.CacheNoStoreHandler,
255 self.CacheNoStoreMaxAgeHandler,
256 self.CacheNoTransformHandler,
257 self.DownloadHandler,
258 self.DownloadFinishHandler,
259 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000260 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000261 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000262 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000263 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000264 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000265 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000266 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000267 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000268 self.AuthBasicHandler,
269 self.AuthDigestHandler,
270 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000271 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000272 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000273 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000274 self.ServerRedirectHandler,
275 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000276 self.MultipartHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000277 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000278 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000279 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000280 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000281 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000282 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000283 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000284 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000285 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000286 self.PostOnlyFileHandler,
287 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000288 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000289 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000290 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000291 head_handlers = [
292 self.FileHandler,
293 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000294
maruel@google.come250a9b2009-03-10 17:39:46 +0000295 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000296 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000297 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000298 'gif': 'image/gif',
299 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000300 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000301 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000302 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000303 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000304 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000305 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000306 }
initial.commit94958cf2008-07-26 22:42:52 +0000307 self._default_mime_type = 'text/html'
308
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000309 testserver_base.BasePageHandler.__init__(self, request, client_address,
310 socket_server, connect_handlers,
311 get_handlers, head_handlers,
312 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000313
initial.commit94958cf2008-07-26 22:42:52 +0000314 def GetMIMETypeFromName(self, file_name):
315 """Returns the mime type for the specified file_name. So far it only looks
316 at the file extension."""
317
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000318 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000319 if len(extension) == 0:
320 # no extension.
321 return self._default_mime_type
322
ericroman@google.comc17ca532009-05-07 03:51:05 +0000323 # extension starts with a dot, so we need to remove it
324 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000325
initial.commit94958cf2008-07-26 22:42:52 +0000326 def NoCacheMaxAgeTimeHandler(self):
327 """This request handler yields a page with the title set to the current
328 system time, and no caching requested."""
329
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000330 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000331 return False
332
333 self.send_response(200)
334 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000335 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000336 self.end_headers()
337
maruel@google.come250a9b2009-03-10 17:39:46 +0000338 self.wfile.write('<html><head><title>%s</title></head></html>' %
339 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000340
341 return True
342
343 def NoCacheTimeHandler(self):
344 """This request handler yields a page with the title set to the current
345 system time, and no caching requested."""
346
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000347 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000348 return False
349
350 self.send_response(200)
351 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000352 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000353 self.end_headers()
354
maruel@google.come250a9b2009-03-10 17:39:46 +0000355 self.wfile.write('<html><head><title>%s</title></head></html>' %
356 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000357
358 return True
359
360 def CacheTimeHandler(self):
361 """This request handler yields a page with the title set to the current
362 system time, and allows caching for one minute."""
363
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000364 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000365 return False
366
367 self.send_response(200)
368 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000369 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000370 self.end_headers()
371
maruel@google.come250a9b2009-03-10 17:39:46 +0000372 self.wfile.write('<html><head><title>%s</title></head></html>' %
373 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000374
375 return True
376
377 def CacheExpiresHandler(self):
378 """This request handler yields a page with the title set to the current
379 system time, and set the page to expire on 1 Jan 2099."""
380
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000381 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000382 return False
383
384 self.send_response(200)
385 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000386 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000387 self.end_headers()
388
maruel@google.come250a9b2009-03-10 17:39:46 +0000389 self.wfile.write('<html><head><title>%s</title></head></html>' %
390 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000391
392 return True
393
394 def CacheProxyRevalidateHandler(self):
395 """This request handler yields a page with the title set to the current
396 system time, and allows caching for 60 seconds"""
397
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000398 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000399 return False
400
401 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000402 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000403 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
404 self.end_headers()
405
maruel@google.come250a9b2009-03-10 17:39:46 +0000406 self.wfile.write('<html><head><title>%s</title></head></html>' %
407 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000408
409 return True
410
411 def CachePrivateHandler(self):
412 """This request handler yields a page with the title set to the current
413 system time, and allows caching for 5 seconds."""
414
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000415 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000416 return False
417
418 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000419 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000420 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000421 self.end_headers()
422
maruel@google.come250a9b2009-03-10 17:39:46 +0000423 self.wfile.write('<html><head><title>%s</title></head></html>' %
424 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000425
426 return True
427
428 def CachePublicHandler(self):
429 """This request handler yields a page with the title set to the current
430 system time, and allows caching for 5 seconds."""
431
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000432 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000433 return False
434
435 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000436 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000437 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000438 self.end_headers()
439
maruel@google.come250a9b2009-03-10 17:39:46 +0000440 self.wfile.write('<html><head><title>%s</title></head></html>' %
441 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000442
443 return True
444
445 def CacheSMaxAgeHandler(self):
446 """This request handler yields a page with the title set to the current
447 system time, and does not allow for caching."""
448
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000449 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000450 return False
451
452 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000453 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000454 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
455 self.end_headers()
456
maruel@google.come250a9b2009-03-10 17:39:46 +0000457 self.wfile.write('<html><head><title>%s</title></head></html>' %
458 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000459
460 return True
461
462 def CacheMustRevalidateHandler(self):
463 """This request handler yields a page with the title set to the current
464 system time, and does not allow caching."""
465
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000466 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000467 return False
468
469 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000470 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000471 self.send_header('Cache-Control', 'must-revalidate')
472 self.end_headers()
473
maruel@google.come250a9b2009-03-10 17:39:46 +0000474 self.wfile.write('<html><head><title>%s</title></head></html>' %
475 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000476
477 return True
478
479 def CacheMustRevalidateMaxAgeHandler(self):
480 """This request handler yields a page with the title set to the current
481 system time, and does not allow caching event though max-age of 60
482 seconds is specified."""
483
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000484 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000485 return False
486
487 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000488 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000489 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
490 self.end_headers()
491
maruel@google.come250a9b2009-03-10 17:39:46 +0000492 self.wfile.write('<html><head><title>%s</title></head></html>' %
493 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000494
495 return True
496
initial.commit94958cf2008-07-26 22:42:52 +0000497 def CacheNoStoreHandler(self):
498 """This request handler yields a page with the title set to the current
499 system time, and does not allow the page to be stored."""
500
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000501 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000502 return False
503
504 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000505 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000506 self.send_header('Cache-Control', 'no-store')
507 self.end_headers()
508
maruel@google.come250a9b2009-03-10 17:39:46 +0000509 self.wfile.write('<html><head><title>%s</title></head></html>' %
510 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000511
512 return True
513
514 def CacheNoStoreMaxAgeHandler(self):
515 """This request handler yields a page with the title set to the current
516 system time, and does not allow the page to be stored even though max-age
517 of 60 seconds is specified."""
518
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000519 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000520 return False
521
522 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000523 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000524 self.send_header('Cache-Control', 'max-age=60, no-store')
525 self.end_headers()
526
maruel@google.come250a9b2009-03-10 17:39:46 +0000527 self.wfile.write('<html><head><title>%s</title></head></html>' %
528 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000529
530 return True
531
532
533 def CacheNoTransformHandler(self):
534 """This request handler yields a page with the title set to the current
535 system time, and does not allow the content to transformed during
536 user-agent caching"""
537
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000538 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000539 return False
540
541 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000542 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000543 self.send_header('Cache-Control', 'no-transform')
544 self.end_headers()
545
maruel@google.come250a9b2009-03-10 17:39:46 +0000546 self.wfile.write('<html><head><title>%s</title></head></html>' %
547 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000548
549 return True
550
551 def EchoHeader(self):
552 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000553
ananta@chromium.org219b2062009-10-23 16:09:41 +0000554 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000555
ananta@chromium.org56812d02011-04-07 17:52:05 +0000556 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000557 """This function echoes back the value of a specific request header while
558 allowing caching for 16 hours."""
559
ananta@chromium.org56812d02011-04-07 17:52:05 +0000560 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000561
562 def EchoHeaderHelper(self, echo_header):
563 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000564
ananta@chromium.org219b2062009-10-23 16:09:41 +0000565 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000566 return False
567
568 query_char = self.path.find('?')
569 if query_char != -1:
570 header_name = self.path[query_char+1:]
571
572 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000573 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000574 if echo_header == '/echoheadercache':
575 self.send_header('Cache-control', 'max-age=60000')
576 else:
577 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000578 # insert a vary header to properly indicate that the cachability of this
579 # request is subject to value of the request header being echoed.
580 if len(header_name) > 0:
581 self.send_header('Vary', header_name)
582 self.end_headers()
583
584 if len(header_name) > 0:
585 self.wfile.write(self.headers.getheader(header_name))
586
587 return True
588
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000589 def ReadRequestBody(self):
590 """This function reads the body of the current HTTP request, handling
591 both plain and chunked transfer encoded requests."""
592
593 if self.headers.getheader('transfer-encoding') != 'chunked':
594 length = int(self.headers.getheader('content-length'))
595 return self.rfile.read(length)
596
597 # Read the request body as chunks.
598 body = ""
599 while True:
600 line = self.rfile.readline()
601 length = int(line, 16)
602 if length == 0:
603 self.rfile.readline()
604 break
605 body += self.rfile.read(length)
606 self.rfile.read(2)
607 return body
608
initial.commit94958cf2008-07-26 22:42:52 +0000609 def EchoHandler(self):
610 """This handler just echoes back the payload of the request, for testing
611 form submission."""
612
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000613 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000614 return False
615
616 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000617 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000618 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000619 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000620 return True
621
622 def EchoTitleHandler(self):
623 """This handler is like Echo, but sets the page title to the request."""
624
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000625 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000626 return False
627
628 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000629 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000630 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000631 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000632 self.wfile.write('<html><head><title>')
633 self.wfile.write(request)
634 self.wfile.write('</title></head></html>')
635 return True
636
637 def EchoAllHandler(self):
638 """This handler yields a (more) human-readable page listing information
639 about the request header & contents."""
640
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000641 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000642 return False
643
644 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000645 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000646 self.end_headers()
647 self.wfile.write('<html><head><style>'
648 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
649 '</style></head><body>'
650 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000651 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000652 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000653
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000654 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000655 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000656 params = cgi.parse_qs(qs, keep_blank_values=1)
657
658 for param in params:
659 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000660
661 self.wfile.write('</pre>')
662
663 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
664
665 self.wfile.write('</body></html>')
666 return True
667
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000668 def EchoMultipartPostHandler(self):
669 """This handler echoes received multipart post data as json format."""
670
671 if not (self._ShouldHandleRequest("/echomultipartpost") or
672 self._ShouldHandleRequest("/searchbyimage")):
673 return False
674
675 content_type, parameters = cgi.parse_header(
676 self.headers.getheader('content-type'))
677 if content_type == 'multipart/form-data':
678 post_multipart = cgi.parse_multipart(self.rfile, parameters)
679 elif content_type == 'application/x-www-form-urlencoded':
680 raise Exception('POST by application/x-www-form-urlencoded is '
681 'not implemented.')
682 else:
683 post_multipart = {}
684
685 # Since the data can be binary, we encode them by base64.
686 post_multipart_base64_encoded = {}
687 for field, values in post_multipart.items():
688 post_multipart_base64_encoded[field] = [base64.b64encode(value)
689 for value in values]
690
691 result = {'POST_multipart' : post_multipart_base64_encoded}
692
693 self.send_response(200)
694 self.send_header("Content-type", "text/plain")
695 self.end_headers()
696 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
697 return True
698
initial.commit94958cf2008-07-26 22:42:52 +0000699 def DownloadHandler(self):
700 """This handler sends a downloadable file with or without reporting
701 the size (6K)."""
702
703 if self.path.startswith("/download-unknown-size"):
704 send_length = False
705 elif self.path.startswith("/download-known-size"):
706 send_length = True
707 else:
708 return False
709
710 #
711 # The test which uses this functionality is attempting to send
712 # small chunks of data to the client. Use a fairly large buffer
713 # so that we'll fill chrome's IO buffer enough to force it to
714 # actually write the data.
715 # See also the comments in the client-side of this test in
716 # download_uitest.cc
717 #
718 size_chunk1 = 35*1024
719 size_chunk2 = 10*1024
720
721 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000722 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000723 self.send_header('Cache-Control', 'max-age=0')
724 if send_length:
725 self.send_header('Content-Length', size_chunk1 + size_chunk2)
726 self.end_headers()
727
728 # First chunk of data:
729 self.wfile.write("*" * size_chunk1)
730 self.wfile.flush()
731
732 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000733 self.server.wait_for_download = True
734 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000735 self.server.handle_request()
736
737 # Second chunk of data:
738 self.wfile.write("*" * size_chunk2)
739 return True
740
741 def DownloadFinishHandler(self):
742 """This handler just tells the server to finish the current download."""
743
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000744 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000745 return False
746
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000747 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000748 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000749 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000750 self.send_header('Cache-Control', 'max-age=0')
751 self.end_headers()
752 return True
753
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000754 def _ReplaceFileData(self, data, query_parameters):
755 """Replaces matching substrings in a file.
756
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000757 If the 'replace_text' URL query parameter is present, it is expected to be
758 of the form old_text:new_text, which indicates that any old_text strings in
759 the file are replaced with new_text. Multiple 'replace_text' parameters may
760 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000761
762 If the parameters are not present, |data| is returned.
763 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000764
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000765 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000766 replace_text_values = query_dict.get('replace_text', [])
767 for replace_text_value in replace_text_values:
768 replace_text_args = replace_text_value.split(':')
769 if len(replace_text_args) != 2:
770 raise ValueError(
771 'replace_text must be of form old_text:new_text. Actual value: %s' %
772 replace_text_value)
773 old_text_b64, new_text_b64 = replace_text_args
774 old_text = base64.urlsafe_b64decode(old_text_b64)
775 new_text = base64.urlsafe_b64decode(new_text_b64)
776 data = data.replace(old_text, new_text)
777 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000778
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000779 def ZipFileHandler(self):
780 """This handler sends the contents of the requested file in compressed form.
781 Can pass in a parameter that specifies that the content length be
782 C - the compressed size (OK),
783 U - the uncompressed size (Non-standard, but handled),
784 S - less than compressed (OK because we keep going),
785 M - larger than compressed but less than uncompressed (an error),
786 L - larger than uncompressed (an error)
787 Example: compressedfiles/Picture_1.doc?C
788 """
789
790 prefix = "/compressedfiles/"
791 if not self.path.startswith(prefix):
792 return False
793
794 # Consume a request body if present.
795 if self.command == 'POST' or self.command == 'PUT' :
796 self.ReadRequestBody()
797
798 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
799
800 if not query in ('C', 'U', 'S', 'M', 'L'):
801 return False
802
803 sub_path = url_path[len(prefix):]
804 entries = sub_path.split('/')
805 file_path = os.path.join(self.server.data_dir, *entries)
806 if os.path.isdir(file_path):
807 file_path = os.path.join(file_path, 'index.html')
808
809 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000810 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000811 self.send_error(404)
812 return True
813
814 f = open(file_path, "rb")
815 data = f.read()
816 uncompressed_len = len(data)
817 f.close()
818
819 # Compress the data.
820 data = zlib.compress(data)
821 compressed_len = len(data)
822
823 content_length = compressed_len
824 if query == 'U':
825 content_length = uncompressed_len
826 elif query == 'S':
827 content_length = compressed_len / 2
828 elif query == 'M':
829 content_length = (compressed_len + uncompressed_len) / 2
830 elif query == 'L':
831 content_length = compressed_len + uncompressed_len
832
833 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000834 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000835 self.send_header('Content-encoding', 'deflate')
836 self.send_header('Connection', 'close')
837 self.send_header('Content-Length', content_length)
838 self.send_header('ETag', '\'' + file_path + '\'')
839 self.end_headers()
840
841 self.wfile.write(data)
842
843 return True
844
initial.commit94958cf2008-07-26 22:42:52 +0000845 def FileHandler(self):
846 """This handler sends the contents of the requested file. Wow, it's like
847 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000848
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000849 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000850 if not self.path.startswith(prefix):
851 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000852 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000853
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000854 def PostOnlyFileHandler(self):
855 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000856
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000857 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000858 if not self.path.startswith(prefix):
859 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000860 return self._FileHandlerHelper(prefix)
861
862 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000863 request_body = ''
864 if self.command == 'POST' or self.command == 'PUT':
865 # Consume a request body if present.
866 request_body = self.ReadRequestBody()
867
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000868 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000869 query_dict = cgi.parse_qs(query)
870
871 expected_body = query_dict.get('expected_body', [])
872 if expected_body and request_body not in expected_body:
873 self.send_response(404)
874 self.end_headers()
875 self.wfile.write('')
876 return True
877
878 expected_headers = query_dict.get('expected_headers', [])
879 for expected_header in expected_headers:
880 header_name, expected_value = expected_header.split(':')
881 if self.headers.getheader(header_name) != expected_value:
882 self.send_response(404)
883 self.end_headers()
884 self.wfile.write('')
885 return True
886
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000887 sub_path = url_path[len(prefix):]
888 entries = sub_path.split('/')
889 file_path = os.path.join(self.server.data_dir, *entries)
890 if os.path.isdir(file_path):
891 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000892
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000893 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000894 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000895 self.send_error(404)
896 return True
897
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000898 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000899 data = f.read()
900 f.close()
901
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000902 data = self._ReplaceFileData(data, query)
903
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000904 old_protocol_version = self.protocol_version
905
initial.commit94958cf2008-07-26 22:42:52 +0000906 # If file.mock-http-headers exists, it contains the headers we
907 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000908 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000909 if os.path.isfile(headers_path):
910 f = open(headers_path, "r")
911
912 # "HTTP/1.1 200 OK"
913 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000914 http_major, http_minor, status_code = re.findall(
915 'HTTP/(\d+).(\d+) (\d+)', response)[0]
916 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000917 self.send_response(int(status_code))
918
919 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000920 header_values = re.findall('(\S+):\s*(.*)', line)
921 if len(header_values) > 0:
922 # "name: value"
923 name, value = header_values[0]
924 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000925 f.close()
926 else:
927 # Could be more generic once we support mime-type sniffing, but for
928 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000929
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000930 range_header = self.headers.get('Range')
931 if range_header and range_header.startswith('bytes='):
932 # Note this doesn't handle all valid byte range_header values (i.e.
933 # left open ended ones), just enough for what we needed so far.
934 range_header = range_header[6:].split('-')
935 start = int(range_header[0])
936 if range_header[1]:
937 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000938 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000939 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000940
941 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000942 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
943 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000944 self.send_header('Content-Range', content_range)
945 data = data[start: end + 1]
946 else:
947 self.send_response(200)
948
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000949 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000950 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000951 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000952 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000953 self.end_headers()
954
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000955 if (self.command != 'HEAD'):
956 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000957
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000958 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000959 return True
960
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000961 def SetCookieHandler(self):
962 """This handler just sets a cookie, for testing cookie handling."""
963
964 if not self._ShouldHandleRequest("/set-cookie"):
965 return False
966
967 query_char = self.path.find('?')
968 if query_char != -1:
969 cookie_values = self.path[query_char + 1:].split('&')
970 else:
971 cookie_values = ("",)
972 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000973 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000974 for cookie_value in cookie_values:
975 self.send_header('Set-Cookie', '%s' % cookie_value)
976 self.end_headers()
977 for cookie_value in cookie_values:
978 self.wfile.write('%s' % cookie_value)
979 return True
980
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000981 def SetManyCookiesHandler(self):
982 """This handler just sets a given number of cookies, for testing handling
983 of large numbers of cookies."""
984
985 if not self._ShouldHandleRequest("/set-many-cookies"):
986 return False
987
988 query_char = self.path.find('?')
989 if query_char != -1:
990 num_cookies = int(self.path[query_char + 1:])
991 else:
992 num_cookies = 0
993 self.send_response(200)
994 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000995 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000996 self.send_header('Set-Cookie', 'a=')
997 self.end_headers()
998 self.wfile.write('%d cookies were sent' % num_cookies)
999 return True
1000
mattm@chromium.org983fc462012-06-30 00:52:08 +00001001 def ExpectAndSetCookieHandler(self):
1002 """Expects some cookies to be sent, and if they are, sets more cookies.
1003
1004 The expect parameter specifies a required cookie. May be specified multiple
1005 times.
1006 The set parameter specifies a cookie to set if all required cookies are
1007 preset. May be specified multiple times.
1008 The data parameter specifies the response body data to be returned."""
1009
1010 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1011 return False
1012
1013 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1014 query_dict = cgi.parse_qs(query)
1015 cookies = set()
1016 if 'Cookie' in self.headers:
1017 cookie_header = self.headers.getheader('Cookie')
1018 cookies.update([s.strip() for s in cookie_header.split(';')])
1019 got_all_expected_cookies = True
1020 for expected_cookie in query_dict.get('expect', []):
1021 if expected_cookie not in cookies:
1022 got_all_expected_cookies = False
1023 self.send_response(200)
1024 self.send_header('Content-Type', 'text/html')
1025 if got_all_expected_cookies:
1026 for cookie_value in query_dict.get('set', []):
1027 self.send_header('Set-Cookie', '%s' % cookie_value)
1028 self.end_headers()
1029 for data_value in query_dict.get('data', []):
1030 self.wfile.write(data_value)
1031 return True
1032
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001033 def SetHeaderHandler(self):
1034 """This handler sets a response header. Parameters are in the
1035 key%3A%20value&key2%3A%20value2 format."""
1036
1037 if not self._ShouldHandleRequest("/set-header"):
1038 return False
1039
1040 query_char = self.path.find('?')
1041 if query_char != -1:
1042 headers_values = self.path[query_char + 1:].split('&')
1043 else:
1044 headers_values = ("",)
1045 self.send_response(200)
1046 self.send_header('Content-Type', 'text/html')
1047 for header_value in headers_values:
1048 header_value = urllib.unquote(header_value)
1049 (key, value) = header_value.split(': ', 1)
1050 self.send_header(key, value)
1051 self.end_headers()
1052 for header_value in headers_values:
1053 self.wfile.write('%s' % header_value)
1054 return True
1055
initial.commit94958cf2008-07-26 22:42:52 +00001056 def AuthBasicHandler(self):
1057 """This handler tests 'Basic' authentication. It just sends a page with
1058 title 'user/pass' if you succeed."""
1059
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001060 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001061 return False
1062
1063 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001064 expected_password = 'secret'
1065 realm = 'testrealm'
1066 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001067
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001068 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1069 query_params = cgi.parse_qs(query, True)
1070 if 'set-cookie-if-challenged' in query_params:
1071 set_cookie_if_challenged = True
1072 if 'password' in query_params:
1073 expected_password = query_params['password'][0]
1074 if 'realm' in query_params:
1075 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001076
initial.commit94958cf2008-07-26 22:42:52 +00001077 auth = self.headers.getheader('authorization')
1078 try:
1079 if not auth:
1080 raise Exception('no auth')
1081 b64str = re.findall(r'Basic (\S+)', auth)[0]
1082 userpass = base64.b64decode(b64str)
1083 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001084 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001085 raise Exception('wrong password')
1086 except Exception, e:
1087 # Authentication failed.
1088 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001089 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001090 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001091 if set_cookie_if_challenged:
1092 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001093 self.end_headers()
1094 self.wfile.write('<html><head>')
1095 self.wfile.write('<title>Denied: %s</title>' % e)
1096 self.wfile.write('</head><body>')
1097 self.wfile.write('auth=%s<p>' % auth)
1098 self.wfile.write('b64str=%s<p>' % b64str)
1099 self.wfile.write('username: %s<p>' % username)
1100 self.wfile.write('userpass: %s<p>' % userpass)
1101 self.wfile.write('password: %s<p>' % password)
1102 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1103 self.wfile.write('</body></html>')
1104 return True
1105
1106 # Authentication successful. (Return a cachable response to allow for
1107 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001108 old_protocol_version = self.protocol_version
1109 self.protocol_version = "HTTP/1.1"
1110
initial.commit94958cf2008-07-26 22:42:52 +00001111 if_none_match = self.headers.getheader('if-none-match')
1112 if if_none_match == "abc":
1113 self.send_response(304)
1114 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001115 elif url_path.endswith(".gif"):
1116 # Using chrome/test/data/google/logo.gif as the test image
1117 test_image_path = ['google', 'logo.gif']
1118 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1119 if not os.path.isfile(gif_path):
1120 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001121 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001122 return True
1123
1124 f = open(gif_path, "rb")
1125 data = f.read()
1126 f.close()
1127
1128 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001129 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001130 self.send_header('Cache-control', 'max-age=60000')
1131 self.send_header('Etag', 'abc')
1132 self.end_headers()
1133 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001134 else:
1135 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001136 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001137 self.send_header('Cache-control', 'max-age=60000')
1138 self.send_header('Etag', 'abc')
1139 self.end_headers()
1140 self.wfile.write('<html><head>')
1141 self.wfile.write('<title>%s/%s</title>' % (username, password))
1142 self.wfile.write('</head><body>')
1143 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001144 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001145 self.wfile.write('</body></html>')
1146
rvargas@google.com54453b72011-05-19 01:11:11 +00001147 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001148 return True
1149
tonyg@chromium.org75054202010-03-31 22:06:10 +00001150 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001151 """Returns a nonce that's stable per request path for the server's lifetime.
1152 This is a fake implementation. A real implementation would only use a given
1153 nonce a single time (hence the name n-once). However, for the purposes of
1154 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001155
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001156 Args:
1157 force_reset: Iff set, the nonce will be changed. Useful for testing the
1158 "stale" response.
1159 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001160
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001161 if force_reset or not self.server.nonce_time:
1162 self.server.nonce_time = time.time()
1163 return hashlib.md5('privatekey%s%d' %
1164 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001165
1166 def AuthDigestHandler(self):
1167 """This handler tests 'Digest' authentication.
1168
1169 It just sends a page with title 'user/pass' if you succeed.
1170
1171 A stale response is sent iff "stale" is present in the request path.
1172 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001173
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001174 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001175 return False
1176
tonyg@chromium.org75054202010-03-31 22:06:10 +00001177 stale = 'stale' in self.path
1178 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001179 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001180 password = 'secret'
1181 realm = 'testrealm'
1182
1183 auth = self.headers.getheader('authorization')
1184 pairs = {}
1185 try:
1186 if not auth:
1187 raise Exception('no auth')
1188 if not auth.startswith('Digest'):
1189 raise Exception('not digest')
1190 # Pull out all the name="value" pairs as a dictionary.
1191 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1192
1193 # Make sure it's all valid.
1194 if pairs['nonce'] != nonce:
1195 raise Exception('wrong nonce')
1196 if pairs['opaque'] != opaque:
1197 raise Exception('wrong opaque')
1198
1199 # Check the 'response' value and make sure it matches our magic hash.
1200 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001201 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001202 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001203 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001204 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001205 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001206 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1207 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001208 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001209
1210 if pairs['response'] != response:
1211 raise Exception('wrong password')
1212 except Exception, e:
1213 # Authentication failed.
1214 self.send_response(401)
1215 hdr = ('Digest '
1216 'realm="%s", '
1217 'domain="/", '
1218 'qop="auth", '
1219 'algorithm=MD5, '
1220 'nonce="%s", '
1221 'opaque="%s"') % (realm, nonce, opaque)
1222 if stale:
1223 hdr += ', stale="TRUE"'
1224 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001225 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001226 self.end_headers()
1227 self.wfile.write('<html><head>')
1228 self.wfile.write('<title>Denied: %s</title>' % e)
1229 self.wfile.write('</head><body>')
1230 self.wfile.write('auth=%s<p>' % auth)
1231 self.wfile.write('pairs=%s<p>' % pairs)
1232 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1233 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1234 self.wfile.write('</body></html>')
1235 return True
1236
1237 # Authentication successful.
1238 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001239 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001240 self.end_headers()
1241 self.wfile.write('<html><head>')
1242 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1243 self.wfile.write('</head><body>')
1244 self.wfile.write('auth=%s<p>' % auth)
1245 self.wfile.write('pairs=%s<p>' % pairs)
1246 self.wfile.write('</body></html>')
1247
1248 return True
1249
1250 def SlowServerHandler(self):
1251 """Wait for the user suggested time before responding. The syntax is
1252 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001253
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001254 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001255 return False
1256 query_char = self.path.find('?')
1257 wait_sec = 1.0
1258 if query_char >= 0:
1259 try:
1260 wait_sec = int(self.path[query_char + 1:])
1261 except ValueError:
1262 pass
1263 time.sleep(wait_sec)
1264 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001265 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001266 self.end_headers()
1267 self.wfile.write("waited %d seconds" % wait_sec)
1268 return True
1269
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001270 def ChunkedServerHandler(self):
1271 """Send chunked response. Allows to specify chunks parameters:
1272 - waitBeforeHeaders - ms to wait before sending headers
1273 - waitBetweenChunks - ms to wait between chunks
1274 - chunkSize - size of each chunk in bytes
1275 - chunksNumber - number of chunks
1276 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1277 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001278
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001279 if not self._ShouldHandleRequest("/chunked"):
1280 return False
1281 query_char = self.path.find('?')
1282 chunkedSettings = {'waitBeforeHeaders' : 0,
1283 'waitBetweenChunks' : 0,
1284 'chunkSize' : 5,
1285 'chunksNumber' : 5}
1286 if query_char >= 0:
1287 params = self.path[query_char + 1:].split('&')
1288 for param in params:
1289 keyValue = param.split('=')
1290 if len(keyValue) == 2:
1291 try:
1292 chunkedSettings[keyValue[0]] = int(keyValue[1])
1293 except ValueError:
1294 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001295 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001296 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1297 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001298 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001299 self.send_header('Connection', 'close')
1300 self.send_header('Transfer-Encoding', 'chunked')
1301 self.end_headers()
1302 # Chunked encoding: sending all chunks, then final zero-length chunk and
1303 # then final CRLF.
1304 for i in range(0, chunkedSettings['chunksNumber']):
1305 if i > 0:
1306 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1307 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001308 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001309 self.sendChunkHelp('')
1310 return True
1311
initial.commit94958cf2008-07-26 22:42:52 +00001312 def ContentTypeHandler(self):
1313 """Returns a string of html with the given content type. E.g.,
1314 /contenttype?text/css returns an html file with the Content-Type
1315 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001316
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001317 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001318 return False
1319 query_char = self.path.find('?')
1320 content_type = self.path[query_char + 1:].strip()
1321 if not content_type:
1322 content_type = 'text/html'
1323 self.send_response(200)
1324 self.send_header('Content-Type', content_type)
1325 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001326 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001327 return True
1328
creis@google.com2f4f6a42011-03-25 19:44:19 +00001329 def NoContentHandler(self):
1330 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001331
creis@google.com2f4f6a42011-03-25 19:44:19 +00001332 if not self._ShouldHandleRequest("/nocontent"):
1333 return False
1334 self.send_response(204)
1335 self.end_headers()
1336 return True
1337
initial.commit94958cf2008-07-26 22:42:52 +00001338 def ServerRedirectHandler(self):
1339 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001340 '/server-redirect?http://foo.bar/asdf' to redirect to
1341 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001342
1343 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001344 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001345 return False
1346
1347 query_char = self.path.find('?')
1348 if query_char < 0 or len(self.path) <= query_char + 1:
1349 self.sendRedirectHelp(test_name)
1350 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001351 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001352
1353 self.send_response(301) # moved permanently
1354 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001355 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001356 self.end_headers()
1357 self.wfile.write('<html><head>')
1358 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1359
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001360 return True
initial.commit94958cf2008-07-26 22:42:52 +00001361
1362 def ClientRedirectHandler(self):
1363 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001364 '/client-redirect?http://foo.bar/asdf' to redirect to
1365 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001366
1367 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001368 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001369 return False
1370
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001371 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001372 if query_char < 0 or len(self.path) <= query_char + 1:
1373 self.sendRedirectHelp(test_name)
1374 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001375 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001376
1377 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001378 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001379 self.end_headers()
1380 self.wfile.write('<html><head>')
1381 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1382 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1383
1384 return True
1385
tony@chromium.org03266982010-03-05 03:18:42 +00001386 def MultipartHandler(self):
1387 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001388
tony@chromium.org4cb88302011-09-27 22:13:49 +00001389 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001390 if not self._ShouldHandleRequest(test_name):
1391 return False
1392
1393 num_frames = 10
1394 bound = '12345'
1395 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001396 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001397 'multipart/x-mixed-replace;boundary=' + bound)
1398 self.end_headers()
1399
1400 for i in xrange(num_frames):
1401 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001402 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001403 self.wfile.write('<title>page ' + str(i) + '</title>')
1404 self.wfile.write('page ' + str(i))
1405
1406 self.wfile.write('--' + bound + '--')
1407 return True
1408
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001409 def GetSSLSessionCacheHandler(self):
1410 """Send a reply containing a log of the session cache operations."""
1411
1412 if not self._ShouldHandleRequest('/ssl-session-cache'):
1413 return False
1414
1415 self.send_response(200)
1416 self.send_header('Content-Type', 'text/plain')
1417 self.end_headers()
1418 try:
1419 for (action, sessionID) in self.server.session_cache.log:
1420 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001421 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001422 self.wfile.write('Pass --https-record-resume in order to use' +
1423 ' this request')
1424 return True
1425
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001426 def SSLManySmallRecords(self):
1427 """Sends a reply consisting of a variety of small writes. These will be
1428 translated into a series of small SSL records when used over an HTTPS
1429 server."""
1430
1431 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1432 return False
1433
1434 self.send_response(200)
1435 self.send_header('Content-Type', 'text/plain')
1436 self.end_headers()
1437
1438 # Write ~26K of data, in 1350 byte chunks
1439 for i in xrange(20):
1440 self.wfile.write('*' * 1350)
1441 self.wfile.flush()
1442 return True
1443
agl@chromium.org04700be2013-03-02 18:40:41 +00001444 def GetChannelID(self):
1445 """Send a reply containing the hashed ChannelID that the client provided."""
1446
1447 if not self._ShouldHandleRequest('/channel-id'):
1448 return False
1449
1450 self.send_response(200)
1451 self.send_header('Content-Type', 'text/plain')
1452 self.end_headers()
1453 channel_id = self.server.tlsConnection.channel_id.tostring()
1454 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1455 return True
1456
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001457 def CloseSocketHandler(self):
1458 """Closes the socket without sending anything."""
1459
1460 if not self._ShouldHandleRequest('/close-socket'):
1461 return False
1462
1463 self.wfile.close()
1464 return True
1465
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001466 def RangeResetHandler(self):
1467 """Send data broken up by connection resets every N (default 4K) bytes.
1468 Support range requests. If the data requested doesn't straddle a reset
1469 boundary, it will all be sent. Used for testing resuming downloads."""
1470
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001471 def DataForRange(start, end):
1472 """Data to be provided for a particular range of bytes."""
1473 # Offset and scale to avoid too obvious (and hence potentially
1474 # collidable) data.
1475 return ''.join([chr(y % 256)
1476 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1477
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001478 if not self._ShouldHandleRequest('/rangereset'):
1479 return False
1480
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001481 # HTTP/1.1 is required for ETag and range support.
1482 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001483 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1484
1485 # Defaults
1486 size = 8000
1487 # Note that the rst is sent just before sending the rst_boundary byte.
1488 rst_boundary = 4000
1489 respond_to_range = True
1490 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001491 rst_limit = -1
1492 token = 'DEFAULT'
1493 fail_precondition = 0
1494 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001495
1496 # Parse the query
1497 qdict = urlparse.parse_qs(query, True)
1498 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001499 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001500 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001501 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001502 if 'token' in qdict:
1503 # Identifying token for stateful tests.
1504 token = qdict['token'][0]
1505 if 'rst_limit' in qdict:
1506 # Max number of rsts for a given token.
1507 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001508 if 'bounce_range' in qdict:
1509 respond_to_range = False
1510 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001511 # Note that hold_for_signal will not work with null range requests;
1512 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001513 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001514 if 'no_verifiers' in qdict:
1515 send_verifiers = False
1516 if 'fail_precondition' in qdict:
1517 fail_precondition = int(qdict['fail_precondition'][0])
1518
1519 # Record already set information, or set it.
1520 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1521 if rst_limit != 0:
1522 TestPageHandler.rst_limits[token] -= 1
1523 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1524 token, fail_precondition)
1525 if fail_precondition != 0:
1526 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001527
1528 first_byte = 0
1529 last_byte = size - 1
1530
1531 # Does that define what we want to return, or do we need to apply
1532 # a range?
1533 range_response = False
1534 range_header = self.headers.getheader('range')
1535 if range_header and respond_to_range:
1536 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1537 if mo.group(1):
1538 first_byte = int(mo.group(1))
1539 if mo.group(2):
1540 last_byte = int(mo.group(2))
1541 if last_byte > size - 1:
1542 last_byte = size - 1
1543 range_response = True
1544 if last_byte < first_byte:
1545 return False
1546
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001547 if (fail_precondition and
1548 (self.headers.getheader('If-Modified-Since') or
1549 self.headers.getheader('If-Match'))):
1550 self.send_response(412)
1551 self.end_headers()
1552 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001553
1554 if range_response:
1555 self.send_response(206)
1556 self.send_header('Content-Range',
1557 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1558 else:
1559 self.send_response(200)
1560 self.send_header('Content-Type', 'application/octet-stream')
1561 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001562 if send_verifiers:
1563 self.send_header('Etag', '"XYZZY"')
1564 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001565 self.end_headers()
1566
1567 if hold_for_signal:
1568 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1569 # a single byte, the self.server.handle_request() below hangs
1570 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001571 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001572 first_byte = first_byte + 1
1573 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001574 self.server.wait_for_download = True
1575 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001576 self.server.handle_request()
1577
1578 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001579 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001580 # No RST has been requested in this range, so we don't need to
1581 # do anything fancy; just write the data and let the python
1582 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001583 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001584 self.wfile.flush()
1585 return True
1586
1587 # We're resetting the connection part way in; go to the RST
1588 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001589 # Because socket semantics do not guarantee that all the data will be
1590 # sent when using the linger semantics to hard close a socket,
1591 # we send the data and then wait for our peer to release us
1592 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001593 data = DataForRange(first_byte, possible_rst)
1594 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001595 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001596 self.server.wait_for_download = True
1597 while self.server.wait_for_download:
1598 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001599 l_onoff = 1 # Linger is active.
1600 l_linger = 0 # Seconds to linger for.
1601 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1602 struct.pack('ii', l_onoff, l_linger))
1603
1604 # Close all duplicates of the underlying socket to force the RST.
1605 self.wfile.close()
1606 self.rfile.close()
1607 self.connection.close()
1608
1609 return True
1610
initial.commit94958cf2008-07-26 22:42:52 +00001611 def DefaultResponseHandler(self):
1612 """This is the catch-all response handler for requests that aren't handled
1613 by one of the special handlers above.
1614 Note that we specify the content-length as without it the https connection
1615 is not closed properly (and the browser keeps expecting data)."""
1616
1617 contents = "Default response given for path: " + self.path
1618 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001619 self.send_header('Content-Type', 'text/html')
1620 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001621 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001622 if (self.command != 'HEAD'):
1623 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001624 return True
1625
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001626 def RedirectConnectHandler(self):
1627 """Sends a redirect to the CONNECT request for www.redirect.com. This
1628 response is not specified by the RFC, so the browser should not follow
1629 the redirect."""
1630
1631 if (self.path.find("www.redirect.com") < 0):
1632 return False
1633
1634 dest = "http://www.destination.com/foo.js"
1635
1636 self.send_response(302) # moved temporarily
1637 self.send_header('Location', dest)
1638 self.send_header('Connection', 'close')
1639 self.end_headers()
1640 return True
1641
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001642 def ServerAuthConnectHandler(self):
1643 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1644 response doesn't make sense because the proxy server cannot request
1645 server authentication."""
1646
1647 if (self.path.find("www.server-auth.com") < 0):
1648 return False
1649
1650 challenge = 'Basic realm="WallyWorld"'
1651
1652 self.send_response(401) # unauthorized
1653 self.send_header('WWW-Authenticate', challenge)
1654 self.send_header('Connection', 'close')
1655 self.end_headers()
1656 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001657
1658 def DefaultConnectResponseHandler(self):
1659 """This is the catch-all response handler for CONNECT requests that aren't
1660 handled by one of the special handlers above. Real Web servers respond
1661 with 400 to CONNECT requests."""
1662
1663 contents = "Your client has issued a malformed or illegal request."
1664 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001665 self.send_header('Content-Type', 'text/html')
1666 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001667 self.end_headers()
1668 self.wfile.write(contents)
1669 return True
1670
initial.commit94958cf2008-07-26 22:42:52 +00001671 # called by the redirect handling function when there is no parameter
1672 def sendRedirectHelp(self, redirect_name):
1673 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001674 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001675 self.end_headers()
1676 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1677 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1678 self.wfile.write('</body></html>')
1679
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001680 # called by chunked handling function
1681 def sendChunkHelp(self, chunk):
1682 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1683 self.wfile.write('%X\r\n' % len(chunk))
1684 self.wfile.write(chunk)
1685 self.wfile.write('\r\n')
1686
akalin@chromium.org154bb132010-11-12 02:20:27 +00001687
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001688class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001689 def __init__(self, request, client_address, socket_server):
1690 handlers = [self.OCSPResponse]
1691 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001692 testserver_base.BasePageHandler.__init__(self, request, client_address,
1693 socket_server, [], handlers, [],
1694 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001695
1696 def OCSPResponse(self):
1697 self.send_response(200)
1698 self.send_header('Content-Type', 'application/ocsp-response')
1699 self.send_header('Content-Length', str(len(self.ocsp_response)))
1700 self.end_headers()
1701
1702 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001703
mattm@chromium.org830a3712012-11-07 23:00:07 +00001704
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001705class TCPEchoHandler(SocketServer.BaseRequestHandler):
1706 """The RequestHandler class for TCP echo server.
1707
1708 It is instantiated once per connection to the server, and overrides the
1709 handle() method to implement communication to the client.
1710 """
1711
1712 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001713 """Handles the request from the client and constructs a response."""
1714
1715 data = self.request.recv(65536).strip()
1716 # Verify the "echo request" message received from the client. Send back
1717 # "echo response" message if "echo request" message is valid.
1718 try:
1719 return_data = echo_message.GetEchoResponseData(data)
1720 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001721 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001722 except ValueError:
1723 return
1724
1725 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001726
1727
1728class UDPEchoHandler(SocketServer.BaseRequestHandler):
1729 """The RequestHandler class for UDP echo server.
1730
1731 It is instantiated once per connection to the server, and overrides the
1732 handle() method to implement communication to the client.
1733 """
1734
1735 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001736 """Handles the request from the client and constructs a response."""
1737
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001738 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001739 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001740 # Verify the "echo request" message received from the client. Send back
1741 # "echo response" message if "echo request" message is valid.
1742 try:
1743 return_data = echo_message.GetEchoResponseData(data)
1744 if not return_data:
1745 return
1746 except ValueError:
1747 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001748 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001749
1750
bashi@chromium.org33233532012-09-08 17:37:24 +00001751class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1752 """A request handler that behaves as a proxy server which requires
1753 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1754 """
1755
1756 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1757
1758 def parse_request(self):
1759 """Overrides parse_request to check credential."""
1760
1761 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1762 return False
1763
1764 auth = self.headers.getheader('Proxy-Authorization')
1765 if auth != self._AUTH_CREDENTIAL:
1766 self.send_response(407)
1767 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1768 self.end_headers()
1769 return False
1770
1771 return True
1772
1773 def _start_read_write(self, sock):
1774 sock.setblocking(0)
1775 self.request.setblocking(0)
1776 rlist = [self.request, sock]
1777 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001778 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001779 if errors:
1780 self.send_response(500)
1781 self.end_headers()
1782 return
1783 for s in ready_sockets:
1784 received = s.recv(1024)
1785 if len(received) == 0:
1786 return
1787 if s == self.request:
1788 other = sock
1789 else:
1790 other = self.request
1791 other.send(received)
1792
1793 def _do_common_method(self):
1794 url = urlparse.urlparse(self.path)
1795 port = url.port
1796 if not port:
1797 if url.scheme == 'http':
1798 port = 80
1799 elif url.scheme == 'https':
1800 port = 443
1801 if not url.hostname or not port:
1802 self.send_response(400)
1803 self.end_headers()
1804 return
1805
1806 if len(url.path) == 0:
1807 path = '/'
1808 else:
1809 path = url.path
1810 if len(url.query) > 0:
1811 path = '%s?%s' % (url.path, url.query)
1812
1813 sock = None
1814 try:
1815 sock = socket.create_connection((url.hostname, port))
1816 sock.send('%s %s %s\r\n' % (
1817 self.command, path, self.protocol_version))
1818 for header in self.headers.headers:
1819 header = header.strip()
1820 if (header.lower().startswith('connection') or
1821 header.lower().startswith('proxy')):
1822 continue
1823 sock.send('%s\r\n' % header)
1824 sock.send('\r\n')
1825 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001826 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001827 self.send_response(500)
1828 self.end_headers()
1829 finally:
1830 if sock is not None:
1831 sock.close()
1832
1833 def do_CONNECT(self):
1834 try:
1835 pos = self.path.rfind(':')
1836 host = self.path[:pos]
1837 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001838 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001839 self.send_response(400)
1840 self.end_headers()
1841
1842 try:
1843 sock = socket.create_connection((host, port))
1844 self.send_response(200, 'Connection established')
1845 self.end_headers()
1846 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001847 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001848 self.send_response(500)
1849 self.end_headers()
1850 finally:
1851 sock.close()
1852
1853 def do_GET(self):
1854 self._do_common_method()
1855
1856 def do_HEAD(self):
1857 self._do_common_method()
1858
1859
mattm@chromium.org830a3712012-11-07 23:00:07 +00001860class ServerRunner(testserver_base.TestServerRunner):
1861 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001862
mattm@chromium.org830a3712012-11-07 23:00:07 +00001863 def __init__(self):
1864 super(ServerRunner, self).__init__()
1865 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001866
mattm@chromium.org830a3712012-11-07 23:00:07 +00001867 def __make_data_dir(self):
1868 if self.options.data_dir:
1869 if not os.path.isdir(self.options.data_dir):
1870 raise testserver_base.OptionError('specified data dir not found: ' +
1871 self.options.data_dir + ' exiting...')
1872 my_data_dir = self.options.data_dir
1873 else:
1874 # Create the default path to our data dir, relative to the exe dir.
1875 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1876 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001877
mattm@chromium.org830a3712012-11-07 23:00:07 +00001878 #TODO(ibrar): Must use Find* funtion defined in google\tools
1879 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001880
mattm@chromium.org830a3712012-11-07 23:00:07 +00001881 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001882
mattm@chromium.org830a3712012-11-07 23:00:07 +00001883 def create_server(self, server_data):
1884 port = self.options.port
1885 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001886
mattm@chromium.org830a3712012-11-07 23:00:07 +00001887 if self.options.server_type == SERVER_HTTP:
1888 if self.options.https:
1889 pem_cert_and_key = None
1890 if self.options.cert_and_key_file:
1891 if not os.path.isfile(self.options.cert_and_key_file):
1892 raise testserver_base.OptionError(
1893 'specified server cert file not found: ' +
1894 self.options.cert_and_key_file + ' exiting...')
1895 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001896 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001897 # generate a new certificate and run an OCSP server for it.
1898 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001899 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001900 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001901
mattm@chromium.org830a3712012-11-07 23:00:07 +00001902 ocsp_der = None
1903 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001904
mattm@chromium.org830a3712012-11-07 23:00:07 +00001905 if self.options.ocsp == 'ok':
1906 ocsp_state = minica.OCSP_STATE_GOOD
1907 elif self.options.ocsp == 'revoked':
1908 ocsp_state = minica.OCSP_STATE_REVOKED
1909 elif self.options.ocsp == 'invalid':
1910 ocsp_state = minica.OCSP_STATE_INVALID
1911 elif self.options.ocsp == 'unauthorized':
1912 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1913 elif self.options.ocsp == 'unknown':
1914 ocsp_state = minica.OCSP_STATE_UNKNOWN
1915 else:
1916 raise testserver_base.OptionError('unknown OCSP status: ' +
1917 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001918
mattm@chromium.org830a3712012-11-07 23:00:07 +00001919 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1920 subject = "127.0.0.1",
1921 ocsp_url = ("http://%s:%d/ocsp" %
1922 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00001923 ocsp_state = ocsp_state,
1924 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001925
1926 self.__ocsp_server.ocsp_response = ocsp_der
1927
1928 for ca_cert in self.options.ssl_client_ca:
1929 if not os.path.isfile(ca_cert):
1930 raise testserver_base.OptionError(
1931 'specified trusted client CA file not found: ' + ca_cert +
1932 ' exiting...')
1933 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1934 self.options.ssl_client_auth,
1935 self.options.ssl_client_ca,
1936 self.options.ssl_bulk_cipher,
1937 self.options.record_resume,
1938 self.options.tls_intolerant)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001939 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001940 else:
1941 server = HTTPServer((host, port), TestPageHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001942 print 'HTTP server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001943
1944 server.data_dir = self.__make_data_dir()
1945 server.file_root_url = self.options.file_root_url
1946 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001947 elif self.options.server_type == SERVER_WEBSOCKET:
1948 # Launch pywebsocket via WebSocketServer.
1949 logger = logging.getLogger()
1950 logger.addHandler(logging.StreamHandler())
1951 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1952 # is required to work correctly. It should be fixed from pywebsocket side.
1953 os.chdir(self.__make_data_dir())
1954 websocket_options = WebSocketOptions(host, port, '.')
1955 if self.options.cert_and_key_file:
1956 websocket_options.use_tls = True
1957 websocket_options.private_key = self.options.cert_and_key_file
1958 websocket_options.certificate = self.options.cert_and_key_file
1959 if self.options.ssl_client_auth:
1960 websocket_options.tls_client_auth = True
1961 if len(self.options.ssl_client_ca) != 1:
1962 raise testserver_base.OptionError(
1963 'one trusted client CA file should be specified')
1964 if not os.path.isfile(self.options.ssl_client_ca[0]):
1965 raise testserver_base.OptionError(
1966 'specified trusted client CA file not found: ' +
1967 self.options.ssl_client_ca[0] + ' exiting...')
1968 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
1969 server = WebSocketServer(websocket_options)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001970 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001971 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001972 elif self.options.server_type == SERVER_TCP_ECHO:
1973 # Used for generating the key (randomly) that encodes the "echo request"
1974 # message.
1975 random.seed()
1976 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001977 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001978 server_data['port'] = server.server_port
1979 elif self.options.server_type == SERVER_UDP_ECHO:
1980 # Used for generating the key (randomly) that encodes the "echo request"
1981 # message.
1982 random.seed()
1983 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001984 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001985 server_data['port'] = server.server_port
1986 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
1987 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001988 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001989 server_data['port'] = server.server_port
1990 elif self.options.server_type == SERVER_FTP:
1991 my_data_dir = self.__make_data_dir()
1992
1993 # Instantiate a dummy authorizer for managing 'virtual' users
1994 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1995
1996 # Define a new user having full r/w permissions and a read-only
1997 # anonymous user
1998 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1999
2000 authorizer.add_anonymous(my_data_dir)
2001
2002 # Instantiate FTP handler class
2003 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2004 ftp_handler.authorizer = authorizer
2005
2006 # Define a customized banner (string returned when client connects)
2007 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2008 pyftpdlib.ftpserver.__ver__)
2009
2010 # Instantiate FTP server class and listen to address:port
2011 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2012 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002013 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002014 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002015 raise testserver_base.OptionError('unknown server type' +
2016 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002017
mattm@chromium.org830a3712012-11-07 23:00:07 +00002018 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002019
mattm@chromium.org830a3712012-11-07 23:00:07 +00002020 def run_server(self):
2021 if self.__ocsp_server:
2022 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002023
mattm@chromium.org830a3712012-11-07 23:00:07 +00002024 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002025
mattm@chromium.org830a3712012-11-07 23:00:07 +00002026 if self.__ocsp_server:
2027 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002028
mattm@chromium.org830a3712012-11-07 23:00:07 +00002029 def add_options(self):
2030 testserver_base.TestServerRunner.add_options(self)
2031 self.option_parser.add_option('-f', '--ftp', action='store_const',
2032 const=SERVER_FTP, default=SERVER_HTTP,
2033 dest='server_type',
2034 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002035 self.option_parser.add_option('--tcp-echo', action='store_const',
2036 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2037 dest='server_type',
2038 help='start up a tcp echo server.')
2039 self.option_parser.add_option('--udp-echo', action='store_const',
2040 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2041 dest='server_type',
2042 help='start up a udp echo server.')
2043 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2044 const=SERVER_BASIC_AUTH_PROXY,
2045 default=SERVER_HTTP, dest='server_type',
2046 help='start up a proxy server which requires '
2047 'basic authentication.')
2048 self.option_parser.add_option('--websocket', action='store_const',
2049 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2050 dest='server_type',
2051 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002052 self.option_parser.add_option('--https', action='store_true',
2053 dest='https', help='Specify that https '
2054 'should be used.')
2055 self.option_parser.add_option('--cert-and-key-file',
2056 dest='cert_and_key_file', help='specify the '
2057 'path to the file containing the certificate '
2058 'and private key for the server in PEM '
2059 'format')
2060 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2061 help='The type of OCSP response generated '
2062 'for the automatically generated '
2063 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002064 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2065 default=0, type=int,
2066 help='If non-zero then the generated '
2067 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002068 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2069 default='0', type='int',
2070 help='If nonzero, certain TLS connections '
2071 'will be aborted in order to test version '
2072 'fallback. 1 means all TLS versions will be '
2073 'aborted. 2 means TLS 1.1 or higher will be '
2074 'aborted. 3 means TLS 1.2 or higher will be '
2075 'aborted.')
2076 self.option_parser.add_option('--https-record-resume',
2077 dest='record_resume', const=True,
2078 default=False, action='store_const',
2079 help='Record resumption cache events rather '
2080 'than resuming as normal. Allows the use of '
2081 'the /ssl-session-cache request')
2082 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2083 help='Require SSL client auth on every '
2084 'connection.')
2085 self.option_parser.add_option('--ssl-client-ca', action='append',
2086 default=[], help='Specify that the client '
2087 'certificate request should include the CA '
2088 'named in the subject of the DER-encoded '
2089 'certificate contained in the specified '
2090 'file. This option may appear multiple '
2091 'times, indicating multiple CA names should '
2092 'be sent in the request.')
2093 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2094 help='Specify the bulk encryption '
2095 'algorithm(s) that will be accepted by the '
2096 'SSL server. Valid values are "aes256", '
2097 '"aes128", "3des", "rc4". If omitted, all '
2098 'algorithms will be used. This option may '
2099 'appear multiple times, indicating '
2100 'multiple algorithms should be enabled.');
2101 self.option_parser.add_option('--file-root-url', default='/files/',
2102 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002103
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002104
initial.commit94958cf2008-07-26 22:42:52 +00002105if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002106 sys.exit(ServerRunner().main())