blob: 82e9b612d743bd57e6ad5dd0533ad56e9a34b6ec [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00002# Copyright (c) 2012 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
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00006"""This is a simple HTTP/FTP/SYNC/TCP/UDP/ server used for testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00007
8It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00009By default, it listens on an ephemeral port and sends the port number back to
10the originating process over a pipe. The originating process can specify an
11explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000012It can use https if you specify the flag --https=CERT where CERT is the path
13to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000014"""
15
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000016import asyncore
initial.commit94958cf2008-07-26 22:42:52 +000017import base64
18import BaseHTTPServer
19import cgi
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000020import errno
satorux@chromium.orgfdc70122012-03-07 18:08:41 +000021import httplib
agl@chromium.org77a9ad92012-03-20 15:14:27 +000022import minica
initial.commit94958cf2008-07-26 22:42:52 +000023import optparse
24import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000025import random
initial.commit94958cf2008-07-26 22:42:52 +000026import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000028import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000029import SocketServer
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000030import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000031import sys
32import threading
initial.commit94958cf2008-07-26 22:42:52 +000033import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000034import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000035import urlparse
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000036import warnings
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000037import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000038
39# Ignore deprecation warnings, they make our output more cluttered.
40warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000041
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000042import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000043import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000044import tlslite
45import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000046
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000047try:
48 import hashlib
49 _new_md5 = hashlib.md5
50except ImportError:
51 import md5
52 _new_md5 = md5.new
53
dpranke@chromium.org70049b72011-10-14 00:38:18 +000054try:
55 import json
56except ImportError:
57 import simplejson as json
58
davidben@chromium.org06fcf202010-09-22 18:15:23 +000059if sys.platform == 'win32':
60 import msvcrt
61
maruel@chromium.org756cf982009-03-05 12:46:38 +000062SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000063SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000064SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000065SERVER_TCP_ECHO = 3
66SERVER_UDP_ECHO = 4
initial.commit94958cf2008-07-26 22:42:52 +000067
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000068# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000069debug_output = sys.stderr
70def debug(str):
71 debug_output.write(str + "\n")
72 debug_output.flush()
73
agl@chromium.orgf9e66792011-12-12 22:22:19 +000074class RecordingSSLSessionCache(object):
75 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
76 lookups and inserts in order to test session cache behaviours."""
77
78 def __init__(self):
79 self.log = []
80
81 def __getitem__(self, sessionID):
82 self.log.append(('lookup', sessionID))
83 raise KeyError()
84
85 def __setitem__(self, sessionID, session):
86 self.log.append(('insert', sessionID))
87
erikwright@chromium.org847ef282012-02-22 16:41:10 +000088
89class ClientRestrictingServerMixIn:
90 """Implements verify_request to limit connections to our configured IP
91 address."""
92
93 def verify_request(self, request, client_address):
94 return client_address[0] == self.server_address[0]
95
96
initial.commit94958cf2008-07-26 22:42:52 +000097class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +000098 """This is a specialization of BaseHTTPServer to allow it
initial.commit94958cf2008-07-26 22:42:52 +000099 to be exited cleanly (by setting its "stop" member to True)."""
100
101 def serve_forever(self):
102 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +0000103 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +0000104 while not self.stop:
105 self.handle_request()
106 self.socket.close()
107
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000108
109class HTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000110 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000111 verification."""
112
113 pass
114
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000115class OCSPServer(ClientRestrictingServerMixIn, BaseHTTPServer.HTTPServer):
116 """This is a specialization of HTTPServer that serves an
117 OCSP response"""
118
119 def serve_forever_on_thread(self):
120 self.thread = threading.Thread(target = self.serve_forever,
121 name = "OCSPServerThread")
122 self.thread.start()
123
124 def stop_serving(self):
125 self.shutdown()
126 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000127
128class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
129 ClientRestrictingServerMixIn,
130 StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000131 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000132 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000133
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000134 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000135 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000136 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000137 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
138 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
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:
144 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()
initial.commit94958cf2008-07-26 22:42:52 +0000158 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
159
160 def handshake(self, tlsConnection):
161 """Creates the SSL connection."""
162 try:
163 tlsConnection.handshakeServer(certChain=self.cert_chain,
164 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000165 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000166 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000167 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000168 reqCAs=self.ssl_client_cas,
169 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000170 tlsConnection.ignoreAbruptClose = True
171 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000172 except tlslite.api.TLSAbruptCloseError:
173 # Ignore abrupt close.
174 return True
initial.commit94958cf2008-07-26 22:42:52 +0000175 except tlslite.api.TLSError, error:
176 print "Handshake failure:", str(error)
177 return False
178
akalin@chromium.org154bb132010-11-12 02:20:27 +0000179
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000180class SyncHTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000181 """An HTTP server that handles sync commands."""
182
183 def __init__(self, server_address, request_handler_class):
184 # We import here to avoid pulling in chromiumsync's dependencies
185 # unless strictly necessary.
186 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000187 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000188 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000189 self._sync_handler = chromiumsync.TestServer()
190 self._xmpp_socket_map = {}
191 self._xmpp_server = xmppserver.XmppServer(
192 self._xmpp_socket_map, ('localhost', 0))
193 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000194 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000195
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000196 def GetXmppServer(self):
197 return self._xmpp_server
198
akalin@chromium.org154bb132010-11-12 02:20:27 +0000199 def HandleCommand(self, query, raw_request):
200 return self._sync_handler.HandleCommand(query, raw_request)
201
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000202 def HandleRequestNoBlock(self):
203 """Handles a single request.
204
205 Copied from SocketServer._handle_request_noblock().
206 """
207 try:
208 request, client_address = self.get_request()
209 except socket.error:
210 return
211 if self.verify_request(request, client_address):
212 try:
213 self.process_request(request, client_address)
214 except:
215 self.handle_error(request, client_address)
216 self.close_request(request)
217
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000218 def SetAuthenticated(self, auth_valid):
219 self.authenticated = auth_valid
220
221 def GetAuthenticated(self):
222 return self.authenticated
223
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000224 def serve_forever(self):
225 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
226 """
227
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000228 def HandleXmppSocket(fd, socket_map, handler):
229 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000230
231 Adapted from asyncore.read() et al.
232 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000233 xmpp_connection = socket_map.get(fd)
234 # This could happen if a previous handler call caused fd to get
235 # removed from socket_map.
236 if xmpp_connection is None:
237 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000238 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000239 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000240 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
241 raise
242 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000243 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000244
245 while True:
246 read_fds = [ self.fileno() ]
247 write_fds = []
248 exceptional_fds = []
249
250 for fd, xmpp_connection in self._xmpp_socket_map.items():
251 is_r = xmpp_connection.readable()
252 is_w = xmpp_connection.writable()
253 if is_r:
254 read_fds.append(fd)
255 if is_w:
256 write_fds.append(fd)
257 if is_r or is_w:
258 exceptional_fds.append(fd)
259
260 try:
261 read_fds, write_fds, exceptional_fds = (
262 select.select(read_fds, write_fds, exceptional_fds))
263 except select.error, err:
264 if err.args[0] != errno.EINTR:
265 raise
266 else:
267 continue
268
269 for fd in read_fds:
270 if fd == self.fileno():
271 self.HandleRequestNoBlock()
272 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000273 HandleXmppSocket(fd, self._xmpp_socket_map,
274 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000275
276 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000277 HandleXmppSocket(fd, self._xmpp_socket_map,
278 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000279
280 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000281 HandleXmppSocket(fd, self._xmpp_socket_map,
282 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000283
akalin@chromium.org154bb132010-11-12 02:20:27 +0000284
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000285class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
286 """This is a specialization of FTPServer that adds client verification."""
287
288 pass
289
290
291class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000292 """A TCP echo server that echoes back what it has received."""
293
294 def server_bind(self):
295 """Override server_bind to store the server name."""
296 SocketServer.TCPServer.server_bind(self)
297 host, port = self.socket.getsockname()[:2]
298 self.server_name = socket.getfqdn(host)
299 self.server_port = port
300
301 def serve_forever(self):
302 self.stop = False
303 self.nonce_time = None
304 while not self.stop:
305 self.handle_request()
306 self.socket.close()
307
308
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000309class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000310 """A UDP echo server that echoes back what it has received."""
311
312 def server_bind(self):
313 """Override server_bind to store the server name."""
314 SocketServer.UDPServer.server_bind(self)
315 host, port = self.socket.getsockname()[:2]
316 self.server_name = socket.getfqdn(host)
317 self.server_port = port
318
319 def serve_forever(self):
320 self.stop = False
321 self.nonce_time = None
322 while not self.stop:
323 self.handle_request()
324 self.socket.close()
325
326
akalin@chromium.org154bb132010-11-12 02:20:27 +0000327class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
328
329 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000330 connect_handlers, get_handlers, head_handlers, post_handlers,
331 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000332 self._connect_handlers = connect_handlers
333 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000334 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000335 self._post_handlers = post_handlers
336 self._put_handlers = put_handlers
337 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
338 self, request, client_address, socket_server)
339
340 def log_request(self, *args, **kwargs):
341 # Disable request logging to declutter test log output.
342 pass
343
344 def _ShouldHandleRequest(self, handler_name):
345 """Determines if the path can be handled by the handler.
346
347 We consider a handler valid if the path begins with the
348 handler name. It can optionally be followed by "?*", "/*".
349 """
350
351 pattern = re.compile('%s($|\?|/).*' % handler_name)
352 return pattern.match(self.path)
353
354 def do_CONNECT(self):
355 for handler in self._connect_handlers:
356 if handler():
357 return
358
359 def do_GET(self):
360 for handler in self._get_handlers:
361 if handler():
362 return
363
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000364 def do_HEAD(self):
365 for handler in self._head_handlers:
366 if handler():
367 return
368
akalin@chromium.org154bb132010-11-12 02:20:27 +0000369 def do_POST(self):
370 for handler in self._post_handlers:
371 if handler():
372 return
373
374 def do_PUT(self):
375 for handler in self._put_handlers:
376 if handler():
377 return
378
379
380class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000381
382 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000383 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000384 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000385 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000386 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000387 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000388 self.NoCacheMaxAgeTimeHandler,
389 self.NoCacheTimeHandler,
390 self.CacheTimeHandler,
391 self.CacheExpiresHandler,
392 self.CacheProxyRevalidateHandler,
393 self.CachePrivateHandler,
394 self.CachePublicHandler,
395 self.CacheSMaxAgeHandler,
396 self.CacheMustRevalidateHandler,
397 self.CacheMustRevalidateMaxAgeHandler,
398 self.CacheNoStoreHandler,
399 self.CacheNoStoreMaxAgeHandler,
400 self.CacheNoTransformHandler,
401 self.DownloadHandler,
402 self.DownloadFinishHandler,
403 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000404 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000405 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000406 self.ZipFileHandler,
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000407 self.GDataAuthHandler,
408 self.GDataDocumentsFeedQueryHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000409 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000410 self.SetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000411 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000412 self.AuthBasicHandler,
413 self.AuthDigestHandler,
414 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000415 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000416 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000417 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000418 self.ServerRedirectHandler,
419 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000420 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000421 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000422 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000423 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000424 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000425 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000426 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000427 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000428 self.DeviceManagementHandler,
429 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000430 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000431 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000432 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000433 head_handlers = [
434 self.FileHandler,
435 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000436
maruel@google.come250a9b2009-03-10 17:39:46 +0000437 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000438 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000439 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000440 'gif': 'image/gif',
441 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000442 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000443 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000444 'pdf' : 'application/pdf',
445 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000446 }
initial.commit94958cf2008-07-26 22:42:52 +0000447 self._default_mime_type = 'text/html'
448
akalin@chromium.org154bb132010-11-12 02:20:27 +0000449 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000450 connect_handlers, get_handlers, head_handlers,
451 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000452
initial.commit94958cf2008-07-26 22:42:52 +0000453 def GetMIMETypeFromName(self, file_name):
454 """Returns the mime type for the specified file_name. So far it only looks
455 at the file extension."""
456
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000457 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000458 if len(extension) == 0:
459 # no extension.
460 return self._default_mime_type
461
ericroman@google.comc17ca532009-05-07 03:51:05 +0000462 # extension starts with a dot, so we need to remove it
463 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000464
initial.commit94958cf2008-07-26 22:42:52 +0000465 def NoCacheMaxAgeTimeHandler(self):
466 """This request handler yields a page with the title set to the current
467 system time, and no caching requested."""
468
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000469 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000470 return False
471
472 self.send_response(200)
473 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000474 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000475 self.end_headers()
476
maruel@google.come250a9b2009-03-10 17:39:46 +0000477 self.wfile.write('<html><head><title>%s</title></head></html>' %
478 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000479
480 return True
481
482 def NoCacheTimeHandler(self):
483 """This request handler yields a page with the title set to the current
484 system time, and no caching requested."""
485
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000486 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000487 return False
488
489 self.send_response(200)
490 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000491 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000492 self.end_headers()
493
maruel@google.come250a9b2009-03-10 17:39:46 +0000494 self.wfile.write('<html><head><title>%s</title></head></html>' %
495 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000496
497 return True
498
499 def CacheTimeHandler(self):
500 """This request handler yields a page with the title set to the current
501 system time, and allows caching for one minute."""
502
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000503 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000504 return False
505
506 self.send_response(200)
507 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000508 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000509 self.end_headers()
510
maruel@google.come250a9b2009-03-10 17:39:46 +0000511 self.wfile.write('<html><head><title>%s</title></head></html>' %
512 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000513
514 return True
515
516 def CacheExpiresHandler(self):
517 """This request handler yields a page with the title set to the current
518 system time, and set the page to expire on 1 Jan 2099."""
519
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000520 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000521 return False
522
523 self.send_response(200)
524 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000525 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000526 self.end_headers()
527
maruel@google.come250a9b2009-03-10 17:39:46 +0000528 self.wfile.write('<html><head><title>%s</title></head></html>' %
529 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000530
531 return True
532
533 def CacheProxyRevalidateHandler(self):
534 """This request handler yields a page with the title set to the current
535 system time, and allows caching for 60 seconds"""
536
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000537 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000538 return False
539
540 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000541 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000542 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
543 self.end_headers()
544
maruel@google.come250a9b2009-03-10 17:39:46 +0000545 self.wfile.write('<html><head><title>%s</title></head></html>' %
546 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000547
548 return True
549
550 def CachePrivateHandler(self):
551 """This request handler yields a page with the title set to the current
552 system time, and allows caching for 5 seconds."""
553
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000554 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000555 return False
556
557 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000558 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000559 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000560 self.end_headers()
561
maruel@google.come250a9b2009-03-10 17:39:46 +0000562 self.wfile.write('<html><head><title>%s</title></head></html>' %
563 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000564
565 return True
566
567 def CachePublicHandler(self):
568 """This request handler yields a page with the title set to the current
569 system time, and allows caching for 5 seconds."""
570
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000571 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000572 return False
573
574 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000575 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000576 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000577 self.end_headers()
578
maruel@google.come250a9b2009-03-10 17:39:46 +0000579 self.wfile.write('<html><head><title>%s</title></head></html>' %
580 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000581
582 return True
583
584 def CacheSMaxAgeHandler(self):
585 """This request handler yields a page with the title set to the current
586 system time, and does not allow for caching."""
587
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000588 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000589 return False
590
591 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000592 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000593 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
594 self.end_headers()
595
maruel@google.come250a9b2009-03-10 17:39:46 +0000596 self.wfile.write('<html><head><title>%s</title></head></html>' %
597 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000598
599 return True
600
601 def CacheMustRevalidateHandler(self):
602 """This request handler yields a page with the title set to the current
603 system time, and does not allow caching."""
604
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000605 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000606 return False
607
608 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000609 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000610 self.send_header('Cache-Control', 'must-revalidate')
611 self.end_headers()
612
maruel@google.come250a9b2009-03-10 17:39:46 +0000613 self.wfile.write('<html><head><title>%s</title></head></html>' %
614 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000615
616 return True
617
618 def CacheMustRevalidateMaxAgeHandler(self):
619 """This request handler yields a page with the title set to the current
620 system time, and does not allow caching event though max-age of 60
621 seconds is specified."""
622
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000623 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000624 return False
625
626 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000627 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000628 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
629 self.end_headers()
630
maruel@google.come250a9b2009-03-10 17:39:46 +0000631 self.wfile.write('<html><head><title>%s</title></head></html>' %
632 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000633
634 return True
635
initial.commit94958cf2008-07-26 22:42:52 +0000636 def CacheNoStoreHandler(self):
637 """This request handler yields a page with the title set to the current
638 system time, and does not allow the page to be stored."""
639
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000640 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000641 return False
642
643 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000644 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000645 self.send_header('Cache-Control', 'no-store')
646 self.end_headers()
647
maruel@google.come250a9b2009-03-10 17:39:46 +0000648 self.wfile.write('<html><head><title>%s</title></head></html>' %
649 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000650
651 return True
652
653 def CacheNoStoreMaxAgeHandler(self):
654 """This request handler yields a page with the title set to the current
655 system time, and does not allow the page to be stored even though max-age
656 of 60 seconds is specified."""
657
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000658 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000659 return False
660
661 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000662 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000663 self.send_header('Cache-Control', 'max-age=60, no-store')
664 self.end_headers()
665
maruel@google.come250a9b2009-03-10 17:39:46 +0000666 self.wfile.write('<html><head><title>%s</title></head></html>' %
667 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000668
669 return True
670
671
672 def CacheNoTransformHandler(self):
673 """This request handler yields a page with the title set to the current
674 system time, and does not allow the content to transformed during
675 user-agent caching"""
676
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000677 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000678 return False
679
680 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000681 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000682 self.send_header('Cache-Control', 'no-transform')
683 self.end_headers()
684
maruel@google.come250a9b2009-03-10 17:39:46 +0000685 self.wfile.write('<html><head><title>%s</title></head></html>' %
686 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000687
688 return True
689
690 def EchoHeader(self):
691 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000692 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000693
ananta@chromium.org56812d02011-04-07 17:52:05 +0000694 """This function echoes back the value of a specific request header"""
695 """while allowing caching for 16 hours."""
696 def EchoHeaderCache(self):
697 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000698
699 def EchoHeaderHelper(self, echo_header):
700 """This function echoes back the value of the request header passed in."""
701 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000702 return False
703
704 query_char = self.path.find('?')
705 if query_char != -1:
706 header_name = self.path[query_char+1:]
707
708 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000709 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000710 if echo_header == '/echoheadercache':
711 self.send_header('Cache-control', 'max-age=60000')
712 else:
713 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000714 # insert a vary header to properly indicate that the cachability of this
715 # request is subject to value of the request header being echoed.
716 if len(header_name) > 0:
717 self.send_header('Vary', header_name)
718 self.end_headers()
719
720 if len(header_name) > 0:
721 self.wfile.write(self.headers.getheader(header_name))
722
723 return True
724
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000725 def ReadRequestBody(self):
726 """This function reads the body of the current HTTP request, handling
727 both plain and chunked transfer encoded requests."""
728
729 if self.headers.getheader('transfer-encoding') != 'chunked':
730 length = int(self.headers.getheader('content-length'))
731 return self.rfile.read(length)
732
733 # Read the request body as chunks.
734 body = ""
735 while True:
736 line = self.rfile.readline()
737 length = int(line, 16)
738 if length == 0:
739 self.rfile.readline()
740 break
741 body += self.rfile.read(length)
742 self.rfile.read(2)
743 return body
744
initial.commit94958cf2008-07-26 22:42:52 +0000745 def EchoHandler(self):
746 """This handler just echoes back the payload of the request, for testing
747 form submission."""
748
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000749 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000750 return False
751
752 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000753 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000754 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000755 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000756 return True
757
758 def EchoTitleHandler(self):
759 """This handler is like Echo, but sets the page title to the request."""
760
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000761 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000762 return False
763
764 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000765 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000766 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000767 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000768 self.wfile.write('<html><head><title>')
769 self.wfile.write(request)
770 self.wfile.write('</title></head></html>')
771 return True
772
773 def EchoAllHandler(self):
774 """This handler yields a (more) human-readable page listing information
775 about the request header & contents."""
776
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000777 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000778 return False
779
780 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000781 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000782 self.end_headers()
783 self.wfile.write('<html><head><style>'
784 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
785 '</style></head><body>'
786 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000787 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000788 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000789
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000790 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000791 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000792 params = cgi.parse_qs(qs, keep_blank_values=1)
793
794 for param in params:
795 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000796
797 self.wfile.write('</pre>')
798
799 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
800
801 self.wfile.write('</body></html>')
802 return True
803
804 def DownloadHandler(self):
805 """This handler sends a downloadable file with or without reporting
806 the size (6K)."""
807
808 if self.path.startswith("/download-unknown-size"):
809 send_length = False
810 elif self.path.startswith("/download-known-size"):
811 send_length = True
812 else:
813 return False
814
815 #
816 # The test which uses this functionality is attempting to send
817 # small chunks of data to the client. Use a fairly large buffer
818 # so that we'll fill chrome's IO buffer enough to force it to
819 # actually write the data.
820 # See also the comments in the client-side of this test in
821 # download_uitest.cc
822 #
823 size_chunk1 = 35*1024
824 size_chunk2 = 10*1024
825
826 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000827 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000828 self.send_header('Cache-Control', 'max-age=0')
829 if send_length:
830 self.send_header('Content-Length', size_chunk1 + size_chunk2)
831 self.end_headers()
832
833 # First chunk of data:
834 self.wfile.write("*" * size_chunk1)
835 self.wfile.flush()
836
837 # handle requests until one of them clears this flag.
838 self.server.waitForDownload = True
839 while self.server.waitForDownload:
840 self.server.handle_request()
841
842 # Second chunk of data:
843 self.wfile.write("*" * size_chunk2)
844 return True
845
846 def DownloadFinishHandler(self):
847 """This handler just tells the server to finish the current download."""
848
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000849 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000850 return False
851
852 self.server.waitForDownload = False
853 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000854 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000855 self.send_header('Cache-Control', 'max-age=0')
856 self.end_headers()
857 return True
858
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000859 def _ReplaceFileData(self, data, query_parameters):
860 """Replaces matching substrings in a file.
861
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000862 If the 'replace_text' URL query parameter is present, it is expected to be
863 of the form old_text:new_text, which indicates that any old_text strings in
864 the file are replaced with new_text. Multiple 'replace_text' parameters may
865 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000866
867 If the parameters are not present, |data| is returned.
868 """
869 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000870 replace_text_values = query_dict.get('replace_text', [])
871 for replace_text_value in replace_text_values:
872 replace_text_args = replace_text_value.split(':')
873 if len(replace_text_args) != 2:
874 raise ValueError(
875 'replace_text must be of form old_text:new_text. Actual value: %s' %
876 replace_text_value)
877 old_text_b64, new_text_b64 = replace_text_args
878 old_text = base64.urlsafe_b64decode(old_text_b64)
879 new_text = base64.urlsafe_b64decode(new_text_b64)
880 data = data.replace(old_text, new_text)
881 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000882
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000883 def ZipFileHandler(self):
884 """This handler sends the contents of the requested file in compressed form.
885 Can pass in a parameter that specifies that the content length be
886 C - the compressed size (OK),
887 U - the uncompressed size (Non-standard, but handled),
888 S - less than compressed (OK because we keep going),
889 M - larger than compressed but less than uncompressed (an error),
890 L - larger than uncompressed (an error)
891 Example: compressedfiles/Picture_1.doc?C
892 """
893
894 prefix = "/compressedfiles/"
895 if not self.path.startswith(prefix):
896 return False
897
898 # Consume a request body if present.
899 if self.command == 'POST' or self.command == 'PUT' :
900 self.ReadRequestBody()
901
902 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
903
904 if not query in ('C', 'U', 'S', 'M', 'L'):
905 return False
906
907 sub_path = url_path[len(prefix):]
908 entries = sub_path.split('/')
909 file_path = os.path.join(self.server.data_dir, *entries)
910 if os.path.isdir(file_path):
911 file_path = os.path.join(file_path, 'index.html')
912
913 if not os.path.isfile(file_path):
914 print "File not found " + sub_path + " full path:" + file_path
915 self.send_error(404)
916 return True
917
918 f = open(file_path, "rb")
919 data = f.read()
920 uncompressed_len = len(data)
921 f.close()
922
923 # Compress the data.
924 data = zlib.compress(data)
925 compressed_len = len(data)
926
927 content_length = compressed_len
928 if query == 'U':
929 content_length = uncompressed_len
930 elif query == 'S':
931 content_length = compressed_len / 2
932 elif query == 'M':
933 content_length = (compressed_len + uncompressed_len) / 2
934 elif query == 'L':
935 content_length = compressed_len + uncompressed_len
936
937 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000938 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000939 self.send_header('Content-encoding', 'deflate')
940 self.send_header('Connection', 'close')
941 self.send_header('Content-Length', content_length)
942 self.send_header('ETag', '\'' + file_path + '\'')
943 self.end_headers()
944
945 self.wfile.write(data)
946
947 return True
948
initial.commit94958cf2008-07-26 22:42:52 +0000949 def FileHandler(self):
950 """This handler sends the contents of the requested file. Wow, it's like
951 a real webserver!"""
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000952 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000953 if not self.path.startswith(prefix):
954 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000955 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000956
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000957 def PostOnlyFileHandler(self):
958 """This handler sends the contents of the requested file on a POST."""
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000959 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000960 if not self.path.startswith(prefix):
961 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000962 return self._FileHandlerHelper(prefix)
963
964 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000965 request_body = ''
966 if self.command == 'POST' or self.command == 'PUT':
967 # Consume a request body if present.
968 request_body = self.ReadRequestBody()
969
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000970 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000971 query_dict = cgi.parse_qs(query)
972
973 expected_body = query_dict.get('expected_body', [])
974 if expected_body and request_body not in expected_body:
975 self.send_response(404)
976 self.end_headers()
977 self.wfile.write('')
978 return True
979
980 expected_headers = query_dict.get('expected_headers', [])
981 for expected_header in expected_headers:
982 header_name, expected_value = expected_header.split(':')
983 if self.headers.getheader(header_name) != expected_value:
984 self.send_response(404)
985 self.end_headers()
986 self.wfile.write('')
987 return True
988
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000989 sub_path = url_path[len(prefix):]
990 entries = sub_path.split('/')
991 file_path = os.path.join(self.server.data_dir, *entries)
992 if os.path.isdir(file_path):
993 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000994
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000995 if not os.path.isfile(file_path):
996 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000997 self.send_error(404)
998 return True
999
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001000 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +00001001 data = f.read()
1002 f.close()
1003
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001004 data = self._ReplaceFileData(data, query)
1005
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001006 old_protocol_version = self.protocol_version
1007
initial.commit94958cf2008-07-26 22:42:52 +00001008 # If file.mock-http-headers exists, it contains the headers we
1009 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001010 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +00001011 if os.path.isfile(headers_path):
1012 f = open(headers_path, "r")
1013
1014 # "HTTP/1.1 200 OK"
1015 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001016 http_major, http_minor, status_code = re.findall(
1017 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1018 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001019 self.send_response(int(status_code))
1020
1021 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001022 header_values = re.findall('(\S+):\s*(.*)', line)
1023 if len(header_values) > 0:
1024 # "name: value"
1025 name, value = header_values[0]
1026 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001027 f.close()
1028 else:
1029 # Could be more generic once we support mime-type sniffing, but for
1030 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001031
1032 range = self.headers.get('Range')
1033 if range and range.startswith('bytes='):
shishir@chromium.orge6444822011-12-09 02:45:44 +00001034 # Note this doesn't handle all valid byte range values (i.e. left
1035 # open ended ones), just enough for what we needed so far.
jam@chromium.org41550782010-11-17 23:47:50 +00001036 range = range[6:].split('-')
1037 start = int(range[0])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001038 if range[1]:
1039 end = int(range[1])
1040 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001041 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001042
1043 self.send_response(206)
1044 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
1045 str(len(data))
1046 self.send_header('Content-Range', content_range)
1047 data = data[start: end + 1]
1048 else:
1049 self.send_response(200)
1050
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001051 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001052 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001053 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001054 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001055 self.end_headers()
1056
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001057 if (self.command != 'HEAD'):
1058 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001059
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001060 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001061 return True
1062
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001063 def SetCookieHandler(self):
1064 """This handler just sets a cookie, for testing cookie handling."""
1065
1066 if not self._ShouldHandleRequest("/set-cookie"):
1067 return False
1068
1069 query_char = self.path.find('?')
1070 if query_char != -1:
1071 cookie_values = self.path[query_char + 1:].split('&')
1072 else:
1073 cookie_values = ("",)
1074 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001075 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001076 for cookie_value in cookie_values:
1077 self.send_header('Set-Cookie', '%s' % cookie_value)
1078 self.end_headers()
1079 for cookie_value in cookie_values:
1080 self.wfile.write('%s' % cookie_value)
1081 return True
1082
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001083 def SetHeaderHandler(self):
1084 """This handler sets a response header. Parameters are in the
1085 key%3A%20value&key2%3A%20value2 format."""
1086
1087 if not self._ShouldHandleRequest("/set-header"):
1088 return False
1089
1090 query_char = self.path.find('?')
1091 if query_char != -1:
1092 headers_values = self.path[query_char + 1:].split('&')
1093 else:
1094 headers_values = ("",)
1095 self.send_response(200)
1096 self.send_header('Content-Type', 'text/html')
1097 for header_value in headers_values:
1098 header_value = urllib.unquote(header_value)
1099 (key, value) = header_value.split(': ', 1)
1100 self.send_header(key, value)
1101 self.end_headers()
1102 for header_value in headers_values:
1103 self.wfile.write('%s' % header_value)
1104 return True
1105
initial.commit94958cf2008-07-26 22:42:52 +00001106 def AuthBasicHandler(self):
1107 """This handler tests 'Basic' authentication. It just sends a page with
1108 title 'user/pass' if you succeed."""
1109
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001110 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001111 return False
1112
1113 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001114 expected_password = 'secret'
1115 realm = 'testrealm'
1116 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001117
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001118 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1119 query_params = cgi.parse_qs(query, True)
1120 if 'set-cookie-if-challenged' in query_params:
1121 set_cookie_if_challenged = True
1122 if 'password' in query_params:
1123 expected_password = query_params['password'][0]
1124 if 'realm' in query_params:
1125 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001126
initial.commit94958cf2008-07-26 22:42:52 +00001127 auth = self.headers.getheader('authorization')
1128 try:
1129 if not auth:
1130 raise Exception('no auth')
1131 b64str = re.findall(r'Basic (\S+)', auth)[0]
1132 userpass = base64.b64decode(b64str)
1133 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001134 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001135 raise Exception('wrong password')
1136 except Exception, e:
1137 # Authentication failed.
1138 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001139 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001140 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001141 if set_cookie_if_challenged:
1142 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001143 self.end_headers()
1144 self.wfile.write('<html><head>')
1145 self.wfile.write('<title>Denied: %s</title>' % e)
1146 self.wfile.write('</head><body>')
1147 self.wfile.write('auth=%s<p>' % auth)
1148 self.wfile.write('b64str=%s<p>' % b64str)
1149 self.wfile.write('username: %s<p>' % username)
1150 self.wfile.write('userpass: %s<p>' % userpass)
1151 self.wfile.write('password: %s<p>' % password)
1152 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1153 self.wfile.write('</body></html>')
1154 return True
1155
1156 # Authentication successful. (Return a cachable response to allow for
1157 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001158 old_protocol_version = self.protocol_version
1159 self.protocol_version = "HTTP/1.1"
1160
initial.commit94958cf2008-07-26 22:42:52 +00001161 if_none_match = self.headers.getheader('if-none-match')
1162 if if_none_match == "abc":
1163 self.send_response(304)
1164 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001165 elif url_path.endswith(".gif"):
1166 # Using chrome/test/data/google/logo.gif as the test image
1167 test_image_path = ['google', 'logo.gif']
1168 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1169 if not os.path.isfile(gif_path):
1170 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001171 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001172 return True
1173
1174 f = open(gif_path, "rb")
1175 data = f.read()
1176 f.close()
1177
1178 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001179 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001180 self.send_header('Cache-control', 'max-age=60000')
1181 self.send_header('Etag', 'abc')
1182 self.end_headers()
1183 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001184 else:
1185 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001186 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001187 self.send_header('Cache-control', 'max-age=60000')
1188 self.send_header('Etag', 'abc')
1189 self.end_headers()
1190 self.wfile.write('<html><head>')
1191 self.wfile.write('<title>%s/%s</title>' % (username, password))
1192 self.wfile.write('</head><body>')
1193 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001194 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001195 self.wfile.write('</body></html>')
1196
rvargas@google.com54453b72011-05-19 01:11:11 +00001197 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001198 return True
1199
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001200 def GDataAuthHandler(self):
1201 """This handler verifies the Authentication header for GData requests."""
1202 if not self.server.gdata_auth_token:
1203 # --auth-token is not specified, not the test case for GData.
1204 return False
1205
1206 if not self._ShouldHandleRequest('/files/chromeos/gdata'):
1207 return False
1208
1209 if 'GData-Version' not in self.headers:
1210 self.send_error(httplib.BAD_REQUEST, 'GData-Version header is missing.')
1211 return True
1212
1213 if 'Authorization' not in self.headers:
1214 self.send_error(httplib.UNAUTHORIZED)
1215 return True
1216
1217 field_prefix = 'Bearer '
1218 authorization = self.headers['Authorization']
1219 if not authorization.startswith(field_prefix):
1220 self.send_error(httplib.UNAUTHORIZED)
1221 return True
1222
1223 code = authorization[len(field_prefix):]
1224 if code != self.server.gdata_auth_token:
1225 self.send_error(httplib.UNAUTHORIZED)
1226 return True
1227
1228 return False
1229
1230 def GDataDocumentsFeedQueryHandler(self):
1231 """This handler verifies if required parameters are properly
1232 specified for the GData DocumentsFeed request."""
1233 if not self.server.gdata_auth_token:
1234 # --auth-token is not specified, not the test case for GData.
1235 return False
1236
1237 if not self._ShouldHandleRequest('/files/chromeos/gdata/root_feed.json'):
1238 return False
1239
1240 (path, question, query_params) = self.path.partition('?')
1241 self.query_params = urlparse.parse_qs(query_params)
1242
1243 if 'v' not in self.query_params:
1244 self.send_error(httplib.BAD_REQUEST, 'v is not specified.')
1245 return True
1246 elif 'alt' not in self.query_params or self.query_params['alt'] != ['json']:
1247 # currently our GData client only uses JSON format.
1248 self.send_error(httplib.BAD_REQUEST, 'alt parameter is wrong.')
1249 return True
1250
1251 return False
1252
tonyg@chromium.org75054202010-03-31 22:06:10 +00001253 def GetNonce(self, force_reset=False):
1254 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001255
tonyg@chromium.org75054202010-03-31 22:06:10 +00001256 This is a fake implementation. A real implementation would only use a given
1257 nonce a single time (hence the name n-once). However, for the purposes of
1258 unittesting, we don't care about the security of the nonce.
1259
1260 Args:
1261 force_reset: Iff set, the nonce will be changed. Useful for testing the
1262 "stale" response.
1263 """
1264 if force_reset or not self.server.nonce_time:
1265 self.server.nonce_time = time.time()
1266 return _new_md5('privatekey%s%d' %
1267 (self.path, self.server.nonce_time)).hexdigest()
1268
1269 def AuthDigestHandler(self):
1270 """This handler tests 'Digest' authentication.
1271
1272 It just sends a page with title 'user/pass' if you succeed.
1273
1274 A stale response is sent iff "stale" is present in the request path.
1275 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001276 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001277 return False
1278
tonyg@chromium.org75054202010-03-31 22:06:10 +00001279 stale = 'stale' in self.path
1280 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001281 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001282 password = 'secret'
1283 realm = 'testrealm'
1284
1285 auth = self.headers.getheader('authorization')
1286 pairs = {}
1287 try:
1288 if not auth:
1289 raise Exception('no auth')
1290 if not auth.startswith('Digest'):
1291 raise Exception('not digest')
1292 # Pull out all the name="value" pairs as a dictionary.
1293 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1294
1295 # Make sure it's all valid.
1296 if pairs['nonce'] != nonce:
1297 raise Exception('wrong nonce')
1298 if pairs['opaque'] != opaque:
1299 raise Exception('wrong opaque')
1300
1301 # Check the 'response' value and make sure it matches our magic hash.
1302 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001303 hash_a1 = _new_md5(
1304 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001305 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001306 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001307 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001308 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1309 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001310 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001311
1312 if pairs['response'] != response:
1313 raise Exception('wrong password')
1314 except Exception, e:
1315 # Authentication failed.
1316 self.send_response(401)
1317 hdr = ('Digest '
1318 'realm="%s", '
1319 'domain="/", '
1320 'qop="auth", '
1321 'algorithm=MD5, '
1322 'nonce="%s", '
1323 'opaque="%s"') % (realm, nonce, opaque)
1324 if stale:
1325 hdr += ', stale="TRUE"'
1326 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001327 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001328 self.end_headers()
1329 self.wfile.write('<html><head>')
1330 self.wfile.write('<title>Denied: %s</title>' % e)
1331 self.wfile.write('</head><body>')
1332 self.wfile.write('auth=%s<p>' % auth)
1333 self.wfile.write('pairs=%s<p>' % pairs)
1334 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1335 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1336 self.wfile.write('</body></html>')
1337 return True
1338
1339 # Authentication successful.
1340 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001341 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001342 self.end_headers()
1343 self.wfile.write('<html><head>')
1344 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1345 self.wfile.write('</head><body>')
1346 self.wfile.write('auth=%s<p>' % auth)
1347 self.wfile.write('pairs=%s<p>' % pairs)
1348 self.wfile.write('</body></html>')
1349
1350 return True
1351
1352 def SlowServerHandler(self):
1353 """Wait for the user suggested time before responding. The syntax is
1354 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001355 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001356 return False
1357 query_char = self.path.find('?')
1358 wait_sec = 1.0
1359 if query_char >= 0:
1360 try:
1361 wait_sec = int(self.path[query_char + 1:])
1362 except ValueError:
1363 pass
1364 time.sleep(wait_sec)
1365 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001366 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001367 self.end_headers()
1368 self.wfile.write("waited %d seconds" % wait_sec)
1369 return True
1370
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001371 def ChunkedServerHandler(self):
1372 """Send chunked response. Allows to specify chunks parameters:
1373 - waitBeforeHeaders - ms to wait before sending headers
1374 - waitBetweenChunks - ms to wait between chunks
1375 - chunkSize - size of each chunk in bytes
1376 - chunksNumber - number of chunks
1377 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1378 waits one second, then sends headers and five chunks five bytes each."""
1379 if not self._ShouldHandleRequest("/chunked"):
1380 return False
1381 query_char = self.path.find('?')
1382 chunkedSettings = {'waitBeforeHeaders' : 0,
1383 'waitBetweenChunks' : 0,
1384 'chunkSize' : 5,
1385 'chunksNumber' : 5}
1386 if query_char >= 0:
1387 params = self.path[query_char + 1:].split('&')
1388 for param in params:
1389 keyValue = param.split('=')
1390 if len(keyValue) == 2:
1391 try:
1392 chunkedSettings[keyValue[0]] = int(keyValue[1])
1393 except ValueError:
1394 pass
1395 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1396 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1397 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001398 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001399 self.send_header('Connection', 'close')
1400 self.send_header('Transfer-Encoding', 'chunked')
1401 self.end_headers()
1402 # Chunked encoding: sending all chunks, then final zero-length chunk and
1403 # then final CRLF.
1404 for i in range(0, chunkedSettings['chunksNumber']):
1405 if i > 0:
1406 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1407 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1408 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1409 self.sendChunkHelp('')
1410 return True
1411
initial.commit94958cf2008-07-26 22:42:52 +00001412 def ContentTypeHandler(self):
1413 """Returns a string of html with the given content type. E.g.,
1414 /contenttype?text/css returns an html file with the Content-Type
1415 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001416 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001417 return False
1418 query_char = self.path.find('?')
1419 content_type = self.path[query_char + 1:].strip()
1420 if not content_type:
1421 content_type = 'text/html'
1422 self.send_response(200)
1423 self.send_header('Content-Type', content_type)
1424 self.end_headers()
1425 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1426 return True
1427
creis@google.com2f4f6a42011-03-25 19:44:19 +00001428 def NoContentHandler(self):
1429 """Returns a 204 No Content response."""
1430 if not self._ShouldHandleRequest("/nocontent"):
1431 return False
1432 self.send_response(204)
1433 self.end_headers()
1434 return True
1435
initial.commit94958cf2008-07-26 22:42:52 +00001436 def ServerRedirectHandler(self):
1437 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001438 '/server-redirect?http://foo.bar/asdf' to redirect to
1439 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001440
1441 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001442 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001443 return False
1444
1445 query_char = self.path.find('?')
1446 if query_char < 0 or len(self.path) <= query_char + 1:
1447 self.sendRedirectHelp(test_name)
1448 return True
1449 dest = self.path[query_char + 1:]
1450
1451 self.send_response(301) # moved permanently
1452 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001453 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001454 self.end_headers()
1455 self.wfile.write('<html><head>')
1456 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1457
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001458 return True
initial.commit94958cf2008-07-26 22:42:52 +00001459
1460 def ClientRedirectHandler(self):
1461 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001462 '/client-redirect?http://foo.bar/asdf' to redirect to
1463 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001464
1465 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001466 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001467 return False
1468
1469 query_char = self.path.find('?');
1470 if query_char < 0 or len(self.path) <= query_char + 1:
1471 self.sendRedirectHelp(test_name)
1472 return True
1473 dest = self.path[query_char + 1:]
1474
1475 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001476 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001477 self.end_headers()
1478 self.wfile.write('<html><head>')
1479 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1480 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1481
1482 return True
1483
tony@chromium.org03266982010-03-05 03:18:42 +00001484 def MultipartHandler(self):
1485 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001486 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001487 if not self._ShouldHandleRequest(test_name):
1488 return False
1489
1490 num_frames = 10
1491 bound = '12345'
1492 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001493 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001494 'multipart/x-mixed-replace;boundary=' + bound)
1495 self.end_headers()
1496
1497 for i in xrange(num_frames):
1498 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001499 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001500 self.wfile.write('<title>page ' + str(i) + '</title>')
1501 self.wfile.write('page ' + str(i))
1502
1503 self.wfile.write('--' + bound + '--')
1504 return True
1505
tony@chromium.org4cb88302011-09-27 22:13:49 +00001506 def MultipartSlowHandler(self):
1507 """Send a multipart response (3 text/html pages) with a slight delay
1508 between each page. This is similar to how some pages show status using
1509 multipart."""
1510 test_name = '/multipart-slow'
1511 if not self._ShouldHandleRequest(test_name):
1512 return False
1513
1514 num_frames = 3
1515 bound = '12345'
1516 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001517 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001518 'multipart/x-mixed-replace;boundary=' + bound)
1519 self.end_headers()
1520
1521 for i in xrange(num_frames):
1522 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001523 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001524 time.sleep(0.25)
1525 if i == 2:
1526 self.wfile.write('<title>PASS</title>')
1527 else:
1528 self.wfile.write('<title>page ' + str(i) + '</title>')
1529 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1530
1531 self.wfile.write('--' + bound + '--')
1532 return True
1533
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001534 def GetSSLSessionCacheHandler(self):
1535 """Send a reply containing a log of the session cache operations."""
1536
1537 if not self._ShouldHandleRequest('/ssl-session-cache'):
1538 return False
1539
1540 self.send_response(200)
1541 self.send_header('Content-Type', 'text/plain')
1542 self.end_headers()
1543 try:
1544 for (action, sessionID) in self.server.session_cache.log:
1545 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
1546 except AttributeError, e:
1547 self.wfile.write('Pass --https-record-resume in order to use' +
1548 ' this request')
1549 return True
1550
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001551 def CloseSocketHandler(self):
1552 """Closes the socket without sending anything."""
1553
1554 if not self._ShouldHandleRequest('/close-socket'):
1555 return False
1556
1557 self.wfile.close()
1558 return True
1559
initial.commit94958cf2008-07-26 22:42:52 +00001560 def DefaultResponseHandler(self):
1561 """This is the catch-all response handler for requests that aren't handled
1562 by one of the special handlers above.
1563 Note that we specify the content-length as without it the https connection
1564 is not closed properly (and the browser keeps expecting data)."""
1565
1566 contents = "Default response given for path: " + self.path
1567 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001568 self.send_header('Content-Type', 'text/html')
1569 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001570 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001571 if (self.command != 'HEAD'):
1572 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001573 return True
1574
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001575 def RedirectConnectHandler(self):
1576 """Sends a redirect to the CONNECT request for www.redirect.com. This
1577 response is not specified by the RFC, so the browser should not follow
1578 the redirect."""
1579
1580 if (self.path.find("www.redirect.com") < 0):
1581 return False
1582
1583 dest = "http://www.destination.com/foo.js"
1584
1585 self.send_response(302) # moved temporarily
1586 self.send_header('Location', dest)
1587 self.send_header('Connection', 'close')
1588 self.end_headers()
1589 return True
1590
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001591 def ServerAuthConnectHandler(self):
1592 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1593 response doesn't make sense because the proxy server cannot request
1594 server authentication."""
1595
1596 if (self.path.find("www.server-auth.com") < 0):
1597 return False
1598
1599 challenge = 'Basic realm="WallyWorld"'
1600
1601 self.send_response(401) # unauthorized
1602 self.send_header('WWW-Authenticate', challenge)
1603 self.send_header('Connection', 'close')
1604 self.end_headers()
1605 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001606
1607 def DefaultConnectResponseHandler(self):
1608 """This is the catch-all response handler for CONNECT requests that aren't
1609 handled by one of the special handlers above. Real Web servers respond
1610 with 400 to CONNECT requests."""
1611
1612 contents = "Your client has issued a malformed or illegal request."
1613 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001614 self.send_header('Content-Type', 'text/html')
1615 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001616 self.end_headers()
1617 self.wfile.write(contents)
1618 return True
1619
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001620 def DeviceManagementHandler(self):
1621 """Delegates to the device management service used for cloud policy."""
1622 if not self._ShouldHandleRequest("/device_management"):
1623 return False
1624
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001625 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001626
1627 if not self.server._device_management_handler:
1628 import device_management
1629 policy_path = os.path.join(self.server.data_dir, 'device_management')
1630 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001631 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001632 self.server.policy_keys,
1633 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001634
1635 http_response, raw_reply = (
1636 self.server._device_management_handler.HandleRequest(self.path,
1637 self.headers,
1638 raw_request))
1639 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001640 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001641 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001642 self.end_headers()
1643 self.wfile.write(raw_reply)
1644 return True
1645
initial.commit94958cf2008-07-26 22:42:52 +00001646 # called by the redirect handling function when there is no parameter
1647 def sendRedirectHelp(self, redirect_name):
1648 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001649 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001650 self.end_headers()
1651 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1652 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1653 self.wfile.write('</body></html>')
1654
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001655 # called by chunked handling function
1656 def sendChunkHelp(self, chunk):
1657 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1658 self.wfile.write('%X\r\n' % len(chunk))
1659 self.wfile.write(chunk)
1660 self.wfile.write('\r\n')
1661
akalin@chromium.org154bb132010-11-12 02:20:27 +00001662
1663class SyncPageHandler(BasePageHandler):
1664 """Handler for the main HTTP sync server."""
1665
1666 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001667 get_handlers = [self.ChromiumSyncTimeHandler,
1668 self.ChromiumSyncMigrationOpHandler,
1669 self.ChromiumSyncCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001670 self.ChromiumSyncDisableNotificationsOpHandler,
1671 self.ChromiumSyncEnableNotificationsOpHandler,
1672 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001673 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001674 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001675 self.ChromiumSyncErrorOpHandler,
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001676 self.ChromiumSyncSyncTabsOpHandler,
1677 self.ChromiumSyncCreateSyncedBookmarksOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001678
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001679 post_handlers = [self.ChromiumSyncCommandHandler,
1680 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001681 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001682 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001683 post_handlers, [])
1684
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001685
akalin@chromium.org154bb132010-11-12 02:20:27 +00001686 def ChromiumSyncTimeHandler(self):
1687 """Handle Chromium sync .../time requests.
1688
1689 The syncer sometimes checks server reachability by examining /time.
1690 """
1691 test_name = "/chromiumsync/time"
1692 if not self._ShouldHandleRequest(test_name):
1693 return False
1694
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001695 # Chrome hates it if we send a response before reading the request.
1696 if self.headers.getheader('content-length'):
1697 length = int(self.headers.getheader('content-length'))
1698 raw_request = self.rfile.read(length)
1699
akalin@chromium.org154bb132010-11-12 02:20:27 +00001700 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001701 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001702 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001703 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001704 return True
1705
1706 def ChromiumSyncCommandHandler(self):
1707 """Handle a chromiumsync command arriving via http.
1708
1709 This covers all sync protocol commands: authentication, getupdates, and
1710 commit.
1711 """
1712 test_name = "/chromiumsync/command"
1713 if not self._ShouldHandleRequest(test_name):
1714 return False
1715
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001716 length = int(self.headers.getheader('content-length'))
1717 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001718 http_response = 200
1719 raw_reply = None
1720 if not self.server.GetAuthenticated():
1721 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001722 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1723 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001724 else:
1725 http_response, raw_reply = self.server.HandleCommand(
1726 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001727
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001728 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001729 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001730 if http_response == 401:
1731 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001732 self.end_headers()
1733 self.wfile.write(raw_reply)
1734 return True
1735
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001736 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001737 test_name = "/chromiumsync/migrate"
1738 if not self._ShouldHandleRequest(test_name):
1739 return False
1740
1741 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1742 self.path)
1743 self.send_response(http_response)
1744 self.send_header('Content-Type', 'text/html')
1745 self.send_header('Content-Length', len(raw_reply))
1746 self.end_headers()
1747 self.wfile.write(raw_reply)
1748 return True
1749
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001750 def ChromiumSyncCredHandler(self):
1751 test_name = "/chromiumsync/cred"
1752 if not self._ShouldHandleRequest(test_name):
1753 return False
1754 try:
1755 query = urlparse.urlparse(self.path)[4]
1756 cred_valid = urlparse.parse_qs(query)['valid']
1757 if cred_valid[0] == 'True':
1758 self.server.SetAuthenticated(True)
1759 else:
1760 self.server.SetAuthenticated(False)
1761 except:
1762 self.server.SetAuthenticated(False)
1763
1764 http_response = 200
1765 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1766 self.send_response(http_response)
1767 self.send_header('Content-Type', 'text/html')
1768 self.send_header('Content-Length', len(raw_reply))
1769 self.end_headers()
1770 self.wfile.write(raw_reply)
1771 return True
1772
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001773 def ChromiumSyncDisableNotificationsOpHandler(self):
1774 test_name = "/chromiumsync/disablenotifications"
1775 if not self._ShouldHandleRequest(test_name):
1776 return False
1777 self.server.GetXmppServer().DisableNotifications()
1778 result = 200
1779 raw_reply = ('<html><title>Notifications disabled</title>'
1780 '<H1>Notifications disabled</H1></html>')
1781 self.send_response(result)
1782 self.send_header('Content-Type', 'text/html')
1783 self.send_header('Content-Length', len(raw_reply))
1784 self.end_headers()
1785 self.wfile.write(raw_reply)
1786 return True;
1787
1788 def ChromiumSyncEnableNotificationsOpHandler(self):
1789 test_name = "/chromiumsync/enablenotifications"
1790 if not self._ShouldHandleRequest(test_name):
1791 return False
1792 self.server.GetXmppServer().EnableNotifications()
1793 result = 200
1794 raw_reply = ('<html><title>Notifications enabled</title>'
1795 '<H1>Notifications enabled</H1></html>')
1796 self.send_response(result)
1797 self.send_header('Content-Type', 'text/html')
1798 self.send_header('Content-Length', len(raw_reply))
1799 self.end_headers()
1800 self.wfile.write(raw_reply)
1801 return True;
1802
1803 def ChromiumSyncSendNotificationOpHandler(self):
1804 test_name = "/chromiumsync/sendnotification"
1805 if not self._ShouldHandleRequest(test_name):
1806 return False
1807 query = urlparse.urlparse(self.path)[4]
1808 query_params = urlparse.parse_qs(query)
1809 channel = ''
1810 data = ''
1811 if 'channel' in query_params:
1812 channel = query_params['channel'][0]
1813 if 'data' in query_params:
1814 data = query_params['data'][0]
1815 self.server.GetXmppServer().SendNotification(channel, data)
1816 result = 200
1817 raw_reply = ('<html><title>Notification sent</title>'
1818 '<H1>Notification sent with channel "%s" '
1819 'and data "%s"</H1></html>'
1820 % (channel, data))
1821 self.send_response(result)
1822 self.send_header('Content-Type', 'text/html')
1823 self.send_header('Content-Length', len(raw_reply))
1824 self.end_headers()
1825 self.wfile.write(raw_reply)
1826 return True;
1827
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001828 def ChromiumSyncBirthdayErrorOpHandler(self):
1829 test_name = "/chromiumsync/birthdayerror"
1830 if not self._ShouldHandleRequest(test_name):
1831 return False
1832 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1833 self.send_response(result)
1834 self.send_header('Content-Type', 'text/html')
1835 self.send_header('Content-Length', len(raw_reply))
1836 self.end_headers()
1837 self.wfile.write(raw_reply)
1838 return True;
1839
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001840 def ChromiumSyncTransientErrorOpHandler(self):
1841 test_name = "/chromiumsync/transienterror"
1842 if not self._ShouldHandleRequest(test_name):
1843 return False
1844 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1845 self.send_response(result)
1846 self.send_header('Content-Type', 'text/html')
1847 self.send_header('Content-Length', len(raw_reply))
1848 self.end_headers()
1849 self.wfile.write(raw_reply)
1850 return True;
1851
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001852 def ChromiumSyncErrorOpHandler(self):
1853 test_name = "/chromiumsync/error"
1854 if not self._ShouldHandleRequest(test_name):
1855 return False
1856 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1857 self.path)
1858 self.send_response(result)
1859 self.send_header('Content-Type', 'text/html')
1860 self.send_header('Content-Length', len(raw_reply))
1861 self.end_headers()
1862 self.wfile.write(raw_reply)
1863 return True;
1864
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001865 def ChromiumSyncSyncTabsOpHandler(self):
1866 test_name = "/chromiumsync/synctabs"
1867 if not self._ShouldHandleRequest(test_name):
1868 return False
1869 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1870 self.send_response(result)
1871 self.send_header('Content-Type', 'text/html')
1872 self.send_header('Content-Length', len(raw_reply))
1873 self.end_headers()
1874 self.wfile.write(raw_reply)
1875 return True;
1876
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001877 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1878 test_name = "/chromiumsync/createsyncedbookmarks"
1879 if not self._ShouldHandleRequest(test_name):
1880 return False
1881 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1882 self.send_response(result)
1883 self.send_header('Content-Type', 'text/html')
1884 self.send_header('Content-Length', len(raw_reply))
1885 self.end_headers()
1886 self.wfile.write(raw_reply)
1887 return True;
1888
akalin@chromium.org154bb132010-11-12 02:20:27 +00001889
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001890def MakeDataDir():
1891 if options.data_dir:
1892 if not os.path.isdir(options.data_dir):
1893 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1894 return None
1895 my_data_dir = options.data_dir
1896 else:
1897 # Create the default path to our data dir, relative to the exe dir.
1898 my_data_dir = os.path.dirname(sys.argv[0])
1899 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001900 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001901
1902 #TODO(ibrar): Must use Find* funtion defined in google\tools
1903 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1904
1905 return my_data_dir
1906
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001907class OCSPHandler(BasePageHandler):
1908 def __init__(self, request, client_address, socket_server):
1909 handlers = [self.OCSPResponse]
1910 self.ocsp_response = socket_server.ocsp_response
1911 BasePageHandler.__init__(self, request, client_address, socket_server,
1912 [], handlers, [], handlers, [])
1913
1914 def OCSPResponse(self):
1915 self.send_response(200)
1916 self.send_header('Content-Type', 'application/ocsp-response')
1917 self.send_header('Content-Length', str(len(self.ocsp_response)))
1918 self.end_headers()
1919
1920 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001921
1922class TCPEchoHandler(SocketServer.BaseRequestHandler):
1923 """The RequestHandler class for TCP echo server.
1924
1925 It is instantiated once per connection to the server, and overrides the
1926 handle() method to implement communication to the client.
1927 """
1928
1929 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001930 """Handles the request from the client and constructs a response."""
1931
1932 data = self.request.recv(65536).strip()
1933 # Verify the "echo request" message received from the client. Send back
1934 # "echo response" message if "echo request" message is valid.
1935 try:
1936 return_data = echo_message.GetEchoResponseData(data)
1937 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001938 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001939 except ValueError:
1940 return
1941
1942 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001943
1944
1945class UDPEchoHandler(SocketServer.BaseRequestHandler):
1946 """The RequestHandler class for UDP echo server.
1947
1948 It is instantiated once per connection to the server, and overrides the
1949 handle() method to implement communication to the client.
1950 """
1951
1952 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001953 """Handles the request from the client and constructs a response."""
1954
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001955 data = self.request[0].strip()
1956 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001957 # Verify the "echo request" message received from the client. Send back
1958 # "echo response" message if "echo request" message is valid.
1959 try:
1960 return_data = echo_message.GetEchoResponseData(data)
1961 if not return_data:
1962 return
1963 except ValueError:
1964 return
1965 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001966
1967
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001968class FileMultiplexer:
1969 def __init__(self, fd1, fd2) :
1970 self.__fd1 = fd1
1971 self.__fd2 = fd2
1972
1973 def __del__(self) :
1974 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1975 self.__fd1.close()
1976 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1977 self.__fd2.close()
1978
1979 def write(self, text) :
1980 self.__fd1.write(text)
1981 self.__fd2.write(text)
1982
1983 def flush(self) :
1984 self.__fd1.flush()
1985 self.__fd2.flush()
1986
initial.commit94958cf2008-07-26 22:42:52 +00001987def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001988 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001989 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001990 if options.log_to_console:
1991 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1992 else:
1993 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001994
1995 port = options.port
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001996 host = options.host
initial.commit94958cf2008-07-26 22:42:52 +00001997
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001998 server_data = {}
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001999 server_data['host'] = host
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00002000
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002001 ocsp_server = None
2002
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002003 if options.server_type == SERVER_HTTP:
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002004 if options.https:
2005 pem_cert_and_key = None
2006 if options.cert_and_key_file:
2007 if not os.path.isfile(options.cert_and_key_file):
2008 print ('specified server cert file not found: ' +
2009 options.cert_and_key_file + ' exiting...')
2010 return
2011 pem_cert_and_key = file(options.cert_and_key_file, 'r').read()
2012 else:
2013 # generate a new certificate and run an OCSP server for it.
2014 ocsp_server = OCSPServer((host, 0), OCSPHandler)
2015 print ('OCSP server started on %s:%d...' %
2016 (host, ocsp_server.server_port))
2017
2018 ocsp_der = None
2019 ocsp_revoked = False
2020 ocsp_invalid = False
2021
2022 if options.ocsp == 'ok':
2023 pass
2024 elif options.ocsp == 'revoked':
2025 ocsp_revoked = True
2026 elif options.ocsp == 'invalid':
2027 ocsp_invalid = True
2028 else:
2029 print 'unknown OCSP status: ' + options.ocsp_status
2030 return
2031
2032 (pem_cert_and_key, ocsp_der) = \
2033 minica.GenerateCertKeyAndOCSP(
2034 subject = "127.0.0.1",
2035 ocsp_url = ("http://%s:%d/ocsp" %
2036 (host, ocsp_server.server_port)),
2037 ocsp_revoked = ocsp_revoked)
2038
2039 if ocsp_invalid:
2040 ocsp_der = '3'
2041
2042 ocsp_server.ocsp_response = ocsp_der
2043
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00002044 for ca_cert in options.ssl_client_ca:
2045 if not os.path.isfile(ca_cert):
2046 print 'specified trusted client CA file not found: ' + ca_cert + \
2047 ' exiting...'
2048 return
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002049 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00002050 options.ssl_client_auth, options.ssl_client_ca,
agl@chromium.org143daa42012-04-26 18:45:34 +00002051 options.ssl_bulk_cipher, options.record_resume,
2052 options.tls_intolerant)
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002053 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002054 else:
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002055 server = HTTPServer((host, port), TestPageHandler)
2056 print 'HTTP server started on %s:%d...' % (host, server.server_port)
erikkay@google.com70397b62008-12-30 21:49:21 +00002057
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002058 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00002059 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00002060 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00002061 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002062 server.policy_keys = options.policy_keys
2063 server.policy_user = options.policy_user
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00002064 server.gdata_auth_token = options.auth_token
akalin@chromium.org154bb132010-11-12 02:20:27 +00002065 elif options.server_type == SERVER_SYNC:
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002066 server = SyncHTTPServer((host, port), SyncPageHandler)
akalin@chromium.org154bb132010-11-12 02:20:27 +00002067 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00002068 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2069 server_data['port'] = server.server_port
2070 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002071 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002072 # Used for generating the key (randomly) that encodes the "echo request"
2073 # message.
2074 random.seed()
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002075 server = TCPEchoServer((host, port), TCPEchoHandler)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002076 print 'Echo TCP server started on port %d...' % server.server_port
2077 server_data['port'] = server.server_port
2078 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002079 # Used for generating the key (randomly) that encodes the "echo request"
2080 # message.
2081 random.seed()
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002082 server = UDPEchoServer((host, port), UDPEchoHandler)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002083 print 'Echo UDP server started on port %d...' % server.server_port
2084 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002085 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00002086 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002087 my_data_dir = MakeDataDir()
2088
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002089 # Instantiate a dummy authorizer for managing 'virtual' users
2090 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2091
2092 # Define a new user having full r/w permissions and a read-only
2093 # anonymous user
2094 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2095
2096 authorizer.add_anonymous(my_data_dir)
2097
2098 # Instantiate FTP handler class
2099 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2100 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002101
2102 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00002103 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2104 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002105
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002106 # Instantiate FTP server class and listen to address:port
2107 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00002108 server_data['port'] = server.socket.getsockname()[1]
2109 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00002110
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002111 # Notify the parent that we've started. (BaseServer subclasses
2112 # bind their sockets on construction.)
2113 if options.startup_pipe is not None:
dpranke@chromium.org70049b72011-10-14 00:38:18 +00002114 server_data_json = json.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00002115 server_data_len = len(server_data_json)
2116 print 'sending server_data: %s (%d bytes)' % (
2117 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002118 if sys.platform == 'win32':
2119 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
2120 else:
2121 fd = options.startup_pipe
2122 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00002123 # First write the data length as an unsigned 4-byte value. This
2124 # is _not_ using network byte ordering since the other end of the
2125 # pipe is on the same machine.
2126 startup_pipe.write(struct.pack('=L', server_data_len))
2127 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002128 startup_pipe.close()
2129
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002130 if ocsp_server is not None:
2131 ocsp_server.serve_forever_on_thread()
2132
initial.commit94958cf2008-07-26 22:42:52 +00002133 try:
2134 server.serve_forever()
2135 except KeyboardInterrupt:
2136 print 'shutting down server'
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002137 if ocsp_server is not None:
2138 ocsp_server.stop_serving()
initial.commit94958cf2008-07-26 22:42:52 +00002139 server.stop = True
2140
2141if __name__ == '__main__':
2142 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002143 option_parser.add_option("-f", '--ftp', action='store_const',
2144 const=SERVER_FTP, default=SERVER_HTTP,
2145 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00002146 help='start up an FTP server.')
2147 option_parser.add_option('', '--sync', action='store_const',
2148 const=SERVER_SYNC, default=SERVER_HTTP,
2149 dest='server_type',
2150 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002151 option_parser.add_option('', '--tcp-echo', action='store_const',
2152 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2153 dest='server_type',
2154 help='start up a tcp echo server.')
2155 option_parser.add_option('', '--udp-echo', action='store_const',
2156 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2157 dest='server_type',
2158 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00002159 option_parser.add_option('', '--log-to-console', action='store_const',
2160 const=True, default=False,
2161 dest='log_to_console',
2162 help='Enables or disables sys.stdout logging to '
2163 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00002164 option_parser.add_option('', '--port', default='0', type='int',
2165 help='Port used by the server. If unspecified, the '
2166 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002167 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00002168 help='Directory from which to read the files.')
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002169 option_parser.add_option('', '--https', action='store_true', dest='https',
2170 help='Specify that https should be used.')
2171 option_parser.add_option('', '--cert-and-key-file', dest='cert_and_key_file',
2172 help='specify the path to the file containing the '
2173 'certificate and private key for the server in PEM '
2174 'format')
2175 option_parser.add_option('', '--ocsp', dest='ocsp', default='ok',
2176 help='The type of OCSP response generated for the '
2177 'automatically generated certificate. One of '
2178 '[ok,revoked,invalid]')
agl@chromium.org143daa42012-04-26 18:45:34 +00002179 option_parser.add_option('', '--tls-intolerant', dest='tls_intolerant',
wtc@chromium.org8d0b5ee2012-05-23 18:32:23 +00002180 default='0', type='int',
2181 help='If nonzero, certain TLS connections will be'
2182 ' aborted in order to test version fallback. 1'
2183 ' means all TLS versions will be aborted. 2 means'
2184 ' TLS 1.1 or higher will be aborted. 3 means TLS'
2185 ' 1.2 or higher will be aborted.')
agl@chromium.orgf9e66792011-12-12 22:22:19 +00002186 option_parser.add_option('', '--https-record-resume', dest='record_resume',
2187 const=True, default=False, action='store_const',
2188 help='Record resumption cache events rather than'
2189 ' resuming as normal. Allows the use of the'
2190 ' /ssl-session-cache request')
davidben@chromium.org31282a12010-08-07 01:10:02 +00002191 option_parser.add_option('', '--ssl-client-auth', action='store_true',
2192 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00002193 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
2194 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00002195 'should include the CA named in the subject of '
2196 'the DER-encoded certificate contained in the '
2197 'specified file. This option may appear multiple '
2198 'times, indicating multiple CA names should be '
2199 'sent in the request.')
2200 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
2201 help='Specify the bulk encryption algorithm(s)'
2202 'that will be accepted by the SSL server. Valid '
2203 'values are "aes256", "aes128", "3des", "rc4". If '
2204 'omitted, all algorithms will be used. This '
2205 'option may appear multiple times, indicating '
2206 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00002207 option_parser.add_option('', '--file-root-url', default='/files/',
2208 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002209 option_parser.add_option('', '--startup-pipe', type='int',
2210 dest='startup_pipe',
2211 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002212 option_parser.add_option('', '--policy-key', action='append',
2213 dest='policy_keys',
2214 help='Specify a path to a PEM-encoded private key '
2215 'to use for policy signing. May be specified '
2216 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00002217 'the server. If ther server has multiple keys, it '
2218 'will rotate through them in at each request a '
2219 'round-robin fashion. The server will generate a '
2220 'random key if none is specified on the command '
2221 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002222 option_parser.add_option('', '--policy-user', default='user@example.com',
2223 dest='policy_user',
2224 help='Specify the user name the server should '
2225 'report back to the client as the user owning the '
2226 'token used for making the policy request.')
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002227 option_parser.add_option('', '--host', default='127.0.0.1',
2228 dest='host',
2229 help='Hostname or IP upon which the server will '
2230 'listen. Client connections will also only be '
2231 'allowed from this address.')
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00002232 option_parser.add_option('', '--auth-token', dest='auth_token',
2233 help='Specify the auth token which should be used'
2234 'in the authorization header for GData.')
initial.commit94958cf2008-07-26 22:42:52 +00002235 options, args = option_parser.parse_args()
2236
2237 sys.exit(main(options, args))