blob: 77a314263001a60d083f5ed0f23e9c563cd42c76 [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
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000058class WebSocketOptions:
59 """Holds options for WebSocketServer."""
60
61 def __init__(self, host, port, data_dir):
62 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
63 self.server_host = host
64 self.port = port
65 self.websock_handlers = data_dir
66 self.scan_dir = None
67 self.allow_handlers_outside_root_dir = False
68 self.websock_handlers_map_file = None
69 self.cgi_directories = []
70 self.is_executable_method = None
71 self.allow_draft75 = False
72 self.strict = True
73
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000074 self.use_tls = False
75 self.private_key = None
76 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000077 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000078 self.tls_client_ca = None
79 self.use_basic_auth = False
80
mattm@chromium.org830a3712012-11-07 23:00:07 +000081
agl@chromium.orgf9e66792011-12-12 22:22:19 +000082class RecordingSSLSessionCache(object):
83 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
84 lookups and inserts in order to test session cache behaviours."""
85
86 def __init__(self):
87 self.log = []
88
89 def __getitem__(self, sessionID):
90 self.log.append(('lookup', sessionID))
91 raise KeyError()
92
93 def __setitem__(self, sessionID, session):
94 self.log.append(('insert', sessionID))
95
erikwright@chromium.org847ef282012-02-22 16:41:10 +000096
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000097class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
98 testserver_base.BrokenPipeHandlerMixIn,
99 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000100 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000101 verification."""
102
103 pass
104
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000105class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
106 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000107 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000108 """This is a specialization of HTTPServer that serves an
109 OCSP response"""
110
111 def serve_forever_on_thread(self):
112 self.thread = threading.Thread(target = self.serve_forever,
113 name = "OCSPServerThread")
114 self.thread.start()
115
116 def stop_serving(self):
117 self.shutdown()
118 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000119
mattm@chromium.org830a3712012-11-07 23:00:07 +0000120
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000121class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000122 testserver_base.ClientRestrictingServerMixIn,
123 testserver_base.BrokenPipeHandlerMixIn,
124 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000125 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000126 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000127
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000128 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000129 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000130 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000131 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000132 # Force using only python implementation - otherwise behavior is different
133 # depending on whether m2crypto Python module is present (error is thrown
134 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
135 # the hood.
136 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
137 private=True,
138 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000139 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000140 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000141 self.tls_intolerant = tls_intolerant
142
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000143 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000144 s = open(ca_file).read()
145 x509 = tlslite.api.X509()
146 x509.parse(s)
147 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000148 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
149 if ssl_bulk_ciphers is not None:
150 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000151
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000152 if record_resume_info:
153 # If record_resume_info is true then we'll replace the session cache with
154 # an object that records the lookups and inserts that it sees.
155 self.session_cache = RecordingSSLSessionCache()
156 else:
157 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000158 testserver_base.StoppableHTTPServer.__init__(self,
159 server_address,
160 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000161
162 def handshake(self, tlsConnection):
163 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000164
initial.commit94958cf2008-07-26 22:42:52 +0000165 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000166 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000167 tlsConnection.handshakeServer(certChain=self.cert_chain,
168 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000169 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000170 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000171 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000172 reqCAs=self.ssl_client_cas,
173 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000174 tlsConnection.ignoreAbruptClose = True
175 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000176 except tlslite.api.TLSAbruptCloseError:
177 # Ignore abrupt close.
178 return True
initial.commit94958cf2008-07-26 22:42:52 +0000179 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000180 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000181 return False
182
akalin@chromium.org154bb132010-11-12 02:20:27 +0000183
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000184class FTPServer(testserver_base.ClientRestrictingServerMixIn,
185 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000186 """This is a specialization of FTPServer that adds client verification."""
187
188 pass
189
190
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000191class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
192 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000193 """A TCP echo server that echoes back what it has received."""
194
195 def server_bind(self):
196 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000197
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000198 SocketServer.TCPServer.server_bind(self)
199 host, port = self.socket.getsockname()[:2]
200 self.server_name = socket.getfqdn(host)
201 self.server_port = port
202
203 def serve_forever(self):
204 self.stop = False
205 self.nonce_time = None
206 while not self.stop:
207 self.handle_request()
208 self.socket.close()
209
210
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000211class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
212 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000213 """A UDP echo server that echoes back what it has received."""
214
215 def server_bind(self):
216 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000217
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000218 SocketServer.UDPServer.server_bind(self)
219 host, port = self.socket.getsockname()[:2]
220 self.server_name = socket.getfqdn(host)
221 self.server_port = port
222
223 def serve_forever(self):
224 self.stop = False
225 self.nonce_time = None
226 while not self.stop:
227 self.handle_request()
228 self.socket.close()
229
230
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000231class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000232 # Class variables to allow for persistence state between page handler
233 # invocations
234 rst_limits = {}
235 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000236
237 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000238 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000239 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000240 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000241 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000242 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000243 self.NoCacheMaxAgeTimeHandler,
244 self.NoCacheTimeHandler,
245 self.CacheTimeHandler,
246 self.CacheExpiresHandler,
247 self.CacheProxyRevalidateHandler,
248 self.CachePrivateHandler,
249 self.CachePublicHandler,
250 self.CacheSMaxAgeHandler,
251 self.CacheMustRevalidateHandler,
252 self.CacheMustRevalidateMaxAgeHandler,
253 self.CacheNoStoreHandler,
254 self.CacheNoStoreMaxAgeHandler,
255 self.CacheNoTransformHandler,
256 self.DownloadHandler,
257 self.DownloadFinishHandler,
258 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000259 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000260 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000261 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000262 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000263 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000264 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000265 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000266 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000267 self.AuthBasicHandler,
268 self.AuthDigestHandler,
269 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000270 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000271 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000272 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000273 self.ServerRedirectHandler,
274 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000275 self.MultipartHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000276 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000277 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000278 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000279 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000280 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000281 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000282 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000283 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000284 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000285 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000286 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000287 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000288 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000289 head_handlers = [
290 self.FileHandler,
291 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000292
maruel@google.come250a9b2009-03-10 17:39:46 +0000293 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000294 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000295 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000296 'gif': 'image/gif',
297 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000298 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000299 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000300 'pdf' : 'application/pdf',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000301 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000302 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000303 }
initial.commit94958cf2008-07-26 22:42:52 +0000304 self._default_mime_type = 'text/html'
305
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000306 testserver_base.BasePageHandler.__init__(self, request, client_address,
307 socket_server, connect_handlers,
308 get_handlers, head_handlers,
309 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000310
initial.commit94958cf2008-07-26 22:42:52 +0000311 def GetMIMETypeFromName(self, file_name):
312 """Returns the mime type for the specified file_name. So far it only looks
313 at the file extension."""
314
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000315 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000316 if len(extension) == 0:
317 # no extension.
318 return self._default_mime_type
319
ericroman@google.comc17ca532009-05-07 03:51:05 +0000320 # extension starts with a dot, so we need to remove it
321 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000322
initial.commit94958cf2008-07-26 22:42:52 +0000323 def NoCacheMaxAgeTimeHandler(self):
324 """This request handler yields a page with the title set to the current
325 system time, and no caching requested."""
326
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000327 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000328 return False
329
330 self.send_response(200)
331 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000332 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000333 self.end_headers()
334
maruel@google.come250a9b2009-03-10 17:39:46 +0000335 self.wfile.write('<html><head><title>%s</title></head></html>' %
336 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000337
338 return True
339
340 def NoCacheTimeHandler(self):
341 """This request handler yields a page with the title set to the current
342 system time, and no caching requested."""
343
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000344 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000345 return False
346
347 self.send_response(200)
348 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000349 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000350 self.end_headers()
351
maruel@google.come250a9b2009-03-10 17:39:46 +0000352 self.wfile.write('<html><head><title>%s</title></head></html>' %
353 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000354
355 return True
356
357 def CacheTimeHandler(self):
358 """This request handler yields a page with the title set to the current
359 system time, and allows caching for one minute."""
360
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000361 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000362 return False
363
364 self.send_response(200)
365 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000366 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000367 self.end_headers()
368
maruel@google.come250a9b2009-03-10 17:39:46 +0000369 self.wfile.write('<html><head><title>%s</title></head></html>' %
370 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000371
372 return True
373
374 def CacheExpiresHandler(self):
375 """This request handler yields a page with the title set to the current
376 system time, and set the page to expire on 1 Jan 2099."""
377
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000378 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000379 return False
380
381 self.send_response(200)
382 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000383 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000384 self.end_headers()
385
maruel@google.come250a9b2009-03-10 17:39:46 +0000386 self.wfile.write('<html><head><title>%s</title></head></html>' %
387 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000388
389 return True
390
391 def CacheProxyRevalidateHandler(self):
392 """This request handler yields a page with the title set to the current
393 system time, and allows caching for 60 seconds"""
394
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000395 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000396 return False
397
398 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000399 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000400 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
401 self.end_headers()
402
maruel@google.come250a9b2009-03-10 17:39:46 +0000403 self.wfile.write('<html><head><title>%s</title></head></html>' %
404 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000405
406 return True
407
408 def CachePrivateHandler(self):
409 """This request handler yields a page with the title set to the current
410 system time, and allows caching for 5 seconds."""
411
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000412 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000413 return False
414
415 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000416 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000417 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000418 self.end_headers()
419
maruel@google.come250a9b2009-03-10 17:39:46 +0000420 self.wfile.write('<html><head><title>%s</title></head></html>' %
421 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000422
423 return True
424
425 def CachePublicHandler(self):
426 """This request handler yields a page with the title set to the current
427 system time, and allows caching for 5 seconds."""
428
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000429 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000430 return False
431
432 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000433 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000434 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000435 self.end_headers()
436
maruel@google.come250a9b2009-03-10 17:39:46 +0000437 self.wfile.write('<html><head><title>%s</title></head></html>' %
438 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000439
440 return True
441
442 def CacheSMaxAgeHandler(self):
443 """This request handler yields a page with the title set to the current
444 system time, and does not allow for caching."""
445
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000446 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000447 return False
448
449 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000450 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000451 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
452 self.end_headers()
453
maruel@google.come250a9b2009-03-10 17:39:46 +0000454 self.wfile.write('<html><head><title>%s</title></head></html>' %
455 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000456
457 return True
458
459 def CacheMustRevalidateHandler(self):
460 """This request handler yields a page with the title set to the current
461 system time, and does not allow caching."""
462
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000463 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000464 return False
465
466 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000467 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000468 self.send_header('Cache-Control', 'must-revalidate')
469 self.end_headers()
470
maruel@google.come250a9b2009-03-10 17:39:46 +0000471 self.wfile.write('<html><head><title>%s</title></head></html>' %
472 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000473
474 return True
475
476 def CacheMustRevalidateMaxAgeHandler(self):
477 """This request handler yields a page with the title set to the current
478 system time, and does not allow caching event though max-age of 60
479 seconds is specified."""
480
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000481 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000482 return False
483
484 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000485 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000486 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
487 self.end_headers()
488
maruel@google.come250a9b2009-03-10 17:39:46 +0000489 self.wfile.write('<html><head><title>%s</title></head></html>' %
490 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000491
492 return True
493
initial.commit94958cf2008-07-26 22:42:52 +0000494 def CacheNoStoreHandler(self):
495 """This request handler yields a page with the title set to the current
496 system time, and does not allow the page to be stored."""
497
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000498 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000499 return False
500
501 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000502 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000503 self.send_header('Cache-Control', 'no-store')
504 self.end_headers()
505
maruel@google.come250a9b2009-03-10 17:39:46 +0000506 self.wfile.write('<html><head><title>%s</title></head></html>' %
507 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000508
509 return True
510
511 def CacheNoStoreMaxAgeHandler(self):
512 """This request handler yields a page with the title set to the current
513 system time, and does not allow the page to be stored even though max-age
514 of 60 seconds is specified."""
515
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000516 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000517 return False
518
519 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000520 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000521 self.send_header('Cache-Control', 'max-age=60, no-store')
522 self.end_headers()
523
maruel@google.come250a9b2009-03-10 17:39:46 +0000524 self.wfile.write('<html><head><title>%s</title></head></html>' %
525 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000526
527 return True
528
529
530 def CacheNoTransformHandler(self):
531 """This request handler yields a page with the title set to the current
532 system time, and does not allow the content to transformed during
533 user-agent caching"""
534
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000535 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000536 return False
537
538 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000539 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000540 self.send_header('Cache-Control', 'no-transform')
541 self.end_headers()
542
maruel@google.come250a9b2009-03-10 17:39:46 +0000543 self.wfile.write('<html><head><title>%s</title></head></html>' %
544 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000545
546 return True
547
548 def EchoHeader(self):
549 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000550
ananta@chromium.org219b2062009-10-23 16:09:41 +0000551 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000552
ananta@chromium.org56812d02011-04-07 17:52:05 +0000553 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000554 """This function echoes back the value of a specific request header while
555 allowing caching for 16 hours."""
556
ananta@chromium.org56812d02011-04-07 17:52:05 +0000557 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000558
559 def EchoHeaderHelper(self, echo_header):
560 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000561
ananta@chromium.org219b2062009-10-23 16:09:41 +0000562 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000563 return False
564
565 query_char = self.path.find('?')
566 if query_char != -1:
567 header_name = self.path[query_char+1:]
568
569 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000570 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000571 if echo_header == '/echoheadercache':
572 self.send_header('Cache-control', 'max-age=60000')
573 else:
574 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000575 # insert a vary header to properly indicate that the cachability of this
576 # request is subject to value of the request header being echoed.
577 if len(header_name) > 0:
578 self.send_header('Vary', header_name)
579 self.end_headers()
580
581 if len(header_name) > 0:
582 self.wfile.write(self.headers.getheader(header_name))
583
584 return True
585
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000586 def ReadRequestBody(self):
587 """This function reads the body of the current HTTP request, handling
588 both plain and chunked transfer encoded requests."""
589
590 if self.headers.getheader('transfer-encoding') != 'chunked':
591 length = int(self.headers.getheader('content-length'))
592 return self.rfile.read(length)
593
594 # Read the request body as chunks.
595 body = ""
596 while True:
597 line = self.rfile.readline()
598 length = int(line, 16)
599 if length == 0:
600 self.rfile.readline()
601 break
602 body += self.rfile.read(length)
603 self.rfile.read(2)
604 return body
605
initial.commit94958cf2008-07-26 22:42:52 +0000606 def EchoHandler(self):
607 """This handler just echoes back the payload of the request, for testing
608 form submission."""
609
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000610 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000611 return False
612
613 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000614 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000615 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000616 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000617 return True
618
619 def EchoTitleHandler(self):
620 """This handler is like Echo, but sets the page title to the request."""
621
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000622 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000623 return False
624
625 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000626 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000627 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000628 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000629 self.wfile.write('<html><head><title>')
630 self.wfile.write(request)
631 self.wfile.write('</title></head></html>')
632 return True
633
634 def EchoAllHandler(self):
635 """This handler yields a (more) human-readable page listing information
636 about the request header & contents."""
637
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000638 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000639 return False
640
641 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000642 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000643 self.end_headers()
644 self.wfile.write('<html><head><style>'
645 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
646 '</style></head><body>'
647 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000648 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000649 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000650
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000651 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000652 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000653 params = cgi.parse_qs(qs, keep_blank_values=1)
654
655 for param in params:
656 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000657
658 self.wfile.write('</pre>')
659
660 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
661
662 self.wfile.write('</body></html>')
663 return True
664
665 def DownloadHandler(self):
666 """This handler sends a downloadable file with or without reporting
667 the size (6K)."""
668
669 if self.path.startswith("/download-unknown-size"):
670 send_length = False
671 elif self.path.startswith("/download-known-size"):
672 send_length = True
673 else:
674 return False
675
676 #
677 # The test which uses this functionality is attempting to send
678 # small chunks of data to the client. Use a fairly large buffer
679 # so that we'll fill chrome's IO buffer enough to force it to
680 # actually write the data.
681 # See also the comments in the client-side of this test in
682 # download_uitest.cc
683 #
684 size_chunk1 = 35*1024
685 size_chunk2 = 10*1024
686
687 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000688 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000689 self.send_header('Cache-Control', 'max-age=0')
690 if send_length:
691 self.send_header('Content-Length', size_chunk1 + size_chunk2)
692 self.end_headers()
693
694 # First chunk of data:
695 self.wfile.write("*" * size_chunk1)
696 self.wfile.flush()
697
698 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000699 self.server.wait_for_download = True
700 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000701 self.server.handle_request()
702
703 # Second chunk of data:
704 self.wfile.write("*" * size_chunk2)
705 return True
706
707 def DownloadFinishHandler(self):
708 """This handler just tells the server to finish the current download."""
709
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000710 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000711 return False
712
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000713 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000714 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000715 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000716 self.send_header('Cache-Control', 'max-age=0')
717 self.end_headers()
718 return True
719
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000720 def _ReplaceFileData(self, data, query_parameters):
721 """Replaces matching substrings in a file.
722
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000723 If the 'replace_text' URL query parameter is present, it is expected to be
724 of the form old_text:new_text, which indicates that any old_text strings in
725 the file are replaced with new_text. Multiple 'replace_text' parameters may
726 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000727
728 If the parameters are not present, |data| is returned.
729 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000730
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000731 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000732 replace_text_values = query_dict.get('replace_text', [])
733 for replace_text_value in replace_text_values:
734 replace_text_args = replace_text_value.split(':')
735 if len(replace_text_args) != 2:
736 raise ValueError(
737 'replace_text must be of form old_text:new_text. Actual value: %s' %
738 replace_text_value)
739 old_text_b64, new_text_b64 = replace_text_args
740 old_text = base64.urlsafe_b64decode(old_text_b64)
741 new_text = base64.urlsafe_b64decode(new_text_b64)
742 data = data.replace(old_text, new_text)
743 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000744
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000745 def ZipFileHandler(self):
746 """This handler sends the contents of the requested file in compressed form.
747 Can pass in a parameter that specifies that the content length be
748 C - the compressed size (OK),
749 U - the uncompressed size (Non-standard, but handled),
750 S - less than compressed (OK because we keep going),
751 M - larger than compressed but less than uncompressed (an error),
752 L - larger than uncompressed (an error)
753 Example: compressedfiles/Picture_1.doc?C
754 """
755
756 prefix = "/compressedfiles/"
757 if not self.path.startswith(prefix):
758 return False
759
760 # Consume a request body if present.
761 if self.command == 'POST' or self.command == 'PUT' :
762 self.ReadRequestBody()
763
764 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
765
766 if not query in ('C', 'U', 'S', 'M', 'L'):
767 return False
768
769 sub_path = url_path[len(prefix):]
770 entries = sub_path.split('/')
771 file_path = os.path.join(self.server.data_dir, *entries)
772 if os.path.isdir(file_path):
773 file_path = os.path.join(file_path, 'index.html')
774
775 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000776 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000777 self.send_error(404)
778 return True
779
780 f = open(file_path, "rb")
781 data = f.read()
782 uncompressed_len = len(data)
783 f.close()
784
785 # Compress the data.
786 data = zlib.compress(data)
787 compressed_len = len(data)
788
789 content_length = compressed_len
790 if query == 'U':
791 content_length = uncompressed_len
792 elif query == 'S':
793 content_length = compressed_len / 2
794 elif query == 'M':
795 content_length = (compressed_len + uncompressed_len) / 2
796 elif query == 'L':
797 content_length = compressed_len + uncompressed_len
798
799 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000800 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000801 self.send_header('Content-encoding', 'deflate')
802 self.send_header('Connection', 'close')
803 self.send_header('Content-Length', content_length)
804 self.send_header('ETag', '\'' + file_path + '\'')
805 self.end_headers()
806
807 self.wfile.write(data)
808
809 return True
810
initial.commit94958cf2008-07-26 22:42:52 +0000811 def FileHandler(self):
812 """This handler sends the contents of the requested file. Wow, it's like
813 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000814
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000815 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000816 if not self.path.startswith(prefix):
817 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000818 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000819
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000820 def PostOnlyFileHandler(self):
821 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000822
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000823 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000824 if not self.path.startswith(prefix):
825 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000826 return self._FileHandlerHelper(prefix)
827
828 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000829 request_body = ''
830 if self.command == 'POST' or self.command == 'PUT':
831 # Consume a request body if present.
832 request_body = self.ReadRequestBody()
833
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000834 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000835 query_dict = cgi.parse_qs(query)
836
837 expected_body = query_dict.get('expected_body', [])
838 if expected_body and request_body not in expected_body:
839 self.send_response(404)
840 self.end_headers()
841 self.wfile.write('')
842 return True
843
844 expected_headers = query_dict.get('expected_headers', [])
845 for expected_header in expected_headers:
846 header_name, expected_value = expected_header.split(':')
847 if self.headers.getheader(header_name) != expected_value:
848 self.send_response(404)
849 self.end_headers()
850 self.wfile.write('')
851 return True
852
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000853 sub_path = url_path[len(prefix):]
854 entries = sub_path.split('/')
855 file_path = os.path.join(self.server.data_dir, *entries)
856 if os.path.isdir(file_path):
857 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000858
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000859 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000860 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000861 self.send_error(404)
862 return True
863
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000864 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000865 data = f.read()
866 f.close()
867
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000868 data = self._ReplaceFileData(data, query)
869
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000870 old_protocol_version = self.protocol_version
871
initial.commit94958cf2008-07-26 22:42:52 +0000872 # If file.mock-http-headers exists, it contains the headers we
873 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000874 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000875 if os.path.isfile(headers_path):
876 f = open(headers_path, "r")
877
878 # "HTTP/1.1 200 OK"
879 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000880 http_major, http_minor, status_code = re.findall(
881 'HTTP/(\d+).(\d+) (\d+)', response)[0]
882 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000883 self.send_response(int(status_code))
884
885 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000886 header_values = re.findall('(\S+):\s*(.*)', line)
887 if len(header_values) > 0:
888 # "name: value"
889 name, value = header_values[0]
890 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000891 f.close()
892 else:
893 # Could be more generic once we support mime-type sniffing, but for
894 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000895
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000896 range_header = self.headers.get('Range')
897 if range_header and range_header.startswith('bytes='):
898 # Note this doesn't handle all valid byte range_header values (i.e.
899 # left open ended ones), just enough for what we needed so far.
900 range_header = range_header[6:].split('-')
901 start = int(range_header[0])
902 if range_header[1]:
903 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000904 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000905 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000906
907 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000908 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
909 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000910 self.send_header('Content-Range', content_range)
911 data = data[start: end + 1]
912 else:
913 self.send_response(200)
914
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000915 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000916 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000917 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000918 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000919 self.end_headers()
920
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000921 if (self.command != 'HEAD'):
922 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000923
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000924 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000925 return True
926
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000927 def SetCookieHandler(self):
928 """This handler just sets a cookie, for testing cookie handling."""
929
930 if not self._ShouldHandleRequest("/set-cookie"):
931 return False
932
933 query_char = self.path.find('?')
934 if query_char != -1:
935 cookie_values = self.path[query_char + 1:].split('&')
936 else:
937 cookie_values = ("",)
938 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000939 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000940 for cookie_value in cookie_values:
941 self.send_header('Set-Cookie', '%s' % cookie_value)
942 self.end_headers()
943 for cookie_value in cookie_values:
944 self.wfile.write('%s' % cookie_value)
945 return True
946
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000947 def SetManyCookiesHandler(self):
948 """This handler just sets a given number of cookies, for testing handling
949 of large numbers of cookies."""
950
951 if not self._ShouldHandleRequest("/set-many-cookies"):
952 return False
953
954 query_char = self.path.find('?')
955 if query_char != -1:
956 num_cookies = int(self.path[query_char + 1:])
957 else:
958 num_cookies = 0
959 self.send_response(200)
960 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000961 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000962 self.send_header('Set-Cookie', 'a=')
963 self.end_headers()
964 self.wfile.write('%d cookies were sent' % num_cookies)
965 return True
966
mattm@chromium.org983fc462012-06-30 00:52:08 +0000967 def ExpectAndSetCookieHandler(self):
968 """Expects some cookies to be sent, and if they are, sets more cookies.
969
970 The expect parameter specifies a required cookie. May be specified multiple
971 times.
972 The set parameter specifies a cookie to set if all required cookies are
973 preset. May be specified multiple times.
974 The data parameter specifies the response body data to be returned."""
975
976 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
977 return False
978
979 _, _, _, _, query, _ = urlparse.urlparse(self.path)
980 query_dict = cgi.parse_qs(query)
981 cookies = set()
982 if 'Cookie' in self.headers:
983 cookie_header = self.headers.getheader('Cookie')
984 cookies.update([s.strip() for s in cookie_header.split(';')])
985 got_all_expected_cookies = True
986 for expected_cookie in query_dict.get('expect', []):
987 if expected_cookie not in cookies:
988 got_all_expected_cookies = False
989 self.send_response(200)
990 self.send_header('Content-Type', 'text/html')
991 if got_all_expected_cookies:
992 for cookie_value in query_dict.get('set', []):
993 self.send_header('Set-Cookie', '%s' % cookie_value)
994 self.end_headers()
995 for data_value in query_dict.get('data', []):
996 self.wfile.write(data_value)
997 return True
998
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000999 def SetHeaderHandler(self):
1000 """This handler sets a response header. Parameters are in the
1001 key%3A%20value&key2%3A%20value2 format."""
1002
1003 if not self._ShouldHandleRequest("/set-header"):
1004 return False
1005
1006 query_char = self.path.find('?')
1007 if query_char != -1:
1008 headers_values = self.path[query_char + 1:].split('&')
1009 else:
1010 headers_values = ("",)
1011 self.send_response(200)
1012 self.send_header('Content-Type', 'text/html')
1013 for header_value in headers_values:
1014 header_value = urllib.unquote(header_value)
1015 (key, value) = header_value.split(': ', 1)
1016 self.send_header(key, value)
1017 self.end_headers()
1018 for header_value in headers_values:
1019 self.wfile.write('%s' % header_value)
1020 return True
1021
initial.commit94958cf2008-07-26 22:42:52 +00001022 def AuthBasicHandler(self):
1023 """This handler tests 'Basic' authentication. It just sends a page with
1024 title 'user/pass' if you succeed."""
1025
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001026 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001027 return False
1028
1029 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001030 expected_password = 'secret'
1031 realm = 'testrealm'
1032 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001033
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001034 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1035 query_params = cgi.parse_qs(query, True)
1036 if 'set-cookie-if-challenged' in query_params:
1037 set_cookie_if_challenged = True
1038 if 'password' in query_params:
1039 expected_password = query_params['password'][0]
1040 if 'realm' in query_params:
1041 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001042
initial.commit94958cf2008-07-26 22:42:52 +00001043 auth = self.headers.getheader('authorization')
1044 try:
1045 if not auth:
1046 raise Exception('no auth')
1047 b64str = re.findall(r'Basic (\S+)', auth)[0]
1048 userpass = base64.b64decode(b64str)
1049 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001050 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001051 raise Exception('wrong password')
1052 except Exception, e:
1053 # Authentication failed.
1054 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001055 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001056 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001057 if set_cookie_if_challenged:
1058 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001059 self.end_headers()
1060 self.wfile.write('<html><head>')
1061 self.wfile.write('<title>Denied: %s</title>' % e)
1062 self.wfile.write('</head><body>')
1063 self.wfile.write('auth=%s<p>' % auth)
1064 self.wfile.write('b64str=%s<p>' % b64str)
1065 self.wfile.write('username: %s<p>' % username)
1066 self.wfile.write('userpass: %s<p>' % userpass)
1067 self.wfile.write('password: %s<p>' % password)
1068 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1069 self.wfile.write('</body></html>')
1070 return True
1071
1072 # Authentication successful. (Return a cachable response to allow for
1073 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001074 old_protocol_version = self.protocol_version
1075 self.protocol_version = "HTTP/1.1"
1076
initial.commit94958cf2008-07-26 22:42:52 +00001077 if_none_match = self.headers.getheader('if-none-match')
1078 if if_none_match == "abc":
1079 self.send_response(304)
1080 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001081 elif url_path.endswith(".gif"):
1082 # Using chrome/test/data/google/logo.gif as the test image
1083 test_image_path = ['google', 'logo.gif']
1084 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1085 if not os.path.isfile(gif_path):
1086 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001087 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001088 return True
1089
1090 f = open(gif_path, "rb")
1091 data = f.read()
1092 f.close()
1093
1094 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001095 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001096 self.send_header('Cache-control', 'max-age=60000')
1097 self.send_header('Etag', 'abc')
1098 self.end_headers()
1099 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001100 else:
1101 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001102 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001103 self.send_header('Cache-control', 'max-age=60000')
1104 self.send_header('Etag', 'abc')
1105 self.end_headers()
1106 self.wfile.write('<html><head>')
1107 self.wfile.write('<title>%s/%s</title>' % (username, password))
1108 self.wfile.write('</head><body>')
1109 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001110 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001111 self.wfile.write('</body></html>')
1112
rvargas@google.com54453b72011-05-19 01:11:11 +00001113 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001114 return True
1115
tonyg@chromium.org75054202010-03-31 22:06:10 +00001116 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001117 """Returns a nonce that's stable per request path for the server's lifetime.
1118 This is a fake implementation. A real implementation would only use a given
1119 nonce a single time (hence the name n-once). However, for the purposes of
1120 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001121
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001122 Args:
1123 force_reset: Iff set, the nonce will be changed. Useful for testing the
1124 "stale" response.
1125 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001126
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001127 if force_reset or not self.server.nonce_time:
1128 self.server.nonce_time = time.time()
1129 return hashlib.md5('privatekey%s%d' %
1130 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001131
1132 def AuthDigestHandler(self):
1133 """This handler tests 'Digest' authentication.
1134
1135 It just sends a page with title 'user/pass' if you succeed.
1136
1137 A stale response is sent iff "stale" is present in the request path.
1138 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001139
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001140 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001141 return False
1142
tonyg@chromium.org75054202010-03-31 22:06:10 +00001143 stale = 'stale' in self.path
1144 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001145 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001146 password = 'secret'
1147 realm = 'testrealm'
1148
1149 auth = self.headers.getheader('authorization')
1150 pairs = {}
1151 try:
1152 if not auth:
1153 raise Exception('no auth')
1154 if not auth.startswith('Digest'):
1155 raise Exception('not digest')
1156 # Pull out all the name="value" pairs as a dictionary.
1157 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1158
1159 # Make sure it's all valid.
1160 if pairs['nonce'] != nonce:
1161 raise Exception('wrong nonce')
1162 if pairs['opaque'] != opaque:
1163 raise Exception('wrong opaque')
1164
1165 # Check the 'response' value and make sure it matches our magic hash.
1166 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001167 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001168 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001169 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001170 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001171 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001172 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1173 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001174 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001175
1176 if pairs['response'] != response:
1177 raise Exception('wrong password')
1178 except Exception, e:
1179 # Authentication failed.
1180 self.send_response(401)
1181 hdr = ('Digest '
1182 'realm="%s", '
1183 'domain="/", '
1184 'qop="auth", '
1185 'algorithm=MD5, '
1186 'nonce="%s", '
1187 'opaque="%s"') % (realm, nonce, opaque)
1188 if stale:
1189 hdr += ', stale="TRUE"'
1190 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001191 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001192 self.end_headers()
1193 self.wfile.write('<html><head>')
1194 self.wfile.write('<title>Denied: %s</title>' % e)
1195 self.wfile.write('</head><body>')
1196 self.wfile.write('auth=%s<p>' % auth)
1197 self.wfile.write('pairs=%s<p>' % pairs)
1198 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1199 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1200 self.wfile.write('</body></html>')
1201 return True
1202
1203 # Authentication successful.
1204 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001205 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001206 self.end_headers()
1207 self.wfile.write('<html><head>')
1208 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1209 self.wfile.write('</head><body>')
1210 self.wfile.write('auth=%s<p>' % auth)
1211 self.wfile.write('pairs=%s<p>' % pairs)
1212 self.wfile.write('</body></html>')
1213
1214 return True
1215
1216 def SlowServerHandler(self):
1217 """Wait for the user suggested time before responding. The syntax is
1218 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001219
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001220 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001221 return False
1222 query_char = self.path.find('?')
1223 wait_sec = 1.0
1224 if query_char >= 0:
1225 try:
1226 wait_sec = int(self.path[query_char + 1:])
1227 except ValueError:
1228 pass
1229 time.sleep(wait_sec)
1230 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001231 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001232 self.end_headers()
1233 self.wfile.write("waited %d seconds" % wait_sec)
1234 return True
1235
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001236 def ChunkedServerHandler(self):
1237 """Send chunked response. Allows to specify chunks parameters:
1238 - waitBeforeHeaders - ms to wait before sending headers
1239 - waitBetweenChunks - ms to wait between chunks
1240 - chunkSize - size of each chunk in bytes
1241 - chunksNumber - number of chunks
1242 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1243 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001244
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001245 if not self._ShouldHandleRequest("/chunked"):
1246 return False
1247 query_char = self.path.find('?')
1248 chunkedSettings = {'waitBeforeHeaders' : 0,
1249 'waitBetweenChunks' : 0,
1250 'chunkSize' : 5,
1251 'chunksNumber' : 5}
1252 if query_char >= 0:
1253 params = self.path[query_char + 1:].split('&')
1254 for param in params:
1255 keyValue = param.split('=')
1256 if len(keyValue) == 2:
1257 try:
1258 chunkedSettings[keyValue[0]] = int(keyValue[1])
1259 except ValueError:
1260 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001261 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001262 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1263 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001264 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001265 self.send_header('Connection', 'close')
1266 self.send_header('Transfer-Encoding', 'chunked')
1267 self.end_headers()
1268 # Chunked encoding: sending all chunks, then final zero-length chunk and
1269 # then final CRLF.
1270 for i in range(0, chunkedSettings['chunksNumber']):
1271 if i > 0:
1272 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1273 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001274 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001275 self.sendChunkHelp('')
1276 return True
1277
initial.commit94958cf2008-07-26 22:42:52 +00001278 def ContentTypeHandler(self):
1279 """Returns a string of html with the given content type. E.g.,
1280 /contenttype?text/css returns an html file with the Content-Type
1281 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001282
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001283 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001284 return False
1285 query_char = self.path.find('?')
1286 content_type = self.path[query_char + 1:].strip()
1287 if not content_type:
1288 content_type = 'text/html'
1289 self.send_response(200)
1290 self.send_header('Content-Type', content_type)
1291 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001292 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001293 return True
1294
creis@google.com2f4f6a42011-03-25 19:44:19 +00001295 def NoContentHandler(self):
1296 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001297
creis@google.com2f4f6a42011-03-25 19:44:19 +00001298 if not self._ShouldHandleRequest("/nocontent"):
1299 return False
1300 self.send_response(204)
1301 self.end_headers()
1302 return True
1303
initial.commit94958cf2008-07-26 22:42:52 +00001304 def ServerRedirectHandler(self):
1305 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001306 '/server-redirect?http://foo.bar/asdf' to redirect to
1307 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001308
1309 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001310 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001311 return False
1312
1313 query_char = self.path.find('?')
1314 if query_char < 0 or len(self.path) <= query_char + 1:
1315 self.sendRedirectHelp(test_name)
1316 return True
1317 dest = self.path[query_char + 1:]
1318
1319 self.send_response(301) # moved permanently
1320 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001321 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001322 self.end_headers()
1323 self.wfile.write('<html><head>')
1324 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1325
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001326 return True
initial.commit94958cf2008-07-26 22:42:52 +00001327
1328 def ClientRedirectHandler(self):
1329 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001330 '/client-redirect?http://foo.bar/asdf' to redirect to
1331 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001332
1333 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001334 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001335 return False
1336
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001337 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001338 if query_char < 0 or len(self.path) <= query_char + 1:
1339 self.sendRedirectHelp(test_name)
1340 return True
1341 dest = self.path[query_char + 1:]
1342
1343 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001344 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001345 self.end_headers()
1346 self.wfile.write('<html><head>')
1347 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1348 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1349
1350 return True
1351
tony@chromium.org03266982010-03-05 03:18:42 +00001352 def MultipartHandler(self):
1353 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001354
tony@chromium.org4cb88302011-09-27 22:13:49 +00001355 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001356 if not self._ShouldHandleRequest(test_name):
1357 return False
1358
1359 num_frames = 10
1360 bound = '12345'
1361 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001362 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001363 'multipart/x-mixed-replace;boundary=' + bound)
1364 self.end_headers()
1365
1366 for i in xrange(num_frames):
1367 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001368 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001369 self.wfile.write('<title>page ' + str(i) + '</title>')
1370 self.wfile.write('page ' + str(i))
1371
1372 self.wfile.write('--' + bound + '--')
1373 return True
1374
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001375 def GetSSLSessionCacheHandler(self):
1376 """Send a reply containing a log of the session cache operations."""
1377
1378 if not self._ShouldHandleRequest('/ssl-session-cache'):
1379 return False
1380
1381 self.send_response(200)
1382 self.send_header('Content-Type', 'text/plain')
1383 self.end_headers()
1384 try:
1385 for (action, sessionID) in self.server.session_cache.log:
1386 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001387 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001388 self.wfile.write('Pass --https-record-resume in order to use' +
1389 ' this request')
1390 return True
1391
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001392 def SSLManySmallRecords(self):
1393 """Sends a reply consisting of a variety of small writes. These will be
1394 translated into a series of small SSL records when used over an HTTPS
1395 server."""
1396
1397 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1398 return False
1399
1400 self.send_response(200)
1401 self.send_header('Content-Type', 'text/plain')
1402 self.end_headers()
1403
1404 # Write ~26K of data, in 1350 byte chunks
1405 for i in xrange(20):
1406 self.wfile.write('*' * 1350)
1407 self.wfile.flush()
1408 return True
1409
agl@chromium.org04700be2013-03-02 18:40:41 +00001410 def GetChannelID(self):
1411 """Send a reply containing the hashed ChannelID that the client provided."""
1412
1413 if not self._ShouldHandleRequest('/channel-id'):
1414 return False
1415
1416 self.send_response(200)
1417 self.send_header('Content-Type', 'text/plain')
1418 self.end_headers()
1419 channel_id = self.server.tlsConnection.channel_id.tostring()
1420 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1421 return True
1422
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001423 def CloseSocketHandler(self):
1424 """Closes the socket without sending anything."""
1425
1426 if not self._ShouldHandleRequest('/close-socket'):
1427 return False
1428
1429 self.wfile.close()
1430 return True
1431
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001432 def RangeResetHandler(self):
1433 """Send data broken up by connection resets every N (default 4K) bytes.
1434 Support range requests. If the data requested doesn't straddle a reset
1435 boundary, it will all be sent. Used for testing resuming downloads."""
1436
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001437 def DataForRange(start, end):
1438 """Data to be provided for a particular range of bytes."""
1439 # Offset and scale to avoid too obvious (and hence potentially
1440 # collidable) data.
1441 return ''.join([chr(y % 256)
1442 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1443
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001444 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
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001455 rst_limit = -1
1456 token = 'DEFAULT'
1457 fail_precondition = 0
1458 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001459
1460 # Parse the query
1461 qdict = urlparse.parse_qs(query, True)
1462 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001463 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001464 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001465 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001466 if 'token' in qdict:
1467 # Identifying token for stateful tests.
1468 token = qdict['token'][0]
1469 if 'rst_limit' in qdict:
1470 # Max number of rsts for a given token.
1471 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001472 if 'bounce_range' in qdict:
1473 respond_to_range = False
1474 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001475 # Note that hold_for_signal will not work with null range requests;
1476 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001477 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001478 if 'no_verifiers' in qdict:
1479 send_verifiers = False
1480 if 'fail_precondition' in qdict:
1481 fail_precondition = int(qdict['fail_precondition'][0])
1482
1483 # Record already set information, or set it.
1484 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1485 if rst_limit != 0:
1486 TestPageHandler.rst_limits[token] -= 1
1487 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1488 token, fail_precondition)
1489 if fail_precondition != 0:
1490 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001491
1492 first_byte = 0
1493 last_byte = size - 1
1494
1495 # Does that define what we want to return, or do we need to apply
1496 # a range?
1497 range_response = False
1498 range_header = self.headers.getheader('range')
1499 if range_header and respond_to_range:
1500 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1501 if mo.group(1):
1502 first_byte = int(mo.group(1))
1503 if mo.group(2):
1504 last_byte = int(mo.group(2))
1505 if last_byte > size - 1:
1506 last_byte = size - 1
1507 range_response = True
1508 if last_byte < first_byte:
1509 return False
1510
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001511 if (fail_precondition and
1512 (self.headers.getheader('If-Modified-Since') or
1513 self.headers.getheader('If-Match'))):
1514 self.send_response(412)
1515 self.end_headers()
1516 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001517
1518 if range_response:
1519 self.send_response(206)
1520 self.send_header('Content-Range',
1521 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1522 else:
1523 self.send_response(200)
1524 self.send_header('Content-Type', 'application/octet-stream')
1525 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001526 if send_verifiers:
1527 self.send_header('Etag', '"XYZZY"')
1528 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001529 self.end_headers()
1530
1531 if hold_for_signal:
1532 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1533 # a single byte, the self.server.handle_request() below hangs
1534 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001535 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001536 first_byte = first_byte + 1
1537 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001538 self.server.wait_for_download = True
1539 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001540 self.server.handle_request()
1541
1542 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001543 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001544 # No RST has been requested in this range, so we don't need to
1545 # do anything fancy; just write the data and let the python
1546 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001547 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001548 self.wfile.flush()
1549 return True
1550
1551 # We're resetting the connection part way in; go to the RST
1552 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001553 # Because socket semantics do not guarantee that all the data will be
1554 # sent when using the linger semantics to hard close a socket,
1555 # we send the data and then wait for our peer to release us
1556 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001557 data = DataForRange(first_byte, possible_rst)
1558 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001559 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001560 self.server.wait_for_download = True
1561 while self.server.wait_for_download:
1562 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001563 l_onoff = 1 # Linger is active.
1564 l_linger = 0 # Seconds to linger for.
1565 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1566 struct.pack('ii', l_onoff, l_linger))
1567
1568 # Close all duplicates of the underlying socket to force the RST.
1569 self.wfile.close()
1570 self.rfile.close()
1571 self.connection.close()
1572
1573 return True
1574
initial.commit94958cf2008-07-26 22:42:52 +00001575 def DefaultResponseHandler(self):
1576 """This is the catch-all response handler for requests that aren't handled
1577 by one of the special handlers above.
1578 Note that we specify the content-length as without it the https connection
1579 is not closed properly (and the browser keeps expecting data)."""
1580
1581 contents = "Default response given for path: " + self.path
1582 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001583 self.send_header('Content-Type', 'text/html')
1584 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001585 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001586 if (self.command != 'HEAD'):
1587 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001588 return True
1589
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001590 def RedirectConnectHandler(self):
1591 """Sends a redirect to the CONNECT request for www.redirect.com. This
1592 response is not specified by the RFC, so the browser should not follow
1593 the redirect."""
1594
1595 if (self.path.find("www.redirect.com") < 0):
1596 return False
1597
1598 dest = "http://www.destination.com/foo.js"
1599
1600 self.send_response(302) # moved temporarily
1601 self.send_header('Location', dest)
1602 self.send_header('Connection', 'close')
1603 self.end_headers()
1604 return True
1605
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001606 def ServerAuthConnectHandler(self):
1607 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1608 response doesn't make sense because the proxy server cannot request
1609 server authentication."""
1610
1611 if (self.path.find("www.server-auth.com") < 0):
1612 return False
1613
1614 challenge = 'Basic realm="WallyWorld"'
1615
1616 self.send_response(401) # unauthorized
1617 self.send_header('WWW-Authenticate', challenge)
1618 self.send_header('Connection', 'close')
1619 self.end_headers()
1620 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001621
1622 def DefaultConnectResponseHandler(self):
1623 """This is the catch-all response handler for CONNECT requests that aren't
1624 handled by one of the special handlers above. Real Web servers respond
1625 with 400 to CONNECT requests."""
1626
1627 contents = "Your client has issued a malformed or illegal request."
1628 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001629 self.send_header('Content-Type', 'text/html')
1630 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001631 self.end_headers()
1632 self.wfile.write(contents)
1633 return True
1634
initial.commit94958cf2008-07-26 22:42:52 +00001635 # called by the redirect handling function when there is no parameter
1636 def sendRedirectHelp(self, redirect_name):
1637 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001638 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001639 self.end_headers()
1640 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1641 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1642 self.wfile.write('</body></html>')
1643
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001644 # called by chunked handling function
1645 def sendChunkHelp(self, chunk):
1646 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1647 self.wfile.write('%X\r\n' % len(chunk))
1648 self.wfile.write(chunk)
1649 self.wfile.write('\r\n')
1650
akalin@chromium.org154bb132010-11-12 02:20:27 +00001651
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001652class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001653 def __init__(self, request, client_address, socket_server):
1654 handlers = [self.OCSPResponse]
1655 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001656 testserver_base.BasePageHandler.__init__(self, request, client_address,
1657 socket_server, [], handlers, [],
1658 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001659
1660 def OCSPResponse(self):
1661 self.send_response(200)
1662 self.send_header('Content-Type', 'application/ocsp-response')
1663 self.send_header('Content-Length', str(len(self.ocsp_response)))
1664 self.end_headers()
1665
1666 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001667
mattm@chromium.org830a3712012-11-07 23:00:07 +00001668
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001669class TCPEchoHandler(SocketServer.BaseRequestHandler):
1670 """The RequestHandler class for TCP echo server.
1671
1672 It is instantiated once per connection to the server, and overrides the
1673 handle() method to implement communication to the client.
1674 """
1675
1676 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001677 """Handles the request from the client and constructs a response."""
1678
1679 data = self.request.recv(65536).strip()
1680 # Verify the "echo request" message received from the client. Send back
1681 # "echo response" message if "echo request" message is valid.
1682 try:
1683 return_data = echo_message.GetEchoResponseData(data)
1684 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001685 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001686 except ValueError:
1687 return
1688
1689 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001690
1691
1692class UDPEchoHandler(SocketServer.BaseRequestHandler):
1693 """The RequestHandler class for UDP echo server.
1694
1695 It is instantiated once per connection to the server, and overrides the
1696 handle() method to implement communication to the client.
1697 """
1698
1699 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001700 """Handles the request from the client and constructs a response."""
1701
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001702 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001703 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001704 # Verify the "echo request" message received from the client. Send back
1705 # "echo response" message if "echo request" message is valid.
1706 try:
1707 return_data = echo_message.GetEchoResponseData(data)
1708 if not return_data:
1709 return
1710 except ValueError:
1711 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001712 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001713
1714
bashi@chromium.org33233532012-09-08 17:37:24 +00001715class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1716 """A request handler that behaves as a proxy server which requires
1717 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1718 """
1719
1720 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1721
1722 def parse_request(self):
1723 """Overrides parse_request to check credential."""
1724
1725 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1726 return False
1727
1728 auth = self.headers.getheader('Proxy-Authorization')
1729 if auth != self._AUTH_CREDENTIAL:
1730 self.send_response(407)
1731 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1732 self.end_headers()
1733 return False
1734
1735 return True
1736
1737 def _start_read_write(self, sock):
1738 sock.setblocking(0)
1739 self.request.setblocking(0)
1740 rlist = [self.request, sock]
1741 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001742 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001743 if errors:
1744 self.send_response(500)
1745 self.end_headers()
1746 return
1747 for s in ready_sockets:
1748 received = s.recv(1024)
1749 if len(received) == 0:
1750 return
1751 if s == self.request:
1752 other = sock
1753 else:
1754 other = self.request
1755 other.send(received)
1756
1757 def _do_common_method(self):
1758 url = urlparse.urlparse(self.path)
1759 port = url.port
1760 if not port:
1761 if url.scheme == 'http':
1762 port = 80
1763 elif url.scheme == 'https':
1764 port = 443
1765 if not url.hostname or not port:
1766 self.send_response(400)
1767 self.end_headers()
1768 return
1769
1770 if len(url.path) == 0:
1771 path = '/'
1772 else:
1773 path = url.path
1774 if len(url.query) > 0:
1775 path = '%s?%s' % (url.path, url.query)
1776
1777 sock = None
1778 try:
1779 sock = socket.create_connection((url.hostname, port))
1780 sock.send('%s %s %s\r\n' % (
1781 self.command, path, self.protocol_version))
1782 for header in self.headers.headers:
1783 header = header.strip()
1784 if (header.lower().startswith('connection') or
1785 header.lower().startswith('proxy')):
1786 continue
1787 sock.send('%s\r\n' % header)
1788 sock.send('\r\n')
1789 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001790 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001791 self.send_response(500)
1792 self.end_headers()
1793 finally:
1794 if sock is not None:
1795 sock.close()
1796
1797 def do_CONNECT(self):
1798 try:
1799 pos = self.path.rfind(':')
1800 host = self.path[:pos]
1801 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001802 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001803 self.send_response(400)
1804 self.end_headers()
1805
1806 try:
1807 sock = socket.create_connection((host, port))
1808 self.send_response(200, 'Connection established')
1809 self.end_headers()
1810 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001811 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001812 self.send_response(500)
1813 self.end_headers()
1814 finally:
1815 sock.close()
1816
1817 def do_GET(self):
1818 self._do_common_method()
1819
1820 def do_HEAD(self):
1821 self._do_common_method()
1822
1823
mattm@chromium.org830a3712012-11-07 23:00:07 +00001824class ServerRunner(testserver_base.TestServerRunner):
1825 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001826
mattm@chromium.org830a3712012-11-07 23:00:07 +00001827 def __init__(self):
1828 super(ServerRunner, self).__init__()
1829 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001830
mattm@chromium.org830a3712012-11-07 23:00:07 +00001831 def __make_data_dir(self):
1832 if self.options.data_dir:
1833 if not os.path.isdir(self.options.data_dir):
1834 raise testserver_base.OptionError('specified data dir not found: ' +
1835 self.options.data_dir + ' exiting...')
1836 my_data_dir = self.options.data_dir
1837 else:
1838 # Create the default path to our data dir, relative to the exe dir.
1839 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1840 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001841
mattm@chromium.org830a3712012-11-07 23:00:07 +00001842 #TODO(ibrar): Must use Find* funtion defined in google\tools
1843 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001844
mattm@chromium.org830a3712012-11-07 23:00:07 +00001845 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001846
mattm@chromium.org830a3712012-11-07 23:00:07 +00001847 def create_server(self, server_data):
1848 port = self.options.port
1849 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001850
mattm@chromium.org830a3712012-11-07 23:00:07 +00001851 if self.options.server_type == SERVER_HTTP:
1852 if self.options.https:
1853 pem_cert_and_key = None
1854 if self.options.cert_and_key_file:
1855 if not os.path.isfile(self.options.cert_and_key_file):
1856 raise testserver_base.OptionError(
1857 'specified server cert file not found: ' +
1858 self.options.cert_and_key_file + ' exiting...')
1859 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001860 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001861 # generate a new certificate and run an OCSP server for it.
1862 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001863 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001864 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001865
mattm@chromium.org830a3712012-11-07 23:00:07 +00001866 ocsp_der = None
1867 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001868
mattm@chromium.org830a3712012-11-07 23:00:07 +00001869 if self.options.ocsp == 'ok':
1870 ocsp_state = minica.OCSP_STATE_GOOD
1871 elif self.options.ocsp == 'revoked':
1872 ocsp_state = minica.OCSP_STATE_REVOKED
1873 elif self.options.ocsp == 'invalid':
1874 ocsp_state = minica.OCSP_STATE_INVALID
1875 elif self.options.ocsp == 'unauthorized':
1876 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1877 elif self.options.ocsp == 'unknown':
1878 ocsp_state = minica.OCSP_STATE_UNKNOWN
1879 else:
1880 raise testserver_base.OptionError('unknown OCSP status: ' +
1881 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001882
mattm@chromium.org830a3712012-11-07 23:00:07 +00001883 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1884 subject = "127.0.0.1",
1885 ocsp_url = ("http://%s:%d/ocsp" %
1886 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00001887 ocsp_state = ocsp_state,
1888 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001889
1890 self.__ocsp_server.ocsp_response = ocsp_der
1891
1892 for ca_cert in self.options.ssl_client_ca:
1893 if not os.path.isfile(ca_cert):
1894 raise testserver_base.OptionError(
1895 'specified trusted client CA file not found: ' + ca_cert +
1896 ' exiting...')
1897 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1898 self.options.ssl_client_auth,
1899 self.options.ssl_client_ca,
1900 self.options.ssl_bulk_cipher,
1901 self.options.record_resume,
1902 self.options.tls_intolerant)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001903 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001904 else:
1905 server = HTTPServer((host, port), TestPageHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001906 print 'HTTP server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001907
1908 server.data_dir = self.__make_data_dir()
1909 server.file_root_url = self.options.file_root_url
1910 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001911 elif self.options.server_type == SERVER_WEBSOCKET:
1912 # Launch pywebsocket via WebSocketServer.
1913 logger = logging.getLogger()
1914 logger.addHandler(logging.StreamHandler())
1915 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1916 # is required to work correctly. It should be fixed from pywebsocket side.
1917 os.chdir(self.__make_data_dir())
1918 websocket_options = WebSocketOptions(host, port, '.')
1919 if self.options.cert_and_key_file:
1920 websocket_options.use_tls = True
1921 websocket_options.private_key = self.options.cert_and_key_file
1922 websocket_options.certificate = self.options.cert_and_key_file
1923 if self.options.ssl_client_auth:
1924 websocket_options.tls_client_auth = True
1925 if len(self.options.ssl_client_ca) != 1:
1926 raise testserver_base.OptionError(
1927 'one trusted client CA file should be specified')
1928 if not os.path.isfile(self.options.ssl_client_ca[0]):
1929 raise testserver_base.OptionError(
1930 'specified trusted client CA file not found: ' +
1931 self.options.ssl_client_ca[0] + ' exiting...')
1932 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
1933 server = WebSocketServer(websocket_options)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001934 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001935 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001936 elif self.options.server_type == SERVER_TCP_ECHO:
1937 # Used for generating the key (randomly) that encodes the "echo request"
1938 # message.
1939 random.seed()
1940 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001941 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001942 server_data['port'] = server.server_port
1943 elif self.options.server_type == SERVER_UDP_ECHO:
1944 # Used for generating the key (randomly) that encodes the "echo request"
1945 # message.
1946 random.seed()
1947 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001948 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001949 server_data['port'] = server.server_port
1950 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
1951 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001952 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001953 server_data['port'] = server.server_port
1954 elif self.options.server_type == SERVER_FTP:
1955 my_data_dir = self.__make_data_dir()
1956
1957 # Instantiate a dummy authorizer for managing 'virtual' users
1958 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1959
1960 # Define a new user having full r/w permissions and a read-only
1961 # anonymous user
1962 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1963
1964 authorizer.add_anonymous(my_data_dir)
1965
1966 # Instantiate FTP handler class
1967 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1968 ftp_handler.authorizer = authorizer
1969
1970 # Define a customized banner (string returned when client connects)
1971 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1972 pyftpdlib.ftpserver.__ver__)
1973
1974 # Instantiate FTP server class and listen to address:port
1975 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
1976 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001977 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001978 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001979 raise testserver_base.OptionError('unknown server type' +
1980 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00001981
mattm@chromium.org830a3712012-11-07 23:00:07 +00001982 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001983
mattm@chromium.org830a3712012-11-07 23:00:07 +00001984 def run_server(self):
1985 if self.__ocsp_server:
1986 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001987
mattm@chromium.org830a3712012-11-07 23:00:07 +00001988 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001989
mattm@chromium.org830a3712012-11-07 23:00:07 +00001990 if self.__ocsp_server:
1991 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001992
mattm@chromium.org830a3712012-11-07 23:00:07 +00001993 def add_options(self):
1994 testserver_base.TestServerRunner.add_options(self)
1995 self.option_parser.add_option('-f', '--ftp', action='store_const',
1996 const=SERVER_FTP, default=SERVER_HTTP,
1997 dest='server_type',
1998 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00001999 self.option_parser.add_option('--tcp-echo', action='store_const',
2000 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2001 dest='server_type',
2002 help='start up a tcp echo server.')
2003 self.option_parser.add_option('--udp-echo', action='store_const',
2004 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2005 dest='server_type',
2006 help='start up a udp echo server.')
2007 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2008 const=SERVER_BASIC_AUTH_PROXY,
2009 default=SERVER_HTTP, dest='server_type',
2010 help='start up a proxy server which requires '
2011 'basic authentication.')
2012 self.option_parser.add_option('--websocket', action='store_const',
2013 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2014 dest='server_type',
2015 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002016 self.option_parser.add_option('--https', action='store_true',
2017 dest='https', help='Specify that https '
2018 'should be used.')
2019 self.option_parser.add_option('--cert-and-key-file',
2020 dest='cert_and_key_file', help='specify the '
2021 'path to the file containing the certificate '
2022 'and private key for the server in PEM '
2023 'format')
2024 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2025 help='The type of OCSP response generated '
2026 'for the automatically generated '
2027 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002028 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2029 default=0, type=int,
2030 help='If non-zero then the generated '
2031 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002032 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2033 default='0', type='int',
2034 help='If nonzero, certain TLS connections '
2035 'will be aborted in order to test version '
2036 'fallback. 1 means all TLS versions will be '
2037 'aborted. 2 means TLS 1.1 or higher will be '
2038 'aborted. 3 means TLS 1.2 or higher will be '
2039 'aborted.')
2040 self.option_parser.add_option('--https-record-resume',
2041 dest='record_resume', const=True,
2042 default=False, action='store_const',
2043 help='Record resumption cache events rather '
2044 'than resuming as normal. Allows the use of '
2045 'the /ssl-session-cache request')
2046 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2047 help='Require SSL client auth on every '
2048 'connection.')
2049 self.option_parser.add_option('--ssl-client-ca', action='append',
2050 default=[], help='Specify that the client '
2051 'certificate request should include the CA '
2052 'named in the subject of the DER-encoded '
2053 'certificate contained in the specified '
2054 'file. This option may appear multiple '
2055 'times, indicating multiple CA names should '
2056 'be sent in the request.')
2057 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2058 help='Specify the bulk encryption '
2059 'algorithm(s) that will be accepted by the '
2060 'SSL server. Valid values are "aes256", '
2061 '"aes128", "3des", "rc4". If omitted, all '
2062 'algorithms will be used. This option may '
2063 'appear multiple times, indicating '
2064 'multiple algorithms should be enabled.');
2065 self.option_parser.add_option('--file-root-url', default='/files/',
2066 help='Specify a root URL for files served.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002067
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002068
initial.commit94958cf2008-07-26 22:42:52 +00002069if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002070 sys.exit(ServerRunner().main())