blob: 6e85b4f64083f24a3b0b1629338f5a28584fdbc9 [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
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000038BASE_DIR = os.path.dirname(os.path.abspath(__file__))
39ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(BASE_DIR)))
40
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000041import echo_message
mattm@chromium.org830a3712012-11-07 23:00:07 +000042import testserver_base
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000043
44# Append at the end of sys.path, it's fine to use the system library.
45sys.path.append(os.path.join(ROOT_DIR, 'third_party', 'pyftpdlib', 'src'))
46sys.path.append(os.path.join(ROOT_DIR, 'third_party', 'tlslite'))
47import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000048import tlslite
49import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000050
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000051# Insert at the beginning of the path, we want this to be used
52# unconditionally.
53sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'pywebsocket', 'src'))
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000054from mod_pywebsocket.standalone import WebSocketServer
davidben@chromium.org06fcf202010-09-22 18:15:23 +000055
maruel@chromium.org756cf982009-03-05 12:46:38 +000056SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000057SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000058SERVER_TCP_ECHO = 2
59SERVER_UDP_ECHO = 3
60SERVER_BASIC_AUTH_PROXY = 4
61SERVER_WEBSOCKET = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000062
63# Default request queue size for WebSocketServer.
64_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000065
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000066class WebSocketOptions:
67 """Holds options for WebSocketServer."""
68
69 def __init__(self, host, port, data_dir):
70 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
71 self.server_host = host
72 self.port = port
73 self.websock_handlers = data_dir
74 self.scan_dir = None
75 self.allow_handlers_outside_root_dir = False
76 self.websock_handlers_map_file = None
77 self.cgi_directories = []
78 self.is_executable_method = None
79 self.allow_draft75 = False
80 self.strict = True
81
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000082 self.use_tls = False
83 self.private_key = None
84 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000085 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000086 self.tls_client_ca = None
87 self.use_basic_auth = False
88
mattm@chromium.org830a3712012-11-07 23:00:07 +000089
agl@chromium.orgf9e66792011-12-12 22:22:19 +000090class RecordingSSLSessionCache(object):
91 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
92 lookups and inserts in order to test session cache behaviours."""
93
94 def __init__(self):
95 self.log = []
96
97 def __getitem__(self, sessionID):
98 self.log.append(('lookup', sessionID))
99 raise KeyError()
100
101 def __setitem__(self, sessionID, session):
102 self.log.append(('insert', sessionID))
103
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000104
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000105class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
106 testserver_base.BrokenPipeHandlerMixIn,
107 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000108 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000109 verification."""
110
111 pass
112
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000113class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
114 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000115 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000116 """This is a specialization of HTTPServer that serves an
117 OCSP response"""
118
119 def serve_forever_on_thread(self):
120 self.thread = threading.Thread(target = self.serve_forever,
121 name = "OCSPServerThread")
122 self.thread.start()
123
124 def stop_serving(self):
125 self.shutdown()
126 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000127
mattm@chromium.org830a3712012-11-07 23:00:07 +0000128
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000129class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000130 testserver_base.ClientRestrictingServerMixIn,
131 testserver_base.BrokenPipeHandlerMixIn,
132 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000133 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000134 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000135
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000136 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000137 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000138 record_resume_info, tls_intolerant, signed_cert_timestamps):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000139 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000140 # Force using only python implementation - otherwise behavior is different
141 # depending on whether m2crypto Python module is present (error is thrown
142 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
143 # the hood.
144 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
145 private=True,
146 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000147 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000148 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000149 self.tls_intolerant = tls_intolerant
ekasper@google.com24aa8222013-11-28 13:43:26 +0000150 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.org143daa42012-04-26 18:45:34 +0000151
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000152 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000153 s = open(ca_file).read()
154 x509 = tlslite.api.X509()
155 x509.parse(s)
156 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000157 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
158 if ssl_bulk_ciphers is not None:
159 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000160
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000161 if record_resume_info:
162 # If record_resume_info is true then we'll replace the session cache with
163 # an object that records the lookups and inserts that it sees.
164 self.session_cache = RecordingSSLSessionCache()
165 else:
166 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000167 testserver_base.StoppableHTTPServer.__init__(self,
168 server_address,
169 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000170
171 def handshake(self, tlsConnection):
172 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000173
initial.commit94958cf2008-07-26 22:42:52 +0000174 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000175 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000176 tlsConnection.handshakeServer(certChain=self.cert_chain,
177 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000178 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000179 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000180 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000181 reqCAs=self.ssl_client_cas,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000182 tlsIntolerant=self.tls_intolerant,
183 signedCertTimestamps=
184 self.signed_cert_timestamps)
initial.commit94958cf2008-07-26 22:42:52 +0000185 tlsConnection.ignoreAbruptClose = True
186 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000187 except tlslite.api.TLSAbruptCloseError:
188 # Ignore abrupt close.
189 return True
initial.commit94958cf2008-07-26 22:42:52 +0000190 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000191 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000192 return False
193
akalin@chromium.org154bb132010-11-12 02:20:27 +0000194
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000195class FTPServer(testserver_base.ClientRestrictingServerMixIn,
196 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000197 """This is a specialization of FTPServer that adds client verification."""
198
199 pass
200
201
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000202class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
203 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000204 """A TCP echo server that echoes back what it has received."""
205
206 def server_bind(self):
207 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000208
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000209 SocketServer.TCPServer.server_bind(self)
210 host, port = self.socket.getsockname()[:2]
211 self.server_name = socket.getfqdn(host)
212 self.server_port = port
213
214 def serve_forever(self):
215 self.stop = False
216 self.nonce_time = None
217 while not self.stop:
218 self.handle_request()
219 self.socket.close()
220
221
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000222class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
223 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000224 """A UDP echo server that echoes back what it has received."""
225
226 def server_bind(self):
227 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000228
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000229 SocketServer.UDPServer.server_bind(self)
230 host, port = self.socket.getsockname()[:2]
231 self.server_name = socket.getfqdn(host)
232 self.server_port = port
233
234 def serve_forever(self):
235 self.stop = False
236 self.nonce_time = None
237 while not self.stop:
238 self.handle_request()
239 self.socket.close()
240
241
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000242class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000243 # Class variables to allow for persistence state between page handler
244 # invocations
245 rst_limits = {}
246 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000247
248 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000249 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000250 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000251 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000252 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000253 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000254 self.NoCacheMaxAgeTimeHandler,
255 self.NoCacheTimeHandler,
256 self.CacheTimeHandler,
257 self.CacheExpiresHandler,
258 self.CacheProxyRevalidateHandler,
259 self.CachePrivateHandler,
260 self.CachePublicHandler,
261 self.CacheSMaxAgeHandler,
262 self.CacheMustRevalidateHandler,
263 self.CacheMustRevalidateMaxAgeHandler,
264 self.CacheNoStoreHandler,
265 self.CacheNoStoreMaxAgeHandler,
266 self.CacheNoTransformHandler,
267 self.DownloadHandler,
268 self.DownloadFinishHandler,
269 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000270 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000271 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000272 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000273 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000274 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000275 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000276 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000277 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000278 self.AuthBasicHandler,
279 self.AuthDigestHandler,
280 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000281 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000282 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000283 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000284 self.ServerRedirectHandler,
285 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000286 self.MultipartHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000287 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000288 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000289 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000290 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000291 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000292 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000293 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000294 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000295 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000296 self.PostOnlyFileHandler,
297 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000298 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000299 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000300 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000301 head_handlers = [
302 self.FileHandler,
303 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000304
maruel@google.come250a9b2009-03-10 17:39:46 +0000305 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000306 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000307 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000308 'gif': 'image/gif',
309 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000310 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000311 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000312 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000313 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000314 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000315 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000316 }
initial.commit94958cf2008-07-26 22:42:52 +0000317 self._default_mime_type = 'text/html'
318
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000319 testserver_base.BasePageHandler.__init__(self, request, client_address,
320 socket_server, connect_handlers,
321 get_handlers, head_handlers,
322 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000323
initial.commit94958cf2008-07-26 22:42:52 +0000324 def GetMIMETypeFromName(self, file_name):
325 """Returns the mime type for the specified file_name. So far it only looks
326 at the file extension."""
327
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000328 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000329 if len(extension) == 0:
330 # no extension.
331 return self._default_mime_type
332
ericroman@google.comc17ca532009-05-07 03:51:05 +0000333 # extension starts with a dot, so we need to remove it
334 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000335
initial.commit94958cf2008-07-26 22:42:52 +0000336 def NoCacheMaxAgeTimeHandler(self):
337 """This request handler yields a page with the title set to the current
338 system time, and no caching requested."""
339
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000340 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000341 return False
342
343 self.send_response(200)
344 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000345 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000346 self.end_headers()
347
maruel@google.come250a9b2009-03-10 17:39:46 +0000348 self.wfile.write('<html><head><title>%s</title></head></html>' %
349 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000350
351 return True
352
353 def NoCacheTimeHandler(self):
354 """This request handler yields a page with the title set to the current
355 system time, and no caching requested."""
356
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000357 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000358 return False
359
360 self.send_response(200)
361 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000362 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000363 self.end_headers()
364
maruel@google.come250a9b2009-03-10 17:39:46 +0000365 self.wfile.write('<html><head><title>%s</title></head></html>' %
366 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000367
368 return True
369
370 def CacheTimeHandler(self):
371 """This request handler yields a page with the title set to the current
372 system time, and allows caching for one minute."""
373
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000374 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000375 return False
376
377 self.send_response(200)
378 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000379 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000380 self.end_headers()
381
maruel@google.come250a9b2009-03-10 17:39:46 +0000382 self.wfile.write('<html><head><title>%s</title></head></html>' %
383 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000384
385 return True
386
387 def CacheExpiresHandler(self):
388 """This request handler yields a page with the title set to the current
389 system time, and set the page to expire on 1 Jan 2099."""
390
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000391 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000392 return False
393
394 self.send_response(200)
395 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000396 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000397 self.end_headers()
398
maruel@google.come250a9b2009-03-10 17:39:46 +0000399 self.wfile.write('<html><head><title>%s</title></head></html>' %
400 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000401
402 return True
403
404 def CacheProxyRevalidateHandler(self):
405 """This request handler yields a page with the title set to the current
406 system time, and allows caching for 60 seconds"""
407
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000408 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000409 return False
410
411 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000412 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000413 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
414 self.end_headers()
415
maruel@google.come250a9b2009-03-10 17:39:46 +0000416 self.wfile.write('<html><head><title>%s</title></head></html>' %
417 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000418
419 return True
420
421 def CachePrivateHandler(self):
422 """This request handler yields a page with the title set to the current
423 system time, and allows caching for 5 seconds."""
424
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000425 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000426 return False
427
428 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000429 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000430 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000431 self.end_headers()
432
maruel@google.come250a9b2009-03-10 17:39:46 +0000433 self.wfile.write('<html><head><title>%s</title></head></html>' %
434 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000435
436 return True
437
438 def CachePublicHandler(self):
439 """This request handler yields a page with the title set to the current
440 system time, and allows caching for 5 seconds."""
441
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000442 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000443 return False
444
445 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000446 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000447 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000448 self.end_headers()
449
maruel@google.come250a9b2009-03-10 17:39:46 +0000450 self.wfile.write('<html><head><title>%s</title></head></html>' %
451 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000452
453 return True
454
455 def CacheSMaxAgeHandler(self):
456 """This request handler yields a page with the title set to the current
457 system time, and does not allow for caching."""
458
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000459 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000460 return False
461
462 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000463 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000464 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
465 self.end_headers()
466
maruel@google.come250a9b2009-03-10 17:39:46 +0000467 self.wfile.write('<html><head><title>%s</title></head></html>' %
468 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000469
470 return True
471
472 def CacheMustRevalidateHandler(self):
473 """This request handler yields a page with the title set to the current
474 system time, and does not allow caching."""
475
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000476 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000477 return False
478
479 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000480 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000481 self.send_header('Cache-Control', 'must-revalidate')
482 self.end_headers()
483
maruel@google.come250a9b2009-03-10 17:39:46 +0000484 self.wfile.write('<html><head><title>%s</title></head></html>' %
485 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000486
487 return True
488
489 def CacheMustRevalidateMaxAgeHandler(self):
490 """This request handler yields a page with the title set to the current
491 system time, and does not allow caching event though max-age of 60
492 seconds is specified."""
493
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000494 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000495 return False
496
497 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000498 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000499 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
500 self.end_headers()
501
maruel@google.come250a9b2009-03-10 17:39:46 +0000502 self.wfile.write('<html><head><title>%s</title></head></html>' %
503 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000504
505 return True
506
initial.commit94958cf2008-07-26 22:42:52 +0000507 def CacheNoStoreHandler(self):
508 """This request handler yields a page with the title set to the current
509 system time, and does not allow the page to be stored."""
510
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000511 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000512 return False
513
514 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000515 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000516 self.send_header('Cache-Control', 'no-store')
517 self.end_headers()
518
maruel@google.come250a9b2009-03-10 17:39:46 +0000519 self.wfile.write('<html><head><title>%s</title></head></html>' %
520 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000521
522 return True
523
524 def CacheNoStoreMaxAgeHandler(self):
525 """This request handler yields a page with the title set to the current
526 system time, and does not allow the page to be stored even though max-age
527 of 60 seconds is specified."""
528
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000529 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000530 return False
531
532 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000533 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000534 self.send_header('Cache-Control', 'max-age=60, no-store')
535 self.end_headers()
536
maruel@google.come250a9b2009-03-10 17:39:46 +0000537 self.wfile.write('<html><head><title>%s</title></head></html>' %
538 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000539
540 return True
541
542
543 def CacheNoTransformHandler(self):
544 """This request handler yields a page with the title set to the current
545 system time, and does not allow the content to transformed during
546 user-agent caching"""
547
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000548 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000549 return False
550
551 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000552 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000553 self.send_header('Cache-Control', 'no-transform')
554 self.end_headers()
555
maruel@google.come250a9b2009-03-10 17:39:46 +0000556 self.wfile.write('<html><head><title>%s</title></head></html>' %
557 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000558
559 return True
560
561 def EchoHeader(self):
562 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000563
ananta@chromium.org219b2062009-10-23 16:09:41 +0000564 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000565
ananta@chromium.org56812d02011-04-07 17:52:05 +0000566 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000567 """This function echoes back the value of a specific request header while
568 allowing caching for 16 hours."""
569
ananta@chromium.org56812d02011-04-07 17:52:05 +0000570 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000571
572 def EchoHeaderHelper(self, echo_header):
573 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000574
ananta@chromium.org219b2062009-10-23 16:09:41 +0000575 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000576 return False
577
578 query_char = self.path.find('?')
579 if query_char != -1:
580 header_name = self.path[query_char+1:]
581
582 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000583 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000584 if echo_header == '/echoheadercache':
585 self.send_header('Cache-control', 'max-age=60000')
586 else:
587 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000588 # insert a vary header to properly indicate that the cachability of this
589 # request is subject to value of the request header being echoed.
590 if len(header_name) > 0:
591 self.send_header('Vary', header_name)
592 self.end_headers()
593
594 if len(header_name) > 0:
595 self.wfile.write(self.headers.getheader(header_name))
596
597 return True
598
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000599 def ReadRequestBody(self):
600 """This function reads the body of the current HTTP request, handling
601 both plain and chunked transfer encoded requests."""
602
603 if self.headers.getheader('transfer-encoding') != 'chunked':
604 length = int(self.headers.getheader('content-length'))
605 return self.rfile.read(length)
606
607 # Read the request body as chunks.
608 body = ""
609 while True:
610 line = self.rfile.readline()
611 length = int(line, 16)
612 if length == 0:
613 self.rfile.readline()
614 break
615 body += self.rfile.read(length)
616 self.rfile.read(2)
617 return body
618
initial.commit94958cf2008-07-26 22:42:52 +0000619 def EchoHandler(self):
620 """This handler just echoes back the payload of the request, for testing
621 form submission."""
622
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000623 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000624 return False
625
626 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000627 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000628 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000629 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000630 return True
631
632 def EchoTitleHandler(self):
633 """This handler is like Echo, but sets the page title to the request."""
634
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000635 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000636 return False
637
638 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000639 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000640 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000641 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000642 self.wfile.write('<html><head><title>')
643 self.wfile.write(request)
644 self.wfile.write('</title></head></html>')
645 return True
646
647 def EchoAllHandler(self):
648 """This handler yields a (more) human-readable page listing information
649 about the request header & contents."""
650
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000651 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000652 return False
653
654 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000655 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000656 self.end_headers()
657 self.wfile.write('<html><head><style>'
658 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
659 '</style></head><body>'
660 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000661 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000662 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000663
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000664 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000665 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000666 params = cgi.parse_qs(qs, keep_blank_values=1)
667
668 for param in params:
669 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000670
671 self.wfile.write('</pre>')
672
673 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
674
675 self.wfile.write('</body></html>')
676 return True
677
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000678 def EchoMultipartPostHandler(self):
679 """This handler echoes received multipart post data as json format."""
680
681 if not (self._ShouldHandleRequest("/echomultipartpost") or
682 self._ShouldHandleRequest("/searchbyimage")):
683 return False
684
685 content_type, parameters = cgi.parse_header(
686 self.headers.getheader('content-type'))
687 if content_type == 'multipart/form-data':
688 post_multipart = cgi.parse_multipart(self.rfile, parameters)
689 elif content_type == 'application/x-www-form-urlencoded':
690 raise Exception('POST by application/x-www-form-urlencoded is '
691 'not implemented.')
692 else:
693 post_multipart = {}
694
695 # Since the data can be binary, we encode them by base64.
696 post_multipart_base64_encoded = {}
697 for field, values in post_multipart.items():
698 post_multipart_base64_encoded[field] = [base64.b64encode(value)
699 for value in values]
700
701 result = {'POST_multipart' : post_multipart_base64_encoded}
702
703 self.send_response(200)
704 self.send_header("Content-type", "text/plain")
705 self.end_headers()
706 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
707 return True
708
initial.commit94958cf2008-07-26 22:42:52 +0000709 def DownloadHandler(self):
710 """This handler sends a downloadable file with or without reporting
711 the size (6K)."""
712
713 if self.path.startswith("/download-unknown-size"):
714 send_length = False
715 elif self.path.startswith("/download-known-size"):
716 send_length = True
717 else:
718 return False
719
720 #
721 # The test which uses this functionality is attempting to send
722 # small chunks of data to the client. Use a fairly large buffer
723 # so that we'll fill chrome's IO buffer enough to force it to
724 # actually write the data.
725 # See also the comments in the client-side of this test in
726 # download_uitest.cc
727 #
728 size_chunk1 = 35*1024
729 size_chunk2 = 10*1024
730
731 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000732 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000733 self.send_header('Cache-Control', 'max-age=0')
734 if send_length:
735 self.send_header('Content-Length', size_chunk1 + size_chunk2)
736 self.end_headers()
737
738 # First chunk of data:
739 self.wfile.write("*" * size_chunk1)
740 self.wfile.flush()
741
742 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000743 self.server.wait_for_download = True
744 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000745 self.server.handle_request()
746
747 # Second chunk of data:
748 self.wfile.write("*" * size_chunk2)
749 return True
750
751 def DownloadFinishHandler(self):
752 """This handler just tells the server to finish the current download."""
753
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000754 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000755 return False
756
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000757 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000758 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000759 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000760 self.send_header('Cache-Control', 'max-age=0')
761 self.end_headers()
762 return True
763
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000764 def _ReplaceFileData(self, data, query_parameters):
765 """Replaces matching substrings in a file.
766
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000767 If the 'replace_text' URL query parameter is present, it is expected to be
768 of the form old_text:new_text, which indicates that any old_text strings in
769 the file are replaced with new_text. Multiple 'replace_text' parameters may
770 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000771
772 If the parameters are not present, |data| is returned.
773 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000774
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000775 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000776 replace_text_values = query_dict.get('replace_text', [])
777 for replace_text_value in replace_text_values:
778 replace_text_args = replace_text_value.split(':')
779 if len(replace_text_args) != 2:
780 raise ValueError(
781 'replace_text must be of form old_text:new_text. Actual value: %s' %
782 replace_text_value)
783 old_text_b64, new_text_b64 = replace_text_args
784 old_text = base64.urlsafe_b64decode(old_text_b64)
785 new_text = base64.urlsafe_b64decode(new_text_b64)
786 data = data.replace(old_text, new_text)
787 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000788
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000789 def ZipFileHandler(self):
790 """This handler sends the contents of the requested file in compressed form.
791 Can pass in a parameter that specifies that the content length be
792 C - the compressed size (OK),
793 U - the uncompressed size (Non-standard, but handled),
794 S - less than compressed (OK because we keep going),
795 M - larger than compressed but less than uncompressed (an error),
796 L - larger than uncompressed (an error)
797 Example: compressedfiles/Picture_1.doc?C
798 """
799
800 prefix = "/compressedfiles/"
801 if not self.path.startswith(prefix):
802 return False
803
804 # Consume a request body if present.
805 if self.command == 'POST' or self.command == 'PUT' :
806 self.ReadRequestBody()
807
808 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
809
810 if not query in ('C', 'U', 'S', 'M', 'L'):
811 return False
812
813 sub_path = url_path[len(prefix):]
814 entries = sub_path.split('/')
815 file_path = os.path.join(self.server.data_dir, *entries)
816 if os.path.isdir(file_path):
817 file_path = os.path.join(file_path, 'index.html')
818
819 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000820 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000821 self.send_error(404)
822 return True
823
824 f = open(file_path, "rb")
825 data = f.read()
826 uncompressed_len = len(data)
827 f.close()
828
829 # Compress the data.
830 data = zlib.compress(data)
831 compressed_len = len(data)
832
833 content_length = compressed_len
834 if query == 'U':
835 content_length = uncompressed_len
836 elif query == 'S':
837 content_length = compressed_len / 2
838 elif query == 'M':
839 content_length = (compressed_len + uncompressed_len) / 2
840 elif query == 'L':
841 content_length = compressed_len + uncompressed_len
842
843 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000844 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000845 self.send_header('Content-encoding', 'deflate')
846 self.send_header('Connection', 'close')
847 self.send_header('Content-Length', content_length)
848 self.send_header('ETag', '\'' + file_path + '\'')
849 self.end_headers()
850
851 self.wfile.write(data)
852
853 return True
854
initial.commit94958cf2008-07-26 22:42:52 +0000855 def FileHandler(self):
856 """This handler sends the contents of the requested file. Wow, it's like
857 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000858
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000859 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000860 if not self.path.startswith(prefix):
861 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000862 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000863
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000864 def PostOnlyFileHandler(self):
865 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000866
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000867 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000868 if not self.path.startswith(prefix):
869 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000870 return self._FileHandlerHelper(prefix)
871
872 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000873 request_body = ''
874 if self.command == 'POST' or self.command == 'PUT':
875 # Consume a request body if present.
876 request_body = self.ReadRequestBody()
877
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000878 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000879 query_dict = cgi.parse_qs(query)
880
881 expected_body = query_dict.get('expected_body', [])
882 if expected_body and request_body not in expected_body:
883 self.send_response(404)
884 self.end_headers()
885 self.wfile.write('')
886 return True
887
888 expected_headers = query_dict.get('expected_headers', [])
889 for expected_header in expected_headers:
890 header_name, expected_value = expected_header.split(':')
891 if self.headers.getheader(header_name) != expected_value:
892 self.send_response(404)
893 self.end_headers()
894 self.wfile.write('')
895 return True
896
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000897 sub_path = url_path[len(prefix):]
898 entries = sub_path.split('/')
899 file_path = os.path.join(self.server.data_dir, *entries)
900 if os.path.isdir(file_path):
901 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000902
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000903 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000904 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000905 self.send_error(404)
906 return True
907
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000908 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000909 data = f.read()
910 f.close()
911
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000912 data = self._ReplaceFileData(data, query)
913
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000914 old_protocol_version = self.protocol_version
915
initial.commit94958cf2008-07-26 22:42:52 +0000916 # If file.mock-http-headers exists, it contains the headers we
917 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000918 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000919 if os.path.isfile(headers_path):
920 f = open(headers_path, "r")
921
922 # "HTTP/1.1 200 OK"
923 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000924 http_major, http_minor, status_code = re.findall(
925 'HTTP/(\d+).(\d+) (\d+)', response)[0]
926 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000927 self.send_response(int(status_code))
928
929 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000930 header_values = re.findall('(\S+):\s*(.*)', line)
931 if len(header_values) > 0:
932 # "name: value"
933 name, value = header_values[0]
934 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000935 f.close()
936 else:
937 # Could be more generic once we support mime-type sniffing, but for
938 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000939
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000940 range_header = self.headers.get('Range')
941 if range_header and range_header.startswith('bytes='):
942 # Note this doesn't handle all valid byte range_header values (i.e.
943 # left open ended ones), just enough for what we needed so far.
944 range_header = range_header[6:].split('-')
945 start = int(range_header[0])
946 if range_header[1]:
947 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000948 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000949 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000950
951 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000952 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
953 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000954 self.send_header('Content-Range', content_range)
955 data = data[start: end + 1]
956 else:
957 self.send_response(200)
958
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000959 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000960 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000961 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000962 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000963 self.end_headers()
964
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000965 if (self.command != 'HEAD'):
966 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000967
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000968 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000969 return True
970
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000971 def SetCookieHandler(self):
972 """This handler just sets a cookie, for testing cookie handling."""
973
974 if not self._ShouldHandleRequest("/set-cookie"):
975 return False
976
977 query_char = self.path.find('?')
978 if query_char != -1:
979 cookie_values = self.path[query_char + 1:].split('&')
980 else:
981 cookie_values = ("",)
982 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000983 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000984 for cookie_value in cookie_values:
985 self.send_header('Set-Cookie', '%s' % cookie_value)
986 self.end_headers()
987 for cookie_value in cookie_values:
988 self.wfile.write('%s' % cookie_value)
989 return True
990
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000991 def SetManyCookiesHandler(self):
992 """This handler just sets a given number of cookies, for testing handling
993 of large numbers of cookies."""
994
995 if not self._ShouldHandleRequest("/set-many-cookies"):
996 return False
997
998 query_char = self.path.find('?')
999 if query_char != -1:
1000 num_cookies = int(self.path[query_char + 1:])
1001 else:
1002 num_cookies = 0
1003 self.send_response(200)
1004 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001005 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001006 self.send_header('Set-Cookie', 'a=')
1007 self.end_headers()
1008 self.wfile.write('%d cookies were sent' % num_cookies)
1009 return True
1010
mattm@chromium.org983fc462012-06-30 00:52:08 +00001011 def ExpectAndSetCookieHandler(self):
1012 """Expects some cookies to be sent, and if they are, sets more cookies.
1013
1014 The expect parameter specifies a required cookie. May be specified multiple
1015 times.
1016 The set parameter specifies a cookie to set if all required cookies are
1017 preset. May be specified multiple times.
1018 The data parameter specifies the response body data to be returned."""
1019
1020 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1021 return False
1022
1023 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1024 query_dict = cgi.parse_qs(query)
1025 cookies = set()
1026 if 'Cookie' in self.headers:
1027 cookie_header = self.headers.getheader('Cookie')
1028 cookies.update([s.strip() for s in cookie_header.split(';')])
1029 got_all_expected_cookies = True
1030 for expected_cookie in query_dict.get('expect', []):
1031 if expected_cookie not in cookies:
1032 got_all_expected_cookies = False
1033 self.send_response(200)
1034 self.send_header('Content-Type', 'text/html')
1035 if got_all_expected_cookies:
1036 for cookie_value in query_dict.get('set', []):
1037 self.send_header('Set-Cookie', '%s' % cookie_value)
1038 self.end_headers()
1039 for data_value in query_dict.get('data', []):
1040 self.wfile.write(data_value)
1041 return True
1042
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001043 def SetHeaderHandler(self):
1044 """This handler sets a response header. Parameters are in the
1045 key%3A%20value&key2%3A%20value2 format."""
1046
1047 if not self._ShouldHandleRequest("/set-header"):
1048 return False
1049
1050 query_char = self.path.find('?')
1051 if query_char != -1:
1052 headers_values = self.path[query_char + 1:].split('&')
1053 else:
1054 headers_values = ("",)
1055 self.send_response(200)
1056 self.send_header('Content-Type', 'text/html')
1057 for header_value in headers_values:
1058 header_value = urllib.unquote(header_value)
1059 (key, value) = header_value.split(': ', 1)
1060 self.send_header(key, value)
1061 self.end_headers()
1062 for header_value in headers_values:
1063 self.wfile.write('%s' % header_value)
1064 return True
1065
initial.commit94958cf2008-07-26 22:42:52 +00001066 def AuthBasicHandler(self):
1067 """This handler tests 'Basic' authentication. It just sends a page with
1068 title 'user/pass' if you succeed."""
1069
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001070 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001071 return False
1072
1073 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001074 expected_password = 'secret'
1075 realm = 'testrealm'
1076 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001077
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001078 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1079 query_params = cgi.parse_qs(query, True)
1080 if 'set-cookie-if-challenged' in query_params:
1081 set_cookie_if_challenged = True
1082 if 'password' in query_params:
1083 expected_password = query_params['password'][0]
1084 if 'realm' in query_params:
1085 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001086
initial.commit94958cf2008-07-26 22:42:52 +00001087 auth = self.headers.getheader('authorization')
1088 try:
1089 if not auth:
1090 raise Exception('no auth')
1091 b64str = re.findall(r'Basic (\S+)', auth)[0]
1092 userpass = base64.b64decode(b64str)
1093 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001094 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001095 raise Exception('wrong password')
1096 except Exception, e:
1097 # Authentication failed.
1098 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001099 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001100 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001101 if set_cookie_if_challenged:
1102 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001103 self.end_headers()
1104 self.wfile.write('<html><head>')
1105 self.wfile.write('<title>Denied: %s</title>' % e)
1106 self.wfile.write('</head><body>')
1107 self.wfile.write('auth=%s<p>' % auth)
1108 self.wfile.write('b64str=%s<p>' % b64str)
1109 self.wfile.write('username: %s<p>' % username)
1110 self.wfile.write('userpass: %s<p>' % userpass)
1111 self.wfile.write('password: %s<p>' % password)
1112 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1113 self.wfile.write('</body></html>')
1114 return True
1115
1116 # Authentication successful. (Return a cachable response to allow for
1117 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001118 old_protocol_version = self.protocol_version
1119 self.protocol_version = "HTTP/1.1"
1120
initial.commit94958cf2008-07-26 22:42:52 +00001121 if_none_match = self.headers.getheader('if-none-match')
1122 if if_none_match == "abc":
1123 self.send_response(304)
1124 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001125 elif url_path.endswith(".gif"):
1126 # Using chrome/test/data/google/logo.gif as the test image
1127 test_image_path = ['google', 'logo.gif']
1128 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1129 if not os.path.isfile(gif_path):
1130 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001131 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001132 return True
1133
1134 f = open(gif_path, "rb")
1135 data = f.read()
1136 f.close()
1137
1138 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001139 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001140 self.send_header('Cache-control', 'max-age=60000')
1141 self.send_header('Etag', 'abc')
1142 self.end_headers()
1143 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001144 else:
1145 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001146 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001147 self.send_header('Cache-control', 'max-age=60000')
1148 self.send_header('Etag', 'abc')
1149 self.end_headers()
1150 self.wfile.write('<html><head>')
1151 self.wfile.write('<title>%s/%s</title>' % (username, password))
1152 self.wfile.write('</head><body>')
1153 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001154 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001155 self.wfile.write('</body></html>')
1156
rvargas@google.com54453b72011-05-19 01:11:11 +00001157 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001158 return True
1159
tonyg@chromium.org75054202010-03-31 22:06:10 +00001160 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001161 """Returns a nonce that's stable per request path for the server's lifetime.
1162 This is a fake implementation. A real implementation would only use a given
1163 nonce a single time (hence the name n-once). However, for the purposes of
1164 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001165
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001166 Args:
1167 force_reset: Iff set, the nonce will be changed. Useful for testing the
1168 "stale" response.
1169 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001170
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001171 if force_reset or not self.server.nonce_time:
1172 self.server.nonce_time = time.time()
1173 return hashlib.md5('privatekey%s%d' %
1174 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001175
1176 def AuthDigestHandler(self):
1177 """This handler tests 'Digest' authentication.
1178
1179 It just sends a page with title 'user/pass' if you succeed.
1180
1181 A stale response is sent iff "stale" is present in the request path.
1182 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001183
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001184 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001185 return False
1186
tonyg@chromium.org75054202010-03-31 22:06:10 +00001187 stale = 'stale' in self.path
1188 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001189 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001190 password = 'secret'
1191 realm = 'testrealm'
1192
1193 auth = self.headers.getheader('authorization')
1194 pairs = {}
1195 try:
1196 if not auth:
1197 raise Exception('no auth')
1198 if not auth.startswith('Digest'):
1199 raise Exception('not digest')
1200 # Pull out all the name="value" pairs as a dictionary.
1201 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1202
1203 # Make sure it's all valid.
1204 if pairs['nonce'] != nonce:
1205 raise Exception('wrong nonce')
1206 if pairs['opaque'] != opaque:
1207 raise Exception('wrong opaque')
1208
1209 # Check the 'response' value and make sure it matches our magic hash.
1210 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001211 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001212 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001213 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001214 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001215 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001216 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1217 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001218 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001219
1220 if pairs['response'] != response:
1221 raise Exception('wrong password')
1222 except Exception, e:
1223 # Authentication failed.
1224 self.send_response(401)
1225 hdr = ('Digest '
1226 'realm="%s", '
1227 'domain="/", '
1228 'qop="auth", '
1229 'algorithm=MD5, '
1230 'nonce="%s", '
1231 'opaque="%s"') % (realm, nonce, opaque)
1232 if stale:
1233 hdr += ', stale="TRUE"'
1234 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001235 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001236 self.end_headers()
1237 self.wfile.write('<html><head>')
1238 self.wfile.write('<title>Denied: %s</title>' % e)
1239 self.wfile.write('</head><body>')
1240 self.wfile.write('auth=%s<p>' % auth)
1241 self.wfile.write('pairs=%s<p>' % pairs)
1242 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1243 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1244 self.wfile.write('</body></html>')
1245 return True
1246
1247 # Authentication successful.
1248 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001249 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001250 self.end_headers()
1251 self.wfile.write('<html><head>')
1252 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1253 self.wfile.write('</head><body>')
1254 self.wfile.write('auth=%s<p>' % auth)
1255 self.wfile.write('pairs=%s<p>' % pairs)
1256 self.wfile.write('</body></html>')
1257
1258 return True
1259
1260 def SlowServerHandler(self):
1261 """Wait for the user suggested time before responding. The syntax is
1262 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001263
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001264 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001265 return False
1266 query_char = self.path.find('?')
1267 wait_sec = 1.0
1268 if query_char >= 0:
1269 try:
1270 wait_sec = int(self.path[query_char + 1:])
1271 except ValueError:
1272 pass
1273 time.sleep(wait_sec)
1274 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001275 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001276 self.end_headers()
1277 self.wfile.write("waited %d seconds" % wait_sec)
1278 return True
1279
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001280 def ChunkedServerHandler(self):
1281 """Send chunked response. Allows to specify chunks parameters:
1282 - waitBeforeHeaders - ms to wait before sending headers
1283 - waitBetweenChunks - ms to wait between chunks
1284 - chunkSize - size of each chunk in bytes
1285 - chunksNumber - number of chunks
1286 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1287 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001288
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001289 if not self._ShouldHandleRequest("/chunked"):
1290 return False
1291 query_char = self.path.find('?')
1292 chunkedSettings = {'waitBeforeHeaders' : 0,
1293 'waitBetweenChunks' : 0,
1294 'chunkSize' : 5,
1295 'chunksNumber' : 5}
1296 if query_char >= 0:
1297 params = self.path[query_char + 1:].split('&')
1298 for param in params:
1299 keyValue = param.split('=')
1300 if len(keyValue) == 2:
1301 try:
1302 chunkedSettings[keyValue[0]] = int(keyValue[1])
1303 except ValueError:
1304 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001305 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001306 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1307 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001308 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001309 self.send_header('Connection', 'close')
1310 self.send_header('Transfer-Encoding', 'chunked')
1311 self.end_headers()
1312 # Chunked encoding: sending all chunks, then final zero-length chunk and
1313 # then final CRLF.
1314 for i in range(0, chunkedSettings['chunksNumber']):
1315 if i > 0:
1316 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1317 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001318 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001319 self.sendChunkHelp('')
1320 return True
1321
initial.commit94958cf2008-07-26 22:42:52 +00001322 def ContentTypeHandler(self):
1323 """Returns a string of html with the given content type. E.g.,
1324 /contenttype?text/css returns an html file with the Content-Type
1325 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001326
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001327 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001328 return False
1329 query_char = self.path.find('?')
1330 content_type = self.path[query_char + 1:].strip()
1331 if not content_type:
1332 content_type = 'text/html'
1333 self.send_response(200)
1334 self.send_header('Content-Type', content_type)
1335 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001336 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001337 return True
1338
creis@google.com2f4f6a42011-03-25 19:44:19 +00001339 def NoContentHandler(self):
1340 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001341
creis@google.com2f4f6a42011-03-25 19:44:19 +00001342 if not self._ShouldHandleRequest("/nocontent"):
1343 return False
1344 self.send_response(204)
1345 self.end_headers()
1346 return True
1347
initial.commit94958cf2008-07-26 22:42:52 +00001348 def ServerRedirectHandler(self):
1349 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001350 '/server-redirect?http://foo.bar/asdf' to redirect to
1351 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001352
1353 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001354 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001355 return False
1356
1357 query_char = self.path.find('?')
1358 if query_char < 0 or len(self.path) <= query_char + 1:
1359 self.sendRedirectHelp(test_name)
1360 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001361 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001362
1363 self.send_response(301) # moved permanently
1364 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001365 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001366 self.end_headers()
1367 self.wfile.write('<html><head>')
1368 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1369
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001370 return True
initial.commit94958cf2008-07-26 22:42:52 +00001371
1372 def ClientRedirectHandler(self):
1373 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001374 '/client-redirect?http://foo.bar/asdf' to redirect to
1375 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001376
1377 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001378 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001379 return False
1380
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001381 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001382 if query_char < 0 or len(self.path) <= query_char + 1:
1383 self.sendRedirectHelp(test_name)
1384 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001385 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001386
1387 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001388 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001389 self.end_headers()
1390 self.wfile.write('<html><head>')
1391 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1392 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1393
1394 return True
1395
tony@chromium.org03266982010-03-05 03:18:42 +00001396 def MultipartHandler(self):
1397 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001398
tony@chromium.org4cb88302011-09-27 22:13:49 +00001399 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001400 if not self._ShouldHandleRequest(test_name):
1401 return False
1402
1403 num_frames = 10
1404 bound = '12345'
1405 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001406 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001407 'multipart/x-mixed-replace;boundary=' + bound)
1408 self.end_headers()
1409
1410 for i in xrange(num_frames):
1411 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001412 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001413 self.wfile.write('<title>page ' + str(i) + '</title>')
1414 self.wfile.write('page ' + str(i))
1415
1416 self.wfile.write('--' + bound + '--')
1417 return True
1418
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001419 def GetSSLSessionCacheHandler(self):
1420 """Send a reply containing a log of the session cache operations."""
1421
1422 if not self._ShouldHandleRequest('/ssl-session-cache'):
1423 return False
1424
1425 self.send_response(200)
1426 self.send_header('Content-Type', 'text/plain')
1427 self.end_headers()
1428 try:
1429 for (action, sessionID) in self.server.session_cache.log:
1430 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001431 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001432 self.wfile.write('Pass --https-record-resume in order to use' +
1433 ' this request')
1434 return True
1435
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001436 def SSLManySmallRecords(self):
1437 """Sends a reply consisting of a variety of small writes. These will be
1438 translated into a series of small SSL records when used over an HTTPS
1439 server."""
1440
1441 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1442 return False
1443
1444 self.send_response(200)
1445 self.send_header('Content-Type', 'text/plain')
1446 self.end_headers()
1447
1448 # Write ~26K of data, in 1350 byte chunks
1449 for i in xrange(20):
1450 self.wfile.write('*' * 1350)
1451 self.wfile.flush()
1452 return True
1453
agl@chromium.org04700be2013-03-02 18:40:41 +00001454 def GetChannelID(self):
1455 """Send a reply containing the hashed ChannelID that the client provided."""
1456
1457 if not self._ShouldHandleRequest('/channel-id'):
1458 return False
1459
1460 self.send_response(200)
1461 self.send_header('Content-Type', 'text/plain')
1462 self.end_headers()
1463 channel_id = self.server.tlsConnection.channel_id.tostring()
1464 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1465 return True
1466
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001467 def CloseSocketHandler(self):
1468 """Closes the socket without sending anything."""
1469
1470 if not self._ShouldHandleRequest('/close-socket'):
1471 return False
1472
1473 self.wfile.close()
1474 return True
1475
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001476 def RangeResetHandler(self):
1477 """Send data broken up by connection resets every N (default 4K) bytes.
1478 Support range requests. If the data requested doesn't straddle a reset
1479 boundary, it will all be sent. Used for testing resuming downloads."""
1480
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001481 def DataForRange(start, end):
1482 """Data to be provided for a particular range of bytes."""
1483 # Offset and scale to avoid too obvious (and hence potentially
1484 # collidable) data.
1485 return ''.join([chr(y % 256)
1486 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1487
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001488 if not self._ShouldHandleRequest('/rangereset'):
1489 return False
1490
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001491 # HTTP/1.1 is required for ETag and range support.
1492 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001493 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1494
1495 # Defaults
1496 size = 8000
1497 # Note that the rst is sent just before sending the rst_boundary byte.
1498 rst_boundary = 4000
1499 respond_to_range = True
1500 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001501 rst_limit = -1
1502 token = 'DEFAULT'
1503 fail_precondition = 0
1504 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001505
1506 # Parse the query
1507 qdict = urlparse.parse_qs(query, True)
1508 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001509 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001510 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001511 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001512 if 'token' in qdict:
1513 # Identifying token for stateful tests.
1514 token = qdict['token'][0]
1515 if 'rst_limit' in qdict:
1516 # Max number of rsts for a given token.
1517 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001518 if 'bounce_range' in qdict:
1519 respond_to_range = False
1520 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001521 # Note that hold_for_signal will not work with null range requests;
1522 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001523 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001524 if 'no_verifiers' in qdict:
1525 send_verifiers = False
1526 if 'fail_precondition' in qdict:
1527 fail_precondition = int(qdict['fail_precondition'][0])
1528
1529 # Record already set information, or set it.
1530 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1531 if rst_limit != 0:
1532 TestPageHandler.rst_limits[token] -= 1
1533 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1534 token, fail_precondition)
1535 if fail_precondition != 0:
1536 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001537
1538 first_byte = 0
1539 last_byte = size - 1
1540
1541 # Does that define what we want to return, or do we need to apply
1542 # a range?
1543 range_response = False
1544 range_header = self.headers.getheader('range')
1545 if range_header and respond_to_range:
1546 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1547 if mo.group(1):
1548 first_byte = int(mo.group(1))
1549 if mo.group(2):
1550 last_byte = int(mo.group(2))
1551 if last_byte > size - 1:
1552 last_byte = size - 1
1553 range_response = True
1554 if last_byte < first_byte:
1555 return False
1556
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001557 if (fail_precondition and
1558 (self.headers.getheader('If-Modified-Since') or
1559 self.headers.getheader('If-Match'))):
1560 self.send_response(412)
1561 self.end_headers()
1562 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001563
1564 if range_response:
1565 self.send_response(206)
1566 self.send_header('Content-Range',
1567 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1568 else:
1569 self.send_response(200)
1570 self.send_header('Content-Type', 'application/octet-stream')
1571 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001572 if send_verifiers:
1573 self.send_header('Etag', '"XYZZY"')
1574 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001575 self.end_headers()
1576
1577 if hold_for_signal:
1578 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1579 # a single byte, the self.server.handle_request() below hangs
1580 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001581 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001582 first_byte = first_byte + 1
1583 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001584 self.server.wait_for_download = True
1585 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001586 self.server.handle_request()
1587
1588 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001589 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001590 # No RST has been requested in this range, so we don't need to
1591 # do anything fancy; just write the data and let the python
1592 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001593 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001594 self.wfile.flush()
1595 return True
1596
1597 # We're resetting the connection part way in; go to the RST
1598 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001599 # Because socket semantics do not guarantee that all the data will be
1600 # sent when using the linger semantics to hard close a socket,
1601 # we send the data and then wait for our peer to release us
1602 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001603 data = DataForRange(first_byte, possible_rst)
1604 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001605 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001606 self.server.wait_for_download = True
1607 while self.server.wait_for_download:
1608 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001609 l_onoff = 1 # Linger is active.
1610 l_linger = 0 # Seconds to linger for.
1611 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1612 struct.pack('ii', l_onoff, l_linger))
1613
1614 # Close all duplicates of the underlying socket to force the RST.
1615 self.wfile.close()
1616 self.rfile.close()
1617 self.connection.close()
1618
1619 return True
1620
initial.commit94958cf2008-07-26 22:42:52 +00001621 def DefaultResponseHandler(self):
1622 """This is the catch-all response handler for requests that aren't handled
1623 by one of the special handlers above.
1624 Note that we specify the content-length as without it the https connection
1625 is not closed properly (and the browser keeps expecting data)."""
1626
1627 contents = "Default response given for path: " + self.path
1628 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001629 self.send_header('Content-Type', 'text/html')
1630 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001631 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001632 if (self.command != 'HEAD'):
1633 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001634 return True
1635
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001636 def RedirectConnectHandler(self):
1637 """Sends a redirect to the CONNECT request for www.redirect.com. This
1638 response is not specified by the RFC, so the browser should not follow
1639 the redirect."""
1640
1641 if (self.path.find("www.redirect.com") < 0):
1642 return False
1643
1644 dest = "http://www.destination.com/foo.js"
1645
1646 self.send_response(302) # moved temporarily
1647 self.send_header('Location', dest)
1648 self.send_header('Connection', 'close')
1649 self.end_headers()
1650 return True
1651
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001652 def ServerAuthConnectHandler(self):
1653 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1654 response doesn't make sense because the proxy server cannot request
1655 server authentication."""
1656
1657 if (self.path.find("www.server-auth.com") < 0):
1658 return False
1659
1660 challenge = 'Basic realm="WallyWorld"'
1661
1662 self.send_response(401) # unauthorized
1663 self.send_header('WWW-Authenticate', challenge)
1664 self.send_header('Connection', 'close')
1665 self.end_headers()
1666 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001667
1668 def DefaultConnectResponseHandler(self):
1669 """This is the catch-all response handler for CONNECT requests that aren't
1670 handled by one of the special handlers above. Real Web servers respond
1671 with 400 to CONNECT requests."""
1672
1673 contents = "Your client has issued a malformed or illegal request."
1674 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001675 self.send_header('Content-Type', 'text/html')
1676 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001677 self.end_headers()
1678 self.wfile.write(contents)
1679 return True
1680
initial.commit94958cf2008-07-26 22:42:52 +00001681 # called by the redirect handling function when there is no parameter
1682 def sendRedirectHelp(self, redirect_name):
1683 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001684 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001685 self.end_headers()
1686 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1687 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1688 self.wfile.write('</body></html>')
1689
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001690 # called by chunked handling function
1691 def sendChunkHelp(self, chunk):
1692 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1693 self.wfile.write('%X\r\n' % len(chunk))
1694 self.wfile.write(chunk)
1695 self.wfile.write('\r\n')
1696
akalin@chromium.org154bb132010-11-12 02:20:27 +00001697
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001698class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001699 def __init__(self, request, client_address, socket_server):
1700 handlers = [self.OCSPResponse]
1701 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001702 testserver_base.BasePageHandler.__init__(self, request, client_address,
1703 socket_server, [], handlers, [],
1704 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001705
1706 def OCSPResponse(self):
1707 self.send_response(200)
1708 self.send_header('Content-Type', 'application/ocsp-response')
1709 self.send_header('Content-Length', str(len(self.ocsp_response)))
1710 self.end_headers()
1711
1712 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001713
mattm@chromium.org830a3712012-11-07 23:00:07 +00001714
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001715class TCPEchoHandler(SocketServer.BaseRequestHandler):
1716 """The RequestHandler class for TCP echo server.
1717
1718 It is instantiated once per connection to the server, and overrides the
1719 handle() method to implement communication to the client.
1720 """
1721
1722 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001723 """Handles the request from the client and constructs a response."""
1724
1725 data = self.request.recv(65536).strip()
1726 # Verify the "echo request" message received from the client. Send back
1727 # "echo response" message if "echo request" message is valid.
1728 try:
1729 return_data = echo_message.GetEchoResponseData(data)
1730 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001731 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001732 except ValueError:
1733 return
1734
1735 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001736
1737
1738class UDPEchoHandler(SocketServer.BaseRequestHandler):
1739 """The RequestHandler class for UDP echo server.
1740
1741 It is instantiated once per connection to the server, and overrides the
1742 handle() method to implement communication to the client.
1743 """
1744
1745 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001746 """Handles the request from the client and constructs a response."""
1747
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001748 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001749 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001750 # Verify the "echo request" message received from the client. Send back
1751 # "echo response" message if "echo request" message is valid.
1752 try:
1753 return_data = echo_message.GetEchoResponseData(data)
1754 if not return_data:
1755 return
1756 except ValueError:
1757 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001758 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001759
1760
bashi@chromium.org33233532012-09-08 17:37:24 +00001761class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1762 """A request handler that behaves as a proxy server which requires
1763 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1764 """
1765
1766 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1767
1768 def parse_request(self):
1769 """Overrides parse_request to check credential."""
1770
1771 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1772 return False
1773
1774 auth = self.headers.getheader('Proxy-Authorization')
1775 if auth != self._AUTH_CREDENTIAL:
1776 self.send_response(407)
1777 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1778 self.end_headers()
1779 return False
1780
1781 return True
1782
1783 def _start_read_write(self, sock):
1784 sock.setblocking(0)
1785 self.request.setblocking(0)
1786 rlist = [self.request, sock]
1787 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001788 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001789 if errors:
1790 self.send_response(500)
1791 self.end_headers()
1792 return
1793 for s in ready_sockets:
1794 received = s.recv(1024)
1795 if len(received) == 0:
1796 return
1797 if s == self.request:
1798 other = sock
1799 else:
1800 other = self.request
1801 other.send(received)
1802
1803 def _do_common_method(self):
1804 url = urlparse.urlparse(self.path)
1805 port = url.port
1806 if not port:
1807 if url.scheme == 'http':
1808 port = 80
1809 elif url.scheme == 'https':
1810 port = 443
1811 if not url.hostname or not port:
1812 self.send_response(400)
1813 self.end_headers()
1814 return
1815
1816 if len(url.path) == 0:
1817 path = '/'
1818 else:
1819 path = url.path
1820 if len(url.query) > 0:
1821 path = '%s?%s' % (url.path, url.query)
1822
1823 sock = None
1824 try:
1825 sock = socket.create_connection((url.hostname, port))
1826 sock.send('%s %s %s\r\n' % (
1827 self.command, path, self.protocol_version))
1828 for header in self.headers.headers:
1829 header = header.strip()
1830 if (header.lower().startswith('connection') or
1831 header.lower().startswith('proxy')):
1832 continue
1833 sock.send('%s\r\n' % header)
1834 sock.send('\r\n')
1835 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001836 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001837 self.send_response(500)
1838 self.end_headers()
1839 finally:
1840 if sock is not None:
1841 sock.close()
1842
1843 def do_CONNECT(self):
1844 try:
1845 pos = self.path.rfind(':')
1846 host = self.path[:pos]
1847 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001848 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001849 self.send_response(400)
1850 self.end_headers()
1851
1852 try:
1853 sock = socket.create_connection((host, port))
1854 self.send_response(200, 'Connection established')
1855 self.end_headers()
1856 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001857 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001858 self.send_response(500)
1859 self.end_headers()
1860 finally:
1861 sock.close()
1862
1863 def do_GET(self):
1864 self._do_common_method()
1865
1866 def do_HEAD(self):
1867 self._do_common_method()
1868
1869
mattm@chromium.org830a3712012-11-07 23:00:07 +00001870class ServerRunner(testserver_base.TestServerRunner):
1871 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001872
mattm@chromium.org830a3712012-11-07 23:00:07 +00001873 def __init__(self):
1874 super(ServerRunner, self).__init__()
1875 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001876
mattm@chromium.org830a3712012-11-07 23:00:07 +00001877 def __make_data_dir(self):
1878 if self.options.data_dir:
1879 if not os.path.isdir(self.options.data_dir):
1880 raise testserver_base.OptionError('specified data dir not found: ' +
1881 self.options.data_dir + ' exiting...')
1882 my_data_dir = self.options.data_dir
1883 else:
1884 # Create the default path to our data dir, relative to the exe dir.
1885 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1886 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001887
mattm@chromium.org830a3712012-11-07 23:00:07 +00001888 #TODO(ibrar): Must use Find* funtion defined in google\tools
1889 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001890
mattm@chromium.org830a3712012-11-07 23:00:07 +00001891 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001892
mattm@chromium.org830a3712012-11-07 23:00:07 +00001893 def create_server(self, server_data):
1894 port = self.options.port
1895 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001896
mattm@chromium.org830a3712012-11-07 23:00:07 +00001897 if self.options.server_type == SERVER_HTTP:
1898 if self.options.https:
1899 pem_cert_and_key = None
1900 if self.options.cert_and_key_file:
1901 if not os.path.isfile(self.options.cert_and_key_file):
1902 raise testserver_base.OptionError(
1903 'specified server cert file not found: ' +
1904 self.options.cert_and_key_file + ' exiting...')
1905 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001906 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001907 # generate a new certificate and run an OCSP server for it.
1908 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001909 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001910 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001911
mattm@chromium.org830a3712012-11-07 23:00:07 +00001912 ocsp_der = None
1913 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001914
mattm@chromium.org830a3712012-11-07 23:00:07 +00001915 if self.options.ocsp == 'ok':
1916 ocsp_state = minica.OCSP_STATE_GOOD
1917 elif self.options.ocsp == 'revoked':
1918 ocsp_state = minica.OCSP_STATE_REVOKED
1919 elif self.options.ocsp == 'invalid':
1920 ocsp_state = minica.OCSP_STATE_INVALID
1921 elif self.options.ocsp == 'unauthorized':
1922 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1923 elif self.options.ocsp == 'unknown':
1924 ocsp_state = minica.OCSP_STATE_UNKNOWN
1925 else:
1926 raise testserver_base.OptionError('unknown OCSP status: ' +
1927 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001928
mattm@chromium.org830a3712012-11-07 23:00:07 +00001929 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1930 subject = "127.0.0.1",
1931 ocsp_url = ("http://%s:%d/ocsp" %
1932 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00001933 ocsp_state = ocsp_state,
1934 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001935
1936 self.__ocsp_server.ocsp_response = ocsp_der
1937
1938 for ca_cert in self.options.ssl_client_ca:
1939 if not os.path.isfile(ca_cert):
1940 raise testserver_base.OptionError(
1941 'specified trusted client CA file not found: ' + ca_cert +
1942 ' exiting...')
1943 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1944 self.options.ssl_client_auth,
1945 self.options.ssl_client_ca,
1946 self.options.ssl_bulk_cipher,
1947 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00001948 self.options.tls_intolerant,
1949 self.options.signed_cert_timestamps.decode(
1950 "base64"))
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001951 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001952 else:
1953 server = HTTPServer((host, port), TestPageHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001954 print 'HTTP server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001955
1956 server.data_dir = self.__make_data_dir()
1957 server.file_root_url = self.options.file_root_url
1958 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001959 elif self.options.server_type == SERVER_WEBSOCKET:
1960 # Launch pywebsocket via WebSocketServer.
1961 logger = logging.getLogger()
1962 logger.addHandler(logging.StreamHandler())
1963 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1964 # is required to work correctly. It should be fixed from pywebsocket side.
1965 os.chdir(self.__make_data_dir())
1966 websocket_options = WebSocketOptions(host, port, '.')
1967 if self.options.cert_and_key_file:
1968 websocket_options.use_tls = True
1969 websocket_options.private_key = self.options.cert_and_key_file
1970 websocket_options.certificate = self.options.cert_and_key_file
1971 if self.options.ssl_client_auth:
1972 websocket_options.tls_client_auth = True
1973 if len(self.options.ssl_client_ca) != 1:
1974 raise testserver_base.OptionError(
1975 'one trusted client CA file should be specified')
1976 if not os.path.isfile(self.options.ssl_client_ca[0]):
1977 raise testserver_base.OptionError(
1978 'specified trusted client CA file not found: ' +
1979 self.options.ssl_client_ca[0] + ' exiting...')
1980 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
1981 server = WebSocketServer(websocket_options)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001982 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001983 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001984 elif self.options.server_type == SERVER_TCP_ECHO:
1985 # Used for generating the key (randomly) that encodes the "echo request"
1986 # message.
1987 random.seed()
1988 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001989 print 'Echo TCP 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_UDP_ECHO:
1992 # Used for generating the key (randomly) that encodes the "echo request"
1993 # message.
1994 random.seed()
1995 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001996 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001997 server_data['port'] = server.server_port
1998 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
1999 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002000 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002001 server_data['port'] = server.server_port
2002 elif self.options.server_type == SERVER_FTP:
2003 my_data_dir = self.__make_data_dir()
2004
2005 # Instantiate a dummy authorizer for managing 'virtual' users
2006 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2007
2008 # Define a new user having full r/w permissions and a read-only
2009 # anonymous user
2010 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2011
2012 authorizer.add_anonymous(my_data_dir)
2013
2014 # Instantiate FTP handler class
2015 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2016 ftp_handler.authorizer = authorizer
2017
2018 # Define a customized banner (string returned when client connects)
2019 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2020 pyftpdlib.ftpserver.__ver__)
2021
2022 # Instantiate FTP server class and listen to address:port
2023 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2024 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002025 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002026 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002027 raise testserver_base.OptionError('unknown server type' +
2028 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002029
mattm@chromium.org830a3712012-11-07 23:00:07 +00002030 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002031
mattm@chromium.org830a3712012-11-07 23:00:07 +00002032 def run_server(self):
2033 if self.__ocsp_server:
2034 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002035
mattm@chromium.org830a3712012-11-07 23:00:07 +00002036 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002037
mattm@chromium.org830a3712012-11-07 23:00:07 +00002038 if self.__ocsp_server:
2039 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002040
mattm@chromium.org830a3712012-11-07 23:00:07 +00002041 def add_options(self):
2042 testserver_base.TestServerRunner.add_options(self)
2043 self.option_parser.add_option('-f', '--ftp', action='store_const',
2044 const=SERVER_FTP, default=SERVER_HTTP,
2045 dest='server_type',
2046 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002047 self.option_parser.add_option('--tcp-echo', action='store_const',
2048 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2049 dest='server_type',
2050 help='start up a tcp echo server.')
2051 self.option_parser.add_option('--udp-echo', action='store_const',
2052 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2053 dest='server_type',
2054 help='start up a udp echo server.')
2055 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2056 const=SERVER_BASIC_AUTH_PROXY,
2057 default=SERVER_HTTP, dest='server_type',
2058 help='start up a proxy server which requires '
2059 'basic authentication.')
2060 self.option_parser.add_option('--websocket', action='store_const',
2061 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2062 dest='server_type',
2063 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002064 self.option_parser.add_option('--https', action='store_true',
2065 dest='https', help='Specify that https '
2066 'should be used.')
2067 self.option_parser.add_option('--cert-and-key-file',
2068 dest='cert_and_key_file', help='specify the '
2069 'path to the file containing the certificate '
2070 'and private key for the server in PEM '
2071 'format')
2072 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2073 help='The type of OCSP response generated '
2074 'for the automatically generated '
2075 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002076 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2077 default=0, type=int,
2078 help='If non-zero then the generated '
2079 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002080 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2081 default='0', type='int',
2082 help='If nonzero, certain TLS connections '
2083 'will be aborted in order to test version '
2084 'fallback. 1 means all TLS versions will be '
2085 'aborted. 2 means TLS 1.1 or higher will be '
2086 'aborted. 3 means TLS 1.2 or higher will be '
2087 'aborted.')
ekasper@google.com24aa8222013-11-28 13:43:26 +00002088 self.option_parser.add_option('--signed-cert-timestamps',
2089 dest='signed_cert_timestamps',
2090 default='',
2091 help='Base64 encoded SCT list. If set, '
2092 'server will respond with a '
2093 'signed_certificate_timestamp TLS extension '
2094 'whenever the client supports it.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002095 self.option_parser.add_option('--https-record-resume',
2096 dest='record_resume', const=True,
2097 default=False, action='store_const',
2098 help='Record resumption cache events rather '
2099 'than resuming as normal. Allows the use of '
2100 'the /ssl-session-cache request')
2101 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2102 help='Require SSL client auth on every '
2103 'connection.')
2104 self.option_parser.add_option('--ssl-client-ca', action='append',
2105 default=[], help='Specify that the client '
2106 'certificate request should include the CA '
2107 'named in the subject of the DER-encoded '
2108 'certificate contained in the specified '
2109 'file. This option may appear multiple '
2110 'times, indicating multiple CA names should '
2111 'be sent in the request.')
2112 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2113 help='Specify the bulk encryption '
2114 'algorithm(s) that will be accepted by the '
2115 'SSL server. Valid values are "aes256", '
2116 '"aes128", "3des", "rc4". If omitted, all '
2117 'algorithms will be used. This option may '
2118 'appear multiple times, indicating '
2119 'multiple algorithms should be enabled.');
2120 self.option_parser.add_option('--file-root-url', default='/files/',
2121 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002122
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002123
initial.commit94958cf2008-07-26 22:42:52 +00002124if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002125 sys.exit(ServerRunner().main())