blob: f343f70e8ffc6558a950159fac6f7ac07cdb8c5d [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
1351 dest = self.path[query_char + 1:]
1352
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
1375 dest = self.path[query_char + 1:]
1376
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
1481 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1482
1483 # Defaults
1484 size = 8000
1485 # Note that the rst is sent just before sending the rst_boundary byte.
1486 rst_boundary = 4000
1487 respond_to_range = True
1488 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001489 rst_limit = -1
1490 token = 'DEFAULT'
1491 fail_precondition = 0
1492 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001493
1494 # Parse the query
1495 qdict = urlparse.parse_qs(query, True)
1496 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001497 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001498 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001499 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001500 if 'token' in qdict:
1501 # Identifying token for stateful tests.
1502 token = qdict['token'][0]
1503 if 'rst_limit' in qdict:
1504 # Max number of rsts for a given token.
1505 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001506 if 'bounce_range' in qdict:
1507 respond_to_range = False
1508 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001509 # Note that hold_for_signal will not work with null range requests;
1510 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001511 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001512 if 'no_verifiers' in qdict:
1513 send_verifiers = False
1514 if 'fail_precondition' in qdict:
1515 fail_precondition = int(qdict['fail_precondition'][0])
1516
1517 # Record already set information, or set it.
1518 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1519 if rst_limit != 0:
1520 TestPageHandler.rst_limits[token] -= 1
1521 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1522 token, fail_precondition)
1523 if fail_precondition != 0:
1524 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001525
1526 first_byte = 0
1527 last_byte = size - 1
1528
1529 # Does that define what we want to return, or do we need to apply
1530 # a range?
1531 range_response = False
1532 range_header = self.headers.getheader('range')
1533 if range_header and respond_to_range:
1534 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1535 if mo.group(1):
1536 first_byte = int(mo.group(1))
1537 if mo.group(2):
1538 last_byte = int(mo.group(2))
1539 if last_byte > size - 1:
1540 last_byte = size - 1
1541 range_response = True
1542 if last_byte < first_byte:
1543 return False
1544
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001545 if (fail_precondition and
1546 (self.headers.getheader('If-Modified-Since') or
1547 self.headers.getheader('If-Match'))):
1548 self.send_response(412)
1549 self.end_headers()
1550 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001551
1552 if range_response:
1553 self.send_response(206)
1554 self.send_header('Content-Range',
1555 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1556 else:
1557 self.send_response(200)
1558 self.send_header('Content-Type', 'application/octet-stream')
1559 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001560 if send_verifiers:
1561 self.send_header('Etag', '"XYZZY"')
1562 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001563 self.end_headers()
1564
1565 if hold_for_signal:
1566 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1567 # a single byte, the self.server.handle_request() below hangs
1568 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001569 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001570 first_byte = first_byte + 1
1571 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001572 self.server.wait_for_download = True
1573 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001574 self.server.handle_request()
1575
1576 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001577 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001578 # No RST has been requested in this range, so we don't need to
1579 # do anything fancy; just write the data and let the python
1580 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001581 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001582 self.wfile.flush()
1583 return True
1584
1585 # We're resetting the connection part way in; go to the RST
1586 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001587 # Because socket semantics do not guarantee that all the data will be
1588 # sent when using the linger semantics to hard close a socket,
1589 # we send the data and then wait for our peer to release us
1590 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001591 data = DataForRange(first_byte, possible_rst)
1592 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001593 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001594 self.server.wait_for_download = True
1595 while self.server.wait_for_download:
1596 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001597 l_onoff = 1 # Linger is active.
1598 l_linger = 0 # Seconds to linger for.
1599 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1600 struct.pack('ii', l_onoff, l_linger))
1601
1602 # Close all duplicates of the underlying socket to force the RST.
1603 self.wfile.close()
1604 self.rfile.close()
1605 self.connection.close()
1606
1607 return True
1608
initial.commit94958cf2008-07-26 22:42:52 +00001609 def DefaultResponseHandler(self):
1610 """This is the catch-all response handler for requests that aren't handled
1611 by one of the special handlers above.
1612 Note that we specify the content-length as without it the https connection
1613 is not closed properly (and the browser keeps expecting data)."""
1614
1615 contents = "Default response given for path: " + self.path
1616 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001617 self.send_header('Content-Type', 'text/html')
1618 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001619 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001620 if (self.command != 'HEAD'):
1621 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001622 return True
1623
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001624 def RedirectConnectHandler(self):
1625 """Sends a redirect to the CONNECT request for www.redirect.com. This
1626 response is not specified by the RFC, so the browser should not follow
1627 the redirect."""
1628
1629 if (self.path.find("www.redirect.com") < 0):
1630 return False
1631
1632 dest = "http://www.destination.com/foo.js"
1633
1634 self.send_response(302) # moved temporarily
1635 self.send_header('Location', dest)
1636 self.send_header('Connection', 'close')
1637 self.end_headers()
1638 return True
1639
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001640 def ServerAuthConnectHandler(self):
1641 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1642 response doesn't make sense because the proxy server cannot request
1643 server authentication."""
1644
1645 if (self.path.find("www.server-auth.com") < 0):
1646 return False
1647
1648 challenge = 'Basic realm="WallyWorld"'
1649
1650 self.send_response(401) # unauthorized
1651 self.send_header('WWW-Authenticate', challenge)
1652 self.send_header('Connection', 'close')
1653 self.end_headers()
1654 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001655
1656 def DefaultConnectResponseHandler(self):
1657 """This is the catch-all response handler for CONNECT requests that aren't
1658 handled by one of the special handlers above. Real Web servers respond
1659 with 400 to CONNECT requests."""
1660
1661 contents = "Your client has issued a malformed or illegal request."
1662 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001663 self.send_header('Content-Type', 'text/html')
1664 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001665 self.end_headers()
1666 self.wfile.write(contents)
1667 return True
1668
initial.commit94958cf2008-07-26 22:42:52 +00001669 # called by the redirect handling function when there is no parameter
1670 def sendRedirectHelp(self, redirect_name):
1671 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001672 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001673 self.end_headers()
1674 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1675 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1676 self.wfile.write('</body></html>')
1677
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001678 # called by chunked handling function
1679 def sendChunkHelp(self, chunk):
1680 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1681 self.wfile.write('%X\r\n' % len(chunk))
1682 self.wfile.write(chunk)
1683 self.wfile.write('\r\n')
1684
akalin@chromium.org154bb132010-11-12 02:20:27 +00001685
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001686class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001687 def __init__(self, request, client_address, socket_server):
1688 handlers = [self.OCSPResponse]
1689 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001690 testserver_base.BasePageHandler.__init__(self, request, client_address,
1691 socket_server, [], handlers, [],
1692 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001693
1694 def OCSPResponse(self):
1695 self.send_response(200)
1696 self.send_header('Content-Type', 'application/ocsp-response')
1697 self.send_header('Content-Length', str(len(self.ocsp_response)))
1698 self.end_headers()
1699
1700 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001701
mattm@chromium.org830a3712012-11-07 23:00:07 +00001702
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001703class TCPEchoHandler(SocketServer.BaseRequestHandler):
1704 """The RequestHandler class for TCP echo server.
1705
1706 It is instantiated once per connection to the server, and overrides the
1707 handle() method to implement communication to the client.
1708 """
1709
1710 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001711 """Handles the request from the client and constructs a response."""
1712
1713 data = self.request.recv(65536).strip()
1714 # Verify the "echo request" message received from the client. Send back
1715 # "echo response" message if "echo request" message is valid.
1716 try:
1717 return_data = echo_message.GetEchoResponseData(data)
1718 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001719 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001720 except ValueError:
1721 return
1722
1723 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001724
1725
1726class UDPEchoHandler(SocketServer.BaseRequestHandler):
1727 """The RequestHandler class for UDP echo server.
1728
1729 It is instantiated once per connection to the server, and overrides the
1730 handle() method to implement communication to the client.
1731 """
1732
1733 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001734 """Handles the request from the client and constructs a response."""
1735
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001736 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001737 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001738 # Verify the "echo request" message received from the client. Send back
1739 # "echo response" message if "echo request" message is valid.
1740 try:
1741 return_data = echo_message.GetEchoResponseData(data)
1742 if not return_data:
1743 return
1744 except ValueError:
1745 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001746 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001747
1748
bashi@chromium.org33233532012-09-08 17:37:24 +00001749class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1750 """A request handler that behaves as a proxy server which requires
1751 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1752 """
1753
1754 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1755
1756 def parse_request(self):
1757 """Overrides parse_request to check credential."""
1758
1759 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1760 return False
1761
1762 auth = self.headers.getheader('Proxy-Authorization')
1763 if auth != self._AUTH_CREDENTIAL:
1764 self.send_response(407)
1765 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1766 self.end_headers()
1767 return False
1768
1769 return True
1770
1771 def _start_read_write(self, sock):
1772 sock.setblocking(0)
1773 self.request.setblocking(0)
1774 rlist = [self.request, sock]
1775 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001776 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001777 if errors:
1778 self.send_response(500)
1779 self.end_headers()
1780 return
1781 for s in ready_sockets:
1782 received = s.recv(1024)
1783 if len(received) == 0:
1784 return
1785 if s == self.request:
1786 other = sock
1787 else:
1788 other = self.request
1789 other.send(received)
1790
1791 def _do_common_method(self):
1792 url = urlparse.urlparse(self.path)
1793 port = url.port
1794 if not port:
1795 if url.scheme == 'http':
1796 port = 80
1797 elif url.scheme == 'https':
1798 port = 443
1799 if not url.hostname or not port:
1800 self.send_response(400)
1801 self.end_headers()
1802 return
1803
1804 if len(url.path) == 0:
1805 path = '/'
1806 else:
1807 path = url.path
1808 if len(url.query) > 0:
1809 path = '%s?%s' % (url.path, url.query)
1810
1811 sock = None
1812 try:
1813 sock = socket.create_connection((url.hostname, port))
1814 sock.send('%s %s %s\r\n' % (
1815 self.command, path, self.protocol_version))
1816 for header in self.headers.headers:
1817 header = header.strip()
1818 if (header.lower().startswith('connection') or
1819 header.lower().startswith('proxy')):
1820 continue
1821 sock.send('%s\r\n' % header)
1822 sock.send('\r\n')
1823 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001824 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001825 self.send_response(500)
1826 self.end_headers()
1827 finally:
1828 if sock is not None:
1829 sock.close()
1830
1831 def do_CONNECT(self):
1832 try:
1833 pos = self.path.rfind(':')
1834 host = self.path[:pos]
1835 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001836 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001837 self.send_response(400)
1838 self.end_headers()
1839
1840 try:
1841 sock = socket.create_connection((host, port))
1842 self.send_response(200, 'Connection established')
1843 self.end_headers()
1844 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001845 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001846 self.send_response(500)
1847 self.end_headers()
1848 finally:
1849 sock.close()
1850
1851 def do_GET(self):
1852 self._do_common_method()
1853
1854 def do_HEAD(self):
1855 self._do_common_method()
1856
1857
mattm@chromium.org830a3712012-11-07 23:00:07 +00001858class ServerRunner(testserver_base.TestServerRunner):
1859 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001860
mattm@chromium.org830a3712012-11-07 23:00:07 +00001861 def __init__(self):
1862 super(ServerRunner, self).__init__()
1863 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001864
mattm@chromium.org830a3712012-11-07 23:00:07 +00001865 def __make_data_dir(self):
1866 if self.options.data_dir:
1867 if not os.path.isdir(self.options.data_dir):
1868 raise testserver_base.OptionError('specified data dir not found: ' +
1869 self.options.data_dir + ' exiting...')
1870 my_data_dir = self.options.data_dir
1871 else:
1872 # Create the default path to our data dir, relative to the exe dir.
1873 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1874 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001875
mattm@chromium.org830a3712012-11-07 23:00:07 +00001876 #TODO(ibrar): Must use Find* funtion defined in google\tools
1877 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001878
mattm@chromium.org830a3712012-11-07 23:00:07 +00001879 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001880
mattm@chromium.org830a3712012-11-07 23:00:07 +00001881 def create_server(self, server_data):
1882 port = self.options.port
1883 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001884
mattm@chromium.org830a3712012-11-07 23:00:07 +00001885 if self.options.server_type == SERVER_HTTP:
1886 if self.options.https:
1887 pem_cert_and_key = None
1888 if self.options.cert_and_key_file:
1889 if not os.path.isfile(self.options.cert_and_key_file):
1890 raise testserver_base.OptionError(
1891 'specified server cert file not found: ' +
1892 self.options.cert_and_key_file + ' exiting...')
1893 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001894 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001895 # generate a new certificate and run an OCSP server for it.
1896 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001897 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001898 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001899
mattm@chromium.org830a3712012-11-07 23:00:07 +00001900 ocsp_der = None
1901 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001902
mattm@chromium.org830a3712012-11-07 23:00:07 +00001903 if self.options.ocsp == 'ok':
1904 ocsp_state = minica.OCSP_STATE_GOOD
1905 elif self.options.ocsp == 'revoked':
1906 ocsp_state = minica.OCSP_STATE_REVOKED
1907 elif self.options.ocsp == 'invalid':
1908 ocsp_state = minica.OCSP_STATE_INVALID
1909 elif self.options.ocsp == 'unauthorized':
1910 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1911 elif self.options.ocsp == 'unknown':
1912 ocsp_state = minica.OCSP_STATE_UNKNOWN
1913 else:
1914 raise testserver_base.OptionError('unknown OCSP status: ' +
1915 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001916
mattm@chromium.org830a3712012-11-07 23:00:07 +00001917 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1918 subject = "127.0.0.1",
1919 ocsp_url = ("http://%s:%d/ocsp" %
1920 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00001921 ocsp_state = ocsp_state,
1922 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001923
1924 self.__ocsp_server.ocsp_response = ocsp_der
1925
1926 for ca_cert in self.options.ssl_client_ca:
1927 if not os.path.isfile(ca_cert):
1928 raise testserver_base.OptionError(
1929 'specified trusted client CA file not found: ' + ca_cert +
1930 ' exiting...')
1931 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1932 self.options.ssl_client_auth,
1933 self.options.ssl_client_ca,
1934 self.options.ssl_bulk_cipher,
1935 self.options.record_resume,
1936 self.options.tls_intolerant)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001937 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001938 else:
1939 server = HTTPServer((host, port), TestPageHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001940 print 'HTTP server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001941
1942 server.data_dir = self.__make_data_dir()
1943 server.file_root_url = self.options.file_root_url
1944 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001945 elif self.options.server_type == SERVER_WEBSOCKET:
1946 # Launch pywebsocket via WebSocketServer.
1947 logger = logging.getLogger()
1948 logger.addHandler(logging.StreamHandler())
1949 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1950 # is required to work correctly. It should be fixed from pywebsocket side.
1951 os.chdir(self.__make_data_dir())
1952 websocket_options = WebSocketOptions(host, port, '.')
1953 if self.options.cert_and_key_file:
1954 websocket_options.use_tls = True
1955 websocket_options.private_key = self.options.cert_and_key_file
1956 websocket_options.certificate = self.options.cert_and_key_file
1957 if self.options.ssl_client_auth:
1958 websocket_options.tls_client_auth = True
1959 if len(self.options.ssl_client_ca) != 1:
1960 raise testserver_base.OptionError(
1961 'one trusted client CA file should be specified')
1962 if not os.path.isfile(self.options.ssl_client_ca[0]):
1963 raise testserver_base.OptionError(
1964 'specified trusted client CA file not found: ' +
1965 self.options.ssl_client_ca[0] + ' exiting...')
1966 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
1967 server = WebSocketServer(websocket_options)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001968 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001969 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001970 elif self.options.server_type == SERVER_TCP_ECHO:
1971 # Used for generating the key (randomly) that encodes the "echo request"
1972 # message.
1973 random.seed()
1974 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001975 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001976 server_data['port'] = server.server_port
1977 elif self.options.server_type == SERVER_UDP_ECHO:
1978 # Used for generating the key (randomly) that encodes the "echo request"
1979 # message.
1980 random.seed()
1981 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001982 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001983 server_data['port'] = server.server_port
1984 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
1985 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001986 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001987 server_data['port'] = server.server_port
1988 elif self.options.server_type == SERVER_FTP:
1989 my_data_dir = self.__make_data_dir()
1990
1991 # Instantiate a dummy authorizer for managing 'virtual' users
1992 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1993
1994 # Define a new user having full r/w permissions and a read-only
1995 # anonymous user
1996 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1997
1998 authorizer.add_anonymous(my_data_dir)
1999
2000 # Instantiate FTP handler class
2001 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2002 ftp_handler.authorizer = authorizer
2003
2004 # Define a customized banner (string returned when client connects)
2005 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2006 pyftpdlib.ftpserver.__ver__)
2007
2008 # Instantiate FTP server class and listen to address:port
2009 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2010 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002011 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002012 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002013 raise testserver_base.OptionError('unknown server type' +
2014 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002015
mattm@chromium.org830a3712012-11-07 23:00:07 +00002016 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002017
mattm@chromium.org830a3712012-11-07 23:00:07 +00002018 def run_server(self):
2019 if self.__ocsp_server:
2020 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002021
mattm@chromium.org830a3712012-11-07 23:00:07 +00002022 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002023
mattm@chromium.org830a3712012-11-07 23:00:07 +00002024 if self.__ocsp_server:
2025 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002026
mattm@chromium.org830a3712012-11-07 23:00:07 +00002027 def add_options(self):
2028 testserver_base.TestServerRunner.add_options(self)
2029 self.option_parser.add_option('-f', '--ftp', action='store_const',
2030 const=SERVER_FTP, default=SERVER_HTTP,
2031 dest='server_type',
2032 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002033 self.option_parser.add_option('--tcp-echo', action='store_const',
2034 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2035 dest='server_type',
2036 help='start up a tcp echo server.')
2037 self.option_parser.add_option('--udp-echo', action='store_const',
2038 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2039 dest='server_type',
2040 help='start up a udp echo server.')
2041 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2042 const=SERVER_BASIC_AUTH_PROXY,
2043 default=SERVER_HTTP, dest='server_type',
2044 help='start up a proxy server which requires '
2045 'basic authentication.')
2046 self.option_parser.add_option('--websocket', action='store_const',
2047 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2048 dest='server_type',
2049 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002050 self.option_parser.add_option('--https', action='store_true',
2051 dest='https', help='Specify that https '
2052 'should be used.')
2053 self.option_parser.add_option('--cert-and-key-file',
2054 dest='cert_and_key_file', help='specify the '
2055 'path to the file containing the certificate '
2056 'and private key for the server in PEM '
2057 'format')
2058 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2059 help='The type of OCSP response generated '
2060 'for the automatically generated '
2061 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002062 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2063 default=0, type=int,
2064 help='If non-zero then the generated '
2065 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002066 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2067 default='0', type='int',
2068 help='If nonzero, certain TLS connections '
2069 'will be aborted in order to test version '
2070 'fallback. 1 means all TLS versions will be '
2071 'aborted. 2 means TLS 1.1 or higher will be '
2072 'aborted. 3 means TLS 1.2 or higher will be '
2073 'aborted.')
2074 self.option_parser.add_option('--https-record-resume',
2075 dest='record_resume', const=True,
2076 default=False, action='store_const',
2077 help='Record resumption cache events rather '
2078 'than resuming as normal. Allows the use of '
2079 'the /ssl-session-cache request')
2080 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2081 help='Require SSL client auth on every '
2082 'connection.')
2083 self.option_parser.add_option('--ssl-client-ca', action='append',
2084 default=[], help='Specify that the client '
2085 'certificate request should include the CA '
2086 'named in the subject of the DER-encoded '
2087 'certificate contained in the specified '
2088 'file. This option may appear multiple '
2089 'times, indicating multiple CA names should '
2090 'be sent in the request.')
2091 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2092 help='Specify the bulk encryption '
2093 'algorithm(s) that will be accepted by the '
2094 'SSL server. Valid values are "aes256", '
2095 '"aes128", "3des", "rc4". If omitted, all '
2096 'algorithms will be used. This option may '
2097 'appear multiple times, indicating '
2098 'multiple algorithms should be enabled.');
2099 self.option_parser.add_option('--file-root-url', default='/files/',
2100 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002101
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002102
initial.commit94958cf2008-07-26 22:42:52 +00002103if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002104 sys.exit(ServerRunner().main())