blob: d442671085fa2acebd94c70b435b871b52cd642a [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,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000268 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000269 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000270 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000271 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000272 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000273 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000274 self.DeviceManagementHandler,
275 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
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001411 def CloseSocketHandler(self):
1412 """Closes the socket without sending anything."""
1413
1414 if not self._ShouldHandleRequest('/close-socket'):
1415 return False
1416
1417 self.wfile.close()
1418 return True
1419
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001420 def RangeResetHandler(self):
1421 """Send data broken up by connection resets every N (default 4K) bytes.
1422 Support range requests. If the data requested doesn't straddle a reset
1423 boundary, it will all be sent. Used for testing resuming downloads."""
1424
1425 if not self._ShouldHandleRequest('/rangereset'):
1426 return False
1427
1428 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1429
1430 # Defaults
1431 size = 8000
1432 # Note that the rst is sent just before sending the rst_boundary byte.
1433 rst_boundary = 4000
1434 respond_to_range = True
1435 hold_for_signal = False
1436
1437 # Parse the query
1438 qdict = urlparse.parse_qs(query, True)
1439 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001440 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001441 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001442 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001443 if 'bounce_range' in qdict:
1444 respond_to_range = False
1445 if 'hold' in qdict:
1446 hold_for_signal = True
1447
1448 first_byte = 0
1449 last_byte = size - 1
1450
1451 # Does that define what we want to return, or do we need to apply
1452 # a range?
1453 range_response = False
1454 range_header = self.headers.getheader('range')
1455 if range_header and respond_to_range:
1456 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1457 if mo.group(1):
1458 first_byte = int(mo.group(1))
1459 if mo.group(2):
1460 last_byte = int(mo.group(2))
1461 if last_byte > size - 1:
1462 last_byte = size - 1
1463 range_response = True
1464 if last_byte < first_byte:
1465 return False
1466
1467 # Set socket send buf high enough that we don't need to worry
1468 # about asynchronous closes when sending RSTs.
1469 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF,
1470 16284)
1471
1472 if range_response:
1473 self.send_response(206)
1474 self.send_header('Content-Range',
1475 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1476 else:
1477 self.send_response(200)
1478 self.send_header('Content-Type', 'application/octet-stream')
1479 self.send_header('Content-Length', last_byte - first_byte + 1)
1480 self.end_headers()
1481
1482 if hold_for_signal:
1483 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1484 # a single byte, the self.server.handle_request() below hangs
1485 # without processing new incoming requests.
1486 self.wfile.write('X')
1487 first_byte = first_byte + 1
1488 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001489 self.server.wait_for_download = True
1490 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001491 self.server.handle_request()
1492
1493 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
1494 if possible_rst >= last_byte:
1495 # No RST has been requested in this range, so we don't need to
1496 # do anything fancy; just write the data and let the python
1497 # infrastructure close the connection.
1498 self.wfile.write('X' * (last_byte - first_byte + 1))
1499 self.wfile.flush()
1500 return True
1501
1502 # We're resetting the connection part way in; go to the RST
1503 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001504 # Because socket semantics do not guarantee that all the data will be
1505 # sent when using the linger semantics to hard close a socket,
1506 # we send the data and then wait for our peer to release us
1507 # before sending the reset.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001508 self.wfile.write('X' * (possible_rst - first_byte))
1509 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001510 self.server.wait_for_download = True
1511 while self.server.wait_for_download:
1512 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001513 l_onoff = 1 # Linger is active.
1514 l_linger = 0 # Seconds to linger for.
1515 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1516 struct.pack('ii', l_onoff, l_linger))
1517
1518 # Close all duplicates of the underlying socket to force the RST.
1519 self.wfile.close()
1520 self.rfile.close()
1521 self.connection.close()
1522
1523 return True
1524
initial.commit94958cf2008-07-26 22:42:52 +00001525 def DefaultResponseHandler(self):
1526 """This is the catch-all response handler for requests that aren't handled
1527 by one of the special handlers above.
1528 Note that we specify the content-length as without it the https connection
1529 is not closed properly (and the browser keeps expecting data)."""
1530
1531 contents = "Default response given for path: " + self.path
1532 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001533 self.send_header('Content-Type', 'text/html')
1534 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001535 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001536 if (self.command != 'HEAD'):
1537 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001538 return True
1539
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001540 def RedirectConnectHandler(self):
1541 """Sends a redirect to the CONNECT request for www.redirect.com. This
1542 response is not specified by the RFC, so the browser should not follow
1543 the redirect."""
1544
1545 if (self.path.find("www.redirect.com") < 0):
1546 return False
1547
1548 dest = "http://www.destination.com/foo.js"
1549
1550 self.send_response(302) # moved temporarily
1551 self.send_header('Location', dest)
1552 self.send_header('Connection', 'close')
1553 self.end_headers()
1554 return True
1555
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001556 def ServerAuthConnectHandler(self):
1557 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1558 response doesn't make sense because the proxy server cannot request
1559 server authentication."""
1560
1561 if (self.path.find("www.server-auth.com") < 0):
1562 return False
1563
1564 challenge = 'Basic realm="WallyWorld"'
1565
1566 self.send_response(401) # unauthorized
1567 self.send_header('WWW-Authenticate', challenge)
1568 self.send_header('Connection', 'close')
1569 self.end_headers()
1570 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001571
1572 def DefaultConnectResponseHandler(self):
1573 """This is the catch-all response handler for CONNECT requests that aren't
1574 handled by one of the special handlers above. Real Web servers respond
1575 with 400 to CONNECT requests."""
1576
1577 contents = "Your client has issued a malformed or illegal request."
1578 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001579 self.send_header('Content-Type', 'text/html')
1580 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001581 self.end_headers()
1582 self.wfile.write(contents)
1583 return True
1584
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001585 def DeviceManagementHandler(self):
1586 """Delegates to the device management service used for cloud policy."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001587
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001588 if not self._ShouldHandleRequest("/device_management"):
1589 return False
1590
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001591 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001592
1593 if not self.server._device_management_handler:
1594 import device_management
1595 policy_path = os.path.join(self.server.data_dir, 'device_management')
1596 self.server._device_management_handler = (
joaodasilva@chromium.org3c069da2012-11-20 16:17:15 +00001597 device_management.TestServer(policy_path, self.server.policy_keys))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001598
1599 http_response, raw_reply = (
1600 self.server._device_management_handler.HandleRequest(self.path,
1601 self.headers,
1602 raw_request))
1603 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001604 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001605 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001606 self.end_headers()
1607 self.wfile.write(raw_reply)
1608 return True
1609
initial.commit94958cf2008-07-26 22:42:52 +00001610 # called by the redirect handling function when there is no parameter
1611 def sendRedirectHelp(self, redirect_name):
1612 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001613 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001614 self.end_headers()
1615 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1616 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1617 self.wfile.write('</body></html>')
1618
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001619 # called by chunked handling function
1620 def sendChunkHelp(self, chunk):
1621 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1622 self.wfile.write('%X\r\n' % len(chunk))
1623 self.wfile.write(chunk)
1624 self.wfile.write('\r\n')
1625
akalin@chromium.org154bb132010-11-12 02:20:27 +00001626
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001627class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001628 def __init__(self, request, client_address, socket_server):
1629 handlers = [self.OCSPResponse]
1630 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001631 testserver_base.BasePageHandler.__init__(self, request, client_address,
1632 socket_server, [], handlers, [],
1633 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001634
1635 def OCSPResponse(self):
1636 self.send_response(200)
1637 self.send_header('Content-Type', 'application/ocsp-response')
1638 self.send_header('Content-Length', str(len(self.ocsp_response)))
1639 self.end_headers()
1640
1641 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001642
mattm@chromium.org830a3712012-11-07 23:00:07 +00001643
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001644class TCPEchoHandler(SocketServer.BaseRequestHandler):
1645 """The RequestHandler class for TCP echo server.
1646
1647 It is instantiated once per connection to the server, and overrides the
1648 handle() method to implement communication to the client.
1649 """
1650
1651 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001652 """Handles the request from the client and constructs a response."""
1653
1654 data = self.request.recv(65536).strip()
1655 # Verify the "echo request" message received from the client. Send back
1656 # "echo response" message if "echo request" message is valid.
1657 try:
1658 return_data = echo_message.GetEchoResponseData(data)
1659 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001660 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001661 except ValueError:
1662 return
1663
1664 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001665
1666
1667class UDPEchoHandler(SocketServer.BaseRequestHandler):
1668 """The RequestHandler class for UDP echo server.
1669
1670 It is instantiated once per connection to the server, and overrides the
1671 handle() method to implement communication to the client.
1672 """
1673
1674 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001675 """Handles the request from the client and constructs a response."""
1676
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001677 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001678 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001679 # Verify the "echo request" message received from the client. Send back
1680 # "echo response" message if "echo request" message is valid.
1681 try:
1682 return_data = echo_message.GetEchoResponseData(data)
1683 if not return_data:
1684 return
1685 except ValueError:
1686 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001687 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001688
1689
bashi@chromium.org33233532012-09-08 17:37:24 +00001690class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1691 """A request handler that behaves as a proxy server which requires
1692 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1693 """
1694
1695 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1696
1697 def parse_request(self):
1698 """Overrides parse_request to check credential."""
1699
1700 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1701 return False
1702
1703 auth = self.headers.getheader('Proxy-Authorization')
1704 if auth != self._AUTH_CREDENTIAL:
1705 self.send_response(407)
1706 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1707 self.end_headers()
1708 return False
1709
1710 return True
1711
1712 def _start_read_write(self, sock):
1713 sock.setblocking(0)
1714 self.request.setblocking(0)
1715 rlist = [self.request, sock]
1716 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001717 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001718 if errors:
1719 self.send_response(500)
1720 self.end_headers()
1721 return
1722 for s in ready_sockets:
1723 received = s.recv(1024)
1724 if len(received) == 0:
1725 return
1726 if s == self.request:
1727 other = sock
1728 else:
1729 other = self.request
1730 other.send(received)
1731
1732 def _do_common_method(self):
1733 url = urlparse.urlparse(self.path)
1734 port = url.port
1735 if not port:
1736 if url.scheme == 'http':
1737 port = 80
1738 elif url.scheme == 'https':
1739 port = 443
1740 if not url.hostname or not port:
1741 self.send_response(400)
1742 self.end_headers()
1743 return
1744
1745 if len(url.path) == 0:
1746 path = '/'
1747 else:
1748 path = url.path
1749 if len(url.query) > 0:
1750 path = '%s?%s' % (url.path, url.query)
1751
1752 sock = None
1753 try:
1754 sock = socket.create_connection((url.hostname, port))
1755 sock.send('%s %s %s\r\n' % (
1756 self.command, path, self.protocol_version))
1757 for header in self.headers.headers:
1758 header = header.strip()
1759 if (header.lower().startswith('connection') or
1760 header.lower().startswith('proxy')):
1761 continue
1762 sock.send('%s\r\n' % header)
1763 sock.send('\r\n')
1764 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001765 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001766 self.send_response(500)
1767 self.end_headers()
1768 finally:
1769 if sock is not None:
1770 sock.close()
1771
1772 def do_CONNECT(self):
1773 try:
1774 pos = self.path.rfind(':')
1775 host = self.path[:pos]
1776 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001777 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001778 self.send_response(400)
1779 self.end_headers()
1780
1781 try:
1782 sock = socket.create_connection((host, port))
1783 self.send_response(200, 'Connection established')
1784 self.end_headers()
1785 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001786 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001787 self.send_response(500)
1788 self.end_headers()
1789 finally:
1790 sock.close()
1791
1792 def do_GET(self):
1793 self._do_common_method()
1794
1795 def do_HEAD(self):
1796 self._do_common_method()
1797
1798
mattm@chromium.org830a3712012-11-07 23:00:07 +00001799class ServerRunner(testserver_base.TestServerRunner):
1800 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001801
mattm@chromium.org830a3712012-11-07 23:00:07 +00001802 def __init__(self):
1803 super(ServerRunner, self).__init__()
1804 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001805
mattm@chromium.org830a3712012-11-07 23:00:07 +00001806 def __make_data_dir(self):
1807 if self.options.data_dir:
1808 if not os.path.isdir(self.options.data_dir):
1809 raise testserver_base.OptionError('specified data dir not found: ' +
1810 self.options.data_dir + ' exiting...')
1811 my_data_dir = self.options.data_dir
1812 else:
1813 # Create the default path to our data dir, relative to the exe dir.
1814 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1815 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001816
mattm@chromium.org830a3712012-11-07 23:00:07 +00001817 #TODO(ibrar): Must use Find* funtion defined in google\tools
1818 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001819
mattm@chromium.org830a3712012-11-07 23:00:07 +00001820 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001821
mattm@chromium.org830a3712012-11-07 23:00:07 +00001822 def create_server(self, server_data):
1823 port = self.options.port
1824 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001825
mattm@chromium.org830a3712012-11-07 23:00:07 +00001826 if self.options.server_type == SERVER_HTTP:
1827 if self.options.https:
1828 pem_cert_and_key = None
1829 if self.options.cert_and_key_file:
1830 if not os.path.isfile(self.options.cert_and_key_file):
1831 raise testserver_base.OptionError(
1832 'specified server cert file not found: ' +
1833 self.options.cert_and_key_file + ' exiting...')
1834 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001835 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001836 # generate a new certificate and run an OCSP server for it.
1837 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
1838 print ('OCSP server started on %s:%d...' %
1839 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001840
mattm@chromium.org830a3712012-11-07 23:00:07 +00001841 ocsp_der = None
1842 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001843
mattm@chromium.org830a3712012-11-07 23:00:07 +00001844 if self.options.ocsp == 'ok':
1845 ocsp_state = minica.OCSP_STATE_GOOD
1846 elif self.options.ocsp == 'revoked':
1847 ocsp_state = minica.OCSP_STATE_REVOKED
1848 elif self.options.ocsp == 'invalid':
1849 ocsp_state = minica.OCSP_STATE_INVALID
1850 elif self.options.ocsp == 'unauthorized':
1851 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1852 elif self.options.ocsp == 'unknown':
1853 ocsp_state = minica.OCSP_STATE_UNKNOWN
1854 else:
1855 raise testserver_base.OptionError('unknown OCSP status: ' +
1856 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001857
mattm@chromium.org830a3712012-11-07 23:00:07 +00001858 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1859 subject = "127.0.0.1",
1860 ocsp_url = ("http://%s:%d/ocsp" %
1861 (host, self.__ocsp_server.server_port)),
1862 ocsp_state = ocsp_state)
1863
1864 self.__ocsp_server.ocsp_response = ocsp_der
1865
1866 for ca_cert in self.options.ssl_client_ca:
1867 if not os.path.isfile(ca_cert):
1868 raise testserver_base.OptionError(
1869 'specified trusted client CA file not found: ' + ca_cert +
1870 ' exiting...')
1871 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1872 self.options.ssl_client_auth,
1873 self.options.ssl_client_ca,
1874 self.options.ssl_bulk_cipher,
1875 self.options.record_resume,
1876 self.options.tls_intolerant)
1877 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
1878 else:
1879 server = HTTPServer((host, port), TestPageHandler)
1880 print 'HTTP server started on %s:%d...' % (host, server.server_port)
1881
1882 server.data_dir = self.__make_data_dir()
1883 server.file_root_url = self.options.file_root_url
1884 server_data['port'] = server.server_port
1885 server._device_management_handler = None
1886 server.policy_keys = self.options.policy_keys
mattm@chromium.org830a3712012-11-07 23:00:07 +00001887 elif self.options.server_type == SERVER_WEBSOCKET:
1888 # Launch pywebsocket via WebSocketServer.
1889 logger = logging.getLogger()
1890 logger.addHandler(logging.StreamHandler())
1891 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1892 # is required to work correctly. It should be fixed from pywebsocket side.
1893 os.chdir(self.__make_data_dir())
1894 websocket_options = WebSocketOptions(host, port, '.')
1895 if self.options.cert_and_key_file:
1896 websocket_options.use_tls = True
1897 websocket_options.private_key = self.options.cert_and_key_file
1898 websocket_options.certificate = self.options.cert_and_key_file
1899 if self.options.ssl_client_auth:
1900 websocket_options.tls_client_auth = True
1901 if len(self.options.ssl_client_ca) != 1:
1902 raise testserver_base.OptionError(
1903 'one trusted client CA file should be specified')
1904 if not os.path.isfile(self.options.ssl_client_ca[0]):
1905 raise testserver_base.OptionError(
1906 'specified trusted client CA file not found: ' +
1907 self.options.ssl_client_ca[0] + ' exiting...')
1908 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
1909 server = WebSocketServer(websocket_options)
1910 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
1911 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001912 elif self.options.server_type == SERVER_TCP_ECHO:
1913 # Used for generating the key (randomly) that encodes the "echo request"
1914 # message.
1915 random.seed()
1916 server = TCPEchoServer((host, port), TCPEchoHandler)
1917 print 'Echo TCP server started on port %d...' % server.server_port
1918 server_data['port'] = server.server_port
1919 elif self.options.server_type == SERVER_UDP_ECHO:
1920 # Used for generating the key (randomly) that encodes the "echo request"
1921 # message.
1922 random.seed()
1923 server = UDPEchoServer((host, port), UDPEchoHandler)
1924 print 'Echo UDP server started on port %d...' % server.server_port
1925 server_data['port'] = server.server_port
1926 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
1927 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
1928 print 'BasicAuthProxy server started on port %d...' % server.server_port
1929 server_data['port'] = server.server_port
1930 elif self.options.server_type == SERVER_FTP:
1931 my_data_dir = self.__make_data_dir()
1932
1933 # Instantiate a dummy authorizer for managing 'virtual' users
1934 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1935
1936 # Define a new user having full r/w permissions and a read-only
1937 # anonymous user
1938 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1939
1940 authorizer.add_anonymous(my_data_dir)
1941
1942 # Instantiate FTP handler class
1943 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1944 ftp_handler.authorizer = authorizer
1945
1946 # Define a customized banner (string returned when client connects)
1947 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1948 pyftpdlib.ftpserver.__ver__)
1949
1950 # Instantiate FTP server class and listen to address:port
1951 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
1952 server_data['port'] = server.socket.getsockname()[1]
1953 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001954 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001955 raise testserver_base.OptionError('unknown server type' +
1956 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00001957
mattm@chromium.org830a3712012-11-07 23:00:07 +00001958 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001959
mattm@chromium.org830a3712012-11-07 23:00:07 +00001960 def run_server(self):
1961 if self.__ocsp_server:
1962 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001963
mattm@chromium.org830a3712012-11-07 23:00:07 +00001964 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001965
mattm@chromium.org830a3712012-11-07 23:00:07 +00001966 if self.__ocsp_server:
1967 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001968
mattm@chromium.org830a3712012-11-07 23:00:07 +00001969 def add_options(self):
1970 testserver_base.TestServerRunner.add_options(self)
1971 self.option_parser.add_option('-f', '--ftp', action='store_const',
1972 const=SERVER_FTP, default=SERVER_HTTP,
1973 dest='server_type',
1974 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001975 self.option_parser.add_option('--tcp-echo', action='store_const',
1976 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1977 dest='server_type',
1978 help='start up a tcp echo server.')
1979 self.option_parser.add_option('--udp-echo', action='store_const',
1980 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
1981 dest='server_type',
1982 help='start up a udp echo server.')
1983 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
1984 const=SERVER_BASIC_AUTH_PROXY,
1985 default=SERVER_HTTP, dest='server_type',
1986 help='start up a proxy server which requires '
1987 'basic authentication.')
1988 self.option_parser.add_option('--websocket', action='store_const',
1989 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
1990 dest='server_type',
1991 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001992 self.option_parser.add_option('--https', action='store_true',
1993 dest='https', help='Specify that https '
1994 'should be used.')
1995 self.option_parser.add_option('--cert-and-key-file',
1996 dest='cert_and_key_file', help='specify the '
1997 'path to the file containing the certificate '
1998 'and private key for the server in PEM '
1999 'format')
2000 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2001 help='The type of OCSP response generated '
2002 'for the automatically generated '
2003 'certificate. One of [ok,revoked,invalid]')
2004 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2005 default='0', type='int',
2006 help='If nonzero, certain TLS connections '
2007 'will be aborted in order to test version '
2008 'fallback. 1 means all TLS versions will be '
2009 'aborted. 2 means TLS 1.1 or higher will be '
2010 'aborted. 3 means TLS 1.2 or higher will be '
2011 'aborted.')
2012 self.option_parser.add_option('--https-record-resume',
2013 dest='record_resume', const=True,
2014 default=False, action='store_const',
2015 help='Record resumption cache events rather '
2016 'than resuming as normal. Allows the use of '
2017 'the /ssl-session-cache request')
2018 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2019 help='Require SSL client auth on every '
2020 'connection.')
2021 self.option_parser.add_option('--ssl-client-ca', action='append',
2022 default=[], help='Specify that the client '
2023 'certificate request should include the CA '
2024 'named in the subject of the DER-encoded '
2025 'certificate contained in the specified '
2026 'file. This option may appear multiple '
2027 'times, indicating multiple CA names should '
2028 'be sent in the request.')
2029 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2030 help='Specify the bulk encryption '
2031 'algorithm(s) that will be accepted by the '
2032 'SSL server. Valid values are "aes256", '
2033 '"aes128", "3des", "rc4". If omitted, all '
2034 'algorithms will be used. This option may '
2035 'appear multiple times, indicating '
2036 'multiple algorithms should be enabled.');
2037 self.option_parser.add_option('--file-root-url', default='/files/',
2038 help='Specify a root URL for files served.')
2039 self.option_parser.add_option('--policy-key', action='append',
2040 dest='policy_keys',
2041 help='Specify a path to a PEM-encoded '
2042 'private key to use for policy signing. May '
2043 'be specified multiple times in order to '
2044 'load multipe keys into the server. If the '
2045 'server has multiple keys, it will rotate '
2046 'through them in at each request a '
2047 'round-robin fashion. The server will '
2048 'generate a random key if none is specified '
2049 'on the command line.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002050
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002051
initial.commit94958cf2008-07-26 22:42:52 +00002052if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002053 sys.exit(ServerRunner().main())