blob: 83c14d638d33219009b0c7baa176107e80e44131 [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,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000131 record_resume_info, tls_intolerant, signed_cert_timestamps):
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
ekasper@google.com24aa8222013-11-28 13:43:26 +0000143 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.org143daa42012-04-26 18:45:34 +0000144
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000145 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000146 s = open(ca_file).read()
147 x509 = tlslite.api.X509()
148 x509.parse(s)
149 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000150 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
151 if ssl_bulk_ciphers is not None:
152 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000153
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000154 if record_resume_info:
155 # If record_resume_info is true then we'll replace the session cache with
156 # an object that records the lookups and inserts that it sees.
157 self.session_cache = RecordingSSLSessionCache()
158 else:
159 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000160 testserver_base.StoppableHTTPServer.__init__(self,
161 server_address,
162 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000163
164 def handshake(self, tlsConnection):
165 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000166
initial.commit94958cf2008-07-26 22:42:52 +0000167 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000168 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000169 tlsConnection.handshakeServer(certChain=self.cert_chain,
170 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000171 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000172 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000173 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000174 reqCAs=self.ssl_client_cas,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000175 tlsIntolerant=self.tls_intolerant,
176 signedCertTimestamps=
177 self.signed_cert_timestamps)
initial.commit94958cf2008-07-26 22:42:52 +0000178 tlsConnection.ignoreAbruptClose = True
179 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000180 except tlslite.api.TLSAbruptCloseError:
181 # Ignore abrupt close.
182 return True
initial.commit94958cf2008-07-26 22:42:52 +0000183 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000184 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000185 return False
186
akalin@chromium.org154bb132010-11-12 02:20:27 +0000187
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000188class FTPServer(testserver_base.ClientRestrictingServerMixIn,
189 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000190 """This is a specialization of FTPServer that adds client verification."""
191
192 pass
193
194
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000195class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
196 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000197 """A TCP echo server that echoes back what it has received."""
198
199 def server_bind(self):
200 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000201
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000202 SocketServer.TCPServer.server_bind(self)
203 host, port = self.socket.getsockname()[:2]
204 self.server_name = socket.getfqdn(host)
205 self.server_port = port
206
207 def serve_forever(self):
208 self.stop = False
209 self.nonce_time = None
210 while not self.stop:
211 self.handle_request()
212 self.socket.close()
213
214
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000215class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
216 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000217 """A UDP echo server that echoes back what it has received."""
218
219 def server_bind(self):
220 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000221
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000222 SocketServer.UDPServer.server_bind(self)
223 host, port = self.socket.getsockname()[:2]
224 self.server_name = socket.getfqdn(host)
225 self.server_port = port
226
227 def serve_forever(self):
228 self.stop = False
229 self.nonce_time = None
230 while not self.stop:
231 self.handle_request()
232 self.socket.close()
233
234
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000235class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000236 # Class variables to allow for persistence state between page handler
237 # invocations
238 rst_limits = {}
239 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000240
241 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000242 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000243 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000244 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000245 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000246 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000247 self.NoCacheMaxAgeTimeHandler,
248 self.NoCacheTimeHandler,
249 self.CacheTimeHandler,
250 self.CacheExpiresHandler,
251 self.CacheProxyRevalidateHandler,
252 self.CachePrivateHandler,
253 self.CachePublicHandler,
254 self.CacheSMaxAgeHandler,
255 self.CacheMustRevalidateHandler,
256 self.CacheMustRevalidateMaxAgeHandler,
257 self.CacheNoStoreHandler,
258 self.CacheNoStoreMaxAgeHandler,
259 self.CacheNoTransformHandler,
260 self.DownloadHandler,
261 self.DownloadFinishHandler,
262 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000263 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000264 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000265 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000266 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000267 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000268 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000269 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000270 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000271 self.AuthBasicHandler,
272 self.AuthDigestHandler,
273 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000274 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000275 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000276 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000277 self.ServerRedirectHandler,
278 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000279 self.MultipartHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000280 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000281 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000282 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000283 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000284 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000285 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000286 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000287 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000288 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000289 self.PostOnlyFileHandler,
290 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000291 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000292 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000293 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000294 head_handlers = [
295 self.FileHandler,
296 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000297
maruel@google.come250a9b2009-03-10 17:39:46 +0000298 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000299 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000300 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000301 'gif': 'image/gif',
302 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000303 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000304 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000305 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000306 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000307 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000308 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000309 }
initial.commit94958cf2008-07-26 22:42:52 +0000310 self._default_mime_type = 'text/html'
311
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000312 testserver_base.BasePageHandler.__init__(self, request, client_address,
313 socket_server, connect_handlers,
314 get_handlers, head_handlers,
315 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000316
initial.commit94958cf2008-07-26 22:42:52 +0000317 def GetMIMETypeFromName(self, file_name):
318 """Returns the mime type for the specified file_name. So far it only looks
319 at the file extension."""
320
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000321 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000322 if len(extension) == 0:
323 # no extension.
324 return self._default_mime_type
325
ericroman@google.comc17ca532009-05-07 03:51:05 +0000326 # extension starts with a dot, so we need to remove it
327 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000328
initial.commit94958cf2008-07-26 22:42:52 +0000329 def NoCacheMaxAgeTimeHandler(self):
330 """This request handler yields a page with the title set to the current
331 system time, and no caching requested."""
332
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000333 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000334 return False
335
336 self.send_response(200)
337 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000338 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000339 self.end_headers()
340
maruel@google.come250a9b2009-03-10 17:39:46 +0000341 self.wfile.write('<html><head><title>%s</title></head></html>' %
342 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000343
344 return True
345
346 def NoCacheTimeHandler(self):
347 """This request handler yields a page with the title set to the current
348 system time, and no caching requested."""
349
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000350 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000351 return False
352
353 self.send_response(200)
354 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000355 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000356 self.end_headers()
357
maruel@google.come250a9b2009-03-10 17:39:46 +0000358 self.wfile.write('<html><head><title>%s</title></head></html>' %
359 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000360
361 return True
362
363 def CacheTimeHandler(self):
364 """This request handler yields a page with the title set to the current
365 system time, and allows caching for one minute."""
366
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000367 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000368 return False
369
370 self.send_response(200)
371 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000372 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000373 self.end_headers()
374
maruel@google.come250a9b2009-03-10 17:39:46 +0000375 self.wfile.write('<html><head><title>%s</title></head></html>' %
376 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000377
378 return True
379
380 def CacheExpiresHandler(self):
381 """This request handler yields a page with the title set to the current
382 system time, and set the page to expire on 1 Jan 2099."""
383
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000384 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000385 return False
386
387 self.send_response(200)
388 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000389 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000390 self.end_headers()
391
maruel@google.come250a9b2009-03-10 17:39:46 +0000392 self.wfile.write('<html><head><title>%s</title></head></html>' %
393 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000394
395 return True
396
397 def CacheProxyRevalidateHandler(self):
398 """This request handler yields a page with the title set to the current
399 system time, and allows caching for 60 seconds"""
400
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000401 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000402 return False
403
404 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000405 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000406 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
407 self.end_headers()
408
maruel@google.come250a9b2009-03-10 17:39:46 +0000409 self.wfile.write('<html><head><title>%s</title></head></html>' %
410 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000411
412 return True
413
414 def CachePrivateHandler(self):
415 """This request handler yields a page with the title set to the current
416 system time, and allows caching for 5 seconds."""
417
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000418 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000419 return False
420
421 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000422 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000423 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000424 self.end_headers()
425
maruel@google.come250a9b2009-03-10 17:39:46 +0000426 self.wfile.write('<html><head><title>%s</title></head></html>' %
427 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000428
429 return True
430
431 def CachePublicHandler(self):
432 """This request handler yields a page with the title set to the current
433 system time, and allows caching for 5 seconds."""
434
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000435 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000436 return False
437
438 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000439 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000440 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000441 self.end_headers()
442
maruel@google.come250a9b2009-03-10 17:39:46 +0000443 self.wfile.write('<html><head><title>%s</title></head></html>' %
444 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000445
446 return True
447
448 def CacheSMaxAgeHandler(self):
449 """This request handler yields a page with the title set to the current
450 system time, and does not allow for caching."""
451
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000452 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000453 return False
454
455 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000456 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000457 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
458 self.end_headers()
459
maruel@google.come250a9b2009-03-10 17:39:46 +0000460 self.wfile.write('<html><head><title>%s</title></head></html>' %
461 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000462
463 return True
464
465 def CacheMustRevalidateHandler(self):
466 """This request handler yields a page with the title set to the current
467 system time, and does not allow caching."""
468
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000469 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000470 return False
471
472 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000473 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000474 self.send_header('Cache-Control', 'must-revalidate')
475 self.end_headers()
476
maruel@google.come250a9b2009-03-10 17:39:46 +0000477 self.wfile.write('<html><head><title>%s</title></head></html>' %
478 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000479
480 return True
481
482 def CacheMustRevalidateMaxAgeHandler(self):
483 """This request handler yields a page with the title set to the current
484 system time, and does not allow caching event though max-age of 60
485 seconds is specified."""
486
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000487 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000488 return False
489
490 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000491 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000492 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
493 self.end_headers()
494
maruel@google.come250a9b2009-03-10 17:39:46 +0000495 self.wfile.write('<html><head><title>%s</title></head></html>' %
496 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000497
498 return True
499
initial.commit94958cf2008-07-26 22:42:52 +0000500 def CacheNoStoreHandler(self):
501 """This request handler yields a page with the title set to the current
502 system time, and does not allow the page to be stored."""
503
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000504 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000505 return False
506
507 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000508 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000509 self.send_header('Cache-Control', 'no-store')
510 self.end_headers()
511
maruel@google.come250a9b2009-03-10 17:39:46 +0000512 self.wfile.write('<html><head><title>%s</title></head></html>' %
513 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000514
515 return True
516
517 def CacheNoStoreMaxAgeHandler(self):
518 """This request handler yields a page with the title set to the current
519 system time, and does not allow the page to be stored even though max-age
520 of 60 seconds is specified."""
521
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000522 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000523 return False
524
525 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000526 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000527 self.send_header('Cache-Control', 'max-age=60, no-store')
528 self.end_headers()
529
maruel@google.come250a9b2009-03-10 17:39:46 +0000530 self.wfile.write('<html><head><title>%s</title></head></html>' %
531 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000532
533 return True
534
535
536 def CacheNoTransformHandler(self):
537 """This request handler yields a page with the title set to the current
538 system time, and does not allow the content to transformed during
539 user-agent caching"""
540
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000541 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000542 return False
543
544 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000545 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000546 self.send_header('Cache-Control', 'no-transform')
547 self.end_headers()
548
maruel@google.come250a9b2009-03-10 17:39:46 +0000549 self.wfile.write('<html><head><title>%s</title></head></html>' %
550 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000551
552 return True
553
554 def EchoHeader(self):
555 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000556
ananta@chromium.org219b2062009-10-23 16:09:41 +0000557 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000558
ananta@chromium.org56812d02011-04-07 17:52:05 +0000559 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000560 """This function echoes back the value of a specific request header while
561 allowing caching for 16 hours."""
562
ananta@chromium.org56812d02011-04-07 17:52:05 +0000563 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000564
565 def EchoHeaderHelper(self, echo_header):
566 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000567
ananta@chromium.org219b2062009-10-23 16:09:41 +0000568 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000569 return False
570
571 query_char = self.path.find('?')
572 if query_char != -1:
573 header_name = self.path[query_char+1:]
574
575 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000576 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000577 if echo_header == '/echoheadercache':
578 self.send_header('Cache-control', 'max-age=60000')
579 else:
580 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000581 # insert a vary header to properly indicate that the cachability of this
582 # request is subject to value of the request header being echoed.
583 if len(header_name) > 0:
584 self.send_header('Vary', header_name)
585 self.end_headers()
586
587 if len(header_name) > 0:
588 self.wfile.write(self.headers.getheader(header_name))
589
590 return True
591
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000592 def ReadRequestBody(self):
593 """This function reads the body of the current HTTP request, handling
594 both plain and chunked transfer encoded requests."""
595
596 if self.headers.getheader('transfer-encoding') != 'chunked':
597 length = int(self.headers.getheader('content-length'))
598 return self.rfile.read(length)
599
600 # Read the request body as chunks.
601 body = ""
602 while True:
603 line = self.rfile.readline()
604 length = int(line, 16)
605 if length == 0:
606 self.rfile.readline()
607 break
608 body += self.rfile.read(length)
609 self.rfile.read(2)
610 return body
611
initial.commit94958cf2008-07-26 22:42:52 +0000612 def EchoHandler(self):
613 """This handler just echoes back the payload of the request, for testing
614 form submission."""
615
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000616 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000617 return False
618
619 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000620 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000621 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000622 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000623 return True
624
625 def EchoTitleHandler(self):
626 """This handler is like Echo, but sets the page title to the request."""
627
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000628 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000629 return False
630
631 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000632 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000633 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000634 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000635 self.wfile.write('<html><head><title>')
636 self.wfile.write(request)
637 self.wfile.write('</title></head></html>')
638 return True
639
640 def EchoAllHandler(self):
641 """This handler yields a (more) human-readable page listing information
642 about the request header & contents."""
643
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000644 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000645 return False
646
647 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000648 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000649 self.end_headers()
650 self.wfile.write('<html><head><style>'
651 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
652 '</style></head><body>'
653 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000654 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000655 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000656
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000657 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000658 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000659 params = cgi.parse_qs(qs, keep_blank_values=1)
660
661 for param in params:
662 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000663
664 self.wfile.write('</pre>')
665
666 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
667
668 self.wfile.write('</body></html>')
669 return True
670
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000671 def EchoMultipartPostHandler(self):
672 """This handler echoes received multipart post data as json format."""
673
674 if not (self._ShouldHandleRequest("/echomultipartpost") or
675 self._ShouldHandleRequest("/searchbyimage")):
676 return False
677
678 content_type, parameters = cgi.parse_header(
679 self.headers.getheader('content-type'))
680 if content_type == 'multipart/form-data':
681 post_multipart = cgi.parse_multipart(self.rfile, parameters)
682 elif content_type == 'application/x-www-form-urlencoded':
683 raise Exception('POST by application/x-www-form-urlencoded is '
684 'not implemented.')
685 else:
686 post_multipart = {}
687
688 # Since the data can be binary, we encode them by base64.
689 post_multipart_base64_encoded = {}
690 for field, values in post_multipart.items():
691 post_multipart_base64_encoded[field] = [base64.b64encode(value)
692 for value in values]
693
694 result = {'POST_multipart' : post_multipart_base64_encoded}
695
696 self.send_response(200)
697 self.send_header("Content-type", "text/plain")
698 self.end_headers()
699 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
700 return True
701
initial.commit94958cf2008-07-26 22:42:52 +0000702 def DownloadHandler(self):
703 """This handler sends a downloadable file with or without reporting
704 the size (6K)."""
705
706 if self.path.startswith("/download-unknown-size"):
707 send_length = False
708 elif self.path.startswith("/download-known-size"):
709 send_length = True
710 else:
711 return False
712
713 #
714 # The test which uses this functionality is attempting to send
715 # small chunks of data to the client. Use a fairly large buffer
716 # so that we'll fill chrome's IO buffer enough to force it to
717 # actually write the data.
718 # See also the comments in the client-side of this test in
719 # download_uitest.cc
720 #
721 size_chunk1 = 35*1024
722 size_chunk2 = 10*1024
723
724 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000725 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000726 self.send_header('Cache-Control', 'max-age=0')
727 if send_length:
728 self.send_header('Content-Length', size_chunk1 + size_chunk2)
729 self.end_headers()
730
731 # First chunk of data:
732 self.wfile.write("*" * size_chunk1)
733 self.wfile.flush()
734
735 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000736 self.server.wait_for_download = True
737 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000738 self.server.handle_request()
739
740 # Second chunk of data:
741 self.wfile.write("*" * size_chunk2)
742 return True
743
744 def DownloadFinishHandler(self):
745 """This handler just tells the server to finish the current download."""
746
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000747 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000748 return False
749
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000750 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000751 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000752 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000753 self.send_header('Cache-Control', 'max-age=0')
754 self.end_headers()
755 return True
756
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000757 def _ReplaceFileData(self, data, query_parameters):
758 """Replaces matching substrings in a file.
759
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000760 If the 'replace_text' URL query parameter is present, it is expected to be
761 of the form old_text:new_text, which indicates that any old_text strings in
762 the file are replaced with new_text. Multiple 'replace_text' parameters may
763 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000764
765 If the parameters are not present, |data| is returned.
766 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000767
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000768 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000769 replace_text_values = query_dict.get('replace_text', [])
770 for replace_text_value in replace_text_values:
771 replace_text_args = replace_text_value.split(':')
772 if len(replace_text_args) != 2:
773 raise ValueError(
774 'replace_text must be of form old_text:new_text. Actual value: %s' %
775 replace_text_value)
776 old_text_b64, new_text_b64 = replace_text_args
777 old_text = base64.urlsafe_b64decode(old_text_b64)
778 new_text = base64.urlsafe_b64decode(new_text_b64)
779 data = data.replace(old_text, new_text)
780 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000781
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000782 def ZipFileHandler(self):
783 """This handler sends the contents of the requested file in compressed form.
784 Can pass in a parameter that specifies that the content length be
785 C - the compressed size (OK),
786 U - the uncompressed size (Non-standard, but handled),
787 S - less than compressed (OK because we keep going),
788 M - larger than compressed but less than uncompressed (an error),
789 L - larger than uncompressed (an error)
790 Example: compressedfiles/Picture_1.doc?C
791 """
792
793 prefix = "/compressedfiles/"
794 if not self.path.startswith(prefix):
795 return False
796
797 # Consume a request body if present.
798 if self.command == 'POST' or self.command == 'PUT' :
799 self.ReadRequestBody()
800
801 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
802
803 if not query in ('C', 'U', 'S', 'M', 'L'):
804 return False
805
806 sub_path = url_path[len(prefix):]
807 entries = sub_path.split('/')
808 file_path = os.path.join(self.server.data_dir, *entries)
809 if os.path.isdir(file_path):
810 file_path = os.path.join(file_path, 'index.html')
811
812 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000813 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000814 self.send_error(404)
815 return True
816
817 f = open(file_path, "rb")
818 data = f.read()
819 uncompressed_len = len(data)
820 f.close()
821
822 # Compress the data.
823 data = zlib.compress(data)
824 compressed_len = len(data)
825
826 content_length = compressed_len
827 if query == 'U':
828 content_length = uncompressed_len
829 elif query == 'S':
830 content_length = compressed_len / 2
831 elif query == 'M':
832 content_length = (compressed_len + uncompressed_len) / 2
833 elif query == 'L':
834 content_length = compressed_len + uncompressed_len
835
836 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000837 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000838 self.send_header('Content-encoding', 'deflate')
839 self.send_header('Connection', 'close')
840 self.send_header('Content-Length', content_length)
841 self.send_header('ETag', '\'' + file_path + '\'')
842 self.end_headers()
843
844 self.wfile.write(data)
845
846 return True
847
initial.commit94958cf2008-07-26 22:42:52 +0000848 def FileHandler(self):
849 """This handler sends the contents of the requested file. Wow, it's like
850 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000851
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000852 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000853 if not self.path.startswith(prefix):
854 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000855 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000856
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000857 def PostOnlyFileHandler(self):
858 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000859
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000860 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000861 if not self.path.startswith(prefix):
862 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000863 return self._FileHandlerHelper(prefix)
864
865 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000866 request_body = ''
867 if self.command == 'POST' or self.command == 'PUT':
868 # Consume a request body if present.
869 request_body = self.ReadRequestBody()
870
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000871 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000872 query_dict = cgi.parse_qs(query)
873
874 expected_body = query_dict.get('expected_body', [])
875 if expected_body and request_body not in expected_body:
876 self.send_response(404)
877 self.end_headers()
878 self.wfile.write('')
879 return True
880
881 expected_headers = query_dict.get('expected_headers', [])
882 for expected_header in expected_headers:
883 header_name, expected_value = expected_header.split(':')
884 if self.headers.getheader(header_name) != expected_value:
885 self.send_response(404)
886 self.end_headers()
887 self.wfile.write('')
888 return True
889
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000890 sub_path = url_path[len(prefix):]
891 entries = sub_path.split('/')
892 file_path = os.path.join(self.server.data_dir, *entries)
893 if os.path.isdir(file_path):
894 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000895
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000896 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000897 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000898 self.send_error(404)
899 return True
900
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000901 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000902 data = f.read()
903 f.close()
904
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000905 data = self._ReplaceFileData(data, query)
906
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000907 old_protocol_version = self.protocol_version
908
initial.commit94958cf2008-07-26 22:42:52 +0000909 # If file.mock-http-headers exists, it contains the headers we
910 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000911 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000912 if os.path.isfile(headers_path):
913 f = open(headers_path, "r")
914
915 # "HTTP/1.1 200 OK"
916 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000917 http_major, http_minor, status_code = re.findall(
918 'HTTP/(\d+).(\d+) (\d+)', response)[0]
919 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000920 self.send_response(int(status_code))
921
922 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000923 header_values = re.findall('(\S+):\s*(.*)', line)
924 if len(header_values) > 0:
925 # "name: value"
926 name, value = header_values[0]
927 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000928 f.close()
929 else:
930 # Could be more generic once we support mime-type sniffing, but for
931 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000932
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000933 range_header = self.headers.get('Range')
934 if range_header and range_header.startswith('bytes='):
935 # Note this doesn't handle all valid byte range_header values (i.e.
936 # left open ended ones), just enough for what we needed so far.
937 range_header = range_header[6:].split('-')
938 start = int(range_header[0])
939 if range_header[1]:
940 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000941 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000942 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000943
944 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000945 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
946 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000947 self.send_header('Content-Range', content_range)
948 data = data[start: end + 1]
949 else:
950 self.send_response(200)
951
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000952 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000953 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000954 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000955 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000956 self.end_headers()
957
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000958 if (self.command != 'HEAD'):
959 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000960
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000961 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000962 return True
963
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000964 def SetCookieHandler(self):
965 """This handler just sets a cookie, for testing cookie handling."""
966
967 if not self._ShouldHandleRequest("/set-cookie"):
968 return False
969
970 query_char = self.path.find('?')
971 if query_char != -1:
972 cookie_values = self.path[query_char + 1:].split('&')
973 else:
974 cookie_values = ("",)
975 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000976 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000977 for cookie_value in cookie_values:
978 self.send_header('Set-Cookie', '%s' % cookie_value)
979 self.end_headers()
980 for cookie_value in cookie_values:
981 self.wfile.write('%s' % cookie_value)
982 return True
983
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000984 def SetManyCookiesHandler(self):
985 """This handler just sets a given number of cookies, for testing handling
986 of large numbers of cookies."""
987
988 if not self._ShouldHandleRequest("/set-many-cookies"):
989 return False
990
991 query_char = self.path.find('?')
992 if query_char != -1:
993 num_cookies = int(self.path[query_char + 1:])
994 else:
995 num_cookies = 0
996 self.send_response(200)
997 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000998 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000999 self.send_header('Set-Cookie', 'a=')
1000 self.end_headers()
1001 self.wfile.write('%d cookies were sent' % num_cookies)
1002 return True
1003
mattm@chromium.org983fc462012-06-30 00:52:08 +00001004 def ExpectAndSetCookieHandler(self):
1005 """Expects some cookies to be sent, and if they are, sets more cookies.
1006
1007 The expect parameter specifies a required cookie. May be specified multiple
1008 times.
1009 The set parameter specifies a cookie to set if all required cookies are
1010 preset. May be specified multiple times.
1011 The data parameter specifies the response body data to be returned."""
1012
1013 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1014 return False
1015
1016 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1017 query_dict = cgi.parse_qs(query)
1018 cookies = set()
1019 if 'Cookie' in self.headers:
1020 cookie_header = self.headers.getheader('Cookie')
1021 cookies.update([s.strip() for s in cookie_header.split(';')])
1022 got_all_expected_cookies = True
1023 for expected_cookie in query_dict.get('expect', []):
1024 if expected_cookie not in cookies:
1025 got_all_expected_cookies = False
1026 self.send_response(200)
1027 self.send_header('Content-Type', 'text/html')
1028 if got_all_expected_cookies:
1029 for cookie_value in query_dict.get('set', []):
1030 self.send_header('Set-Cookie', '%s' % cookie_value)
1031 self.end_headers()
1032 for data_value in query_dict.get('data', []):
1033 self.wfile.write(data_value)
1034 return True
1035
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001036 def SetHeaderHandler(self):
1037 """This handler sets a response header. Parameters are in the
1038 key%3A%20value&key2%3A%20value2 format."""
1039
1040 if not self._ShouldHandleRequest("/set-header"):
1041 return False
1042
1043 query_char = self.path.find('?')
1044 if query_char != -1:
1045 headers_values = self.path[query_char + 1:].split('&')
1046 else:
1047 headers_values = ("",)
1048 self.send_response(200)
1049 self.send_header('Content-Type', 'text/html')
1050 for header_value in headers_values:
1051 header_value = urllib.unquote(header_value)
1052 (key, value) = header_value.split(': ', 1)
1053 self.send_header(key, value)
1054 self.end_headers()
1055 for header_value in headers_values:
1056 self.wfile.write('%s' % header_value)
1057 return True
1058
initial.commit94958cf2008-07-26 22:42:52 +00001059 def AuthBasicHandler(self):
1060 """This handler tests 'Basic' authentication. It just sends a page with
1061 title 'user/pass' if you succeed."""
1062
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001063 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001064 return False
1065
1066 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001067 expected_password = 'secret'
1068 realm = 'testrealm'
1069 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001070
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001071 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1072 query_params = cgi.parse_qs(query, True)
1073 if 'set-cookie-if-challenged' in query_params:
1074 set_cookie_if_challenged = True
1075 if 'password' in query_params:
1076 expected_password = query_params['password'][0]
1077 if 'realm' in query_params:
1078 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001079
initial.commit94958cf2008-07-26 22:42:52 +00001080 auth = self.headers.getheader('authorization')
1081 try:
1082 if not auth:
1083 raise Exception('no auth')
1084 b64str = re.findall(r'Basic (\S+)', auth)[0]
1085 userpass = base64.b64decode(b64str)
1086 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001087 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001088 raise Exception('wrong password')
1089 except Exception, e:
1090 # Authentication failed.
1091 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001092 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001093 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001094 if set_cookie_if_challenged:
1095 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001096 self.end_headers()
1097 self.wfile.write('<html><head>')
1098 self.wfile.write('<title>Denied: %s</title>' % e)
1099 self.wfile.write('</head><body>')
1100 self.wfile.write('auth=%s<p>' % auth)
1101 self.wfile.write('b64str=%s<p>' % b64str)
1102 self.wfile.write('username: %s<p>' % username)
1103 self.wfile.write('userpass: %s<p>' % userpass)
1104 self.wfile.write('password: %s<p>' % password)
1105 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1106 self.wfile.write('</body></html>')
1107 return True
1108
1109 # Authentication successful. (Return a cachable response to allow for
1110 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001111 old_protocol_version = self.protocol_version
1112 self.protocol_version = "HTTP/1.1"
1113
initial.commit94958cf2008-07-26 22:42:52 +00001114 if_none_match = self.headers.getheader('if-none-match')
1115 if if_none_match == "abc":
1116 self.send_response(304)
1117 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001118 elif url_path.endswith(".gif"):
1119 # Using chrome/test/data/google/logo.gif as the test image
1120 test_image_path = ['google', 'logo.gif']
1121 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1122 if not os.path.isfile(gif_path):
1123 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001124 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001125 return True
1126
1127 f = open(gif_path, "rb")
1128 data = f.read()
1129 f.close()
1130
1131 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001132 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001133 self.send_header('Cache-control', 'max-age=60000')
1134 self.send_header('Etag', 'abc')
1135 self.end_headers()
1136 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001137 else:
1138 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001139 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001140 self.send_header('Cache-control', 'max-age=60000')
1141 self.send_header('Etag', 'abc')
1142 self.end_headers()
1143 self.wfile.write('<html><head>')
1144 self.wfile.write('<title>%s/%s</title>' % (username, password))
1145 self.wfile.write('</head><body>')
1146 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001147 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001148 self.wfile.write('</body></html>')
1149
rvargas@google.com54453b72011-05-19 01:11:11 +00001150 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001151 return True
1152
tonyg@chromium.org75054202010-03-31 22:06:10 +00001153 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001154 """Returns a nonce that's stable per request path for the server's lifetime.
1155 This is a fake implementation. A real implementation would only use a given
1156 nonce a single time (hence the name n-once). However, for the purposes of
1157 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001158
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001159 Args:
1160 force_reset: Iff set, the nonce will be changed. Useful for testing the
1161 "stale" response.
1162 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001163
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001164 if force_reset or not self.server.nonce_time:
1165 self.server.nonce_time = time.time()
1166 return hashlib.md5('privatekey%s%d' %
1167 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001168
1169 def AuthDigestHandler(self):
1170 """This handler tests 'Digest' authentication.
1171
1172 It just sends a page with title 'user/pass' if you succeed.
1173
1174 A stale response is sent iff "stale" is present in the request path.
1175 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001176
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001177 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001178 return False
1179
tonyg@chromium.org75054202010-03-31 22:06:10 +00001180 stale = 'stale' in self.path
1181 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001182 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001183 password = 'secret'
1184 realm = 'testrealm'
1185
1186 auth = self.headers.getheader('authorization')
1187 pairs = {}
1188 try:
1189 if not auth:
1190 raise Exception('no auth')
1191 if not auth.startswith('Digest'):
1192 raise Exception('not digest')
1193 # Pull out all the name="value" pairs as a dictionary.
1194 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1195
1196 # Make sure it's all valid.
1197 if pairs['nonce'] != nonce:
1198 raise Exception('wrong nonce')
1199 if pairs['opaque'] != opaque:
1200 raise Exception('wrong opaque')
1201
1202 # Check the 'response' value and make sure it matches our magic hash.
1203 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001204 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001205 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001206 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001207 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001208 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001209 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1210 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001211 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001212
1213 if pairs['response'] != response:
1214 raise Exception('wrong password')
1215 except Exception, e:
1216 # Authentication failed.
1217 self.send_response(401)
1218 hdr = ('Digest '
1219 'realm="%s", '
1220 'domain="/", '
1221 'qop="auth", '
1222 'algorithm=MD5, '
1223 'nonce="%s", '
1224 'opaque="%s"') % (realm, nonce, opaque)
1225 if stale:
1226 hdr += ', stale="TRUE"'
1227 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001228 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001229 self.end_headers()
1230 self.wfile.write('<html><head>')
1231 self.wfile.write('<title>Denied: %s</title>' % e)
1232 self.wfile.write('</head><body>')
1233 self.wfile.write('auth=%s<p>' % auth)
1234 self.wfile.write('pairs=%s<p>' % pairs)
1235 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1236 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1237 self.wfile.write('</body></html>')
1238 return True
1239
1240 # Authentication successful.
1241 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001242 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001243 self.end_headers()
1244 self.wfile.write('<html><head>')
1245 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1246 self.wfile.write('</head><body>')
1247 self.wfile.write('auth=%s<p>' % auth)
1248 self.wfile.write('pairs=%s<p>' % pairs)
1249 self.wfile.write('</body></html>')
1250
1251 return True
1252
1253 def SlowServerHandler(self):
1254 """Wait for the user suggested time before responding. The syntax is
1255 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001256
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001257 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001258 return False
1259 query_char = self.path.find('?')
1260 wait_sec = 1.0
1261 if query_char >= 0:
1262 try:
1263 wait_sec = int(self.path[query_char + 1:])
1264 except ValueError:
1265 pass
1266 time.sleep(wait_sec)
1267 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001268 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001269 self.end_headers()
1270 self.wfile.write("waited %d seconds" % wait_sec)
1271 return True
1272
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001273 def ChunkedServerHandler(self):
1274 """Send chunked response. Allows to specify chunks parameters:
1275 - waitBeforeHeaders - ms to wait before sending headers
1276 - waitBetweenChunks - ms to wait between chunks
1277 - chunkSize - size of each chunk in bytes
1278 - chunksNumber - number of chunks
1279 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1280 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001281
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001282 if not self._ShouldHandleRequest("/chunked"):
1283 return False
1284 query_char = self.path.find('?')
1285 chunkedSettings = {'waitBeforeHeaders' : 0,
1286 'waitBetweenChunks' : 0,
1287 'chunkSize' : 5,
1288 'chunksNumber' : 5}
1289 if query_char >= 0:
1290 params = self.path[query_char + 1:].split('&')
1291 for param in params:
1292 keyValue = param.split('=')
1293 if len(keyValue) == 2:
1294 try:
1295 chunkedSettings[keyValue[0]] = int(keyValue[1])
1296 except ValueError:
1297 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001298 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001299 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1300 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001301 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001302 self.send_header('Connection', 'close')
1303 self.send_header('Transfer-Encoding', 'chunked')
1304 self.end_headers()
1305 # Chunked encoding: sending all chunks, then final zero-length chunk and
1306 # then final CRLF.
1307 for i in range(0, chunkedSettings['chunksNumber']):
1308 if i > 0:
1309 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1310 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001311 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001312 self.sendChunkHelp('')
1313 return True
1314
initial.commit94958cf2008-07-26 22:42:52 +00001315 def ContentTypeHandler(self):
1316 """Returns a string of html with the given content type. E.g.,
1317 /contenttype?text/css returns an html file with the Content-Type
1318 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001319
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001320 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001321 return False
1322 query_char = self.path.find('?')
1323 content_type = self.path[query_char + 1:].strip()
1324 if not content_type:
1325 content_type = 'text/html'
1326 self.send_response(200)
1327 self.send_header('Content-Type', content_type)
1328 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001329 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001330 return True
1331
creis@google.com2f4f6a42011-03-25 19:44:19 +00001332 def NoContentHandler(self):
1333 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001334
creis@google.com2f4f6a42011-03-25 19:44:19 +00001335 if not self._ShouldHandleRequest("/nocontent"):
1336 return False
1337 self.send_response(204)
1338 self.end_headers()
1339 return True
1340
initial.commit94958cf2008-07-26 22:42:52 +00001341 def ServerRedirectHandler(self):
1342 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001343 '/server-redirect?http://foo.bar/asdf' to redirect to
1344 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001345
1346 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001347 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001348 return False
1349
1350 query_char = self.path.find('?')
1351 if query_char < 0 or len(self.path) <= query_char + 1:
1352 self.sendRedirectHelp(test_name)
1353 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001354 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001355
1356 self.send_response(301) # moved permanently
1357 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001358 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001359 self.end_headers()
1360 self.wfile.write('<html><head>')
1361 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1362
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001363 return True
initial.commit94958cf2008-07-26 22:42:52 +00001364
1365 def ClientRedirectHandler(self):
1366 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001367 '/client-redirect?http://foo.bar/asdf' to redirect to
1368 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001369
1370 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001371 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001372 return False
1373
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001374 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001375 if query_char < 0 or len(self.path) <= query_char + 1:
1376 self.sendRedirectHelp(test_name)
1377 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001378 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001379
1380 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001381 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001382 self.end_headers()
1383 self.wfile.write('<html><head>')
1384 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1385 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1386
1387 return True
1388
tony@chromium.org03266982010-03-05 03:18:42 +00001389 def MultipartHandler(self):
1390 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001391
tony@chromium.org4cb88302011-09-27 22:13:49 +00001392 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001393 if not self._ShouldHandleRequest(test_name):
1394 return False
1395
1396 num_frames = 10
1397 bound = '12345'
1398 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001399 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001400 'multipart/x-mixed-replace;boundary=' + bound)
1401 self.end_headers()
1402
1403 for i in xrange(num_frames):
1404 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001405 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001406 self.wfile.write('<title>page ' + str(i) + '</title>')
1407 self.wfile.write('page ' + str(i))
1408
1409 self.wfile.write('--' + bound + '--')
1410 return True
1411
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001412 def GetSSLSessionCacheHandler(self):
1413 """Send a reply containing a log of the session cache operations."""
1414
1415 if not self._ShouldHandleRequest('/ssl-session-cache'):
1416 return False
1417
1418 self.send_response(200)
1419 self.send_header('Content-Type', 'text/plain')
1420 self.end_headers()
1421 try:
1422 for (action, sessionID) in self.server.session_cache.log:
1423 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001424 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001425 self.wfile.write('Pass --https-record-resume in order to use' +
1426 ' this request')
1427 return True
1428
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001429 def SSLManySmallRecords(self):
1430 """Sends a reply consisting of a variety of small writes. These will be
1431 translated into a series of small SSL records when used over an HTTPS
1432 server."""
1433
1434 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1435 return False
1436
1437 self.send_response(200)
1438 self.send_header('Content-Type', 'text/plain')
1439 self.end_headers()
1440
1441 # Write ~26K of data, in 1350 byte chunks
1442 for i in xrange(20):
1443 self.wfile.write('*' * 1350)
1444 self.wfile.flush()
1445 return True
1446
agl@chromium.org04700be2013-03-02 18:40:41 +00001447 def GetChannelID(self):
1448 """Send a reply containing the hashed ChannelID that the client provided."""
1449
1450 if not self._ShouldHandleRequest('/channel-id'):
1451 return False
1452
1453 self.send_response(200)
1454 self.send_header('Content-Type', 'text/plain')
1455 self.end_headers()
1456 channel_id = self.server.tlsConnection.channel_id.tostring()
1457 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1458 return True
1459
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001460 def CloseSocketHandler(self):
1461 """Closes the socket without sending anything."""
1462
1463 if not self._ShouldHandleRequest('/close-socket'):
1464 return False
1465
1466 self.wfile.close()
1467 return True
1468
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001469 def RangeResetHandler(self):
1470 """Send data broken up by connection resets every N (default 4K) bytes.
1471 Support range requests. If the data requested doesn't straddle a reset
1472 boundary, it will all be sent. Used for testing resuming downloads."""
1473
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001474 def DataForRange(start, end):
1475 """Data to be provided for a particular range of bytes."""
1476 # Offset and scale to avoid too obvious (and hence potentially
1477 # collidable) data.
1478 return ''.join([chr(y % 256)
1479 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1480
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001481 if not self._ShouldHandleRequest('/rangereset'):
1482 return False
1483
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001484 # HTTP/1.1 is required for ETag and range support.
1485 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001486 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1487
1488 # Defaults
1489 size = 8000
1490 # Note that the rst is sent just before sending the rst_boundary byte.
1491 rst_boundary = 4000
1492 respond_to_range = True
1493 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001494 rst_limit = -1
1495 token = 'DEFAULT'
1496 fail_precondition = 0
1497 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001498
1499 # Parse the query
1500 qdict = urlparse.parse_qs(query, True)
1501 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001502 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001503 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001504 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001505 if 'token' in qdict:
1506 # Identifying token for stateful tests.
1507 token = qdict['token'][0]
1508 if 'rst_limit' in qdict:
1509 # Max number of rsts for a given token.
1510 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001511 if 'bounce_range' in qdict:
1512 respond_to_range = False
1513 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001514 # Note that hold_for_signal will not work with null range requests;
1515 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001516 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001517 if 'no_verifiers' in qdict:
1518 send_verifiers = False
1519 if 'fail_precondition' in qdict:
1520 fail_precondition = int(qdict['fail_precondition'][0])
1521
1522 # Record already set information, or set it.
1523 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1524 if rst_limit != 0:
1525 TestPageHandler.rst_limits[token] -= 1
1526 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1527 token, fail_precondition)
1528 if fail_precondition != 0:
1529 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001530
1531 first_byte = 0
1532 last_byte = size - 1
1533
1534 # Does that define what we want to return, or do we need to apply
1535 # a range?
1536 range_response = False
1537 range_header = self.headers.getheader('range')
1538 if range_header and respond_to_range:
1539 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1540 if mo.group(1):
1541 first_byte = int(mo.group(1))
1542 if mo.group(2):
1543 last_byte = int(mo.group(2))
1544 if last_byte > size - 1:
1545 last_byte = size - 1
1546 range_response = True
1547 if last_byte < first_byte:
1548 return False
1549
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001550 if (fail_precondition and
1551 (self.headers.getheader('If-Modified-Since') or
1552 self.headers.getheader('If-Match'))):
1553 self.send_response(412)
1554 self.end_headers()
1555 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001556
1557 if range_response:
1558 self.send_response(206)
1559 self.send_header('Content-Range',
1560 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1561 else:
1562 self.send_response(200)
1563 self.send_header('Content-Type', 'application/octet-stream')
1564 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001565 if send_verifiers:
1566 self.send_header('Etag', '"XYZZY"')
1567 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001568 self.end_headers()
1569
1570 if hold_for_signal:
1571 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1572 # a single byte, the self.server.handle_request() below hangs
1573 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001574 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001575 first_byte = first_byte + 1
1576 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001577 self.server.wait_for_download = True
1578 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001579 self.server.handle_request()
1580
1581 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001582 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001583 # No RST has been requested in this range, so we don't need to
1584 # do anything fancy; just write the data and let the python
1585 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001586 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001587 self.wfile.flush()
1588 return True
1589
1590 # We're resetting the connection part way in; go to the RST
1591 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001592 # Because socket semantics do not guarantee that all the data will be
1593 # sent when using the linger semantics to hard close a socket,
1594 # we send the data and then wait for our peer to release us
1595 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001596 data = DataForRange(first_byte, possible_rst)
1597 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001598 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001599 self.server.wait_for_download = True
1600 while self.server.wait_for_download:
1601 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001602 l_onoff = 1 # Linger is active.
1603 l_linger = 0 # Seconds to linger for.
1604 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1605 struct.pack('ii', l_onoff, l_linger))
1606
1607 # Close all duplicates of the underlying socket to force the RST.
1608 self.wfile.close()
1609 self.rfile.close()
1610 self.connection.close()
1611
1612 return True
1613
initial.commit94958cf2008-07-26 22:42:52 +00001614 def DefaultResponseHandler(self):
1615 """This is the catch-all response handler for requests that aren't handled
1616 by one of the special handlers above.
1617 Note that we specify the content-length as without it the https connection
1618 is not closed properly (and the browser keeps expecting data)."""
1619
1620 contents = "Default response given for path: " + self.path
1621 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001622 self.send_header('Content-Type', 'text/html')
1623 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001624 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001625 if (self.command != 'HEAD'):
1626 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001627 return True
1628
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001629 def RedirectConnectHandler(self):
1630 """Sends a redirect to the CONNECT request for www.redirect.com. This
1631 response is not specified by the RFC, so the browser should not follow
1632 the redirect."""
1633
1634 if (self.path.find("www.redirect.com") < 0):
1635 return False
1636
1637 dest = "http://www.destination.com/foo.js"
1638
1639 self.send_response(302) # moved temporarily
1640 self.send_header('Location', dest)
1641 self.send_header('Connection', 'close')
1642 self.end_headers()
1643 return True
1644
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001645 def ServerAuthConnectHandler(self):
1646 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1647 response doesn't make sense because the proxy server cannot request
1648 server authentication."""
1649
1650 if (self.path.find("www.server-auth.com") < 0):
1651 return False
1652
1653 challenge = 'Basic realm="WallyWorld"'
1654
1655 self.send_response(401) # unauthorized
1656 self.send_header('WWW-Authenticate', challenge)
1657 self.send_header('Connection', 'close')
1658 self.end_headers()
1659 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001660
1661 def DefaultConnectResponseHandler(self):
1662 """This is the catch-all response handler for CONNECT requests that aren't
1663 handled by one of the special handlers above. Real Web servers respond
1664 with 400 to CONNECT requests."""
1665
1666 contents = "Your client has issued a malformed or illegal request."
1667 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001668 self.send_header('Content-Type', 'text/html')
1669 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001670 self.end_headers()
1671 self.wfile.write(contents)
1672 return True
1673
initial.commit94958cf2008-07-26 22:42:52 +00001674 # called by the redirect handling function when there is no parameter
1675 def sendRedirectHelp(self, redirect_name):
1676 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001677 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001678 self.end_headers()
1679 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1680 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1681 self.wfile.write('</body></html>')
1682
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001683 # called by chunked handling function
1684 def sendChunkHelp(self, chunk):
1685 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1686 self.wfile.write('%X\r\n' % len(chunk))
1687 self.wfile.write(chunk)
1688 self.wfile.write('\r\n')
1689
akalin@chromium.org154bb132010-11-12 02:20:27 +00001690
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001691class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001692 def __init__(self, request, client_address, socket_server):
1693 handlers = [self.OCSPResponse]
1694 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001695 testserver_base.BasePageHandler.__init__(self, request, client_address,
1696 socket_server, [], handlers, [],
1697 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001698
1699 def OCSPResponse(self):
1700 self.send_response(200)
1701 self.send_header('Content-Type', 'application/ocsp-response')
1702 self.send_header('Content-Length', str(len(self.ocsp_response)))
1703 self.end_headers()
1704
1705 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001706
mattm@chromium.org830a3712012-11-07 23:00:07 +00001707
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001708class TCPEchoHandler(SocketServer.BaseRequestHandler):
1709 """The RequestHandler class for TCP echo server.
1710
1711 It is instantiated once per connection to the server, and overrides the
1712 handle() method to implement communication to the client.
1713 """
1714
1715 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001716 """Handles the request from the client and constructs a response."""
1717
1718 data = self.request.recv(65536).strip()
1719 # Verify the "echo request" message received from the client. Send back
1720 # "echo response" message if "echo request" message is valid.
1721 try:
1722 return_data = echo_message.GetEchoResponseData(data)
1723 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001724 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001725 except ValueError:
1726 return
1727
1728 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001729
1730
1731class UDPEchoHandler(SocketServer.BaseRequestHandler):
1732 """The RequestHandler class for UDP echo server.
1733
1734 It is instantiated once per connection to the server, and overrides the
1735 handle() method to implement communication to the client.
1736 """
1737
1738 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001739 """Handles the request from the client and constructs a response."""
1740
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001741 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001742 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001743 # Verify the "echo request" message received from the client. Send back
1744 # "echo response" message if "echo request" message is valid.
1745 try:
1746 return_data = echo_message.GetEchoResponseData(data)
1747 if not return_data:
1748 return
1749 except ValueError:
1750 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001751 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001752
1753
bashi@chromium.org33233532012-09-08 17:37:24 +00001754class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1755 """A request handler that behaves as a proxy server which requires
1756 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1757 """
1758
1759 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1760
1761 def parse_request(self):
1762 """Overrides parse_request to check credential."""
1763
1764 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1765 return False
1766
1767 auth = self.headers.getheader('Proxy-Authorization')
1768 if auth != self._AUTH_CREDENTIAL:
1769 self.send_response(407)
1770 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1771 self.end_headers()
1772 return False
1773
1774 return True
1775
1776 def _start_read_write(self, sock):
1777 sock.setblocking(0)
1778 self.request.setblocking(0)
1779 rlist = [self.request, sock]
1780 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001781 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001782 if errors:
1783 self.send_response(500)
1784 self.end_headers()
1785 return
1786 for s in ready_sockets:
1787 received = s.recv(1024)
1788 if len(received) == 0:
1789 return
1790 if s == self.request:
1791 other = sock
1792 else:
1793 other = self.request
1794 other.send(received)
1795
1796 def _do_common_method(self):
1797 url = urlparse.urlparse(self.path)
1798 port = url.port
1799 if not port:
1800 if url.scheme == 'http':
1801 port = 80
1802 elif url.scheme == 'https':
1803 port = 443
1804 if not url.hostname or not port:
1805 self.send_response(400)
1806 self.end_headers()
1807 return
1808
1809 if len(url.path) == 0:
1810 path = '/'
1811 else:
1812 path = url.path
1813 if len(url.query) > 0:
1814 path = '%s?%s' % (url.path, url.query)
1815
1816 sock = None
1817 try:
1818 sock = socket.create_connection((url.hostname, port))
1819 sock.send('%s %s %s\r\n' % (
1820 self.command, path, self.protocol_version))
1821 for header in self.headers.headers:
1822 header = header.strip()
1823 if (header.lower().startswith('connection') or
1824 header.lower().startswith('proxy')):
1825 continue
1826 sock.send('%s\r\n' % header)
1827 sock.send('\r\n')
1828 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001829 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001830 self.send_response(500)
1831 self.end_headers()
1832 finally:
1833 if sock is not None:
1834 sock.close()
1835
1836 def do_CONNECT(self):
1837 try:
1838 pos = self.path.rfind(':')
1839 host = self.path[:pos]
1840 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001841 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001842 self.send_response(400)
1843 self.end_headers()
1844
1845 try:
1846 sock = socket.create_connection((host, port))
1847 self.send_response(200, 'Connection established')
1848 self.end_headers()
1849 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001850 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001851 self.send_response(500)
1852 self.end_headers()
1853 finally:
1854 sock.close()
1855
1856 def do_GET(self):
1857 self._do_common_method()
1858
1859 def do_HEAD(self):
1860 self._do_common_method()
1861
1862
mattm@chromium.org830a3712012-11-07 23:00:07 +00001863class ServerRunner(testserver_base.TestServerRunner):
1864 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001865
mattm@chromium.org830a3712012-11-07 23:00:07 +00001866 def __init__(self):
1867 super(ServerRunner, self).__init__()
1868 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001869
mattm@chromium.org830a3712012-11-07 23:00:07 +00001870 def __make_data_dir(self):
1871 if self.options.data_dir:
1872 if not os.path.isdir(self.options.data_dir):
1873 raise testserver_base.OptionError('specified data dir not found: ' +
1874 self.options.data_dir + ' exiting...')
1875 my_data_dir = self.options.data_dir
1876 else:
1877 # Create the default path to our data dir, relative to the exe dir.
1878 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1879 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001880
mattm@chromium.org830a3712012-11-07 23:00:07 +00001881 #TODO(ibrar): Must use Find* funtion defined in google\tools
1882 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001883
mattm@chromium.org830a3712012-11-07 23:00:07 +00001884 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001885
mattm@chromium.org830a3712012-11-07 23:00:07 +00001886 def create_server(self, server_data):
1887 port = self.options.port
1888 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001889
mattm@chromium.org830a3712012-11-07 23:00:07 +00001890 if self.options.server_type == SERVER_HTTP:
1891 if self.options.https:
1892 pem_cert_and_key = None
1893 if self.options.cert_and_key_file:
1894 if not os.path.isfile(self.options.cert_and_key_file):
1895 raise testserver_base.OptionError(
1896 'specified server cert file not found: ' +
1897 self.options.cert_and_key_file + ' exiting...')
1898 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001899 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001900 # generate a new certificate and run an OCSP server for it.
1901 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001902 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001903 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001904
mattm@chromium.org830a3712012-11-07 23:00:07 +00001905 ocsp_der = None
1906 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001907
mattm@chromium.org830a3712012-11-07 23:00:07 +00001908 if self.options.ocsp == 'ok':
1909 ocsp_state = minica.OCSP_STATE_GOOD
1910 elif self.options.ocsp == 'revoked':
1911 ocsp_state = minica.OCSP_STATE_REVOKED
1912 elif self.options.ocsp == 'invalid':
1913 ocsp_state = minica.OCSP_STATE_INVALID
1914 elif self.options.ocsp == 'unauthorized':
1915 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1916 elif self.options.ocsp == 'unknown':
1917 ocsp_state = minica.OCSP_STATE_UNKNOWN
1918 else:
1919 raise testserver_base.OptionError('unknown OCSP status: ' +
1920 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001921
mattm@chromium.org830a3712012-11-07 23:00:07 +00001922 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1923 subject = "127.0.0.1",
1924 ocsp_url = ("http://%s:%d/ocsp" %
1925 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00001926 ocsp_state = ocsp_state,
1927 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001928
1929 self.__ocsp_server.ocsp_response = ocsp_der
1930
1931 for ca_cert in self.options.ssl_client_ca:
1932 if not os.path.isfile(ca_cert):
1933 raise testserver_base.OptionError(
1934 'specified trusted client CA file not found: ' + ca_cert +
1935 ' exiting...')
1936 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1937 self.options.ssl_client_auth,
1938 self.options.ssl_client_ca,
1939 self.options.ssl_bulk_cipher,
1940 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00001941 self.options.tls_intolerant,
1942 self.options.signed_cert_timestamps.decode(
1943 "base64"))
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001944 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001945 else:
1946 server = HTTPServer((host, port), TestPageHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001947 print 'HTTP server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001948
1949 server.data_dir = self.__make_data_dir()
1950 server.file_root_url = self.options.file_root_url
1951 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001952 elif self.options.server_type == SERVER_WEBSOCKET:
1953 # Launch pywebsocket via WebSocketServer.
1954 logger = logging.getLogger()
1955 logger.addHandler(logging.StreamHandler())
1956 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1957 # is required to work correctly. It should be fixed from pywebsocket side.
1958 os.chdir(self.__make_data_dir())
1959 websocket_options = WebSocketOptions(host, port, '.')
1960 if self.options.cert_and_key_file:
1961 websocket_options.use_tls = True
1962 websocket_options.private_key = self.options.cert_and_key_file
1963 websocket_options.certificate = self.options.cert_and_key_file
1964 if self.options.ssl_client_auth:
1965 websocket_options.tls_client_auth = True
1966 if len(self.options.ssl_client_ca) != 1:
1967 raise testserver_base.OptionError(
1968 'one trusted client CA file should be specified')
1969 if not os.path.isfile(self.options.ssl_client_ca[0]):
1970 raise testserver_base.OptionError(
1971 'specified trusted client CA file not found: ' +
1972 self.options.ssl_client_ca[0] + ' exiting...')
1973 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
1974 server = WebSocketServer(websocket_options)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001975 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001976 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001977 elif self.options.server_type == SERVER_TCP_ECHO:
1978 # Used for generating the key (randomly) that encodes the "echo request"
1979 # message.
1980 random.seed()
1981 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001982 print 'Echo TCP 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_UDP_ECHO:
1985 # Used for generating the key (randomly) that encodes the "echo request"
1986 # message.
1987 random.seed()
1988 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001989 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001990 server_data['port'] = server.server_port
1991 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
1992 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001993 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001994 server_data['port'] = server.server_port
1995 elif self.options.server_type == SERVER_FTP:
1996 my_data_dir = self.__make_data_dir()
1997
1998 # Instantiate a dummy authorizer for managing 'virtual' users
1999 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2000
2001 # Define a new user having full r/w permissions and a read-only
2002 # anonymous user
2003 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2004
2005 authorizer.add_anonymous(my_data_dir)
2006
2007 # Instantiate FTP handler class
2008 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2009 ftp_handler.authorizer = authorizer
2010
2011 # Define a customized banner (string returned when client connects)
2012 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2013 pyftpdlib.ftpserver.__ver__)
2014
2015 # Instantiate FTP server class and listen to address:port
2016 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2017 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002018 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002019 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002020 raise testserver_base.OptionError('unknown server type' +
2021 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002022
mattm@chromium.org830a3712012-11-07 23:00:07 +00002023 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002024
mattm@chromium.org830a3712012-11-07 23:00:07 +00002025 def run_server(self):
2026 if self.__ocsp_server:
2027 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002028
mattm@chromium.org830a3712012-11-07 23:00:07 +00002029 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002030
mattm@chromium.org830a3712012-11-07 23:00:07 +00002031 if self.__ocsp_server:
2032 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002033
mattm@chromium.org830a3712012-11-07 23:00:07 +00002034 def add_options(self):
2035 testserver_base.TestServerRunner.add_options(self)
2036 self.option_parser.add_option('-f', '--ftp', action='store_const',
2037 const=SERVER_FTP, default=SERVER_HTTP,
2038 dest='server_type',
2039 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002040 self.option_parser.add_option('--tcp-echo', action='store_const',
2041 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2042 dest='server_type',
2043 help='start up a tcp echo server.')
2044 self.option_parser.add_option('--udp-echo', action='store_const',
2045 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2046 dest='server_type',
2047 help='start up a udp echo server.')
2048 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2049 const=SERVER_BASIC_AUTH_PROXY,
2050 default=SERVER_HTTP, dest='server_type',
2051 help='start up a proxy server which requires '
2052 'basic authentication.')
2053 self.option_parser.add_option('--websocket', action='store_const',
2054 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2055 dest='server_type',
2056 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002057 self.option_parser.add_option('--https', action='store_true',
2058 dest='https', help='Specify that https '
2059 'should be used.')
2060 self.option_parser.add_option('--cert-and-key-file',
2061 dest='cert_and_key_file', help='specify the '
2062 'path to the file containing the certificate '
2063 'and private key for the server in PEM '
2064 'format')
2065 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2066 help='The type of OCSP response generated '
2067 'for the automatically generated '
2068 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002069 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2070 default=0, type=int,
2071 help='If non-zero then the generated '
2072 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002073 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2074 default='0', type='int',
2075 help='If nonzero, certain TLS connections '
2076 'will be aborted in order to test version '
2077 'fallback. 1 means all TLS versions will be '
2078 'aborted. 2 means TLS 1.1 or higher will be '
2079 'aborted. 3 means TLS 1.2 or higher will be '
2080 'aborted.')
ekasper@google.com24aa8222013-11-28 13:43:26 +00002081 self.option_parser.add_option('--signed-cert-timestamps',
2082 dest='signed_cert_timestamps',
2083 default='',
2084 help='Base64 encoded SCT list. If set, '
2085 'server will respond with a '
2086 'signed_certificate_timestamp TLS extension '
2087 'whenever the client supports it.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002088 self.option_parser.add_option('--https-record-resume',
2089 dest='record_resume', const=True,
2090 default=False, action='store_const',
2091 help='Record resumption cache events rather '
2092 'than resuming as normal. Allows the use of '
2093 'the /ssl-session-cache request')
2094 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2095 help='Require SSL client auth on every '
2096 'connection.')
2097 self.option_parser.add_option('--ssl-client-ca', action='append',
2098 default=[], help='Specify that the client '
2099 'certificate request should include the CA '
2100 'named in the subject of the DER-encoded '
2101 'certificate contained in the specified '
2102 'file. This option may appear multiple '
2103 'times, indicating multiple CA names should '
2104 'be sent in the request.')
2105 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2106 help='Specify the bulk encryption '
2107 'algorithm(s) that will be accepted by the '
2108 'SSL server. Valid values are "aes256", '
2109 '"aes128", "3des", "rc4". If omitted, all '
2110 'algorithms will be used. This option may '
2111 'appear multiple times, indicating '
2112 'multiple algorithms should be enabled.');
2113 self.option_parser.add_option('--file-root-url', default='/files/',
2114 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002115
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002116
initial.commit94958cf2008-07-26 22:42:52 +00002117if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002118 sys.exit(ServerRunner().main())