blob: 0fe9bd785676375ed6b582ed1decc5876b614e35 [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00002# Copyright 2013 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00006"""This is a simple HTTP/FTP/TCP/UDP/BASIC_AUTH_PROXY/WEBSOCKET server used for
7testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00008
9It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000010By default, it listens on an ephemeral port and sends the port number back to
11the originating process over a pipe. The originating process can specify an
12explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000013It can use https if you specify the flag --https=CERT where CERT is the path
14to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000015"""
16
17import base64
18import BaseHTTPServer
19import cgi
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000020import hashlib
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000021import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000022import minica
initial.commit94958cf2008-07-26 22:42:52 +000023import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000024import random
initial.commit94958cf2008-07-26 22:42:52 +000025import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000026import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000027import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000028import SocketServer
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000029import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000030import sys
31import threading
initial.commit94958cf2008-07-26 22:42:52 +000032import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000033import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000034import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000035import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000036
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000037import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000038import pyftpdlib.ftpserver
mattm@chromium.org830a3712012-11-07 23:00:07 +000039import testserver_base
initial.commit94958cf2008-07-26 22:42:52 +000040import tlslite
41import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000042
mattm@chromium.org830a3712012-11-07 23:00:07 +000043BASE_DIR = os.path.dirname(os.path.abspath(__file__))
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000044sys.path.insert(
45 0, os.path.join(BASE_DIR, '..', '..', '..', 'third_party/pywebsocket/src'))
46from mod_pywebsocket.standalone import WebSocketServer
davidben@chromium.org06fcf202010-09-22 18:15:23 +000047
maruel@chromium.org756cf982009-03-05 12:46:38 +000048SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000049SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000050SERVER_TCP_ECHO = 2
51SERVER_UDP_ECHO = 3
52SERVER_BASIC_AUTH_PROXY = 4
53SERVER_WEBSOCKET = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000054
55# Default request queue size for WebSocketServer.
56_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000057
mattm@chromium.org830a3712012-11-07 23:00:07 +000058
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000059class WebSocketOptions:
60 """Holds options for WebSocketServer."""
61
62 def __init__(self, host, port, data_dir):
63 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
64 self.server_host = host
65 self.port = port
66 self.websock_handlers = data_dir
67 self.scan_dir = None
68 self.allow_handlers_outside_root_dir = False
69 self.websock_handlers_map_file = None
70 self.cgi_directories = []
71 self.is_executable_method = None
72 self.allow_draft75 = False
73 self.strict = True
74
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000075 self.use_tls = False
76 self.private_key = None
77 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000078 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000079 self.tls_client_ca = None
80 self.use_basic_auth = False
81
mattm@chromium.org830a3712012-11-07 23:00:07 +000082
agl@chromium.orgf9e66792011-12-12 22:22:19 +000083class RecordingSSLSessionCache(object):
84 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
85 lookups and inserts in order to test session cache behaviours."""
86
87 def __init__(self):
88 self.log = []
89
90 def __getitem__(self, sessionID):
91 self.log.append(('lookup', sessionID))
92 raise KeyError()
93
94 def __setitem__(self, sessionID, session):
95 self.log.append(('insert', sessionID))
96
erikwright@chromium.org847ef282012-02-22 16:41:10 +000097
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000098class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
99 testserver_base.BrokenPipeHandlerMixIn,
100 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000101 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000102 verification."""
103
104 pass
105
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000106class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
107 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000108 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000109 """This is a specialization of HTTPServer that serves an
110 OCSP response"""
111
112 def serve_forever_on_thread(self):
113 self.thread = threading.Thread(target = self.serve_forever,
114 name = "OCSPServerThread")
115 self.thread.start()
116
117 def stop_serving(self):
118 self.shutdown()
119 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000120
mattm@chromium.org830a3712012-11-07 23:00:07 +0000121
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000122class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000123 testserver_base.ClientRestrictingServerMixIn,
124 testserver_base.BrokenPipeHandlerMixIn,
125 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000126 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000127 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000128
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000129 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000130 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000131 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000132 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
133 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000134 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000135 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000136 self.tls_intolerant = tls_intolerant
137
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000138 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000139 s = open(ca_file).read()
140 x509 = tlslite.api.X509()
141 x509.parse(s)
142 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000143 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
144 if ssl_bulk_ciphers is not None:
145 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000146
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000147 if record_resume_info:
148 # If record_resume_info is true then we'll replace the session cache with
149 # an object that records the lookups and inserts that it sees.
150 self.session_cache = RecordingSSLSessionCache()
151 else:
152 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000153 testserver_base.StoppableHTTPServer.__init__(self,
154 server_address,
155 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000156
157 def handshake(self, tlsConnection):
158 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000159
initial.commit94958cf2008-07-26 22:42:52 +0000160 try:
161 tlsConnection.handshakeServer(certChain=self.cert_chain,
162 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000163 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000164 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000165 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000166 reqCAs=self.ssl_client_cas,
167 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000168 tlsConnection.ignoreAbruptClose = True
169 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000170 except tlslite.api.TLSAbruptCloseError:
171 # Ignore abrupt close.
172 return True
initial.commit94958cf2008-07-26 22:42:52 +0000173 except tlslite.api.TLSError, error:
174 print "Handshake failure:", str(error)
175 return False
176
akalin@chromium.org154bb132010-11-12 02:20:27 +0000177
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000178class FTPServer(testserver_base.ClientRestrictingServerMixIn,
179 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000180 """This is a specialization of FTPServer that adds client verification."""
181
182 pass
183
184
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000185class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
186 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000187 """A TCP echo server that echoes back what it has received."""
188
189 def server_bind(self):
190 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000191
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000192 SocketServer.TCPServer.server_bind(self)
193 host, port = self.socket.getsockname()[:2]
194 self.server_name = socket.getfqdn(host)
195 self.server_port = port
196
197 def serve_forever(self):
198 self.stop = False
199 self.nonce_time = None
200 while not self.stop:
201 self.handle_request()
202 self.socket.close()
203
204
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000205class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
206 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000207 """A UDP echo server that echoes back what it has received."""
208
209 def server_bind(self):
210 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000211
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000212 SocketServer.UDPServer.server_bind(self)
213 host, port = self.socket.getsockname()[:2]
214 self.server_name = socket.getfqdn(host)
215 self.server_port = port
216
217 def serve_forever(self):
218 self.stop = False
219 self.nonce_time = None
220 while not self.stop:
221 self.handle_request()
222 self.socket.close()
223
224
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000225class TestPageHandler(testserver_base.BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000226
227 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000228 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000229 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000230 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000231 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000232 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000233 self.NoCacheMaxAgeTimeHandler,
234 self.NoCacheTimeHandler,
235 self.CacheTimeHandler,
236 self.CacheExpiresHandler,
237 self.CacheProxyRevalidateHandler,
238 self.CachePrivateHandler,
239 self.CachePublicHandler,
240 self.CacheSMaxAgeHandler,
241 self.CacheMustRevalidateHandler,
242 self.CacheMustRevalidateMaxAgeHandler,
243 self.CacheNoStoreHandler,
244 self.CacheNoStoreMaxAgeHandler,
245 self.CacheNoTransformHandler,
246 self.DownloadHandler,
247 self.DownloadFinishHandler,
248 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000249 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000250 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000251 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000252 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000253 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000254 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000255 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000256 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000257 self.AuthBasicHandler,
258 self.AuthDigestHandler,
259 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000260 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000261 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000262 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000263 self.ServerRedirectHandler,
264 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000265 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000266 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000267 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000268 self.SSLManySmallRecords,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000269 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000270 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000271 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000272 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000273 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000274 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000275 self.DeviceManagementHandler,
276 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000277 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000278 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000279 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000280 head_handlers = [
281 self.FileHandler,
282 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000283
maruel@google.come250a9b2009-03-10 17:39:46 +0000284 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000285 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000286 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000287 'gif': 'image/gif',
288 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000289 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000290 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000291 'pdf' : 'application/pdf',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000292 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000293 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000294 }
initial.commit94958cf2008-07-26 22:42:52 +0000295 self._default_mime_type = 'text/html'
296
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000297 testserver_base.BasePageHandler.__init__(self, request, client_address,
298 socket_server, connect_handlers,
299 get_handlers, head_handlers,
300 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000301
initial.commit94958cf2008-07-26 22:42:52 +0000302 def GetMIMETypeFromName(self, file_name):
303 """Returns the mime type for the specified file_name. So far it only looks
304 at the file extension."""
305
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000306 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000307 if len(extension) == 0:
308 # no extension.
309 return self._default_mime_type
310
ericroman@google.comc17ca532009-05-07 03:51:05 +0000311 # extension starts with a dot, so we need to remove it
312 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000313
initial.commit94958cf2008-07-26 22:42:52 +0000314 def NoCacheMaxAgeTimeHandler(self):
315 """This request handler yields a page with the title set to the current
316 system time, and no caching requested."""
317
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000318 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000319 return False
320
321 self.send_response(200)
322 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000323 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000324 self.end_headers()
325
maruel@google.come250a9b2009-03-10 17:39:46 +0000326 self.wfile.write('<html><head><title>%s</title></head></html>' %
327 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000328
329 return True
330
331 def NoCacheTimeHandler(self):
332 """This request handler yields a page with the title set to the current
333 system time, and no caching requested."""
334
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000335 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000336 return False
337
338 self.send_response(200)
339 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000340 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000341 self.end_headers()
342
maruel@google.come250a9b2009-03-10 17:39:46 +0000343 self.wfile.write('<html><head><title>%s</title></head></html>' %
344 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000345
346 return True
347
348 def CacheTimeHandler(self):
349 """This request handler yields a page with the title set to the current
350 system time, and allows caching for one minute."""
351
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000352 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000353 return False
354
355 self.send_response(200)
356 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000357 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000358 self.end_headers()
359
maruel@google.come250a9b2009-03-10 17:39:46 +0000360 self.wfile.write('<html><head><title>%s</title></head></html>' %
361 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000362
363 return True
364
365 def CacheExpiresHandler(self):
366 """This request handler yields a page with the title set to the current
367 system time, and set the page to expire on 1 Jan 2099."""
368
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000369 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000370 return False
371
372 self.send_response(200)
373 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000374 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000375 self.end_headers()
376
maruel@google.come250a9b2009-03-10 17:39:46 +0000377 self.wfile.write('<html><head><title>%s</title></head></html>' %
378 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000379
380 return True
381
382 def CacheProxyRevalidateHandler(self):
383 """This request handler yields a page with the title set to the current
384 system time, and allows caching for 60 seconds"""
385
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000386 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000387 return False
388
389 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000390 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000391 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
392 self.end_headers()
393
maruel@google.come250a9b2009-03-10 17:39:46 +0000394 self.wfile.write('<html><head><title>%s</title></head></html>' %
395 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000396
397 return True
398
399 def CachePrivateHandler(self):
400 """This request handler yields a page with the title set to the current
401 system time, and allows caching for 5 seconds."""
402
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000403 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000404 return False
405
406 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000407 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000408 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000409 self.end_headers()
410
maruel@google.come250a9b2009-03-10 17:39:46 +0000411 self.wfile.write('<html><head><title>%s</title></head></html>' %
412 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000413
414 return True
415
416 def CachePublicHandler(self):
417 """This request handler yields a page with the title set to the current
418 system time, and allows caching for 5 seconds."""
419
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000420 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000421 return False
422
423 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000424 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000425 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000426 self.end_headers()
427
maruel@google.come250a9b2009-03-10 17:39:46 +0000428 self.wfile.write('<html><head><title>%s</title></head></html>' %
429 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000430
431 return True
432
433 def CacheSMaxAgeHandler(self):
434 """This request handler yields a page with the title set to the current
435 system time, and does not allow for caching."""
436
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000437 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000438 return False
439
440 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000441 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000442 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
443 self.end_headers()
444
maruel@google.come250a9b2009-03-10 17:39:46 +0000445 self.wfile.write('<html><head><title>%s</title></head></html>' %
446 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000447
448 return True
449
450 def CacheMustRevalidateHandler(self):
451 """This request handler yields a page with the title set to the current
452 system time, and does not allow caching."""
453
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000454 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000455 return False
456
457 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000458 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000459 self.send_header('Cache-Control', 'must-revalidate')
460 self.end_headers()
461
maruel@google.come250a9b2009-03-10 17:39:46 +0000462 self.wfile.write('<html><head><title>%s</title></head></html>' %
463 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000464
465 return True
466
467 def CacheMustRevalidateMaxAgeHandler(self):
468 """This request handler yields a page with the title set to the current
469 system time, and does not allow caching event though max-age of 60
470 seconds is specified."""
471
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000472 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000473 return False
474
475 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000476 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000477 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
478 self.end_headers()
479
maruel@google.come250a9b2009-03-10 17:39:46 +0000480 self.wfile.write('<html><head><title>%s</title></head></html>' %
481 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000482
483 return True
484
initial.commit94958cf2008-07-26 22:42:52 +0000485 def CacheNoStoreHandler(self):
486 """This request handler yields a page with the title set to the current
487 system time, and does not allow the page to be stored."""
488
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000489 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000490 return False
491
492 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000493 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000494 self.send_header('Cache-Control', 'no-store')
495 self.end_headers()
496
maruel@google.come250a9b2009-03-10 17:39:46 +0000497 self.wfile.write('<html><head><title>%s</title></head></html>' %
498 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000499
500 return True
501
502 def CacheNoStoreMaxAgeHandler(self):
503 """This request handler yields a page with the title set to the current
504 system time, and does not allow the page to be stored even though max-age
505 of 60 seconds is specified."""
506
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000507 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000508 return False
509
510 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000511 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000512 self.send_header('Cache-Control', 'max-age=60, no-store')
513 self.end_headers()
514
maruel@google.come250a9b2009-03-10 17:39:46 +0000515 self.wfile.write('<html><head><title>%s</title></head></html>' %
516 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000517
518 return True
519
520
521 def CacheNoTransformHandler(self):
522 """This request handler yields a page with the title set to the current
523 system time, and does not allow the content to transformed during
524 user-agent caching"""
525
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000526 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000527 return False
528
529 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000530 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000531 self.send_header('Cache-Control', 'no-transform')
532 self.end_headers()
533
maruel@google.come250a9b2009-03-10 17:39:46 +0000534 self.wfile.write('<html><head><title>%s</title></head></html>' %
535 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000536
537 return True
538
539 def EchoHeader(self):
540 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000541
ananta@chromium.org219b2062009-10-23 16:09:41 +0000542 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000543
ananta@chromium.org56812d02011-04-07 17:52:05 +0000544 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000545 """This function echoes back the value of a specific request header while
546 allowing caching for 16 hours."""
547
ananta@chromium.org56812d02011-04-07 17:52:05 +0000548 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000549
550 def EchoHeaderHelper(self, echo_header):
551 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000552
ananta@chromium.org219b2062009-10-23 16:09:41 +0000553 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000554 return False
555
556 query_char = self.path.find('?')
557 if query_char != -1:
558 header_name = self.path[query_char+1:]
559
560 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000561 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000562 if echo_header == '/echoheadercache':
563 self.send_header('Cache-control', 'max-age=60000')
564 else:
565 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000566 # insert a vary header to properly indicate that the cachability of this
567 # request is subject to value of the request header being echoed.
568 if len(header_name) > 0:
569 self.send_header('Vary', header_name)
570 self.end_headers()
571
572 if len(header_name) > 0:
573 self.wfile.write(self.headers.getheader(header_name))
574
575 return True
576
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000577 def ReadRequestBody(self):
578 """This function reads the body of the current HTTP request, handling
579 both plain and chunked transfer encoded requests."""
580
581 if self.headers.getheader('transfer-encoding') != 'chunked':
582 length = int(self.headers.getheader('content-length'))
583 return self.rfile.read(length)
584
585 # Read the request body as chunks.
586 body = ""
587 while True:
588 line = self.rfile.readline()
589 length = int(line, 16)
590 if length == 0:
591 self.rfile.readline()
592 break
593 body += self.rfile.read(length)
594 self.rfile.read(2)
595 return body
596
initial.commit94958cf2008-07-26 22:42:52 +0000597 def EchoHandler(self):
598 """This handler just echoes back the payload of the request, for testing
599 form submission."""
600
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000601 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000602 return False
603
604 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000605 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000606 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000607 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000608 return True
609
610 def EchoTitleHandler(self):
611 """This handler is like Echo, but sets the page title to the request."""
612
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000613 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000614 return False
615
616 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000617 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000618 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000619 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000620 self.wfile.write('<html><head><title>')
621 self.wfile.write(request)
622 self.wfile.write('</title></head></html>')
623 return True
624
625 def EchoAllHandler(self):
626 """This handler yields a (more) human-readable page listing information
627 about the request header & contents."""
628
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000629 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000630 return False
631
632 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000633 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000634 self.end_headers()
635 self.wfile.write('<html><head><style>'
636 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
637 '</style></head><body>'
638 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000639 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000640 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000641
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000642 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000643 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000644 params = cgi.parse_qs(qs, keep_blank_values=1)
645
646 for param in params:
647 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000648
649 self.wfile.write('</pre>')
650
651 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
652
653 self.wfile.write('</body></html>')
654 return True
655
656 def DownloadHandler(self):
657 """This handler sends a downloadable file with or without reporting
658 the size (6K)."""
659
660 if self.path.startswith("/download-unknown-size"):
661 send_length = False
662 elif self.path.startswith("/download-known-size"):
663 send_length = True
664 else:
665 return False
666
667 #
668 # The test which uses this functionality is attempting to send
669 # small chunks of data to the client. Use a fairly large buffer
670 # so that we'll fill chrome's IO buffer enough to force it to
671 # actually write the data.
672 # See also the comments in the client-side of this test in
673 # download_uitest.cc
674 #
675 size_chunk1 = 35*1024
676 size_chunk2 = 10*1024
677
678 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000679 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000680 self.send_header('Cache-Control', 'max-age=0')
681 if send_length:
682 self.send_header('Content-Length', size_chunk1 + size_chunk2)
683 self.end_headers()
684
685 # First chunk of data:
686 self.wfile.write("*" * size_chunk1)
687 self.wfile.flush()
688
689 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000690 self.server.wait_for_download = True
691 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000692 self.server.handle_request()
693
694 # Second chunk of data:
695 self.wfile.write("*" * size_chunk2)
696 return True
697
698 def DownloadFinishHandler(self):
699 """This handler just tells the server to finish the current download."""
700
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000701 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000702 return False
703
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000704 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000705 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000706 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000707 self.send_header('Cache-Control', 'max-age=0')
708 self.end_headers()
709 return True
710
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000711 def _ReplaceFileData(self, data, query_parameters):
712 """Replaces matching substrings in a file.
713
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000714 If the 'replace_text' URL query parameter is present, it is expected to be
715 of the form old_text:new_text, which indicates that any old_text strings in
716 the file are replaced with new_text. Multiple 'replace_text' parameters may
717 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000718
719 If the parameters are not present, |data| is returned.
720 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000721
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000722 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000723 replace_text_values = query_dict.get('replace_text', [])
724 for replace_text_value in replace_text_values:
725 replace_text_args = replace_text_value.split(':')
726 if len(replace_text_args) != 2:
727 raise ValueError(
728 'replace_text must be of form old_text:new_text. Actual value: %s' %
729 replace_text_value)
730 old_text_b64, new_text_b64 = replace_text_args
731 old_text = base64.urlsafe_b64decode(old_text_b64)
732 new_text = base64.urlsafe_b64decode(new_text_b64)
733 data = data.replace(old_text, new_text)
734 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000735
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000736 def ZipFileHandler(self):
737 """This handler sends the contents of the requested file in compressed form.
738 Can pass in a parameter that specifies that the content length be
739 C - the compressed size (OK),
740 U - the uncompressed size (Non-standard, but handled),
741 S - less than compressed (OK because we keep going),
742 M - larger than compressed but less than uncompressed (an error),
743 L - larger than uncompressed (an error)
744 Example: compressedfiles/Picture_1.doc?C
745 """
746
747 prefix = "/compressedfiles/"
748 if not self.path.startswith(prefix):
749 return False
750
751 # Consume a request body if present.
752 if self.command == 'POST' or self.command == 'PUT' :
753 self.ReadRequestBody()
754
755 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
756
757 if not query in ('C', 'U', 'S', 'M', 'L'):
758 return False
759
760 sub_path = url_path[len(prefix):]
761 entries = sub_path.split('/')
762 file_path = os.path.join(self.server.data_dir, *entries)
763 if os.path.isdir(file_path):
764 file_path = os.path.join(file_path, 'index.html')
765
766 if not os.path.isfile(file_path):
767 print "File not found " + sub_path + " full path:" + file_path
768 self.send_error(404)
769 return True
770
771 f = open(file_path, "rb")
772 data = f.read()
773 uncompressed_len = len(data)
774 f.close()
775
776 # Compress the data.
777 data = zlib.compress(data)
778 compressed_len = len(data)
779
780 content_length = compressed_len
781 if query == 'U':
782 content_length = uncompressed_len
783 elif query == 'S':
784 content_length = compressed_len / 2
785 elif query == 'M':
786 content_length = (compressed_len + uncompressed_len) / 2
787 elif query == 'L':
788 content_length = compressed_len + uncompressed_len
789
790 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000791 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000792 self.send_header('Content-encoding', 'deflate')
793 self.send_header('Connection', 'close')
794 self.send_header('Content-Length', content_length)
795 self.send_header('ETag', '\'' + file_path + '\'')
796 self.end_headers()
797
798 self.wfile.write(data)
799
800 return True
801
initial.commit94958cf2008-07-26 22:42:52 +0000802 def FileHandler(self):
803 """This handler sends the contents of the requested file. Wow, it's like
804 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000805
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000806 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000807 if not self.path.startswith(prefix):
808 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000809 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000810
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000811 def PostOnlyFileHandler(self):
812 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000813
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000814 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000815 if not self.path.startswith(prefix):
816 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000817 return self._FileHandlerHelper(prefix)
818
819 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000820 request_body = ''
821 if self.command == 'POST' or self.command == 'PUT':
822 # Consume a request body if present.
823 request_body = self.ReadRequestBody()
824
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000825 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000826 query_dict = cgi.parse_qs(query)
827
828 expected_body = query_dict.get('expected_body', [])
829 if expected_body and request_body not in expected_body:
830 self.send_response(404)
831 self.end_headers()
832 self.wfile.write('')
833 return True
834
835 expected_headers = query_dict.get('expected_headers', [])
836 for expected_header in expected_headers:
837 header_name, expected_value = expected_header.split(':')
838 if self.headers.getheader(header_name) != expected_value:
839 self.send_response(404)
840 self.end_headers()
841 self.wfile.write('')
842 return True
843
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000844 sub_path = url_path[len(prefix):]
845 entries = sub_path.split('/')
846 file_path = os.path.join(self.server.data_dir, *entries)
847 if os.path.isdir(file_path):
848 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000849
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000850 if not os.path.isfile(file_path):
851 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000852 self.send_error(404)
853 return True
854
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000855 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000856 data = f.read()
857 f.close()
858
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000859 data = self._ReplaceFileData(data, query)
860
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000861 old_protocol_version = self.protocol_version
862
initial.commit94958cf2008-07-26 22:42:52 +0000863 # If file.mock-http-headers exists, it contains the headers we
864 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000865 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000866 if os.path.isfile(headers_path):
867 f = open(headers_path, "r")
868
869 # "HTTP/1.1 200 OK"
870 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000871 http_major, http_minor, status_code = re.findall(
872 'HTTP/(\d+).(\d+) (\d+)', response)[0]
873 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000874 self.send_response(int(status_code))
875
876 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000877 header_values = re.findall('(\S+):\s*(.*)', line)
878 if len(header_values) > 0:
879 # "name: value"
880 name, value = header_values[0]
881 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000882 f.close()
883 else:
884 # Could be more generic once we support mime-type sniffing, but for
885 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000886
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000887 range_header = self.headers.get('Range')
888 if range_header and range_header.startswith('bytes='):
889 # Note this doesn't handle all valid byte range_header values (i.e.
890 # left open ended ones), just enough for what we needed so far.
891 range_header = range_header[6:].split('-')
892 start = int(range_header[0])
893 if range_header[1]:
894 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000895 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000896 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000897
898 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000899 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
900 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000901 self.send_header('Content-Range', content_range)
902 data = data[start: end + 1]
903 else:
904 self.send_response(200)
905
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000906 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000907 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000908 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000909 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000910 self.end_headers()
911
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000912 if (self.command != 'HEAD'):
913 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000914
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000915 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000916 return True
917
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000918 def SetCookieHandler(self):
919 """This handler just sets a cookie, for testing cookie handling."""
920
921 if not self._ShouldHandleRequest("/set-cookie"):
922 return False
923
924 query_char = self.path.find('?')
925 if query_char != -1:
926 cookie_values = self.path[query_char + 1:].split('&')
927 else:
928 cookie_values = ("",)
929 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000930 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000931 for cookie_value in cookie_values:
932 self.send_header('Set-Cookie', '%s' % cookie_value)
933 self.end_headers()
934 for cookie_value in cookie_values:
935 self.wfile.write('%s' % cookie_value)
936 return True
937
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000938 def SetManyCookiesHandler(self):
939 """This handler just sets a given number of cookies, for testing handling
940 of large numbers of cookies."""
941
942 if not self._ShouldHandleRequest("/set-many-cookies"):
943 return False
944
945 query_char = self.path.find('?')
946 if query_char != -1:
947 num_cookies = int(self.path[query_char + 1:])
948 else:
949 num_cookies = 0
950 self.send_response(200)
951 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000952 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000953 self.send_header('Set-Cookie', 'a=')
954 self.end_headers()
955 self.wfile.write('%d cookies were sent' % num_cookies)
956 return True
957
mattm@chromium.org983fc462012-06-30 00:52:08 +0000958 def ExpectAndSetCookieHandler(self):
959 """Expects some cookies to be sent, and if they are, sets more cookies.
960
961 The expect parameter specifies a required cookie. May be specified multiple
962 times.
963 The set parameter specifies a cookie to set if all required cookies are
964 preset. May be specified multiple times.
965 The data parameter specifies the response body data to be returned."""
966
967 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
968 return False
969
970 _, _, _, _, query, _ = urlparse.urlparse(self.path)
971 query_dict = cgi.parse_qs(query)
972 cookies = set()
973 if 'Cookie' in self.headers:
974 cookie_header = self.headers.getheader('Cookie')
975 cookies.update([s.strip() for s in cookie_header.split(';')])
976 got_all_expected_cookies = True
977 for expected_cookie in query_dict.get('expect', []):
978 if expected_cookie not in cookies:
979 got_all_expected_cookies = False
980 self.send_response(200)
981 self.send_header('Content-Type', 'text/html')
982 if got_all_expected_cookies:
983 for cookie_value in query_dict.get('set', []):
984 self.send_header('Set-Cookie', '%s' % cookie_value)
985 self.end_headers()
986 for data_value in query_dict.get('data', []):
987 self.wfile.write(data_value)
988 return True
989
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000990 def SetHeaderHandler(self):
991 """This handler sets a response header. Parameters are in the
992 key%3A%20value&key2%3A%20value2 format."""
993
994 if not self._ShouldHandleRequest("/set-header"):
995 return False
996
997 query_char = self.path.find('?')
998 if query_char != -1:
999 headers_values = self.path[query_char + 1:].split('&')
1000 else:
1001 headers_values = ("",)
1002 self.send_response(200)
1003 self.send_header('Content-Type', 'text/html')
1004 for header_value in headers_values:
1005 header_value = urllib.unquote(header_value)
1006 (key, value) = header_value.split(': ', 1)
1007 self.send_header(key, value)
1008 self.end_headers()
1009 for header_value in headers_values:
1010 self.wfile.write('%s' % header_value)
1011 return True
1012
initial.commit94958cf2008-07-26 22:42:52 +00001013 def AuthBasicHandler(self):
1014 """This handler tests 'Basic' authentication. It just sends a page with
1015 title 'user/pass' if you succeed."""
1016
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001017 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001018 return False
1019
1020 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001021 expected_password = 'secret'
1022 realm = 'testrealm'
1023 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001024
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001025 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1026 query_params = cgi.parse_qs(query, True)
1027 if 'set-cookie-if-challenged' in query_params:
1028 set_cookie_if_challenged = True
1029 if 'password' in query_params:
1030 expected_password = query_params['password'][0]
1031 if 'realm' in query_params:
1032 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001033
initial.commit94958cf2008-07-26 22:42:52 +00001034 auth = self.headers.getheader('authorization')
1035 try:
1036 if not auth:
1037 raise Exception('no auth')
1038 b64str = re.findall(r'Basic (\S+)', auth)[0]
1039 userpass = base64.b64decode(b64str)
1040 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001041 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001042 raise Exception('wrong password')
1043 except Exception, e:
1044 # Authentication failed.
1045 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001046 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001047 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001048 if set_cookie_if_challenged:
1049 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001050 self.end_headers()
1051 self.wfile.write('<html><head>')
1052 self.wfile.write('<title>Denied: %s</title>' % e)
1053 self.wfile.write('</head><body>')
1054 self.wfile.write('auth=%s<p>' % auth)
1055 self.wfile.write('b64str=%s<p>' % b64str)
1056 self.wfile.write('username: %s<p>' % username)
1057 self.wfile.write('userpass: %s<p>' % userpass)
1058 self.wfile.write('password: %s<p>' % password)
1059 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1060 self.wfile.write('</body></html>')
1061 return True
1062
1063 # Authentication successful. (Return a cachable response to allow for
1064 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001065 old_protocol_version = self.protocol_version
1066 self.protocol_version = "HTTP/1.1"
1067
initial.commit94958cf2008-07-26 22:42:52 +00001068 if_none_match = self.headers.getheader('if-none-match')
1069 if if_none_match == "abc":
1070 self.send_response(304)
1071 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001072 elif url_path.endswith(".gif"):
1073 # Using chrome/test/data/google/logo.gif as the test image
1074 test_image_path = ['google', 'logo.gif']
1075 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1076 if not os.path.isfile(gif_path):
1077 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001078 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001079 return True
1080
1081 f = open(gif_path, "rb")
1082 data = f.read()
1083 f.close()
1084
1085 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001086 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001087 self.send_header('Cache-control', 'max-age=60000')
1088 self.send_header('Etag', 'abc')
1089 self.end_headers()
1090 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001091 else:
1092 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001093 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001094 self.send_header('Cache-control', 'max-age=60000')
1095 self.send_header('Etag', 'abc')
1096 self.end_headers()
1097 self.wfile.write('<html><head>')
1098 self.wfile.write('<title>%s/%s</title>' % (username, password))
1099 self.wfile.write('</head><body>')
1100 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001101 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001102 self.wfile.write('</body></html>')
1103
rvargas@google.com54453b72011-05-19 01:11:11 +00001104 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001105 return True
1106
tonyg@chromium.org75054202010-03-31 22:06:10 +00001107 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001108 """Returns a nonce that's stable per request path for the server's lifetime.
1109 This is a fake implementation. A real implementation would only use a given
1110 nonce a single time (hence the name n-once). However, for the purposes of
1111 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001112
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001113 Args:
1114 force_reset: Iff set, the nonce will be changed. Useful for testing the
1115 "stale" response.
1116 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001117
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001118 if force_reset or not self.server.nonce_time:
1119 self.server.nonce_time = time.time()
1120 return hashlib.md5('privatekey%s%d' %
1121 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001122
1123 def AuthDigestHandler(self):
1124 """This handler tests 'Digest' authentication.
1125
1126 It just sends a page with title 'user/pass' if you succeed.
1127
1128 A stale response is sent iff "stale" is present in the request path.
1129 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001130
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001131 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001132 return False
1133
tonyg@chromium.org75054202010-03-31 22:06:10 +00001134 stale = 'stale' in self.path
1135 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001136 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001137 password = 'secret'
1138 realm = 'testrealm'
1139
1140 auth = self.headers.getheader('authorization')
1141 pairs = {}
1142 try:
1143 if not auth:
1144 raise Exception('no auth')
1145 if not auth.startswith('Digest'):
1146 raise Exception('not digest')
1147 # Pull out all the name="value" pairs as a dictionary.
1148 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1149
1150 # Make sure it's all valid.
1151 if pairs['nonce'] != nonce:
1152 raise Exception('wrong nonce')
1153 if pairs['opaque'] != opaque:
1154 raise Exception('wrong opaque')
1155
1156 # Check the 'response' value and make sure it matches our magic hash.
1157 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001158 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001159 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001160 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001161 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001162 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001163 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1164 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001165 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001166
1167 if pairs['response'] != response:
1168 raise Exception('wrong password')
1169 except Exception, e:
1170 # Authentication failed.
1171 self.send_response(401)
1172 hdr = ('Digest '
1173 'realm="%s", '
1174 'domain="/", '
1175 'qop="auth", '
1176 'algorithm=MD5, '
1177 'nonce="%s", '
1178 'opaque="%s"') % (realm, nonce, opaque)
1179 if stale:
1180 hdr += ', stale="TRUE"'
1181 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001182 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001183 self.end_headers()
1184 self.wfile.write('<html><head>')
1185 self.wfile.write('<title>Denied: %s</title>' % e)
1186 self.wfile.write('</head><body>')
1187 self.wfile.write('auth=%s<p>' % auth)
1188 self.wfile.write('pairs=%s<p>' % pairs)
1189 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1190 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1191 self.wfile.write('</body></html>')
1192 return True
1193
1194 # Authentication successful.
1195 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001196 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001197 self.end_headers()
1198 self.wfile.write('<html><head>')
1199 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1200 self.wfile.write('</head><body>')
1201 self.wfile.write('auth=%s<p>' % auth)
1202 self.wfile.write('pairs=%s<p>' % pairs)
1203 self.wfile.write('</body></html>')
1204
1205 return True
1206
1207 def SlowServerHandler(self):
1208 """Wait for the user suggested time before responding. The syntax is
1209 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001210
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001211 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001212 return False
1213 query_char = self.path.find('?')
1214 wait_sec = 1.0
1215 if query_char >= 0:
1216 try:
1217 wait_sec = int(self.path[query_char + 1:])
1218 except ValueError:
1219 pass
1220 time.sleep(wait_sec)
1221 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001222 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001223 self.end_headers()
1224 self.wfile.write("waited %d seconds" % wait_sec)
1225 return True
1226
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001227 def ChunkedServerHandler(self):
1228 """Send chunked response. Allows to specify chunks parameters:
1229 - waitBeforeHeaders - ms to wait before sending headers
1230 - waitBetweenChunks - ms to wait between chunks
1231 - chunkSize - size of each chunk in bytes
1232 - chunksNumber - number of chunks
1233 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1234 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001235
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001236 if not self._ShouldHandleRequest("/chunked"):
1237 return False
1238 query_char = self.path.find('?')
1239 chunkedSettings = {'waitBeforeHeaders' : 0,
1240 'waitBetweenChunks' : 0,
1241 'chunkSize' : 5,
1242 'chunksNumber' : 5}
1243 if query_char >= 0:
1244 params = self.path[query_char + 1:].split('&')
1245 for param in params:
1246 keyValue = param.split('=')
1247 if len(keyValue) == 2:
1248 try:
1249 chunkedSettings[keyValue[0]] = int(keyValue[1])
1250 except ValueError:
1251 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001252 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001253 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1254 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001255 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001256 self.send_header('Connection', 'close')
1257 self.send_header('Transfer-Encoding', 'chunked')
1258 self.end_headers()
1259 # Chunked encoding: sending all chunks, then final zero-length chunk and
1260 # then final CRLF.
1261 for i in range(0, chunkedSettings['chunksNumber']):
1262 if i > 0:
1263 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1264 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001265 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001266 self.sendChunkHelp('')
1267 return True
1268
initial.commit94958cf2008-07-26 22:42:52 +00001269 def ContentTypeHandler(self):
1270 """Returns a string of html with the given content type. E.g.,
1271 /contenttype?text/css returns an html file with the Content-Type
1272 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001273
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001274 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001275 return False
1276 query_char = self.path.find('?')
1277 content_type = self.path[query_char + 1:].strip()
1278 if not content_type:
1279 content_type = 'text/html'
1280 self.send_response(200)
1281 self.send_header('Content-Type', content_type)
1282 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001283 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001284 return True
1285
creis@google.com2f4f6a42011-03-25 19:44:19 +00001286 def NoContentHandler(self):
1287 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001288
creis@google.com2f4f6a42011-03-25 19:44:19 +00001289 if not self._ShouldHandleRequest("/nocontent"):
1290 return False
1291 self.send_response(204)
1292 self.end_headers()
1293 return True
1294
initial.commit94958cf2008-07-26 22:42:52 +00001295 def ServerRedirectHandler(self):
1296 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001297 '/server-redirect?http://foo.bar/asdf' to redirect to
1298 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001299
1300 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001301 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001302 return False
1303
1304 query_char = self.path.find('?')
1305 if query_char < 0 or len(self.path) <= query_char + 1:
1306 self.sendRedirectHelp(test_name)
1307 return True
1308 dest = self.path[query_char + 1:]
1309
1310 self.send_response(301) # moved permanently
1311 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001312 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001313 self.end_headers()
1314 self.wfile.write('<html><head>')
1315 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1316
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001317 return True
initial.commit94958cf2008-07-26 22:42:52 +00001318
1319 def ClientRedirectHandler(self):
1320 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001321 '/client-redirect?http://foo.bar/asdf' to redirect to
1322 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001323
1324 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001325 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001326 return False
1327
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001328 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001329 if query_char < 0 or len(self.path) <= query_char + 1:
1330 self.sendRedirectHelp(test_name)
1331 return True
1332 dest = self.path[query_char + 1:]
1333
1334 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001335 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001336 self.end_headers()
1337 self.wfile.write('<html><head>')
1338 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1339 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1340
1341 return True
1342
tony@chromium.org03266982010-03-05 03:18:42 +00001343 def MultipartHandler(self):
1344 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001345
tony@chromium.org4cb88302011-09-27 22:13:49 +00001346 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001347 if not self._ShouldHandleRequest(test_name):
1348 return False
1349
1350 num_frames = 10
1351 bound = '12345'
1352 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001353 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001354 'multipart/x-mixed-replace;boundary=' + bound)
1355 self.end_headers()
1356
1357 for i in xrange(num_frames):
1358 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001359 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001360 self.wfile.write('<title>page ' + str(i) + '</title>')
1361 self.wfile.write('page ' + str(i))
1362
1363 self.wfile.write('--' + bound + '--')
1364 return True
1365
tony@chromium.org4cb88302011-09-27 22:13:49 +00001366 def MultipartSlowHandler(self):
1367 """Send a multipart response (3 text/html pages) with a slight delay
1368 between each page. This is similar to how some pages show status using
1369 multipart."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001370
tony@chromium.org4cb88302011-09-27 22:13:49 +00001371 test_name = '/multipart-slow'
1372 if not self._ShouldHandleRequest(test_name):
1373 return False
1374
1375 num_frames = 3
1376 bound = '12345'
1377 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001378 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001379 'multipart/x-mixed-replace;boundary=' + bound)
1380 self.end_headers()
1381
1382 for i in xrange(num_frames):
1383 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001384 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001385 time.sleep(0.25)
1386 if i == 2:
1387 self.wfile.write('<title>PASS</title>')
1388 else:
1389 self.wfile.write('<title>page ' + str(i) + '</title>')
1390 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1391
1392 self.wfile.write('--' + bound + '--')
1393 return True
1394
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001395 def GetSSLSessionCacheHandler(self):
1396 """Send a reply containing a log of the session cache operations."""
1397
1398 if not self._ShouldHandleRequest('/ssl-session-cache'):
1399 return False
1400
1401 self.send_response(200)
1402 self.send_header('Content-Type', 'text/plain')
1403 self.end_headers()
1404 try:
1405 for (action, sessionID) in self.server.session_cache.log:
1406 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001407 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001408 self.wfile.write('Pass --https-record-resume in order to use' +
1409 ' this request')
1410 return True
1411
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001412 def SSLManySmallRecords(self):
1413 """Sends a reply consisting of a variety of small writes. These will be
1414 translated into a series of small SSL records when used over an HTTPS
1415 server."""
1416
1417 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1418 return False
1419
1420 self.send_response(200)
1421 self.send_header('Content-Type', 'text/plain')
1422 self.end_headers()
1423
1424 # Write ~26K of data, in 1350 byte chunks
1425 for i in xrange(20):
1426 self.wfile.write('*' * 1350)
1427 self.wfile.flush()
1428 return True
1429
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001430 def CloseSocketHandler(self):
1431 """Closes the socket without sending anything."""
1432
1433 if not self._ShouldHandleRequest('/close-socket'):
1434 return False
1435
1436 self.wfile.close()
1437 return True
1438
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001439 def RangeResetHandler(self):
1440 """Send data broken up by connection resets every N (default 4K) bytes.
1441 Support range requests. If the data requested doesn't straddle a reset
1442 boundary, it will all be sent. Used for testing resuming downloads."""
1443
1444 if not self._ShouldHandleRequest('/rangereset'):
1445 return False
1446
1447 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1448
1449 # Defaults
1450 size = 8000
1451 # Note that the rst is sent just before sending the rst_boundary byte.
1452 rst_boundary = 4000
1453 respond_to_range = True
1454 hold_for_signal = False
1455
1456 # Parse the query
1457 qdict = urlparse.parse_qs(query, True)
1458 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001459 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001460 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001461 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001462 if 'bounce_range' in qdict:
1463 respond_to_range = False
1464 if 'hold' in qdict:
1465 hold_for_signal = True
1466
1467 first_byte = 0
1468 last_byte = size - 1
1469
1470 # Does that define what we want to return, or do we need to apply
1471 # a range?
1472 range_response = False
1473 range_header = self.headers.getheader('range')
1474 if range_header and respond_to_range:
1475 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1476 if mo.group(1):
1477 first_byte = int(mo.group(1))
1478 if mo.group(2):
1479 last_byte = int(mo.group(2))
1480 if last_byte > size - 1:
1481 last_byte = size - 1
1482 range_response = True
1483 if last_byte < first_byte:
1484 return False
1485
1486 # Set socket send buf high enough that we don't need to worry
1487 # about asynchronous closes when sending RSTs.
1488 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF,
1489 16284)
1490
1491 if range_response:
1492 self.send_response(206)
1493 self.send_header('Content-Range',
1494 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1495 else:
1496 self.send_response(200)
1497 self.send_header('Content-Type', 'application/octet-stream')
1498 self.send_header('Content-Length', last_byte - first_byte + 1)
1499 self.end_headers()
1500
1501 if hold_for_signal:
1502 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1503 # a single byte, the self.server.handle_request() below hangs
1504 # without processing new incoming requests.
1505 self.wfile.write('X')
1506 first_byte = first_byte + 1
1507 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001508 self.server.wait_for_download = True
1509 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001510 self.server.handle_request()
1511
1512 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
1513 if possible_rst >= last_byte:
1514 # No RST has been requested in this range, so we don't need to
1515 # do anything fancy; just write the data and let the python
1516 # infrastructure close the connection.
1517 self.wfile.write('X' * (last_byte - first_byte + 1))
1518 self.wfile.flush()
1519 return True
1520
1521 # We're resetting the connection part way in; go to the RST
1522 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001523 # Because socket semantics do not guarantee that all the data will be
1524 # sent when using the linger semantics to hard close a socket,
1525 # we send the data and then wait for our peer to release us
1526 # before sending the reset.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001527 self.wfile.write('X' * (possible_rst - first_byte))
1528 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001529 self.server.wait_for_download = True
1530 while self.server.wait_for_download:
1531 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001532 l_onoff = 1 # Linger is active.
1533 l_linger = 0 # Seconds to linger for.
1534 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1535 struct.pack('ii', l_onoff, l_linger))
1536
1537 # Close all duplicates of the underlying socket to force the RST.
1538 self.wfile.close()
1539 self.rfile.close()
1540 self.connection.close()
1541
1542 return True
1543
initial.commit94958cf2008-07-26 22:42:52 +00001544 def DefaultResponseHandler(self):
1545 """This is the catch-all response handler for requests that aren't handled
1546 by one of the special handlers above.
1547 Note that we specify the content-length as without it the https connection
1548 is not closed properly (and the browser keeps expecting data)."""
1549
1550 contents = "Default response given for path: " + self.path
1551 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001552 self.send_header('Content-Type', 'text/html')
1553 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001554 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001555 if (self.command != 'HEAD'):
1556 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001557 return True
1558
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001559 def RedirectConnectHandler(self):
1560 """Sends a redirect to the CONNECT request for www.redirect.com. This
1561 response is not specified by the RFC, so the browser should not follow
1562 the redirect."""
1563
1564 if (self.path.find("www.redirect.com") < 0):
1565 return False
1566
1567 dest = "http://www.destination.com/foo.js"
1568
1569 self.send_response(302) # moved temporarily
1570 self.send_header('Location', dest)
1571 self.send_header('Connection', 'close')
1572 self.end_headers()
1573 return True
1574
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001575 def ServerAuthConnectHandler(self):
1576 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1577 response doesn't make sense because the proxy server cannot request
1578 server authentication."""
1579
1580 if (self.path.find("www.server-auth.com") < 0):
1581 return False
1582
1583 challenge = 'Basic realm="WallyWorld"'
1584
1585 self.send_response(401) # unauthorized
1586 self.send_header('WWW-Authenticate', challenge)
1587 self.send_header('Connection', 'close')
1588 self.end_headers()
1589 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001590
1591 def DefaultConnectResponseHandler(self):
1592 """This is the catch-all response handler for CONNECT requests that aren't
1593 handled by one of the special handlers above. Real Web servers respond
1594 with 400 to CONNECT requests."""
1595
1596 contents = "Your client has issued a malformed or illegal request."
1597 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001598 self.send_header('Content-Type', 'text/html')
1599 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001600 self.end_headers()
1601 self.wfile.write(contents)
1602 return True
1603
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001604 def DeviceManagementHandler(self):
1605 """Delegates to the device management service used for cloud policy."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001606
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001607 if not self._ShouldHandleRequest("/device_management"):
1608 return False
1609
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001610 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001611
1612 if not self.server._device_management_handler:
1613 import device_management
1614 policy_path = os.path.join(self.server.data_dir, 'device_management')
1615 self.server._device_management_handler = (
joaodasilva@chromium.org3c069da2012-11-20 16:17:15 +00001616 device_management.TestServer(policy_path, self.server.policy_keys))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001617
1618 http_response, raw_reply = (
1619 self.server._device_management_handler.HandleRequest(self.path,
1620 self.headers,
1621 raw_request))
1622 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001623 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001624 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001625 self.end_headers()
1626 self.wfile.write(raw_reply)
1627 return True
1628
initial.commit94958cf2008-07-26 22:42:52 +00001629 # called by the redirect handling function when there is no parameter
1630 def sendRedirectHelp(self, redirect_name):
1631 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001632 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001633 self.end_headers()
1634 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1635 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1636 self.wfile.write('</body></html>')
1637
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001638 # called by chunked handling function
1639 def sendChunkHelp(self, chunk):
1640 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1641 self.wfile.write('%X\r\n' % len(chunk))
1642 self.wfile.write(chunk)
1643 self.wfile.write('\r\n')
1644
akalin@chromium.org154bb132010-11-12 02:20:27 +00001645
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001646class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001647 def __init__(self, request, client_address, socket_server):
1648 handlers = [self.OCSPResponse]
1649 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001650 testserver_base.BasePageHandler.__init__(self, request, client_address,
1651 socket_server, [], handlers, [],
1652 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001653
1654 def OCSPResponse(self):
1655 self.send_response(200)
1656 self.send_header('Content-Type', 'application/ocsp-response')
1657 self.send_header('Content-Length', str(len(self.ocsp_response)))
1658 self.end_headers()
1659
1660 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001661
mattm@chromium.org830a3712012-11-07 23:00:07 +00001662
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001663class TCPEchoHandler(SocketServer.BaseRequestHandler):
1664 """The RequestHandler class for TCP echo server.
1665
1666 It is instantiated once per connection to the server, and overrides the
1667 handle() method to implement communication to the client.
1668 """
1669
1670 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001671 """Handles the request from the client and constructs a response."""
1672
1673 data = self.request.recv(65536).strip()
1674 # Verify the "echo request" message received from the client. Send back
1675 # "echo response" message if "echo request" message is valid.
1676 try:
1677 return_data = echo_message.GetEchoResponseData(data)
1678 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001679 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001680 except ValueError:
1681 return
1682
1683 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001684
1685
1686class UDPEchoHandler(SocketServer.BaseRequestHandler):
1687 """The RequestHandler class for UDP echo server.
1688
1689 It is instantiated once per connection to the server, and overrides the
1690 handle() method to implement communication to the client.
1691 """
1692
1693 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001694 """Handles the request from the client and constructs a response."""
1695
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001696 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001697 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001698 # Verify the "echo request" message received from the client. Send back
1699 # "echo response" message if "echo request" message is valid.
1700 try:
1701 return_data = echo_message.GetEchoResponseData(data)
1702 if not return_data:
1703 return
1704 except ValueError:
1705 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001706 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001707
1708
bashi@chromium.org33233532012-09-08 17:37:24 +00001709class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1710 """A request handler that behaves as a proxy server which requires
1711 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1712 """
1713
1714 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1715
1716 def parse_request(self):
1717 """Overrides parse_request to check credential."""
1718
1719 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1720 return False
1721
1722 auth = self.headers.getheader('Proxy-Authorization')
1723 if auth != self._AUTH_CREDENTIAL:
1724 self.send_response(407)
1725 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1726 self.end_headers()
1727 return False
1728
1729 return True
1730
1731 def _start_read_write(self, sock):
1732 sock.setblocking(0)
1733 self.request.setblocking(0)
1734 rlist = [self.request, sock]
1735 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001736 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001737 if errors:
1738 self.send_response(500)
1739 self.end_headers()
1740 return
1741 for s in ready_sockets:
1742 received = s.recv(1024)
1743 if len(received) == 0:
1744 return
1745 if s == self.request:
1746 other = sock
1747 else:
1748 other = self.request
1749 other.send(received)
1750
1751 def _do_common_method(self):
1752 url = urlparse.urlparse(self.path)
1753 port = url.port
1754 if not port:
1755 if url.scheme == 'http':
1756 port = 80
1757 elif url.scheme == 'https':
1758 port = 443
1759 if not url.hostname or not port:
1760 self.send_response(400)
1761 self.end_headers()
1762 return
1763
1764 if len(url.path) == 0:
1765 path = '/'
1766 else:
1767 path = url.path
1768 if len(url.query) > 0:
1769 path = '%s?%s' % (url.path, url.query)
1770
1771 sock = None
1772 try:
1773 sock = socket.create_connection((url.hostname, port))
1774 sock.send('%s %s %s\r\n' % (
1775 self.command, path, self.protocol_version))
1776 for header in self.headers.headers:
1777 header = header.strip()
1778 if (header.lower().startswith('connection') or
1779 header.lower().startswith('proxy')):
1780 continue
1781 sock.send('%s\r\n' % header)
1782 sock.send('\r\n')
1783 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001784 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001785 self.send_response(500)
1786 self.end_headers()
1787 finally:
1788 if sock is not None:
1789 sock.close()
1790
1791 def do_CONNECT(self):
1792 try:
1793 pos = self.path.rfind(':')
1794 host = self.path[:pos]
1795 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001796 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001797 self.send_response(400)
1798 self.end_headers()
1799
1800 try:
1801 sock = socket.create_connection((host, port))
1802 self.send_response(200, 'Connection established')
1803 self.end_headers()
1804 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001805 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001806 self.send_response(500)
1807 self.end_headers()
1808 finally:
1809 sock.close()
1810
1811 def do_GET(self):
1812 self._do_common_method()
1813
1814 def do_HEAD(self):
1815 self._do_common_method()
1816
1817
mattm@chromium.org830a3712012-11-07 23:00:07 +00001818class ServerRunner(testserver_base.TestServerRunner):
1819 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001820
mattm@chromium.org830a3712012-11-07 23:00:07 +00001821 def __init__(self):
1822 super(ServerRunner, self).__init__()
1823 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001824
mattm@chromium.org830a3712012-11-07 23:00:07 +00001825 def __make_data_dir(self):
1826 if self.options.data_dir:
1827 if not os.path.isdir(self.options.data_dir):
1828 raise testserver_base.OptionError('specified data dir not found: ' +
1829 self.options.data_dir + ' exiting...')
1830 my_data_dir = self.options.data_dir
1831 else:
1832 # Create the default path to our data dir, relative to the exe dir.
1833 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1834 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001835
mattm@chromium.org830a3712012-11-07 23:00:07 +00001836 #TODO(ibrar): Must use Find* funtion defined in google\tools
1837 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001838
mattm@chromium.org830a3712012-11-07 23:00:07 +00001839 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001840
mattm@chromium.org830a3712012-11-07 23:00:07 +00001841 def create_server(self, server_data):
1842 port = self.options.port
1843 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001844
mattm@chromium.org830a3712012-11-07 23:00:07 +00001845 if self.options.server_type == SERVER_HTTP:
1846 if self.options.https:
1847 pem_cert_and_key = None
1848 if self.options.cert_and_key_file:
1849 if not os.path.isfile(self.options.cert_and_key_file):
1850 raise testserver_base.OptionError(
1851 'specified server cert file not found: ' +
1852 self.options.cert_and_key_file + ' exiting...')
1853 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001854 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001855 # generate a new certificate and run an OCSP server for it.
1856 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
1857 print ('OCSP server started on %s:%d...' %
1858 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001859
mattm@chromium.org830a3712012-11-07 23:00:07 +00001860 ocsp_der = None
1861 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001862
mattm@chromium.org830a3712012-11-07 23:00:07 +00001863 if self.options.ocsp == 'ok':
1864 ocsp_state = minica.OCSP_STATE_GOOD
1865 elif self.options.ocsp == 'revoked':
1866 ocsp_state = minica.OCSP_STATE_REVOKED
1867 elif self.options.ocsp == 'invalid':
1868 ocsp_state = minica.OCSP_STATE_INVALID
1869 elif self.options.ocsp == 'unauthorized':
1870 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1871 elif self.options.ocsp == 'unknown':
1872 ocsp_state = minica.OCSP_STATE_UNKNOWN
1873 else:
1874 raise testserver_base.OptionError('unknown OCSP status: ' +
1875 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001876
mattm@chromium.org830a3712012-11-07 23:00:07 +00001877 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1878 subject = "127.0.0.1",
1879 ocsp_url = ("http://%s:%d/ocsp" %
1880 (host, self.__ocsp_server.server_port)),
1881 ocsp_state = ocsp_state)
1882
1883 self.__ocsp_server.ocsp_response = ocsp_der
1884
1885 for ca_cert in self.options.ssl_client_ca:
1886 if not os.path.isfile(ca_cert):
1887 raise testserver_base.OptionError(
1888 'specified trusted client CA file not found: ' + ca_cert +
1889 ' exiting...')
1890 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1891 self.options.ssl_client_auth,
1892 self.options.ssl_client_ca,
1893 self.options.ssl_bulk_cipher,
1894 self.options.record_resume,
1895 self.options.tls_intolerant)
1896 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
1897 else:
1898 server = HTTPServer((host, port), TestPageHandler)
1899 print 'HTTP server started on %s:%d...' % (host, server.server_port)
1900
1901 server.data_dir = self.__make_data_dir()
1902 server.file_root_url = self.options.file_root_url
1903 server_data['port'] = server.server_port
1904 server._device_management_handler = None
1905 server.policy_keys = self.options.policy_keys
mattm@chromium.org830a3712012-11-07 23:00:07 +00001906 elif self.options.server_type == SERVER_WEBSOCKET:
1907 # Launch pywebsocket via WebSocketServer.
1908 logger = logging.getLogger()
1909 logger.addHandler(logging.StreamHandler())
1910 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1911 # is required to work correctly. It should be fixed from pywebsocket side.
1912 os.chdir(self.__make_data_dir())
1913 websocket_options = WebSocketOptions(host, port, '.')
1914 if self.options.cert_and_key_file:
1915 websocket_options.use_tls = True
1916 websocket_options.private_key = self.options.cert_and_key_file
1917 websocket_options.certificate = self.options.cert_and_key_file
1918 if self.options.ssl_client_auth:
1919 websocket_options.tls_client_auth = True
1920 if len(self.options.ssl_client_ca) != 1:
1921 raise testserver_base.OptionError(
1922 'one trusted client CA file should be specified')
1923 if not os.path.isfile(self.options.ssl_client_ca[0]):
1924 raise testserver_base.OptionError(
1925 'specified trusted client CA file not found: ' +
1926 self.options.ssl_client_ca[0] + ' exiting...')
1927 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
1928 server = WebSocketServer(websocket_options)
1929 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
1930 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001931 elif self.options.server_type == SERVER_TCP_ECHO:
1932 # Used for generating the key (randomly) that encodes the "echo request"
1933 # message.
1934 random.seed()
1935 server = TCPEchoServer((host, port), TCPEchoHandler)
1936 print 'Echo TCP server started on port %d...' % server.server_port
1937 server_data['port'] = server.server_port
1938 elif self.options.server_type == SERVER_UDP_ECHO:
1939 # Used for generating the key (randomly) that encodes the "echo request"
1940 # message.
1941 random.seed()
1942 server = UDPEchoServer((host, port), UDPEchoHandler)
1943 print 'Echo UDP server started on port %d...' % server.server_port
1944 server_data['port'] = server.server_port
1945 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
1946 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
1947 print 'BasicAuthProxy server started on port %d...' % server.server_port
1948 server_data['port'] = server.server_port
1949 elif self.options.server_type == SERVER_FTP:
1950 my_data_dir = self.__make_data_dir()
1951
1952 # Instantiate a dummy authorizer for managing 'virtual' users
1953 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1954
1955 # Define a new user having full r/w permissions and a read-only
1956 # anonymous user
1957 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1958
1959 authorizer.add_anonymous(my_data_dir)
1960
1961 # Instantiate FTP handler class
1962 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1963 ftp_handler.authorizer = authorizer
1964
1965 # Define a customized banner (string returned when client connects)
1966 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1967 pyftpdlib.ftpserver.__ver__)
1968
1969 # Instantiate FTP server class and listen to address:port
1970 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
1971 server_data['port'] = server.socket.getsockname()[1]
1972 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001973 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001974 raise testserver_base.OptionError('unknown server type' +
1975 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00001976
mattm@chromium.org830a3712012-11-07 23:00:07 +00001977 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001978
mattm@chromium.org830a3712012-11-07 23:00:07 +00001979 def run_server(self):
1980 if self.__ocsp_server:
1981 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001982
mattm@chromium.org830a3712012-11-07 23:00:07 +00001983 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001984
mattm@chromium.org830a3712012-11-07 23:00:07 +00001985 if self.__ocsp_server:
1986 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001987
mattm@chromium.org830a3712012-11-07 23:00:07 +00001988 def add_options(self):
1989 testserver_base.TestServerRunner.add_options(self)
1990 self.option_parser.add_option('-f', '--ftp', action='store_const',
1991 const=SERVER_FTP, default=SERVER_HTTP,
1992 dest='server_type',
1993 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001994 self.option_parser.add_option('--tcp-echo', action='store_const',
1995 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1996 dest='server_type',
1997 help='start up a tcp echo server.')
1998 self.option_parser.add_option('--udp-echo', action='store_const',
1999 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2000 dest='server_type',
2001 help='start up a udp echo server.')
2002 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2003 const=SERVER_BASIC_AUTH_PROXY,
2004 default=SERVER_HTTP, dest='server_type',
2005 help='start up a proxy server which requires '
2006 'basic authentication.')
2007 self.option_parser.add_option('--websocket', action='store_const',
2008 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2009 dest='server_type',
2010 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002011 self.option_parser.add_option('--https', action='store_true',
2012 dest='https', help='Specify that https '
2013 'should be used.')
2014 self.option_parser.add_option('--cert-and-key-file',
2015 dest='cert_and_key_file', help='specify the '
2016 'path to the file containing the certificate '
2017 'and private key for the server in PEM '
2018 'format')
2019 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2020 help='The type of OCSP response generated '
2021 'for the automatically generated '
2022 'certificate. One of [ok,revoked,invalid]')
2023 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2024 default='0', type='int',
2025 help='If nonzero, certain TLS connections '
2026 'will be aborted in order to test version '
2027 'fallback. 1 means all TLS versions will be '
2028 'aborted. 2 means TLS 1.1 or higher will be '
2029 'aborted. 3 means TLS 1.2 or higher will be '
2030 'aborted.')
2031 self.option_parser.add_option('--https-record-resume',
2032 dest='record_resume', const=True,
2033 default=False, action='store_const',
2034 help='Record resumption cache events rather '
2035 'than resuming as normal. Allows the use of '
2036 'the /ssl-session-cache request')
2037 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2038 help='Require SSL client auth on every '
2039 'connection.')
2040 self.option_parser.add_option('--ssl-client-ca', action='append',
2041 default=[], help='Specify that the client '
2042 'certificate request should include the CA '
2043 'named in the subject of the DER-encoded '
2044 'certificate contained in the specified '
2045 'file. This option may appear multiple '
2046 'times, indicating multiple CA names should '
2047 'be sent in the request.')
2048 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2049 help='Specify the bulk encryption '
2050 'algorithm(s) that will be accepted by the '
2051 'SSL server. Valid values are "aes256", '
2052 '"aes128", "3des", "rc4". If omitted, all '
2053 'algorithms will be used. This option may '
2054 'appear multiple times, indicating '
2055 'multiple algorithms should be enabled.');
2056 self.option_parser.add_option('--file-root-url', default='/files/',
2057 help='Specify a root URL for files served.')
2058 self.option_parser.add_option('--policy-key', action='append',
2059 dest='policy_keys',
2060 help='Specify a path to a PEM-encoded '
2061 'private key to use for policy signing. May '
2062 'be specified multiple times in order to '
2063 'load multipe keys into the server. If the '
2064 'server has multiple keys, it will rotate '
2065 'through them in at each request a '
2066 'round-robin fashion. The server will '
2067 'generate a random key if none is specified '
2068 'on the command line.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002069
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002070
initial.commit94958cf2008-07-26 22:42:52 +00002071if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002072 sys.exit(ServerRunner().main())