blob: 931fdd7c17077ae8612422e1afa132136a9098d0 [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00002# Copyright 2013 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00006"""This is a simple HTTP/FTP/TCP/UDP/BASIC_AUTH_PROXY/WEBSOCKET server used for
7testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00008
9It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000010By default, it listens on an ephemeral port and sends the port number back to
11the originating process over a pipe. The originating process can specify an
12explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000013It can use https if you specify the flag --https=CERT where CERT is the path
14to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000015"""
16
17import base64
18import BaseHTTPServer
19import cgi
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000020import hashlib
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000021import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000022import minica
initial.commit94958cf2008-07-26 22:42:52 +000023import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000024import random
initial.commit94958cf2008-07-26 22:42:52 +000025import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000026import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000027import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000028import SocketServer
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000029import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000030import sys
31import threading
initial.commit94958cf2008-07-26 22:42:52 +000032import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000033import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000034import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000035import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000036
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000037import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000038import pyftpdlib.ftpserver
mattm@chromium.org830a3712012-11-07 23:00:07 +000039import testserver_base
initial.commit94958cf2008-07-26 22:42:52 +000040import tlslite
41import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000042
mattm@chromium.org830a3712012-11-07 23:00:07 +000043BASE_DIR = os.path.dirname(os.path.abspath(__file__))
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000044sys.path.insert(
45 0, os.path.join(BASE_DIR, '..', '..', '..', 'third_party/pywebsocket/src'))
46from mod_pywebsocket.standalone import WebSocketServer
davidben@chromium.org06fcf202010-09-22 18:15:23 +000047
maruel@chromium.org756cf982009-03-05 12:46:38 +000048SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000049SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000050SERVER_TCP_ECHO = 2
51SERVER_UDP_ECHO = 3
52SERVER_BASIC_AUTH_PROXY = 4
53SERVER_WEBSOCKET = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000054
55# Default request queue size for WebSocketServer.
56_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000057
mattm@chromium.org830a3712012-11-07 23:00:07 +000058
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000059class WebSocketOptions:
60 """Holds options for WebSocketServer."""
61
62 def __init__(self, host, port, data_dir):
63 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
64 self.server_host = host
65 self.port = port
66 self.websock_handlers = data_dir
67 self.scan_dir = None
68 self.allow_handlers_outside_root_dir = False
69 self.websock_handlers_map_file = None
70 self.cgi_directories = []
71 self.is_executable_method = None
72 self.allow_draft75 = False
73 self.strict = True
74
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000075 self.use_tls = False
76 self.private_key = None
77 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000078 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000079 self.tls_client_ca = None
80 self.use_basic_auth = False
81
mattm@chromium.org830a3712012-11-07 23:00:07 +000082
agl@chromium.orgf9e66792011-12-12 22:22:19 +000083class RecordingSSLSessionCache(object):
84 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
85 lookups and inserts in order to test session cache behaviours."""
86
87 def __init__(self):
88 self.log = []
89
90 def __getitem__(self, sessionID):
91 self.log.append(('lookup', sessionID))
92 raise KeyError()
93
94 def __setitem__(self, sessionID, session):
95 self.log.append(('insert', sessionID))
96
erikwright@chromium.org847ef282012-02-22 16:41:10 +000097
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000098class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
99 testserver_base.BrokenPipeHandlerMixIn,
100 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000101 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000102 verification."""
103
104 pass
105
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000106class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
107 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000108 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000109 """This is a specialization of HTTPServer that serves an
110 OCSP response"""
111
112 def serve_forever_on_thread(self):
113 self.thread = threading.Thread(target = self.serve_forever,
114 name = "OCSPServerThread")
115 self.thread.start()
116
117 def stop_serving(self):
118 self.shutdown()
119 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000120
mattm@chromium.org830a3712012-11-07 23:00:07 +0000121
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000122class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000123 testserver_base.ClientRestrictingServerMixIn,
124 testserver_base.BrokenPipeHandlerMixIn,
125 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000126 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000127 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000128
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000129 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000130 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000131 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000132 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
133 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000134 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000135 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000136 self.tls_intolerant = tls_intolerant
137
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000138 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000139 s = open(ca_file).read()
140 x509 = tlslite.api.X509()
141 x509.parse(s)
142 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000143 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
144 if ssl_bulk_ciphers is not None:
145 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000146
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000147 if record_resume_info:
148 # If record_resume_info is true then we'll replace the session cache with
149 # an object that records the lookups and inserts that it sees.
150 self.session_cache = RecordingSSLSessionCache()
151 else:
152 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000153 testserver_base.StoppableHTTPServer.__init__(self,
154 server_address,
155 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000156
157 def handshake(self, tlsConnection):
158 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000159
initial.commit94958cf2008-07-26 22:42:52 +0000160 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000161 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000162 tlsConnection.handshakeServer(certChain=self.cert_chain,
163 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000164 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000165 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000166 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000167 reqCAs=self.ssl_client_cas,
168 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000169 tlsConnection.ignoreAbruptClose = True
170 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000171 except tlslite.api.TLSAbruptCloseError:
172 # Ignore abrupt close.
173 return True
initial.commit94958cf2008-07-26 22:42:52 +0000174 except tlslite.api.TLSError, error:
175 print "Handshake failure:", str(error)
176 return False
177
akalin@chromium.org154bb132010-11-12 02:20:27 +0000178
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000179class FTPServer(testserver_base.ClientRestrictingServerMixIn,
180 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000181 """This is a specialization of FTPServer that adds client verification."""
182
183 pass
184
185
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000186class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
187 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000188 """A TCP echo server that echoes back what it has received."""
189
190 def server_bind(self):
191 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000192
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000193 SocketServer.TCPServer.server_bind(self)
194 host, port = self.socket.getsockname()[:2]
195 self.server_name = socket.getfqdn(host)
196 self.server_port = port
197
198 def serve_forever(self):
199 self.stop = False
200 self.nonce_time = None
201 while not self.stop:
202 self.handle_request()
203 self.socket.close()
204
205
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000206class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
207 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000208 """A UDP echo server that echoes back what it has received."""
209
210 def server_bind(self):
211 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000212
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000213 SocketServer.UDPServer.server_bind(self)
214 host, port = self.socket.getsockname()[:2]
215 self.server_name = socket.getfqdn(host)
216 self.server_port = port
217
218 def serve_forever(self):
219 self.stop = False
220 self.nonce_time = None
221 while not self.stop:
222 self.handle_request()
223 self.socket.close()
224
225
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000226class TestPageHandler(testserver_base.BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000227
228 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000229 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000230 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000231 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000232 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000233 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000234 self.NoCacheMaxAgeTimeHandler,
235 self.NoCacheTimeHandler,
236 self.CacheTimeHandler,
237 self.CacheExpiresHandler,
238 self.CacheProxyRevalidateHandler,
239 self.CachePrivateHandler,
240 self.CachePublicHandler,
241 self.CacheSMaxAgeHandler,
242 self.CacheMustRevalidateHandler,
243 self.CacheMustRevalidateMaxAgeHandler,
244 self.CacheNoStoreHandler,
245 self.CacheNoStoreMaxAgeHandler,
246 self.CacheNoTransformHandler,
247 self.DownloadHandler,
248 self.DownloadFinishHandler,
249 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000250 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000251 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000252 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000253 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000254 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000255 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000256 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000257 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000258 self.AuthBasicHandler,
259 self.AuthDigestHandler,
260 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000261 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000262 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000263 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000264 self.ServerRedirectHandler,
265 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000266 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000267 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000268 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000269 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000270 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000271 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000272 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000273 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000274 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000275 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000276 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000277 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000278 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000279 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000280 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000281 head_handlers = [
282 self.FileHandler,
283 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000284
maruel@google.come250a9b2009-03-10 17:39:46 +0000285 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000286 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000287 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000288 'gif': 'image/gif',
289 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000290 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000291 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000292 'pdf' : 'application/pdf',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000293 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000294 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000295 }
initial.commit94958cf2008-07-26 22:42:52 +0000296 self._default_mime_type = 'text/html'
297
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000298 testserver_base.BasePageHandler.__init__(self, request, client_address,
299 socket_server, connect_handlers,
300 get_handlers, head_handlers,
301 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000302
initial.commit94958cf2008-07-26 22:42:52 +0000303 def GetMIMETypeFromName(self, file_name):
304 """Returns the mime type for the specified file_name. So far it only looks
305 at the file extension."""
306
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000307 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000308 if len(extension) == 0:
309 # no extension.
310 return self._default_mime_type
311
ericroman@google.comc17ca532009-05-07 03:51:05 +0000312 # extension starts with a dot, so we need to remove it
313 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000314
initial.commit94958cf2008-07-26 22:42:52 +0000315 def NoCacheMaxAgeTimeHandler(self):
316 """This request handler yields a page with the title set to the current
317 system time, and no caching requested."""
318
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000319 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000320 return False
321
322 self.send_response(200)
323 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000324 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000325 self.end_headers()
326
maruel@google.come250a9b2009-03-10 17:39:46 +0000327 self.wfile.write('<html><head><title>%s</title></head></html>' %
328 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000329
330 return True
331
332 def NoCacheTimeHandler(self):
333 """This request handler yields a page with the title set to the current
334 system time, and no caching requested."""
335
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000336 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000337 return False
338
339 self.send_response(200)
340 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000341 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000342 self.end_headers()
343
maruel@google.come250a9b2009-03-10 17:39:46 +0000344 self.wfile.write('<html><head><title>%s</title></head></html>' %
345 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000346
347 return True
348
349 def CacheTimeHandler(self):
350 """This request handler yields a page with the title set to the current
351 system time, and allows caching for one minute."""
352
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000353 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000354 return False
355
356 self.send_response(200)
357 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000358 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000359 self.end_headers()
360
maruel@google.come250a9b2009-03-10 17:39:46 +0000361 self.wfile.write('<html><head><title>%s</title></head></html>' %
362 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000363
364 return True
365
366 def CacheExpiresHandler(self):
367 """This request handler yields a page with the title set to the current
368 system time, and set the page to expire on 1 Jan 2099."""
369
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000370 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000371 return False
372
373 self.send_response(200)
374 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000375 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000376 self.end_headers()
377
maruel@google.come250a9b2009-03-10 17:39:46 +0000378 self.wfile.write('<html><head><title>%s</title></head></html>' %
379 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000380
381 return True
382
383 def CacheProxyRevalidateHandler(self):
384 """This request handler yields a page with the title set to the current
385 system time, and allows caching for 60 seconds"""
386
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000387 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000388 return False
389
390 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000391 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000392 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
393 self.end_headers()
394
maruel@google.come250a9b2009-03-10 17:39:46 +0000395 self.wfile.write('<html><head><title>%s</title></head></html>' %
396 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000397
398 return True
399
400 def CachePrivateHandler(self):
401 """This request handler yields a page with the title set to the current
402 system time, and allows caching for 5 seconds."""
403
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000404 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000405 return False
406
407 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000408 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000409 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000410 self.end_headers()
411
maruel@google.come250a9b2009-03-10 17:39:46 +0000412 self.wfile.write('<html><head><title>%s</title></head></html>' %
413 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000414
415 return True
416
417 def CachePublicHandler(self):
418 """This request handler yields a page with the title set to the current
419 system time, and allows caching for 5 seconds."""
420
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000421 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000422 return False
423
424 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000425 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000426 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000427 self.end_headers()
428
maruel@google.come250a9b2009-03-10 17:39:46 +0000429 self.wfile.write('<html><head><title>%s</title></head></html>' %
430 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000431
432 return True
433
434 def CacheSMaxAgeHandler(self):
435 """This request handler yields a page with the title set to the current
436 system time, and does not allow for caching."""
437
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000438 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000439 return False
440
441 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000442 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000443 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
444 self.end_headers()
445
maruel@google.come250a9b2009-03-10 17:39:46 +0000446 self.wfile.write('<html><head><title>%s</title></head></html>' %
447 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000448
449 return True
450
451 def CacheMustRevalidateHandler(self):
452 """This request handler yields a page with the title set to the current
453 system time, and does not allow caching."""
454
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000455 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000456 return False
457
458 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000459 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000460 self.send_header('Cache-Control', 'must-revalidate')
461 self.end_headers()
462
maruel@google.come250a9b2009-03-10 17:39:46 +0000463 self.wfile.write('<html><head><title>%s</title></head></html>' %
464 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000465
466 return True
467
468 def CacheMustRevalidateMaxAgeHandler(self):
469 """This request handler yields a page with the title set to the current
470 system time, and does not allow caching event though max-age of 60
471 seconds is specified."""
472
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000473 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000474 return False
475
476 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000477 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000478 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
479 self.end_headers()
480
maruel@google.come250a9b2009-03-10 17:39:46 +0000481 self.wfile.write('<html><head><title>%s</title></head></html>' %
482 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000483
484 return True
485
initial.commit94958cf2008-07-26 22:42:52 +0000486 def CacheNoStoreHandler(self):
487 """This request handler yields a page with the title set to the current
488 system time, and does not allow the page to be stored."""
489
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000490 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000491 return False
492
493 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000494 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000495 self.send_header('Cache-Control', 'no-store')
496 self.end_headers()
497
maruel@google.come250a9b2009-03-10 17:39:46 +0000498 self.wfile.write('<html><head><title>%s</title></head></html>' %
499 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000500
501 return True
502
503 def CacheNoStoreMaxAgeHandler(self):
504 """This request handler yields a page with the title set to the current
505 system time, and does not allow the page to be stored even though max-age
506 of 60 seconds is specified."""
507
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000508 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000509 return False
510
511 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000512 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000513 self.send_header('Cache-Control', 'max-age=60, no-store')
514 self.end_headers()
515
maruel@google.come250a9b2009-03-10 17:39:46 +0000516 self.wfile.write('<html><head><title>%s</title></head></html>' %
517 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000518
519 return True
520
521
522 def CacheNoTransformHandler(self):
523 """This request handler yields a page with the title set to the current
524 system time, and does not allow the content to transformed during
525 user-agent caching"""
526
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000527 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000528 return False
529
530 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000531 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000532 self.send_header('Cache-Control', 'no-transform')
533 self.end_headers()
534
maruel@google.come250a9b2009-03-10 17:39:46 +0000535 self.wfile.write('<html><head><title>%s</title></head></html>' %
536 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000537
538 return True
539
540 def EchoHeader(self):
541 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000542
ananta@chromium.org219b2062009-10-23 16:09:41 +0000543 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000544
ananta@chromium.org56812d02011-04-07 17:52:05 +0000545 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000546 """This function echoes back the value of a specific request header while
547 allowing caching for 16 hours."""
548
ananta@chromium.org56812d02011-04-07 17:52:05 +0000549 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000550
551 def EchoHeaderHelper(self, echo_header):
552 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000553
ananta@chromium.org219b2062009-10-23 16:09:41 +0000554 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000555 return False
556
557 query_char = self.path.find('?')
558 if query_char != -1:
559 header_name = self.path[query_char+1:]
560
561 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000562 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000563 if echo_header == '/echoheadercache':
564 self.send_header('Cache-control', 'max-age=60000')
565 else:
566 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000567 # insert a vary header to properly indicate that the cachability of this
568 # request is subject to value of the request header being echoed.
569 if len(header_name) > 0:
570 self.send_header('Vary', header_name)
571 self.end_headers()
572
573 if len(header_name) > 0:
574 self.wfile.write(self.headers.getheader(header_name))
575
576 return True
577
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000578 def ReadRequestBody(self):
579 """This function reads the body of the current HTTP request, handling
580 both plain and chunked transfer encoded requests."""
581
582 if self.headers.getheader('transfer-encoding') != 'chunked':
583 length = int(self.headers.getheader('content-length'))
584 return self.rfile.read(length)
585
586 # Read the request body as chunks.
587 body = ""
588 while True:
589 line = self.rfile.readline()
590 length = int(line, 16)
591 if length == 0:
592 self.rfile.readline()
593 break
594 body += self.rfile.read(length)
595 self.rfile.read(2)
596 return body
597
initial.commit94958cf2008-07-26 22:42:52 +0000598 def EchoHandler(self):
599 """This handler just echoes back the payload of the request, for testing
600 form submission."""
601
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000602 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000603 return False
604
605 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000606 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000607 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000608 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000609 return True
610
611 def EchoTitleHandler(self):
612 """This handler is like Echo, but sets the page title to the request."""
613
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000614 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000615 return False
616
617 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000618 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000619 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000620 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000621 self.wfile.write('<html><head><title>')
622 self.wfile.write(request)
623 self.wfile.write('</title></head></html>')
624 return True
625
626 def EchoAllHandler(self):
627 """This handler yields a (more) human-readable page listing information
628 about the request header & contents."""
629
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000630 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000631 return False
632
633 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000634 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000635 self.end_headers()
636 self.wfile.write('<html><head><style>'
637 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
638 '</style></head><body>'
639 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000640 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000641 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000642
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000643 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000644 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000645 params = cgi.parse_qs(qs, keep_blank_values=1)
646
647 for param in params:
648 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000649
650 self.wfile.write('</pre>')
651
652 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
653
654 self.wfile.write('</body></html>')
655 return True
656
657 def DownloadHandler(self):
658 """This handler sends a downloadable file with or without reporting
659 the size (6K)."""
660
661 if self.path.startswith("/download-unknown-size"):
662 send_length = False
663 elif self.path.startswith("/download-known-size"):
664 send_length = True
665 else:
666 return False
667
668 #
669 # The test which uses this functionality is attempting to send
670 # small chunks of data to the client. Use a fairly large buffer
671 # so that we'll fill chrome's IO buffer enough to force it to
672 # actually write the data.
673 # See also the comments in the client-side of this test in
674 # download_uitest.cc
675 #
676 size_chunk1 = 35*1024
677 size_chunk2 = 10*1024
678
679 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000680 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000681 self.send_header('Cache-Control', 'max-age=0')
682 if send_length:
683 self.send_header('Content-Length', size_chunk1 + size_chunk2)
684 self.end_headers()
685
686 # First chunk of data:
687 self.wfile.write("*" * size_chunk1)
688 self.wfile.flush()
689
690 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000691 self.server.wait_for_download = True
692 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000693 self.server.handle_request()
694
695 # Second chunk of data:
696 self.wfile.write("*" * size_chunk2)
697 return True
698
699 def DownloadFinishHandler(self):
700 """This handler just tells the server to finish the current download."""
701
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000702 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000703 return False
704
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000705 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000706 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000707 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000708 self.send_header('Cache-Control', 'max-age=0')
709 self.end_headers()
710 return True
711
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000712 def _ReplaceFileData(self, data, query_parameters):
713 """Replaces matching substrings in a file.
714
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000715 If the 'replace_text' URL query parameter is present, it is expected to be
716 of the form old_text:new_text, which indicates that any old_text strings in
717 the file are replaced with new_text. Multiple 'replace_text' parameters may
718 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000719
720 If the parameters are not present, |data| is returned.
721 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000722
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000723 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000724 replace_text_values = query_dict.get('replace_text', [])
725 for replace_text_value in replace_text_values:
726 replace_text_args = replace_text_value.split(':')
727 if len(replace_text_args) != 2:
728 raise ValueError(
729 'replace_text must be of form old_text:new_text. Actual value: %s' %
730 replace_text_value)
731 old_text_b64, new_text_b64 = replace_text_args
732 old_text = base64.urlsafe_b64decode(old_text_b64)
733 new_text = base64.urlsafe_b64decode(new_text_b64)
734 data = data.replace(old_text, new_text)
735 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000736
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000737 def ZipFileHandler(self):
738 """This handler sends the contents of the requested file in compressed form.
739 Can pass in a parameter that specifies that the content length be
740 C - the compressed size (OK),
741 U - the uncompressed size (Non-standard, but handled),
742 S - less than compressed (OK because we keep going),
743 M - larger than compressed but less than uncompressed (an error),
744 L - larger than uncompressed (an error)
745 Example: compressedfiles/Picture_1.doc?C
746 """
747
748 prefix = "/compressedfiles/"
749 if not self.path.startswith(prefix):
750 return False
751
752 # Consume a request body if present.
753 if self.command == 'POST' or self.command == 'PUT' :
754 self.ReadRequestBody()
755
756 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
757
758 if not query in ('C', 'U', 'S', 'M', 'L'):
759 return False
760
761 sub_path = url_path[len(prefix):]
762 entries = sub_path.split('/')
763 file_path = os.path.join(self.server.data_dir, *entries)
764 if os.path.isdir(file_path):
765 file_path = os.path.join(file_path, 'index.html')
766
767 if not os.path.isfile(file_path):
768 print "File not found " + sub_path + " full path:" + file_path
769 self.send_error(404)
770 return True
771
772 f = open(file_path, "rb")
773 data = f.read()
774 uncompressed_len = len(data)
775 f.close()
776
777 # Compress the data.
778 data = zlib.compress(data)
779 compressed_len = len(data)
780
781 content_length = compressed_len
782 if query == 'U':
783 content_length = uncompressed_len
784 elif query == 'S':
785 content_length = compressed_len / 2
786 elif query == 'M':
787 content_length = (compressed_len + uncompressed_len) / 2
788 elif query == 'L':
789 content_length = compressed_len + uncompressed_len
790
791 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000792 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000793 self.send_header('Content-encoding', 'deflate')
794 self.send_header('Connection', 'close')
795 self.send_header('Content-Length', content_length)
796 self.send_header('ETag', '\'' + file_path + '\'')
797 self.end_headers()
798
799 self.wfile.write(data)
800
801 return True
802
initial.commit94958cf2008-07-26 22:42:52 +0000803 def FileHandler(self):
804 """This handler sends the contents of the requested file. Wow, it's like
805 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000806
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000807 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000808 if not self.path.startswith(prefix):
809 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000810 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000811
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000812 def PostOnlyFileHandler(self):
813 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000814
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000815 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000816 if not self.path.startswith(prefix):
817 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000818 return self._FileHandlerHelper(prefix)
819
820 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000821 request_body = ''
822 if self.command == 'POST' or self.command == 'PUT':
823 # Consume a request body if present.
824 request_body = self.ReadRequestBody()
825
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000826 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000827 query_dict = cgi.parse_qs(query)
828
829 expected_body = query_dict.get('expected_body', [])
830 if expected_body and request_body not in expected_body:
831 self.send_response(404)
832 self.end_headers()
833 self.wfile.write('')
834 return True
835
836 expected_headers = query_dict.get('expected_headers', [])
837 for expected_header in expected_headers:
838 header_name, expected_value = expected_header.split(':')
839 if self.headers.getheader(header_name) != expected_value:
840 self.send_response(404)
841 self.end_headers()
842 self.wfile.write('')
843 return True
844
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000845 sub_path = url_path[len(prefix):]
846 entries = sub_path.split('/')
847 file_path = os.path.join(self.server.data_dir, *entries)
848 if os.path.isdir(file_path):
849 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000850
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000851 if not os.path.isfile(file_path):
852 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000853 self.send_error(404)
854 return True
855
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000856 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000857 data = f.read()
858 f.close()
859
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000860 data = self._ReplaceFileData(data, query)
861
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000862 old_protocol_version = self.protocol_version
863
initial.commit94958cf2008-07-26 22:42:52 +0000864 # If file.mock-http-headers exists, it contains the headers we
865 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000866 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000867 if os.path.isfile(headers_path):
868 f = open(headers_path, "r")
869
870 # "HTTP/1.1 200 OK"
871 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000872 http_major, http_minor, status_code = re.findall(
873 'HTTP/(\d+).(\d+) (\d+)', response)[0]
874 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000875 self.send_response(int(status_code))
876
877 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000878 header_values = re.findall('(\S+):\s*(.*)', line)
879 if len(header_values) > 0:
880 # "name: value"
881 name, value = header_values[0]
882 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000883 f.close()
884 else:
885 # Could be more generic once we support mime-type sniffing, but for
886 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000887
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000888 range_header = self.headers.get('Range')
889 if range_header and range_header.startswith('bytes='):
890 # Note this doesn't handle all valid byte range_header values (i.e.
891 # left open ended ones), just enough for what we needed so far.
892 range_header = range_header[6:].split('-')
893 start = int(range_header[0])
894 if range_header[1]:
895 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000896 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000897 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000898
899 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000900 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
901 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000902 self.send_header('Content-Range', content_range)
903 data = data[start: end + 1]
904 else:
905 self.send_response(200)
906
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000907 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000908 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000909 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000910 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000911 self.end_headers()
912
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000913 if (self.command != 'HEAD'):
914 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000915
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000916 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000917 return True
918
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000919 def SetCookieHandler(self):
920 """This handler just sets a cookie, for testing cookie handling."""
921
922 if not self._ShouldHandleRequest("/set-cookie"):
923 return False
924
925 query_char = self.path.find('?')
926 if query_char != -1:
927 cookie_values = self.path[query_char + 1:].split('&')
928 else:
929 cookie_values = ("",)
930 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000931 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000932 for cookie_value in cookie_values:
933 self.send_header('Set-Cookie', '%s' % cookie_value)
934 self.end_headers()
935 for cookie_value in cookie_values:
936 self.wfile.write('%s' % cookie_value)
937 return True
938
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000939 def SetManyCookiesHandler(self):
940 """This handler just sets a given number of cookies, for testing handling
941 of large numbers of cookies."""
942
943 if not self._ShouldHandleRequest("/set-many-cookies"):
944 return False
945
946 query_char = self.path.find('?')
947 if query_char != -1:
948 num_cookies = int(self.path[query_char + 1:])
949 else:
950 num_cookies = 0
951 self.send_response(200)
952 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000953 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000954 self.send_header('Set-Cookie', 'a=')
955 self.end_headers()
956 self.wfile.write('%d cookies were sent' % num_cookies)
957 return True
958
mattm@chromium.org983fc462012-06-30 00:52:08 +0000959 def ExpectAndSetCookieHandler(self):
960 """Expects some cookies to be sent, and if they are, sets more cookies.
961
962 The expect parameter specifies a required cookie. May be specified multiple
963 times.
964 The set parameter specifies a cookie to set if all required cookies are
965 preset. May be specified multiple times.
966 The data parameter specifies the response body data to be returned."""
967
968 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
969 return False
970
971 _, _, _, _, query, _ = urlparse.urlparse(self.path)
972 query_dict = cgi.parse_qs(query)
973 cookies = set()
974 if 'Cookie' in self.headers:
975 cookie_header = self.headers.getheader('Cookie')
976 cookies.update([s.strip() for s in cookie_header.split(';')])
977 got_all_expected_cookies = True
978 for expected_cookie in query_dict.get('expect', []):
979 if expected_cookie not in cookies:
980 got_all_expected_cookies = False
981 self.send_response(200)
982 self.send_header('Content-Type', 'text/html')
983 if got_all_expected_cookies:
984 for cookie_value in query_dict.get('set', []):
985 self.send_header('Set-Cookie', '%s' % cookie_value)
986 self.end_headers()
987 for data_value in query_dict.get('data', []):
988 self.wfile.write(data_value)
989 return True
990
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000991 def SetHeaderHandler(self):
992 """This handler sets a response header. Parameters are in the
993 key%3A%20value&key2%3A%20value2 format."""
994
995 if not self._ShouldHandleRequest("/set-header"):
996 return False
997
998 query_char = self.path.find('?')
999 if query_char != -1:
1000 headers_values = self.path[query_char + 1:].split('&')
1001 else:
1002 headers_values = ("",)
1003 self.send_response(200)
1004 self.send_header('Content-Type', 'text/html')
1005 for header_value in headers_values:
1006 header_value = urllib.unquote(header_value)
1007 (key, value) = header_value.split(': ', 1)
1008 self.send_header(key, value)
1009 self.end_headers()
1010 for header_value in headers_values:
1011 self.wfile.write('%s' % header_value)
1012 return True
1013
initial.commit94958cf2008-07-26 22:42:52 +00001014 def AuthBasicHandler(self):
1015 """This handler tests 'Basic' authentication. It just sends a page with
1016 title 'user/pass' if you succeed."""
1017
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001018 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001019 return False
1020
1021 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001022 expected_password = 'secret'
1023 realm = 'testrealm'
1024 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001025
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001026 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1027 query_params = cgi.parse_qs(query, True)
1028 if 'set-cookie-if-challenged' in query_params:
1029 set_cookie_if_challenged = True
1030 if 'password' in query_params:
1031 expected_password = query_params['password'][0]
1032 if 'realm' in query_params:
1033 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001034
initial.commit94958cf2008-07-26 22:42:52 +00001035 auth = self.headers.getheader('authorization')
1036 try:
1037 if not auth:
1038 raise Exception('no auth')
1039 b64str = re.findall(r'Basic (\S+)', auth)[0]
1040 userpass = base64.b64decode(b64str)
1041 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001042 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001043 raise Exception('wrong password')
1044 except Exception, e:
1045 # Authentication failed.
1046 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001047 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001048 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001049 if set_cookie_if_challenged:
1050 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001051 self.end_headers()
1052 self.wfile.write('<html><head>')
1053 self.wfile.write('<title>Denied: %s</title>' % e)
1054 self.wfile.write('</head><body>')
1055 self.wfile.write('auth=%s<p>' % auth)
1056 self.wfile.write('b64str=%s<p>' % b64str)
1057 self.wfile.write('username: %s<p>' % username)
1058 self.wfile.write('userpass: %s<p>' % userpass)
1059 self.wfile.write('password: %s<p>' % password)
1060 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1061 self.wfile.write('</body></html>')
1062 return True
1063
1064 # Authentication successful. (Return a cachable response to allow for
1065 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001066 old_protocol_version = self.protocol_version
1067 self.protocol_version = "HTTP/1.1"
1068
initial.commit94958cf2008-07-26 22:42:52 +00001069 if_none_match = self.headers.getheader('if-none-match')
1070 if if_none_match == "abc":
1071 self.send_response(304)
1072 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001073 elif url_path.endswith(".gif"):
1074 # Using chrome/test/data/google/logo.gif as the test image
1075 test_image_path = ['google', 'logo.gif']
1076 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1077 if not os.path.isfile(gif_path):
1078 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001079 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001080 return True
1081
1082 f = open(gif_path, "rb")
1083 data = f.read()
1084 f.close()
1085
1086 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001087 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001088 self.send_header('Cache-control', 'max-age=60000')
1089 self.send_header('Etag', 'abc')
1090 self.end_headers()
1091 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001092 else:
1093 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001094 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001095 self.send_header('Cache-control', 'max-age=60000')
1096 self.send_header('Etag', 'abc')
1097 self.end_headers()
1098 self.wfile.write('<html><head>')
1099 self.wfile.write('<title>%s/%s</title>' % (username, password))
1100 self.wfile.write('</head><body>')
1101 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001102 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001103 self.wfile.write('</body></html>')
1104
rvargas@google.com54453b72011-05-19 01:11:11 +00001105 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001106 return True
1107
tonyg@chromium.org75054202010-03-31 22:06:10 +00001108 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001109 """Returns a nonce that's stable per request path for the server's lifetime.
1110 This is a fake implementation. A real implementation would only use a given
1111 nonce a single time (hence the name n-once). However, for the purposes of
1112 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001113
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001114 Args:
1115 force_reset: Iff set, the nonce will be changed. Useful for testing the
1116 "stale" response.
1117 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001118
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001119 if force_reset or not self.server.nonce_time:
1120 self.server.nonce_time = time.time()
1121 return hashlib.md5('privatekey%s%d' %
1122 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001123
1124 def AuthDigestHandler(self):
1125 """This handler tests 'Digest' authentication.
1126
1127 It just sends a page with title 'user/pass' if you succeed.
1128
1129 A stale response is sent iff "stale" is present in the request path.
1130 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001131
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001132 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001133 return False
1134
tonyg@chromium.org75054202010-03-31 22:06:10 +00001135 stale = 'stale' in self.path
1136 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001137 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001138 password = 'secret'
1139 realm = 'testrealm'
1140
1141 auth = self.headers.getheader('authorization')
1142 pairs = {}
1143 try:
1144 if not auth:
1145 raise Exception('no auth')
1146 if not auth.startswith('Digest'):
1147 raise Exception('not digest')
1148 # Pull out all the name="value" pairs as a dictionary.
1149 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1150
1151 # Make sure it's all valid.
1152 if pairs['nonce'] != nonce:
1153 raise Exception('wrong nonce')
1154 if pairs['opaque'] != opaque:
1155 raise Exception('wrong opaque')
1156
1157 # Check the 'response' value and make sure it matches our magic hash.
1158 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001159 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001160 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001161 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001162 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001163 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001164 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1165 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001166 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001167
1168 if pairs['response'] != response:
1169 raise Exception('wrong password')
1170 except Exception, e:
1171 # Authentication failed.
1172 self.send_response(401)
1173 hdr = ('Digest '
1174 'realm="%s", '
1175 'domain="/", '
1176 'qop="auth", '
1177 'algorithm=MD5, '
1178 'nonce="%s", '
1179 'opaque="%s"') % (realm, nonce, opaque)
1180 if stale:
1181 hdr += ', stale="TRUE"'
1182 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001183 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001184 self.end_headers()
1185 self.wfile.write('<html><head>')
1186 self.wfile.write('<title>Denied: %s</title>' % e)
1187 self.wfile.write('</head><body>')
1188 self.wfile.write('auth=%s<p>' % auth)
1189 self.wfile.write('pairs=%s<p>' % pairs)
1190 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1191 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1192 self.wfile.write('</body></html>')
1193 return True
1194
1195 # Authentication successful.
1196 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001197 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001198 self.end_headers()
1199 self.wfile.write('<html><head>')
1200 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1201 self.wfile.write('</head><body>')
1202 self.wfile.write('auth=%s<p>' % auth)
1203 self.wfile.write('pairs=%s<p>' % pairs)
1204 self.wfile.write('</body></html>')
1205
1206 return True
1207
1208 def SlowServerHandler(self):
1209 """Wait for the user suggested time before responding. The syntax is
1210 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001211
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001212 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001213 return False
1214 query_char = self.path.find('?')
1215 wait_sec = 1.0
1216 if query_char >= 0:
1217 try:
1218 wait_sec = int(self.path[query_char + 1:])
1219 except ValueError:
1220 pass
1221 time.sleep(wait_sec)
1222 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001223 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001224 self.end_headers()
1225 self.wfile.write("waited %d seconds" % wait_sec)
1226 return True
1227
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001228 def ChunkedServerHandler(self):
1229 """Send chunked response. Allows to specify chunks parameters:
1230 - waitBeforeHeaders - ms to wait before sending headers
1231 - waitBetweenChunks - ms to wait between chunks
1232 - chunkSize - size of each chunk in bytes
1233 - chunksNumber - number of chunks
1234 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1235 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001236
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001237 if not self._ShouldHandleRequest("/chunked"):
1238 return False
1239 query_char = self.path.find('?')
1240 chunkedSettings = {'waitBeforeHeaders' : 0,
1241 'waitBetweenChunks' : 0,
1242 'chunkSize' : 5,
1243 'chunksNumber' : 5}
1244 if query_char >= 0:
1245 params = self.path[query_char + 1:].split('&')
1246 for param in params:
1247 keyValue = param.split('=')
1248 if len(keyValue) == 2:
1249 try:
1250 chunkedSettings[keyValue[0]] = int(keyValue[1])
1251 except ValueError:
1252 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001253 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001254 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1255 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001256 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001257 self.send_header('Connection', 'close')
1258 self.send_header('Transfer-Encoding', 'chunked')
1259 self.end_headers()
1260 # Chunked encoding: sending all chunks, then final zero-length chunk and
1261 # then final CRLF.
1262 for i in range(0, chunkedSettings['chunksNumber']):
1263 if i > 0:
1264 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1265 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001266 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001267 self.sendChunkHelp('')
1268 return True
1269
initial.commit94958cf2008-07-26 22:42:52 +00001270 def ContentTypeHandler(self):
1271 """Returns a string of html with the given content type. E.g.,
1272 /contenttype?text/css returns an html file with the Content-Type
1273 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001274
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001275 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001276 return False
1277 query_char = self.path.find('?')
1278 content_type = self.path[query_char + 1:].strip()
1279 if not content_type:
1280 content_type = 'text/html'
1281 self.send_response(200)
1282 self.send_header('Content-Type', content_type)
1283 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001284 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001285 return True
1286
creis@google.com2f4f6a42011-03-25 19:44:19 +00001287 def NoContentHandler(self):
1288 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001289
creis@google.com2f4f6a42011-03-25 19:44:19 +00001290 if not self._ShouldHandleRequest("/nocontent"):
1291 return False
1292 self.send_response(204)
1293 self.end_headers()
1294 return True
1295
initial.commit94958cf2008-07-26 22:42:52 +00001296 def ServerRedirectHandler(self):
1297 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001298 '/server-redirect?http://foo.bar/asdf' to redirect to
1299 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001300
1301 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001302 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001303 return False
1304
1305 query_char = self.path.find('?')
1306 if query_char < 0 or len(self.path) <= query_char + 1:
1307 self.sendRedirectHelp(test_name)
1308 return True
1309 dest = self.path[query_char + 1:]
1310
1311 self.send_response(301) # moved permanently
1312 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001313 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001314 self.end_headers()
1315 self.wfile.write('<html><head>')
1316 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1317
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001318 return True
initial.commit94958cf2008-07-26 22:42:52 +00001319
1320 def ClientRedirectHandler(self):
1321 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001322 '/client-redirect?http://foo.bar/asdf' to redirect to
1323 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001324
1325 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001326 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001327 return False
1328
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001329 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001330 if query_char < 0 or len(self.path) <= query_char + 1:
1331 self.sendRedirectHelp(test_name)
1332 return True
1333 dest = self.path[query_char + 1:]
1334
1335 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001336 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001337 self.end_headers()
1338 self.wfile.write('<html><head>')
1339 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1340 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1341
1342 return True
1343
tony@chromium.org03266982010-03-05 03:18:42 +00001344 def MultipartHandler(self):
1345 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001346
tony@chromium.org4cb88302011-09-27 22:13:49 +00001347 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001348 if not self._ShouldHandleRequest(test_name):
1349 return False
1350
1351 num_frames = 10
1352 bound = '12345'
1353 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001354 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001355 'multipart/x-mixed-replace;boundary=' + bound)
1356 self.end_headers()
1357
1358 for i in xrange(num_frames):
1359 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001360 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001361 self.wfile.write('<title>page ' + str(i) + '</title>')
1362 self.wfile.write('page ' + str(i))
1363
1364 self.wfile.write('--' + bound + '--')
1365 return True
1366
tony@chromium.org4cb88302011-09-27 22:13:49 +00001367 def MultipartSlowHandler(self):
1368 """Send a multipart response (3 text/html pages) with a slight delay
1369 between each page. This is similar to how some pages show status using
1370 multipart."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001371
tony@chromium.org4cb88302011-09-27 22:13:49 +00001372 test_name = '/multipart-slow'
1373 if not self._ShouldHandleRequest(test_name):
1374 return False
1375
1376 num_frames = 3
1377 bound = '12345'
1378 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001379 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001380 'multipart/x-mixed-replace;boundary=' + bound)
1381 self.end_headers()
1382
1383 for i in xrange(num_frames):
1384 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001385 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001386 time.sleep(0.25)
1387 if i == 2:
1388 self.wfile.write('<title>PASS</title>')
1389 else:
1390 self.wfile.write('<title>page ' + str(i) + '</title>')
1391 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1392
1393 self.wfile.write('--' + bound + '--')
1394 return True
1395
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001396 def GetSSLSessionCacheHandler(self):
1397 """Send a reply containing a log of the session cache operations."""
1398
1399 if not self._ShouldHandleRequest('/ssl-session-cache'):
1400 return False
1401
1402 self.send_response(200)
1403 self.send_header('Content-Type', 'text/plain')
1404 self.end_headers()
1405 try:
1406 for (action, sessionID) in self.server.session_cache.log:
1407 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001408 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001409 self.wfile.write('Pass --https-record-resume in order to use' +
1410 ' this request')
1411 return True
1412
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001413 def SSLManySmallRecords(self):
1414 """Sends a reply consisting of a variety of small writes. These will be
1415 translated into a series of small SSL records when used over an HTTPS
1416 server."""
1417
1418 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1419 return False
1420
1421 self.send_response(200)
1422 self.send_header('Content-Type', 'text/plain')
1423 self.end_headers()
1424
1425 # Write ~26K of data, in 1350 byte chunks
1426 for i in xrange(20):
1427 self.wfile.write('*' * 1350)
1428 self.wfile.flush()
1429 return True
1430
agl@chromium.org04700be2013-03-02 18:40:41 +00001431 def GetChannelID(self):
1432 """Send a reply containing the hashed ChannelID that the client provided."""
1433
1434 if not self._ShouldHandleRequest('/channel-id'):
1435 return False
1436
1437 self.send_response(200)
1438 self.send_header('Content-Type', 'text/plain')
1439 self.end_headers()
1440 channel_id = self.server.tlsConnection.channel_id.tostring()
1441 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1442 return True
1443
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001444 def CloseSocketHandler(self):
1445 """Closes the socket without sending anything."""
1446
1447 if not self._ShouldHandleRequest('/close-socket'):
1448 return False
1449
1450 self.wfile.close()
1451 return True
1452
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001453 def RangeResetHandler(self):
1454 """Send data broken up by connection resets every N (default 4K) bytes.
1455 Support range requests. If the data requested doesn't straddle a reset
1456 boundary, it will all be sent. Used for testing resuming downloads."""
1457
1458 if not self._ShouldHandleRequest('/rangereset'):
1459 return False
1460
1461 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1462
1463 # Defaults
1464 size = 8000
1465 # Note that the rst is sent just before sending the rst_boundary byte.
1466 rst_boundary = 4000
1467 respond_to_range = True
1468 hold_for_signal = False
1469
1470 # Parse the query
1471 qdict = urlparse.parse_qs(query, True)
1472 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001473 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001474 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001475 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001476 if 'bounce_range' in qdict:
1477 respond_to_range = False
1478 if 'hold' in qdict:
1479 hold_for_signal = True
1480
1481 first_byte = 0
1482 last_byte = size - 1
1483
1484 # Does that define what we want to return, or do we need to apply
1485 # a range?
1486 range_response = False
1487 range_header = self.headers.getheader('range')
1488 if range_header and respond_to_range:
1489 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1490 if mo.group(1):
1491 first_byte = int(mo.group(1))
1492 if mo.group(2):
1493 last_byte = int(mo.group(2))
1494 if last_byte > size - 1:
1495 last_byte = size - 1
1496 range_response = True
1497 if last_byte < first_byte:
1498 return False
1499
1500 # Set socket send buf high enough that we don't need to worry
1501 # about asynchronous closes when sending RSTs.
1502 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF,
1503 16284)
1504
1505 if range_response:
1506 self.send_response(206)
1507 self.send_header('Content-Range',
1508 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1509 else:
1510 self.send_response(200)
1511 self.send_header('Content-Type', 'application/octet-stream')
1512 self.send_header('Content-Length', last_byte - first_byte + 1)
1513 self.end_headers()
1514
1515 if hold_for_signal:
1516 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1517 # a single byte, the self.server.handle_request() below hangs
1518 # without processing new incoming requests.
1519 self.wfile.write('X')
1520 first_byte = first_byte + 1
1521 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001522 self.server.wait_for_download = True
1523 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001524 self.server.handle_request()
1525
1526 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
1527 if possible_rst >= last_byte:
1528 # No RST has been requested in this range, so we don't need to
1529 # do anything fancy; just write the data and let the python
1530 # infrastructure close the connection.
1531 self.wfile.write('X' * (last_byte - first_byte + 1))
1532 self.wfile.flush()
1533 return True
1534
1535 # We're resetting the connection part way in; go to the RST
1536 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001537 # Because socket semantics do not guarantee that all the data will be
1538 # sent when using the linger semantics to hard close a socket,
1539 # we send the data and then wait for our peer to release us
1540 # before sending the reset.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001541 self.wfile.write('X' * (possible_rst - first_byte))
1542 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001543 self.server.wait_for_download = True
1544 while self.server.wait_for_download:
1545 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001546 l_onoff = 1 # Linger is active.
1547 l_linger = 0 # Seconds to linger for.
1548 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1549 struct.pack('ii', l_onoff, l_linger))
1550
1551 # Close all duplicates of the underlying socket to force the RST.
1552 self.wfile.close()
1553 self.rfile.close()
1554 self.connection.close()
1555
1556 return True
1557
initial.commit94958cf2008-07-26 22:42:52 +00001558 def DefaultResponseHandler(self):
1559 """This is the catch-all response handler for requests that aren't handled
1560 by one of the special handlers above.
1561 Note that we specify the content-length as without it the https connection
1562 is not closed properly (and the browser keeps expecting data)."""
1563
1564 contents = "Default response given for path: " + self.path
1565 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001566 self.send_header('Content-Type', 'text/html')
1567 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001568 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001569 if (self.command != 'HEAD'):
1570 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001571 return True
1572
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001573 def RedirectConnectHandler(self):
1574 """Sends a redirect to the CONNECT request for www.redirect.com. This
1575 response is not specified by the RFC, so the browser should not follow
1576 the redirect."""
1577
1578 if (self.path.find("www.redirect.com") < 0):
1579 return False
1580
1581 dest = "http://www.destination.com/foo.js"
1582
1583 self.send_response(302) # moved temporarily
1584 self.send_header('Location', dest)
1585 self.send_header('Connection', 'close')
1586 self.end_headers()
1587 return True
1588
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001589 def ServerAuthConnectHandler(self):
1590 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1591 response doesn't make sense because the proxy server cannot request
1592 server authentication."""
1593
1594 if (self.path.find("www.server-auth.com") < 0):
1595 return False
1596
1597 challenge = 'Basic realm="WallyWorld"'
1598
1599 self.send_response(401) # unauthorized
1600 self.send_header('WWW-Authenticate', challenge)
1601 self.send_header('Connection', 'close')
1602 self.end_headers()
1603 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001604
1605 def DefaultConnectResponseHandler(self):
1606 """This is the catch-all response handler for CONNECT requests that aren't
1607 handled by one of the special handlers above. Real Web servers respond
1608 with 400 to CONNECT requests."""
1609
1610 contents = "Your client has issued a malformed or illegal request."
1611 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001612 self.send_header('Content-Type', 'text/html')
1613 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001614 self.end_headers()
1615 self.wfile.write(contents)
1616 return True
1617
initial.commit94958cf2008-07-26 22:42:52 +00001618 # called by the redirect handling function when there is no parameter
1619 def sendRedirectHelp(self, redirect_name):
1620 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001621 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001622 self.end_headers()
1623 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1624 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1625 self.wfile.write('</body></html>')
1626
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001627 # called by chunked handling function
1628 def sendChunkHelp(self, chunk):
1629 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1630 self.wfile.write('%X\r\n' % len(chunk))
1631 self.wfile.write(chunk)
1632 self.wfile.write('\r\n')
1633
akalin@chromium.org154bb132010-11-12 02:20:27 +00001634
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001635class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001636 def __init__(self, request, client_address, socket_server):
1637 handlers = [self.OCSPResponse]
1638 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001639 testserver_base.BasePageHandler.__init__(self, request, client_address,
1640 socket_server, [], handlers, [],
1641 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001642
1643 def OCSPResponse(self):
1644 self.send_response(200)
1645 self.send_header('Content-Type', 'application/ocsp-response')
1646 self.send_header('Content-Length', str(len(self.ocsp_response)))
1647 self.end_headers()
1648
1649 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001650
mattm@chromium.org830a3712012-11-07 23:00:07 +00001651
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001652class TCPEchoHandler(SocketServer.BaseRequestHandler):
1653 """The RequestHandler class for TCP echo server.
1654
1655 It is instantiated once per connection to the server, and overrides the
1656 handle() method to implement communication to the client.
1657 """
1658
1659 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001660 """Handles the request from the client and constructs a response."""
1661
1662 data = self.request.recv(65536).strip()
1663 # Verify the "echo request" message received from the client. Send back
1664 # "echo response" message if "echo request" message is valid.
1665 try:
1666 return_data = echo_message.GetEchoResponseData(data)
1667 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001668 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001669 except ValueError:
1670 return
1671
1672 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001673
1674
1675class UDPEchoHandler(SocketServer.BaseRequestHandler):
1676 """The RequestHandler class for UDP echo server.
1677
1678 It is instantiated once per connection to the server, and overrides the
1679 handle() method to implement communication to the client.
1680 """
1681
1682 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001683 """Handles the request from the client and constructs a response."""
1684
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001685 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001686 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001687 # Verify the "echo request" message received from the client. Send back
1688 # "echo response" message if "echo request" message is valid.
1689 try:
1690 return_data = echo_message.GetEchoResponseData(data)
1691 if not return_data:
1692 return
1693 except ValueError:
1694 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001695 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001696
1697
bashi@chromium.org33233532012-09-08 17:37:24 +00001698class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1699 """A request handler that behaves as a proxy server which requires
1700 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1701 """
1702
1703 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1704
1705 def parse_request(self):
1706 """Overrides parse_request to check credential."""
1707
1708 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1709 return False
1710
1711 auth = self.headers.getheader('Proxy-Authorization')
1712 if auth != self._AUTH_CREDENTIAL:
1713 self.send_response(407)
1714 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1715 self.end_headers()
1716 return False
1717
1718 return True
1719
1720 def _start_read_write(self, sock):
1721 sock.setblocking(0)
1722 self.request.setblocking(0)
1723 rlist = [self.request, sock]
1724 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001725 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001726 if errors:
1727 self.send_response(500)
1728 self.end_headers()
1729 return
1730 for s in ready_sockets:
1731 received = s.recv(1024)
1732 if len(received) == 0:
1733 return
1734 if s == self.request:
1735 other = sock
1736 else:
1737 other = self.request
1738 other.send(received)
1739
1740 def _do_common_method(self):
1741 url = urlparse.urlparse(self.path)
1742 port = url.port
1743 if not port:
1744 if url.scheme == 'http':
1745 port = 80
1746 elif url.scheme == 'https':
1747 port = 443
1748 if not url.hostname or not port:
1749 self.send_response(400)
1750 self.end_headers()
1751 return
1752
1753 if len(url.path) == 0:
1754 path = '/'
1755 else:
1756 path = url.path
1757 if len(url.query) > 0:
1758 path = '%s?%s' % (url.path, url.query)
1759
1760 sock = None
1761 try:
1762 sock = socket.create_connection((url.hostname, port))
1763 sock.send('%s %s %s\r\n' % (
1764 self.command, path, self.protocol_version))
1765 for header in self.headers.headers:
1766 header = header.strip()
1767 if (header.lower().startswith('connection') or
1768 header.lower().startswith('proxy')):
1769 continue
1770 sock.send('%s\r\n' % header)
1771 sock.send('\r\n')
1772 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001773 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001774 self.send_response(500)
1775 self.end_headers()
1776 finally:
1777 if sock is not None:
1778 sock.close()
1779
1780 def do_CONNECT(self):
1781 try:
1782 pos = self.path.rfind(':')
1783 host = self.path[:pos]
1784 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001785 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001786 self.send_response(400)
1787 self.end_headers()
1788
1789 try:
1790 sock = socket.create_connection((host, port))
1791 self.send_response(200, 'Connection established')
1792 self.end_headers()
1793 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001794 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001795 self.send_response(500)
1796 self.end_headers()
1797 finally:
1798 sock.close()
1799
1800 def do_GET(self):
1801 self._do_common_method()
1802
1803 def do_HEAD(self):
1804 self._do_common_method()
1805
1806
mattm@chromium.org830a3712012-11-07 23:00:07 +00001807class ServerRunner(testserver_base.TestServerRunner):
1808 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001809
mattm@chromium.org830a3712012-11-07 23:00:07 +00001810 def __init__(self):
1811 super(ServerRunner, self).__init__()
1812 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001813
mattm@chromium.org830a3712012-11-07 23:00:07 +00001814 def __make_data_dir(self):
1815 if self.options.data_dir:
1816 if not os.path.isdir(self.options.data_dir):
1817 raise testserver_base.OptionError('specified data dir not found: ' +
1818 self.options.data_dir + ' exiting...')
1819 my_data_dir = self.options.data_dir
1820 else:
1821 # Create the default path to our data dir, relative to the exe dir.
1822 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1823 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001824
mattm@chromium.org830a3712012-11-07 23:00:07 +00001825 #TODO(ibrar): Must use Find* funtion defined in google\tools
1826 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001827
mattm@chromium.org830a3712012-11-07 23:00:07 +00001828 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001829
mattm@chromium.org830a3712012-11-07 23:00:07 +00001830 def create_server(self, server_data):
1831 port = self.options.port
1832 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001833
mattm@chromium.org830a3712012-11-07 23:00:07 +00001834 if self.options.server_type == SERVER_HTTP:
1835 if self.options.https:
1836 pem_cert_and_key = None
1837 if self.options.cert_and_key_file:
1838 if not os.path.isfile(self.options.cert_and_key_file):
1839 raise testserver_base.OptionError(
1840 'specified server cert file not found: ' +
1841 self.options.cert_and_key_file + ' exiting...')
1842 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001843 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001844 # generate a new certificate and run an OCSP server for it.
1845 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
1846 print ('OCSP server started on %s:%d...' %
1847 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001848
mattm@chromium.org830a3712012-11-07 23:00:07 +00001849 ocsp_der = None
1850 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001851
mattm@chromium.org830a3712012-11-07 23:00:07 +00001852 if self.options.ocsp == 'ok':
1853 ocsp_state = minica.OCSP_STATE_GOOD
1854 elif self.options.ocsp == 'revoked':
1855 ocsp_state = minica.OCSP_STATE_REVOKED
1856 elif self.options.ocsp == 'invalid':
1857 ocsp_state = minica.OCSP_STATE_INVALID
1858 elif self.options.ocsp == 'unauthorized':
1859 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1860 elif self.options.ocsp == 'unknown':
1861 ocsp_state = minica.OCSP_STATE_UNKNOWN
1862 else:
1863 raise testserver_base.OptionError('unknown OCSP status: ' +
1864 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001865
mattm@chromium.org830a3712012-11-07 23:00:07 +00001866 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1867 subject = "127.0.0.1",
1868 ocsp_url = ("http://%s:%d/ocsp" %
1869 (host, self.__ocsp_server.server_port)),
1870 ocsp_state = ocsp_state)
1871
1872 self.__ocsp_server.ocsp_response = ocsp_der
1873
1874 for ca_cert in self.options.ssl_client_ca:
1875 if not os.path.isfile(ca_cert):
1876 raise testserver_base.OptionError(
1877 'specified trusted client CA file not found: ' + ca_cert +
1878 ' exiting...')
1879 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1880 self.options.ssl_client_auth,
1881 self.options.ssl_client_ca,
1882 self.options.ssl_bulk_cipher,
1883 self.options.record_resume,
1884 self.options.tls_intolerant)
1885 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
1886 else:
1887 server = HTTPServer((host, port), TestPageHandler)
1888 print 'HTTP server started on %s:%d...' % (host, server.server_port)
1889
1890 server.data_dir = self.__make_data_dir()
1891 server.file_root_url = self.options.file_root_url
1892 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001893 elif self.options.server_type == SERVER_WEBSOCKET:
1894 # Launch pywebsocket via WebSocketServer.
1895 logger = logging.getLogger()
1896 logger.addHandler(logging.StreamHandler())
1897 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1898 # is required to work correctly. It should be fixed from pywebsocket side.
1899 os.chdir(self.__make_data_dir())
1900 websocket_options = WebSocketOptions(host, port, '.')
1901 if self.options.cert_and_key_file:
1902 websocket_options.use_tls = True
1903 websocket_options.private_key = self.options.cert_and_key_file
1904 websocket_options.certificate = self.options.cert_and_key_file
1905 if self.options.ssl_client_auth:
1906 websocket_options.tls_client_auth = True
1907 if len(self.options.ssl_client_ca) != 1:
1908 raise testserver_base.OptionError(
1909 'one trusted client CA file should be specified')
1910 if not os.path.isfile(self.options.ssl_client_ca[0]):
1911 raise testserver_base.OptionError(
1912 'specified trusted client CA file not found: ' +
1913 self.options.ssl_client_ca[0] + ' exiting...')
1914 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
1915 server = WebSocketServer(websocket_options)
1916 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
1917 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001918 elif self.options.server_type == SERVER_TCP_ECHO:
1919 # Used for generating the key (randomly) that encodes the "echo request"
1920 # message.
1921 random.seed()
1922 server = TCPEchoServer((host, port), TCPEchoHandler)
1923 print 'Echo TCP server started on port %d...' % server.server_port
1924 server_data['port'] = server.server_port
1925 elif self.options.server_type == SERVER_UDP_ECHO:
1926 # Used for generating the key (randomly) that encodes the "echo request"
1927 # message.
1928 random.seed()
1929 server = UDPEchoServer((host, port), UDPEchoHandler)
1930 print 'Echo UDP server started on port %d...' % server.server_port
1931 server_data['port'] = server.server_port
1932 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
1933 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
1934 print 'BasicAuthProxy server started on port %d...' % server.server_port
1935 server_data['port'] = server.server_port
1936 elif self.options.server_type == SERVER_FTP:
1937 my_data_dir = self.__make_data_dir()
1938
1939 # Instantiate a dummy authorizer for managing 'virtual' users
1940 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1941
1942 # Define a new user having full r/w permissions and a read-only
1943 # anonymous user
1944 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1945
1946 authorizer.add_anonymous(my_data_dir)
1947
1948 # Instantiate FTP handler class
1949 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1950 ftp_handler.authorizer = authorizer
1951
1952 # Define a customized banner (string returned when client connects)
1953 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1954 pyftpdlib.ftpserver.__ver__)
1955
1956 # Instantiate FTP server class and listen to address:port
1957 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
1958 server_data['port'] = server.socket.getsockname()[1]
1959 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001960 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001961 raise testserver_base.OptionError('unknown server type' +
1962 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00001963
mattm@chromium.org830a3712012-11-07 23:00:07 +00001964 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001965
mattm@chromium.org830a3712012-11-07 23:00:07 +00001966 def run_server(self):
1967 if self.__ocsp_server:
1968 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001969
mattm@chromium.org830a3712012-11-07 23:00:07 +00001970 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001971
mattm@chromium.org830a3712012-11-07 23:00:07 +00001972 if self.__ocsp_server:
1973 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001974
mattm@chromium.org830a3712012-11-07 23:00:07 +00001975 def add_options(self):
1976 testserver_base.TestServerRunner.add_options(self)
1977 self.option_parser.add_option('-f', '--ftp', action='store_const',
1978 const=SERVER_FTP, default=SERVER_HTTP,
1979 dest='server_type',
1980 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001981 self.option_parser.add_option('--tcp-echo', action='store_const',
1982 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1983 dest='server_type',
1984 help='start up a tcp echo server.')
1985 self.option_parser.add_option('--udp-echo', action='store_const',
1986 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
1987 dest='server_type',
1988 help='start up a udp echo server.')
1989 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
1990 const=SERVER_BASIC_AUTH_PROXY,
1991 default=SERVER_HTTP, dest='server_type',
1992 help='start up a proxy server which requires '
1993 'basic authentication.')
1994 self.option_parser.add_option('--websocket', action='store_const',
1995 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
1996 dest='server_type',
1997 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001998 self.option_parser.add_option('--https', action='store_true',
1999 dest='https', help='Specify that https '
2000 'should be used.')
2001 self.option_parser.add_option('--cert-and-key-file',
2002 dest='cert_and_key_file', help='specify the '
2003 'path to the file containing the certificate '
2004 'and private key for the server in PEM '
2005 'format')
2006 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2007 help='The type of OCSP response generated '
2008 'for the automatically generated '
2009 'certificate. One of [ok,revoked,invalid]')
2010 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2011 default='0', type='int',
2012 help='If nonzero, certain TLS connections '
2013 'will be aborted in order to test version '
2014 'fallback. 1 means all TLS versions will be '
2015 'aborted. 2 means TLS 1.1 or higher will be '
2016 'aborted. 3 means TLS 1.2 or higher will be '
2017 'aborted.')
2018 self.option_parser.add_option('--https-record-resume',
2019 dest='record_resume', const=True,
2020 default=False, action='store_const',
2021 help='Record resumption cache events rather '
2022 'than resuming as normal. Allows the use of '
2023 'the /ssl-session-cache request')
2024 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2025 help='Require SSL client auth on every '
2026 'connection.')
2027 self.option_parser.add_option('--ssl-client-ca', action='append',
2028 default=[], help='Specify that the client '
2029 'certificate request should include the CA '
2030 'named in the subject of the DER-encoded '
2031 'certificate contained in the specified '
2032 'file. This option may appear multiple '
2033 'times, indicating multiple CA names should '
2034 'be sent in the request.')
2035 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2036 help='Specify the bulk encryption '
2037 'algorithm(s) that will be accepted by the '
2038 'SSL server. Valid values are "aes256", '
2039 '"aes128", "3des", "rc4". If omitted, all '
2040 'algorithms will be used. This option may '
2041 'appear multiple times, indicating '
2042 'multiple algorithms should be enabled.');
2043 self.option_parser.add_option('--file-root-url', default='/files/',
2044 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002045
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002046
initial.commit94958cf2008-07-26 22:42:52 +00002047if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002048 sys.exit(ServerRunner().main())