blob: 54d8b190b425a028dbfc0e3b8673c18eccf95911 [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:
161 tlsConnection.handshakeServer(certChain=self.cert_chain,
162 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000163 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000164 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000165 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000166 reqCAs=self.ssl_client_cas,
167 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000168 tlsConnection.ignoreAbruptClose = True
169 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000170 except tlslite.api.TLSAbruptCloseError:
171 # Ignore abrupt close.
172 return True
initial.commit94958cf2008-07-26 22:42:52 +0000173 except tlslite.api.TLSError, error:
174 print "Handshake failure:", str(error)
175 return False
176
akalin@chromium.org154bb132010-11-12 02:20:27 +0000177
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000178class FTPServer(testserver_base.ClientRestrictingServerMixIn,
179 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000180 """This is a specialization of FTPServer that adds client verification."""
181
182 pass
183
184
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000185class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
186 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000187 """A TCP echo server that echoes back what it has received."""
188
189 def server_bind(self):
190 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000191
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000192 SocketServer.TCPServer.server_bind(self)
193 host, port = self.socket.getsockname()[:2]
194 self.server_name = socket.getfqdn(host)
195 self.server_port = port
196
197 def serve_forever(self):
198 self.stop = False
199 self.nonce_time = None
200 while not self.stop:
201 self.handle_request()
202 self.socket.close()
203
204
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000205class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
206 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000207 """A UDP echo server that echoes back what it has received."""
208
209 def server_bind(self):
210 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000211
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000212 SocketServer.UDPServer.server_bind(self)
213 host, port = self.socket.getsockname()[:2]
214 self.server_name = socket.getfqdn(host)
215 self.server_port = port
216
217 def serve_forever(self):
218 self.stop = False
219 self.nonce_time = None
220 while not self.stop:
221 self.handle_request()
222 self.socket.close()
223
224
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000225class TestPageHandler(testserver_base.BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000226
227 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000228 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000229 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000230 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000231 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000232 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000233 self.NoCacheMaxAgeTimeHandler,
234 self.NoCacheTimeHandler,
235 self.CacheTimeHandler,
236 self.CacheExpiresHandler,
237 self.CacheProxyRevalidateHandler,
238 self.CachePrivateHandler,
239 self.CachePublicHandler,
240 self.CacheSMaxAgeHandler,
241 self.CacheMustRevalidateHandler,
242 self.CacheMustRevalidateMaxAgeHandler,
243 self.CacheNoStoreHandler,
244 self.CacheNoStoreMaxAgeHandler,
245 self.CacheNoTransformHandler,
246 self.DownloadHandler,
247 self.DownloadFinishHandler,
248 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000249 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000250 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000251 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000252 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000253 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000254 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000255 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000256 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000257 self.AuthBasicHandler,
258 self.AuthDigestHandler,
259 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000260 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000261 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000262 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000263 self.ServerRedirectHandler,
264 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000265 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000266 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000267 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000268 self.SSLManySmallRecords,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000269 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000270 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000271 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000272 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000273 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000274 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000275 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000276 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000277 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000278 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000279 head_handlers = [
280 self.FileHandler,
281 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000282
maruel@google.come250a9b2009-03-10 17:39:46 +0000283 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000284 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000285 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000286 'gif': 'image/gif',
287 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000288 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000289 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000290 'pdf' : 'application/pdf',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000291 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000292 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000293 }
initial.commit94958cf2008-07-26 22:42:52 +0000294 self._default_mime_type = 'text/html'
295
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000296 testserver_base.BasePageHandler.__init__(self, request, client_address,
297 socket_server, connect_handlers,
298 get_handlers, head_handlers,
299 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000300
initial.commit94958cf2008-07-26 22:42:52 +0000301 def GetMIMETypeFromName(self, file_name):
302 """Returns the mime type for the specified file_name. So far it only looks
303 at the file extension."""
304
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000305 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000306 if len(extension) == 0:
307 # no extension.
308 return self._default_mime_type
309
ericroman@google.comc17ca532009-05-07 03:51:05 +0000310 # extension starts with a dot, so we need to remove it
311 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000312
initial.commit94958cf2008-07-26 22:42:52 +0000313 def NoCacheMaxAgeTimeHandler(self):
314 """This request handler yields a page with the title set to the current
315 system time, and no caching requested."""
316
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000317 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000318 return False
319
320 self.send_response(200)
321 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000322 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000323 self.end_headers()
324
maruel@google.come250a9b2009-03-10 17:39:46 +0000325 self.wfile.write('<html><head><title>%s</title></head></html>' %
326 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000327
328 return True
329
330 def NoCacheTimeHandler(self):
331 """This request handler yields a page with the title set to the current
332 system time, and no caching requested."""
333
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000334 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000335 return False
336
337 self.send_response(200)
338 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000339 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000340 self.end_headers()
341
maruel@google.come250a9b2009-03-10 17:39:46 +0000342 self.wfile.write('<html><head><title>%s</title></head></html>' %
343 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000344
345 return True
346
347 def CacheTimeHandler(self):
348 """This request handler yields a page with the title set to the current
349 system time, and allows caching for one minute."""
350
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000351 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000352 return False
353
354 self.send_response(200)
355 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000356 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000357 self.end_headers()
358
maruel@google.come250a9b2009-03-10 17:39:46 +0000359 self.wfile.write('<html><head><title>%s</title></head></html>' %
360 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000361
362 return True
363
364 def CacheExpiresHandler(self):
365 """This request handler yields a page with the title set to the current
366 system time, and set the page to expire on 1 Jan 2099."""
367
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000368 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000369 return False
370
371 self.send_response(200)
372 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000373 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000374 self.end_headers()
375
maruel@google.come250a9b2009-03-10 17:39:46 +0000376 self.wfile.write('<html><head><title>%s</title></head></html>' %
377 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000378
379 return True
380
381 def CacheProxyRevalidateHandler(self):
382 """This request handler yields a page with the title set to the current
383 system time, and allows caching for 60 seconds"""
384
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000385 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000386 return False
387
388 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000389 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000390 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
391 self.end_headers()
392
maruel@google.come250a9b2009-03-10 17:39:46 +0000393 self.wfile.write('<html><head><title>%s</title></head></html>' %
394 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000395
396 return True
397
398 def CachePrivateHandler(self):
399 """This request handler yields a page with the title set to the current
400 system time, and allows caching for 5 seconds."""
401
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000402 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000403 return False
404
405 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000406 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000407 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000408 self.end_headers()
409
maruel@google.come250a9b2009-03-10 17:39:46 +0000410 self.wfile.write('<html><head><title>%s</title></head></html>' %
411 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000412
413 return True
414
415 def CachePublicHandler(self):
416 """This request handler yields a page with the title set to the current
417 system time, and allows caching for 5 seconds."""
418
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000419 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000420 return False
421
422 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000423 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000424 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000425 self.end_headers()
426
maruel@google.come250a9b2009-03-10 17:39:46 +0000427 self.wfile.write('<html><head><title>%s</title></head></html>' %
428 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000429
430 return True
431
432 def CacheSMaxAgeHandler(self):
433 """This request handler yields a page with the title set to the current
434 system time, and does not allow for caching."""
435
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000436 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000437 return False
438
439 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000440 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000441 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
442 self.end_headers()
443
maruel@google.come250a9b2009-03-10 17:39:46 +0000444 self.wfile.write('<html><head><title>%s</title></head></html>' %
445 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000446
447 return True
448
449 def CacheMustRevalidateHandler(self):
450 """This request handler yields a page with the title set to the current
451 system time, and does not allow caching."""
452
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000453 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000454 return False
455
456 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000457 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000458 self.send_header('Cache-Control', 'must-revalidate')
459 self.end_headers()
460
maruel@google.come250a9b2009-03-10 17:39:46 +0000461 self.wfile.write('<html><head><title>%s</title></head></html>' %
462 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000463
464 return True
465
466 def CacheMustRevalidateMaxAgeHandler(self):
467 """This request handler yields a page with the title set to the current
468 system time, and does not allow caching event though max-age of 60
469 seconds is specified."""
470
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000471 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000472 return False
473
474 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000475 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000476 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
477 self.end_headers()
478
maruel@google.come250a9b2009-03-10 17:39:46 +0000479 self.wfile.write('<html><head><title>%s</title></head></html>' %
480 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000481
482 return True
483
initial.commit94958cf2008-07-26 22:42:52 +0000484 def CacheNoStoreHandler(self):
485 """This request handler yields a page with the title set to the current
486 system time, and does not allow the page to be stored."""
487
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000488 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000489 return False
490
491 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000492 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000493 self.send_header('Cache-Control', 'no-store')
494 self.end_headers()
495
maruel@google.come250a9b2009-03-10 17:39:46 +0000496 self.wfile.write('<html><head><title>%s</title></head></html>' %
497 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000498
499 return True
500
501 def CacheNoStoreMaxAgeHandler(self):
502 """This request handler yields a page with the title set to the current
503 system time, and does not allow the page to be stored even though max-age
504 of 60 seconds is specified."""
505
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000506 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000507 return False
508
509 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000510 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000511 self.send_header('Cache-Control', 'max-age=60, no-store')
512 self.end_headers()
513
maruel@google.come250a9b2009-03-10 17:39:46 +0000514 self.wfile.write('<html><head><title>%s</title></head></html>' %
515 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000516
517 return True
518
519
520 def CacheNoTransformHandler(self):
521 """This request handler yields a page with the title set to the current
522 system time, and does not allow the content to transformed during
523 user-agent caching"""
524
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000525 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000526 return False
527
528 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000529 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000530 self.send_header('Cache-Control', 'no-transform')
531 self.end_headers()
532
maruel@google.come250a9b2009-03-10 17:39:46 +0000533 self.wfile.write('<html><head><title>%s</title></head></html>' %
534 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000535
536 return True
537
538 def EchoHeader(self):
539 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000540
ananta@chromium.org219b2062009-10-23 16:09:41 +0000541 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000542
ananta@chromium.org56812d02011-04-07 17:52:05 +0000543 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000544 """This function echoes back the value of a specific request header while
545 allowing caching for 16 hours."""
546
ananta@chromium.org56812d02011-04-07 17:52:05 +0000547 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000548
549 def EchoHeaderHelper(self, echo_header):
550 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000551
ananta@chromium.org219b2062009-10-23 16:09:41 +0000552 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000553 return False
554
555 query_char = self.path.find('?')
556 if query_char != -1:
557 header_name = self.path[query_char+1:]
558
559 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000560 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000561 if echo_header == '/echoheadercache':
562 self.send_header('Cache-control', 'max-age=60000')
563 else:
564 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000565 # insert a vary header to properly indicate that the cachability of this
566 # request is subject to value of the request header being echoed.
567 if len(header_name) > 0:
568 self.send_header('Vary', header_name)
569 self.end_headers()
570
571 if len(header_name) > 0:
572 self.wfile.write(self.headers.getheader(header_name))
573
574 return True
575
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000576 def ReadRequestBody(self):
577 """This function reads the body of the current HTTP request, handling
578 both plain and chunked transfer encoded requests."""
579
580 if self.headers.getheader('transfer-encoding') != 'chunked':
581 length = int(self.headers.getheader('content-length'))
582 return self.rfile.read(length)
583
584 # Read the request body as chunks.
585 body = ""
586 while True:
587 line = self.rfile.readline()
588 length = int(line, 16)
589 if length == 0:
590 self.rfile.readline()
591 break
592 body += self.rfile.read(length)
593 self.rfile.read(2)
594 return body
595
initial.commit94958cf2008-07-26 22:42:52 +0000596 def EchoHandler(self):
597 """This handler just echoes back the payload of the request, for testing
598 form submission."""
599
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000600 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000601 return False
602
603 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000604 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000605 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000606 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000607 return True
608
609 def EchoTitleHandler(self):
610 """This handler is like Echo, but sets the page title to the request."""
611
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000612 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000613 return False
614
615 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000616 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000617 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000618 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000619 self.wfile.write('<html><head><title>')
620 self.wfile.write(request)
621 self.wfile.write('</title></head></html>')
622 return True
623
624 def EchoAllHandler(self):
625 """This handler yields a (more) human-readable page listing information
626 about the request header & contents."""
627
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000628 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000629 return False
630
631 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000632 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000633 self.end_headers()
634 self.wfile.write('<html><head><style>'
635 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
636 '</style></head><body>'
637 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000638 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000639 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000640
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000641 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000642 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000643 params = cgi.parse_qs(qs, keep_blank_values=1)
644
645 for param in params:
646 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000647
648 self.wfile.write('</pre>')
649
650 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
651
652 self.wfile.write('</body></html>')
653 return True
654
655 def DownloadHandler(self):
656 """This handler sends a downloadable file with or without reporting
657 the size (6K)."""
658
659 if self.path.startswith("/download-unknown-size"):
660 send_length = False
661 elif self.path.startswith("/download-known-size"):
662 send_length = True
663 else:
664 return False
665
666 #
667 # The test which uses this functionality is attempting to send
668 # small chunks of data to the client. Use a fairly large buffer
669 # so that we'll fill chrome's IO buffer enough to force it to
670 # actually write the data.
671 # See also the comments in the client-side of this test in
672 # download_uitest.cc
673 #
674 size_chunk1 = 35*1024
675 size_chunk2 = 10*1024
676
677 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000678 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000679 self.send_header('Cache-Control', 'max-age=0')
680 if send_length:
681 self.send_header('Content-Length', size_chunk1 + size_chunk2)
682 self.end_headers()
683
684 # First chunk of data:
685 self.wfile.write("*" * size_chunk1)
686 self.wfile.flush()
687
688 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000689 self.server.wait_for_download = True
690 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000691 self.server.handle_request()
692
693 # Second chunk of data:
694 self.wfile.write("*" * size_chunk2)
695 return True
696
697 def DownloadFinishHandler(self):
698 """This handler just tells the server to finish the current download."""
699
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000700 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000701 return False
702
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000703 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000704 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000705 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000706 self.send_header('Cache-Control', 'max-age=0')
707 self.end_headers()
708 return True
709
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000710 def _ReplaceFileData(self, data, query_parameters):
711 """Replaces matching substrings in a file.
712
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000713 If the 'replace_text' URL query parameter is present, it is expected to be
714 of the form old_text:new_text, which indicates that any old_text strings in
715 the file are replaced with new_text. Multiple 'replace_text' parameters may
716 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000717
718 If the parameters are not present, |data| is returned.
719 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000720
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000721 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000722 replace_text_values = query_dict.get('replace_text', [])
723 for replace_text_value in replace_text_values:
724 replace_text_args = replace_text_value.split(':')
725 if len(replace_text_args) != 2:
726 raise ValueError(
727 'replace_text must be of form old_text:new_text. Actual value: %s' %
728 replace_text_value)
729 old_text_b64, new_text_b64 = replace_text_args
730 old_text = base64.urlsafe_b64decode(old_text_b64)
731 new_text = base64.urlsafe_b64decode(new_text_b64)
732 data = data.replace(old_text, new_text)
733 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000734
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000735 def ZipFileHandler(self):
736 """This handler sends the contents of the requested file in compressed form.
737 Can pass in a parameter that specifies that the content length be
738 C - the compressed size (OK),
739 U - the uncompressed size (Non-standard, but handled),
740 S - less than compressed (OK because we keep going),
741 M - larger than compressed but less than uncompressed (an error),
742 L - larger than uncompressed (an error)
743 Example: compressedfiles/Picture_1.doc?C
744 """
745
746 prefix = "/compressedfiles/"
747 if not self.path.startswith(prefix):
748 return False
749
750 # Consume a request body if present.
751 if self.command == 'POST' or self.command == 'PUT' :
752 self.ReadRequestBody()
753
754 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
755
756 if not query in ('C', 'U', 'S', 'M', 'L'):
757 return False
758
759 sub_path = url_path[len(prefix):]
760 entries = sub_path.split('/')
761 file_path = os.path.join(self.server.data_dir, *entries)
762 if os.path.isdir(file_path):
763 file_path = os.path.join(file_path, 'index.html')
764
765 if not os.path.isfile(file_path):
766 print "File not found " + sub_path + " full path:" + file_path
767 self.send_error(404)
768 return True
769
770 f = open(file_path, "rb")
771 data = f.read()
772 uncompressed_len = len(data)
773 f.close()
774
775 # Compress the data.
776 data = zlib.compress(data)
777 compressed_len = len(data)
778
779 content_length = compressed_len
780 if query == 'U':
781 content_length = uncompressed_len
782 elif query == 'S':
783 content_length = compressed_len / 2
784 elif query == 'M':
785 content_length = (compressed_len + uncompressed_len) / 2
786 elif query == 'L':
787 content_length = compressed_len + uncompressed_len
788
789 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000790 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000791 self.send_header('Content-encoding', 'deflate')
792 self.send_header('Connection', 'close')
793 self.send_header('Content-Length', content_length)
794 self.send_header('ETag', '\'' + file_path + '\'')
795 self.end_headers()
796
797 self.wfile.write(data)
798
799 return True
800
initial.commit94958cf2008-07-26 22:42:52 +0000801 def FileHandler(self):
802 """This handler sends the contents of the requested file. Wow, it's like
803 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000804
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000805 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000806 if not self.path.startswith(prefix):
807 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000808 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000809
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000810 def PostOnlyFileHandler(self):
811 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000812
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000813 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000814 if not self.path.startswith(prefix):
815 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000816 return self._FileHandlerHelper(prefix)
817
818 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000819 request_body = ''
820 if self.command == 'POST' or self.command == 'PUT':
821 # Consume a request body if present.
822 request_body = self.ReadRequestBody()
823
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000824 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000825 query_dict = cgi.parse_qs(query)
826
827 expected_body = query_dict.get('expected_body', [])
828 if expected_body and request_body not in expected_body:
829 self.send_response(404)
830 self.end_headers()
831 self.wfile.write('')
832 return True
833
834 expected_headers = query_dict.get('expected_headers', [])
835 for expected_header in expected_headers:
836 header_name, expected_value = expected_header.split(':')
837 if self.headers.getheader(header_name) != expected_value:
838 self.send_response(404)
839 self.end_headers()
840 self.wfile.write('')
841 return True
842
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000843 sub_path = url_path[len(prefix):]
844 entries = sub_path.split('/')
845 file_path = os.path.join(self.server.data_dir, *entries)
846 if os.path.isdir(file_path):
847 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000848
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000849 if not os.path.isfile(file_path):
850 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000851 self.send_error(404)
852 return True
853
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000854 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000855 data = f.read()
856 f.close()
857
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000858 data = self._ReplaceFileData(data, query)
859
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000860 old_protocol_version = self.protocol_version
861
initial.commit94958cf2008-07-26 22:42:52 +0000862 # If file.mock-http-headers exists, it contains the headers we
863 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000864 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000865 if os.path.isfile(headers_path):
866 f = open(headers_path, "r")
867
868 # "HTTP/1.1 200 OK"
869 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000870 http_major, http_minor, status_code = re.findall(
871 'HTTP/(\d+).(\d+) (\d+)', response)[0]
872 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000873 self.send_response(int(status_code))
874
875 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000876 header_values = re.findall('(\S+):\s*(.*)', line)
877 if len(header_values) > 0:
878 # "name: value"
879 name, value = header_values[0]
880 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000881 f.close()
882 else:
883 # Could be more generic once we support mime-type sniffing, but for
884 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000885
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000886 range_header = self.headers.get('Range')
887 if range_header and range_header.startswith('bytes='):
888 # Note this doesn't handle all valid byte range_header values (i.e.
889 # left open ended ones), just enough for what we needed so far.
890 range_header = range_header[6:].split('-')
891 start = int(range_header[0])
892 if range_header[1]:
893 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000894 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000895 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000896
897 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000898 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
899 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000900 self.send_header('Content-Range', content_range)
901 data = data[start: end + 1]
902 else:
903 self.send_response(200)
904
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000905 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000906 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000907 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000908 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000909 self.end_headers()
910
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000911 if (self.command != 'HEAD'):
912 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000913
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000914 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000915 return True
916
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000917 def SetCookieHandler(self):
918 """This handler just sets a cookie, for testing cookie handling."""
919
920 if not self._ShouldHandleRequest("/set-cookie"):
921 return False
922
923 query_char = self.path.find('?')
924 if query_char != -1:
925 cookie_values = self.path[query_char + 1:].split('&')
926 else:
927 cookie_values = ("",)
928 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000929 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000930 for cookie_value in cookie_values:
931 self.send_header('Set-Cookie', '%s' % cookie_value)
932 self.end_headers()
933 for cookie_value in cookie_values:
934 self.wfile.write('%s' % cookie_value)
935 return True
936
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000937 def SetManyCookiesHandler(self):
938 """This handler just sets a given number of cookies, for testing handling
939 of large numbers of cookies."""
940
941 if not self._ShouldHandleRequest("/set-many-cookies"):
942 return False
943
944 query_char = self.path.find('?')
945 if query_char != -1:
946 num_cookies = int(self.path[query_char + 1:])
947 else:
948 num_cookies = 0
949 self.send_response(200)
950 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000951 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000952 self.send_header('Set-Cookie', 'a=')
953 self.end_headers()
954 self.wfile.write('%d cookies were sent' % num_cookies)
955 return True
956
mattm@chromium.org983fc462012-06-30 00:52:08 +0000957 def ExpectAndSetCookieHandler(self):
958 """Expects some cookies to be sent, and if they are, sets more cookies.
959
960 The expect parameter specifies a required cookie. May be specified multiple
961 times.
962 The set parameter specifies a cookie to set if all required cookies are
963 preset. May be specified multiple times.
964 The data parameter specifies the response body data to be returned."""
965
966 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
967 return False
968
969 _, _, _, _, query, _ = urlparse.urlparse(self.path)
970 query_dict = cgi.parse_qs(query)
971 cookies = set()
972 if 'Cookie' in self.headers:
973 cookie_header = self.headers.getheader('Cookie')
974 cookies.update([s.strip() for s in cookie_header.split(';')])
975 got_all_expected_cookies = True
976 for expected_cookie in query_dict.get('expect', []):
977 if expected_cookie not in cookies:
978 got_all_expected_cookies = False
979 self.send_response(200)
980 self.send_header('Content-Type', 'text/html')
981 if got_all_expected_cookies:
982 for cookie_value in query_dict.get('set', []):
983 self.send_header('Set-Cookie', '%s' % cookie_value)
984 self.end_headers()
985 for data_value in query_dict.get('data', []):
986 self.wfile.write(data_value)
987 return True
988
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000989 def SetHeaderHandler(self):
990 """This handler sets a response header. Parameters are in the
991 key%3A%20value&key2%3A%20value2 format."""
992
993 if not self._ShouldHandleRequest("/set-header"):
994 return False
995
996 query_char = self.path.find('?')
997 if query_char != -1:
998 headers_values = self.path[query_char + 1:].split('&')
999 else:
1000 headers_values = ("",)
1001 self.send_response(200)
1002 self.send_header('Content-Type', 'text/html')
1003 for header_value in headers_values:
1004 header_value = urllib.unquote(header_value)
1005 (key, value) = header_value.split(': ', 1)
1006 self.send_header(key, value)
1007 self.end_headers()
1008 for header_value in headers_values:
1009 self.wfile.write('%s' % header_value)
1010 return True
1011
initial.commit94958cf2008-07-26 22:42:52 +00001012 def AuthBasicHandler(self):
1013 """This handler tests 'Basic' authentication. It just sends a page with
1014 title 'user/pass' if you succeed."""
1015
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001016 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001017 return False
1018
1019 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001020 expected_password = 'secret'
1021 realm = 'testrealm'
1022 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001023
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001024 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1025 query_params = cgi.parse_qs(query, True)
1026 if 'set-cookie-if-challenged' in query_params:
1027 set_cookie_if_challenged = True
1028 if 'password' in query_params:
1029 expected_password = query_params['password'][0]
1030 if 'realm' in query_params:
1031 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001032
initial.commit94958cf2008-07-26 22:42:52 +00001033 auth = self.headers.getheader('authorization')
1034 try:
1035 if not auth:
1036 raise Exception('no auth')
1037 b64str = re.findall(r'Basic (\S+)', auth)[0]
1038 userpass = base64.b64decode(b64str)
1039 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001040 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001041 raise Exception('wrong password')
1042 except Exception, e:
1043 # Authentication failed.
1044 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001045 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001046 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001047 if set_cookie_if_challenged:
1048 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001049 self.end_headers()
1050 self.wfile.write('<html><head>')
1051 self.wfile.write('<title>Denied: %s</title>' % e)
1052 self.wfile.write('</head><body>')
1053 self.wfile.write('auth=%s<p>' % auth)
1054 self.wfile.write('b64str=%s<p>' % b64str)
1055 self.wfile.write('username: %s<p>' % username)
1056 self.wfile.write('userpass: %s<p>' % userpass)
1057 self.wfile.write('password: %s<p>' % password)
1058 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1059 self.wfile.write('</body></html>')
1060 return True
1061
1062 # Authentication successful. (Return a cachable response to allow for
1063 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001064 old_protocol_version = self.protocol_version
1065 self.protocol_version = "HTTP/1.1"
1066
initial.commit94958cf2008-07-26 22:42:52 +00001067 if_none_match = self.headers.getheader('if-none-match')
1068 if if_none_match == "abc":
1069 self.send_response(304)
1070 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001071 elif url_path.endswith(".gif"):
1072 # Using chrome/test/data/google/logo.gif as the test image
1073 test_image_path = ['google', 'logo.gif']
1074 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1075 if not os.path.isfile(gif_path):
1076 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001077 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001078 return True
1079
1080 f = open(gif_path, "rb")
1081 data = f.read()
1082 f.close()
1083
1084 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001085 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001086 self.send_header('Cache-control', 'max-age=60000')
1087 self.send_header('Etag', 'abc')
1088 self.end_headers()
1089 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001090 else:
1091 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001092 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001093 self.send_header('Cache-control', 'max-age=60000')
1094 self.send_header('Etag', 'abc')
1095 self.end_headers()
1096 self.wfile.write('<html><head>')
1097 self.wfile.write('<title>%s/%s</title>' % (username, password))
1098 self.wfile.write('</head><body>')
1099 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001100 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001101 self.wfile.write('</body></html>')
1102
rvargas@google.com54453b72011-05-19 01:11:11 +00001103 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001104 return True
1105
tonyg@chromium.org75054202010-03-31 22:06:10 +00001106 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001107 """Returns a nonce that's stable per request path for the server's lifetime.
1108 This is a fake implementation. A real implementation would only use a given
1109 nonce a single time (hence the name n-once). However, for the purposes of
1110 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001111
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001112 Args:
1113 force_reset: Iff set, the nonce will be changed. Useful for testing the
1114 "stale" response.
1115 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001116
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001117 if force_reset or not self.server.nonce_time:
1118 self.server.nonce_time = time.time()
1119 return hashlib.md5('privatekey%s%d' %
1120 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001121
1122 def AuthDigestHandler(self):
1123 """This handler tests 'Digest' authentication.
1124
1125 It just sends a page with title 'user/pass' if you succeed.
1126
1127 A stale response is sent iff "stale" is present in the request path.
1128 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001129
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001130 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001131 return False
1132
tonyg@chromium.org75054202010-03-31 22:06:10 +00001133 stale = 'stale' in self.path
1134 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001135 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001136 password = 'secret'
1137 realm = 'testrealm'
1138
1139 auth = self.headers.getheader('authorization')
1140 pairs = {}
1141 try:
1142 if not auth:
1143 raise Exception('no auth')
1144 if not auth.startswith('Digest'):
1145 raise Exception('not digest')
1146 # Pull out all the name="value" pairs as a dictionary.
1147 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1148
1149 # Make sure it's all valid.
1150 if pairs['nonce'] != nonce:
1151 raise Exception('wrong nonce')
1152 if pairs['opaque'] != opaque:
1153 raise Exception('wrong opaque')
1154
1155 # Check the 'response' value and make sure it matches our magic hash.
1156 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001157 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001158 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001159 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001160 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001161 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001162 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1163 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001164 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001165
1166 if pairs['response'] != response:
1167 raise Exception('wrong password')
1168 except Exception, e:
1169 # Authentication failed.
1170 self.send_response(401)
1171 hdr = ('Digest '
1172 'realm="%s", '
1173 'domain="/", '
1174 'qop="auth", '
1175 'algorithm=MD5, '
1176 'nonce="%s", '
1177 'opaque="%s"') % (realm, nonce, opaque)
1178 if stale:
1179 hdr += ', stale="TRUE"'
1180 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001181 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001182 self.end_headers()
1183 self.wfile.write('<html><head>')
1184 self.wfile.write('<title>Denied: %s</title>' % e)
1185 self.wfile.write('</head><body>')
1186 self.wfile.write('auth=%s<p>' % auth)
1187 self.wfile.write('pairs=%s<p>' % pairs)
1188 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1189 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1190 self.wfile.write('</body></html>')
1191 return True
1192
1193 # Authentication successful.
1194 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001195 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001196 self.end_headers()
1197 self.wfile.write('<html><head>')
1198 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1199 self.wfile.write('</head><body>')
1200 self.wfile.write('auth=%s<p>' % auth)
1201 self.wfile.write('pairs=%s<p>' % pairs)
1202 self.wfile.write('</body></html>')
1203
1204 return True
1205
1206 def SlowServerHandler(self):
1207 """Wait for the user suggested time before responding. The syntax is
1208 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001209
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001210 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001211 return False
1212 query_char = self.path.find('?')
1213 wait_sec = 1.0
1214 if query_char >= 0:
1215 try:
1216 wait_sec = int(self.path[query_char + 1:])
1217 except ValueError:
1218 pass
1219 time.sleep(wait_sec)
1220 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001221 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001222 self.end_headers()
1223 self.wfile.write("waited %d seconds" % wait_sec)
1224 return True
1225
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001226 def ChunkedServerHandler(self):
1227 """Send chunked response. Allows to specify chunks parameters:
1228 - waitBeforeHeaders - ms to wait before sending headers
1229 - waitBetweenChunks - ms to wait between chunks
1230 - chunkSize - size of each chunk in bytes
1231 - chunksNumber - number of chunks
1232 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1233 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001234
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001235 if not self._ShouldHandleRequest("/chunked"):
1236 return False
1237 query_char = self.path.find('?')
1238 chunkedSettings = {'waitBeforeHeaders' : 0,
1239 'waitBetweenChunks' : 0,
1240 'chunkSize' : 5,
1241 'chunksNumber' : 5}
1242 if query_char >= 0:
1243 params = self.path[query_char + 1:].split('&')
1244 for param in params:
1245 keyValue = param.split('=')
1246 if len(keyValue) == 2:
1247 try:
1248 chunkedSettings[keyValue[0]] = int(keyValue[1])
1249 except ValueError:
1250 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001251 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001252 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1253 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001254 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001255 self.send_header('Connection', 'close')
1256 self.send_header('Transfer-Encoding', 'chunked')
1257 self.end_headers()
1258 # Chunked encoding: sending all chunks, then final zero-length chunk and
1259 # then final CRLF.
1260 for i in range(0, chunkedSettings['chunksNumber']):
1261 if i > 0:
1262 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1263 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001264 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001265 self.sendChunkHelp('')
1266 return True
1267
initial.commit94958cf2008-07-26 22:42:52 +00001268 def ContentTypeHandler(self):
1269 """Returns a string of html with the given content type. E.g.,
1270 /contenttype?text/css returns an html file with the Content-Type
1271 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001272
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001273 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001274 return False
1275 query_char = self.path.find('?')
1276 content_type = self.path[query_char + 1:].strip()
1277 if not content_type:
1278 content_type = 'text/html'
1279 self.send_response(200)
1280 self.send_header('Content-Type', content_type)
1281 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001282 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001283 return True
1284
creis@google.com2f4f6a42011-03-25 19:44:19 +00001285 def NoContentHandler(self):
1286 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001287
creis@google.com2f4f6a42011-03-25 19:44:19 +00001288 if not self._ShouldHandleRequest("/nocontent"):
1289 return False
1290 self.send_response(204)
1291 self.end_headers()
1292 return True
1293
initial.commit94958cf2008-07-26 22:42:52 +00001294 def ServerRedirectHandler(self):
1295 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001296 '/server-redirect?http://foo.bar/asdf' to redirect to
1297 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001298
1299 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001300 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001301 return False
1302
1303 query_char = self.path.find('?')
1304 if query_char < 0 or len(self.path) <= query_char + 1:
1305 self.sendRedirectHelp(test_name)
1306 return True
1307 dest = self.path[query_char + 1:]
1308
1309 self.send_response(301) # moved permanently
1310 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001311 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001312 self.end_headers()
1313 self.wfile.write('<html><head>')
1314 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1315
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001316 return True
initial.commit94958cf2008-07-26 22:42:52 +00001317
1318 def ClientRedirectHandler(self):
1319 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001320 '/client-redirect?http://foo.bar/asdf' to redirect to
1321 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001322
1323 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001324 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001325 return False
1326
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001327 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001328 if query_char < 0 or len(self.path) <= query_char + 1:
1329 self.sendRedirectHelp(test_name)
1330 return True
1331 dest = self.path[query_char + 1:]
1332
1333 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001334 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001335 self.end_headers()
1336 self.wfile.write('<html><head>')
1337 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1338 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1339
1340 return True
1341
tony@chromium.org03266982010-03-05 03:18:42 +00001342 def MultipartHandler(self):
1343 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001344
tony@chromium.org4cb88302011-09-27 22:13:49 +00001345 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001346 if not self._ShouldHandleRequest(test_name):
1347 return False
1348
1349 num_frames = 10
1350 bound = '12345'
1351 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001352 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001353 'multipart/x-mixed-replace;boundary=' + bound)
1354 self.end_headers()
1355
1356 for i in xrange(num_frames):
1357 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001358 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001359 self.wfile.write('<title>page ' + str(i) + '</title>')
1360 self.wfile.write('page ' + str(i))
1361
1362 self.wfile.write('--' + bound + '--')
1363 return True
1364
tony@chromium.org4cb88302011-09-27 22:13:49 +00001365 def MultipartSlowHandler(self):
1366 """Send a multipart response (3 text/html pages) with a slight delay
1367 between each page. This is similar to how some pages show status using
1368 multipart."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001369
tony@chromium.org4cb88302011-09-27 22:13:49 +00001370 test_name = '/multipart-slow'
1371 if not self._ShouldHandleRequest(test_name):
1372 return False
1373
1374 num_frames = 3
1375 bound = '12345'
1376 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001377 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001378 'multipart/x-mixed-replace;boundary=' + bound)
1379 self.end_headers()
1380
1381 for i in xrange(num_frames):
1382 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001383 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001384 time.sleep(0.25)
1385 if i == 2:
1386 self.wfile.write('<title>PASS</title>')
1387 else:
1388 self.wfile.write('<title>page ' + str(i) + '</title>')
1389 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1390
1391 self.wfile.write('--' + bound + '--')
1392 return True
1393
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001394 def GetSSLSessionCacheHandler(self):
1395 """Send a reply containing a log of the session cache operations."""
1396
1397 if not self._ShouldHandleRequest('/ssl-session-cache'):
1398 return False
1399
1400 self.send_response(200)
1401 self.send_header('Content-Type', 'text/plain')
1402 self.end_headers()
1403 try:
1404 for (action, sessionID) in self.server.session_cache.log:
1405 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001406 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001407 self.wfile.write('Pass --https-record-resume in order to use' +
1408 ' this request')
1409 return True
1410
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001411 def SSLManySmallRecords(self):
1412 """Sends a reply consisting of a variety of small writes. These will be
1413 translated into a series of small SSL records when used over an HTTPS
1414 server."""
1415
1416 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1417 return False
1418
1419 self.send_response(200)
1420 self.send_header('Content-Type', 'text/plain')
1421 self.end_headers()
1422
1423 # Write ~26K of data, in 1350 byte chunks
1424 for i in xrange(20):
1425 self.wfile.write('*' * 1350)
1426 self.wfile.flush()
1427 return True
1428
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001429 def CloseSocketHandler(self):
1430 """Closes the socket without sending anything."""
1431
1432 if not self._ShouldHandleRequest('/close-socket'):
1433 return False
1434
1435 self.wfile.close()
1436 return True
1437
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001438 def RangeResetHandler(self):
1439 """Send data broken up by connection resets every N (default 4K) bytes.
1440 Support range requests. If the data requested doesn't straddle a reset
1441 boundary, it will all be sent. Used for testing resuming downloads."""
1442
1443 if not self._ShouldHandleRequest('/rangereset'):
1444 return False
1445
1446 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1447
1448 # Defaults
1449 size = 8000
1450 # Note that the rst is sent just before sending the rst_boundary byte.
1451 rst_boundary = 4000
1452 respond_to_range = True
1453 hold_for_signal = False
1454
1455 # Parse the query
1456 qdict = urlparse.parse_qs(query, True)
1457 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001458 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001459 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001460 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001461 if 'bounce_range' in qdict:
1462 respond_to_range = False
1463 if 'hold' in qdict:
1464 hold_for_signal = True
1465
1466 first_byte = 0
1467 last_byte = size - 1
1468
1469 # Does that define what we want to return, or do we need to apply
1470 # a range?
1471 range_response = False
1472 range_header = self.headers.getheader('range')
1473 if range_header and respond_to_range:
1474 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1475 if mo.group(1):
1476 first_byte = int(mo.group(1))
1477 if mo.group(2):
1478 last_byte = int(mo.group(2))
1479 if last_byte > size - 1:
1480 last_byte = size - 1
1481 range_response = True
1482 if last_byte < first_byte:
1483 return False
1484
1485 # Set socket send buf high enough that we don't need to worry
1486 # about asynchronous closes when sending RSTs.
1487 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF,
1488 16284)
1489
1490 if range_response:
1491 self.send_response(206)
1492 self.send_header('Content-Range',
1493 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1494 else:
1495 self.send_response(200)
1496 self.send_header('Content-Type', 'application/octet-stream')
1497 self.send_header('Content-Length', last_byte - first_byte + 1)
1498 self.end_headers()
1499
1500 if hold_for_signal:
1501 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1502 # a single byte, the self.server.handle_request() below hangs
1503 # without processing new incoming requests.
1504 self.wfile.write('X')
1505 first_byte = first_byte + 1
1506 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001507 self.server.wait_for_download = True
1508 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001509 self.server.handle_request()
1510
1511 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
1512 if possible_rst >= last_byte:
1513 # No RST has been requested in this range, so we don't need to
1514 # do anything fancy; just write the data and let the python
1515 # infrastructure close the connection.
1516 self.wfile.write('X' * (last_byte - first_byte + 1))
1517 self.wfile.flush()
1518 return True
1519
1520 # We're resetting the connection part way in; go to the RST
1521 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001522 # Because socket semantics do not guarantee that all the data will be
1523 # sent when using the linger semantics to hard close a socket,
1524 # we send the data and then wait for our peer to release us
1525 # before sending the reset.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001526 self.wfile.write('X' * (possible_rst - first_byte))
1527 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001528 self.server.wait_for_download = True
1529 while self.server.wait_for_download:
1530 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001531 l_onoff = 1 # Linger is active.
1532 l_linger = 0 # Seconds to linger for.
1533 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1534 struct.pack('ii', l_onoff, l_linger))
1535
1536 # Close all duplicates of the underlying socket to force the RST.
1537 self.wfile.close()
1538 self.rfile.close()
1539 self.connection.close()
1540
1541 return True
1542
initial.commit94958cf2008-07-26 22:42:52 +00001543 def DefaultResponseHandler(self):
1544 """This is the catch-all response handler for requests that aren't handled
1545 by one of the special handlers above.
1546 Note that we specify the content-length as without it the https connection
1547 is not closed properly (and the browser keeps expecting data)."""
1548
1549 contents = "Default response given for path: " + self.path
1550 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001551 self.send_header('Content-Type', 'text/html')
1552 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001553 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001554 if (self.command != 'HEAD'):
1555 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001556 return True
1557
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001558 def RedirectConnectHandler(self):
1559 """Sends a redirect to the CONNECT request for www.redirect.com. This
1560 response is not specified by the RFC, so the browser should not follow
1561 the redirect."""
1562
1563 if (self.path.find("www.redirect.com") < 0):
1564 return False
1565
1566 dest = "http://www.destination.com/foo.js"
1567
1568 self.send_response(302) # moved temporarily
1569 self.send_header('Location', dest)
1570 self.send_header('Connection', 'close')
1571 self.end_headers()
1572 return True
1573
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001574 def ServerAuthConnectHandler(self):
1575 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1576 response doesn't make sense because the proxy server cannot request
1577 server authentication."""
1578
1579 if (self.path.find("www.server-auth.com") < 0):
1580 return False
1581
1582 challenge = 'Basic realm="WallyWorld"'
1583
1584 self.send_response(401) # unauthorized
1585 self.send_header('WWW-Authenticate', challenge)
1586 self.send_header('Connection', 'close')
1587 self.end_headers()
1588 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001589
1590 def DefaultConnectResponseHandler(self):
1591 """This is the catch-all response handler for CONNECT requests that aren't
1592 handled by one of the special handlers above. Real Web servers respond
1593 with 400 to CONNECT requests."""
1594
1595 contents = "Your client has issued a malformed or illegal request."
1596 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001597 self.send_header('Content-Type', 'text/html')
1598 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001599 self.end_headers()
1600 self.wfile.write(contents)
1601 return True
1602
initial.commit94958cf2008-07-26 22:42:52 +00001603 # called by the redirect handling function when there is no parameter
1604 def sendRedirectHelp(self, redirect_name):
1605 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001606 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001607 self.end_headers()
1608 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1609 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1610 self.wfile.write('</body></html>')
1611
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001612 # called by chunked handling function
1613 def sendChunkHelp(self, chunk):
1614 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1615 self.wfile.write('%X\r\n' % len(chunk))
1616 self.wfile.write(chunk)
1617 self.wfile.write('\r\n')
1618
akalin@chromium.org154bb132010-11-12 02:20:27 +00001619
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001620class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001621 def __init__(self, request, client_address, socket_server):
1622 handlers = [self.OCSPResponse]
1623 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001624 testserver_base.BasePageHandler.__init__(self, request, client_address,
1625 socket_server, [], handlers, [],
1626 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001627
1628 def OCSPResponse(self):
1629 self.send_response(200)
1630 self.send_header('Content-Type', 'application/ocsp-response')
1631 self.send_header('Content-Length', str(len(self.ocsp_response)))
1632 self.end_headers()
1633
1634 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001635
mattm@chromium.org830a3712012-11-07 23:00:07 +00001636
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001637class TCPEchoHandler(SocketServer.BaseRequestHandler):
1638 """The RequestHandler class for TCP echo server.
1639
1640 It is instantiated once per connection to the server, and overrides the
1641 handle() method to implement communication to the client.
1642 """
1643
1644 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001645 """Handles the request from the client and constructs a response."""
1646
1647 data = self.request.recv(65536).strip()
1648 # Verify the "echo request" message received from the client. Send back
1649 # "echo response" message if "echo request" message is valid.
1650 try:
1651 return_data = echo_message.GetEchoResponseData(data)
1652 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001653 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001654 except ValueError:
1655 return
1656
1657 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001658
1659
1660class UDPEchoHandler(SocketServer.BaseRequestHandler):
1661 """The RequestHandler class for UDP echo server.
1662
1663 It is instantiated once per connection to the server, and overrides the
1664 handle() method to implement communication to the client.
1665 """
1666
1667 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001668 """Handles the request from the client and constructs a response."""
1669
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001670 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001671 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001672 # Verify the "echo request" message received from the client. Send back
1673 # "echo response" message if "echo request" message is valid.
1674 try:
1675 return_data = echo_message.GetEchoResponseData(data)
1676 if not return_data:
1677 return
1678 except ValueError:
1679 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001680 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001681
1682
bashi@chromium.org33233532012-09-08 17:37:24 +00001683class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1684 """A request handler that behaves as a proxy server which requires
1685 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1686 """
1687
1688 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1689
1690 def parse_request(self):
1691 """Overrides parse_request to check credential."""
1692
1693 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1694 return False
1695
1696 auth = self.headers.getheader('Proxy-Authorization')
1697 if auth != self._AUTH_CREDENTIAL:
1698 self.send_response(407)
1699 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1700 self.end_headers()
1701 return False
1702
1703 return True
1704
1705 def _start_read_write(self, sock):
1706 sock.setblocking(0)
1707 self.request.setblocking(0)
1708 rlist = [self.request, sock]
1709 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001710 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001711 if errors:
1712 self.send_response(500)
1713 self.end_headers()
1714 return
1715 for s in ready_sockets:
1716 received = s.recv(1024)
1717 if len(received) == 0:
1718 return
1719 if s == self.request:
1720 other = sock
1721 else:
1722 other = self.request
1723 other.send(received)
1724
1725 def _do_common_method(self):
1726 url = urlparse.urlparse(self.path)
1727 port = url.port
1728 if not port:
1729 if url.scheme == 'http':
1730 port = 80
1731 elif url.scheme == 'https':
1732 port = 443
1733 if not url.hostname or not port:
1734 self.send_response(400)
1735 self.end_headers()
1736 return
1737
1738 if len(url.path) == 0:
1739 path = '/'
1740 else:
1741 path = url.path
1742 if len(url.query) > 0:
1743 path = '%s?%s' % (url.path, url.query)
1744
1745 sock = None
1746 try:
1747 sock = socket.create_connection((url.hostname, port))
1748 sock.send('%s %s %s\r\n' % (
1749 self.command, path, self.protocol_version))
1750 for header in self.headers.headers:
1751 header = header.strip()
1752 if (header.lower().startswith('connection') or
1753 header.lower().startswith('proxy')):
1754 continue
1755 sock.send('%s\r\n' % header)
1756 sock.send('\r\n')
1757 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001758 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001759 self.send_response(500)
1760 self.end_headers()
1761 finally:
1762 if sock is not None:
1763 sock.close()
1764
1765 def do_CONNECT(self):
1766 try:
1767 pos = self.path.rfind(':')
1768 host = self.path[:pos]
1769 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001770 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001771 self.send_response(400)
1772 self.end_headers()
1773
1774 try:
1775 sock = socket.create_connection((host, port))
1776 self.send_response(200, 'Connection established')
1777 self.end_headers()
1778 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001779 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001780 self.send_response(500)
1781 self.end_headers()
1782 finally:
1783 sock.close()
1784
1785 def do_GET(self):
1786 self._do_common_method()
1787
1788 def do_HEAD(self):
1789 self._do_common_method()
1790
1791
mattm@chromium.org830a3712012-11-07 23:00:07 +00001792class ServerRunner(testserver_base.TestServerRunner):
1793 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001794
mattm@chromium.org830a3712012-11-07 23:00:07 +00001795 def __init__(self):
1796 super(ServerRunner, self).__init__()
1797 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001798
mattm@chromium.org830a3712012-11-07 23:00:07 +00001799 def __make_data_dir(self):
1800 if self.options.data_dir:
1801 if not os.path.isdir(self.options.data_dir):
1802 raise testserver_base.OptionError('specified data dir not found: ' +
1803 self.options.data_dir + ' exiting...')
1804 my_data_dir = self.options.data_dir
1805 else:
1806 # Create the default path to our data dir, relative to the exe dir.
1807 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1808 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001809
mattm@chromium.org830a3712012-11-07 23:00:07 +00001810 #TODO(ibrar): Must use Find* funtion defined in google\tools
1811 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001812
mattm@chromium.org830a3712012-11-07 23:00:07 +00001813 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001814
mattm@chromium.org830a3712012-11-07 23:00:07 +00001815 def create_server(self, server_data):
1816 port = self.options.port
1817 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001818
mattm@chromium.org830a3712012-11-07 23:00:07 +00001819 if self.options.server_type == SERVER_HTTP:
1820 if self.options.https:
1821 pem_cert_and_key = None
1822 if self.options.cert_and_key_file:
1823 if not os.path.isfile(self.options.cert_and_key_file):
1824 raise testserver_base.OptionError(
1825 'specified server cert file not found: ' +
1826 self.options.cert_and_key_file + ' exiting...')
1827 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001828 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001829 # generate a new certificate and run an OCSP server for it.
1830 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
1831 print ('OCSP server started on %s:%d...' %
1832 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001833
mattm@chromium.org830a3712012-11-07 23:00:07 +00001834 ocsp_der = None
1835 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001836
mattm@chromium.org830a3712012-11-07 23:00:07 +00001837 if self.options.ocsp == 'ok':
1838 ocsp_state = minica.OCSP_STATE_GOOD
1839 elif self.options.ocsp == 'revoked':
1840 ocsp_state = minica.OCSP_STATE_REVOKED
1841 elif self.options.ocsp == 'invalid':
1842 ocsp_state = minica.OCSP_STATE_INVALID
1843 elif self.options.ocsp == 'unauthorized':
1844 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1845 elif self.options.ocsp == 'unknown':
1846 ocsp_state = minica.OCSP_STATE_UNKNOWN
1847 else:
1848 raise testserver_base.OptionError('unknown OCSP status: ' +
1849 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001850
mattm@chromium.org830a3712012-11-07 23:00:07 +00001851 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1852 subject = "127.0.0.1",
1853 ocsp_url = ("http://%s:%d/ocsp" %
1854 (host, self.__ocsp_server.server_port)),
1855 ocsp_state = ocsp_state)
1856
1857 self.__ocsp_server.ocsp_response = ocsp_der
1858
1859 for ca_cert in self.options.ssl_client_ca:
1860 if not os.path.isfile(ca_cert):
1861 raise testserver_base.OptionError(
1862 'specified trusted client CA file not found: ' + ca_cert +
1863 ' exiting...')
1864 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1865 self.options.ssl_client_auth,
1866 self.options.ssl_client_ca,
1867 self.options.ssl_bulk_cipher,
1868 self.options.record_resume,
1869 self.options.tls_intolerant)
1870 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
1871 else:
1872 server = HTTPServer((host, port), TestPageHandler)
1873 print 'HTTP server started on %s:%d...' % (host, server.server_port)
1874
1875 server.data_dir = self.__make_data_dir()
1876 server.file_root_url = self.options.file_root_url
1877 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001878 elif self.options.server_type == SERVER_WEBSOCKET:
1879 # Launch pywebsocket via WebSocketServer.
1880 logger = logging.getLogger()
1881 logger.addHandler(logging.StreamHandler())
1882 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1883 # is required to work correctly. It should be fixed from pywebsocket side.
1884 os.chdir(self.__make_data_dir())
1885 websocket_options = WebSocketOptions(host, port, '.')
1886 if self.options.cert_and_key_file:
1887 websocket_options.use_tls = True
1888 websocket_options.private_key = self.options.cert_and_key_file
1889 websocket_options.certificate = self.options.cert_and_key_file
1890 if self.options.ssl_client_auth:
1891 websocket_options.tls_client_auth = True
1892 if len(self.options.ssl_client_ca) != 1:
1893 raise testserver_base.OptionError(
1894 'one trusted client CA file should be specified')
1895 if not os.path.isfile(self.options.ssl_client_ca[0]):
1896 raise testserver_base.OptionError(
1897 'specified trusted client CA file not found: ' +
1898 self.options.ssl_client_ca[0] + ' exiting...')
1899 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
1900 server = WebSocketServer(websocket_options)
1901 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
1902 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001903 elif self.options.server_type == SERVER_TCP_ECHO:
1904 # Used for generating the key (randomly) that encodes the "echo request"
1905 # message.
1906 random.seed()
1907 server = TCPEchoServer((host, port), TCPEchoHandler)
1908 print 'Echo TCP server started on port %d...' % server.server_port
1909 server_data['port'] = server.server_port
1910 elif self.options.server_type == SERVER_UDP_ECHO:
1911 # Used for generating the key (randomly) that encodes the "echo request"
1912 # message.
1913 random.seed()
1914 server = UDPEchoServer((host, port), UDPEchoHandler)
1915 print 'Echo UDP server started on port %d...' % server.server_port
1916 server_data['port'] = server.server_port
1917 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
1918 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
1919 print 'BasicAuthProxy server started on port %d...' % server.server_port
1920 server_data['port'] = server.server_port
1921 elif self.options.server_type == SERVER_FTP:
1922 my_data_dir = self.__make_data_dir()
1923
1924 # Instantiate a dummy authorizer for managing 'virtual' users
1925 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1926
1927 # Define a new user having full r/w permissions and a read-only
1928 # anonymous user
1929 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1930
1931 authorizer.add_anonymous(my_data_dir)
1932
1933 # Instantiate FTP handler class
1934 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1935 ftp_handler.authorizer = authorizer
1936
1937 # Define a customized banner (string returned when client connects)
1938 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1939 pyftpdlib.ftpserver.__ver__)
1940
1941 # Instantiate FTP server class and listen to address:port
1942 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
1943 server_data['port'] = server.socket.getsockname()[1]
1944 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001945 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001946 raise testserver_base.OptionError('unknown server type' +
1947 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00001948
mattm@chromium.org830a3712012-11-07 23:00:07 +00001949 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001950
mattm@chromium.org830a3712012-11-07 23:00:07 +00001951 def run_server(self):
1952 if self.__ocsp_server:
1953 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001954
mattm@chromium.org830a3712012-11-07 23:00:07 +00001955 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001956
mattm@chromium.org830a3712012-11-07 23:00:07 +00001957 if self.__ocsp_server:
1958 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001959
mattm@chromium.org830a3712012-11-07 23:00:07 +00001960 def add_options(self):
1961 testserver_base.TestServerRunner.add_options(self)
1962 self.option_parser.add_option('-f', '--ftp', action='store_const',
1963 const=SERVER_FTP, default=SERVER_HTTP,
1964 dest='server_type',
1965 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001966 self.option_parser.add_option('--tcp-echo', action='store_const',
1967 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1968 dest='server_type',
1969 help='start up a tcp echo server.')
1970 self.option_parser.add_option('--udp-echo', action='store_const',
1971 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
1972 dest='server_type',
1973 help='start up a udp echo server.')
1974 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
1975 const=SERVER_BASIC_AUTH_PROXY,
1976 default=SERVER_HTTP, dest='server_type',
1977 help='start up a proxy server which requires '
1978 'basic authentication.')
1979 self.option_parser.add_option('--websocket', action='store_const',
1980 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
1981 dest='server_type',
1982 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001983 self.option_parser.add_option('--https', action='store_true',
1984 dest='https', help='Specify that https '
1985 'should be used.')
1986 self.option_parser.add_option('--cert-and-key-file',
1987 dest='cert_and_key_file', help='specify the '
1988 'path to the file containing the certificate '
1989 'and private key for the server in PEM '
1990 'format')
1991 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
1992 help='The type of OCSP response generated '
1993 'for the automatically generated '
1994 'certificate. One of [ok,revoked,invalid]')
1995 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
1996 default='0', type='int',
1997 help='If nonzero, certain TLS connections '
1998 'will be aborted in order to test version '
1999 'fallback. 1 means all TLS versions will be '
2000 'aborted. 2 means TLS 1.1 or higher will be '
2001 'aborted. 3 means TLS 1.2 or higher will be '
2002 'aborted.')
2003 self.option_parser.add_option('--https-record-resume',
2004 dest='record_resume', const=True,
2005 default=False, action='store_const',
2006 help='Record resumption cache events rather '
2007 'than resuming as normal. Allows the use of '
2008 'the /ssl-session-cache request')
2009 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2010 help='Require SSL client auth on every '
2011 'connection.')
2012 self.option_parser.add_option('--ssl-client-ca', action='append',
2013 default=[], help='Specify that the client '
2014 'certificate request should include the CA '
2015 'named in the subject of the DER-encoded '
2016 'certificate contained in the specified '
2017 'file. This option may appear multiple '
2018 'times, indicating multiple CA names should '
2019 'be sent in the request.')
2020 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2021 help='Specify the bulk encryption '
2022 'algorithm(s) that will be accepted by the '
2023 'SSL server. Valid values are "aes256", '
2024 '"aes128", "3des", "rc4". If omitted, all '
2025 'algorithms will be used. This option may '
2026 'appear multiple times, indicating '
2027 'multiple algorithms should be enabled.');
2028 self.option_parser.add_option('--file-root-url', default='/files/',
2029 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002030
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002031
initial.commit94958cf2008-07-26 22:42:52 +00002032if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002033 sys.exit(ServerRunner().main())