blob: ff20c0f2e235558918d6fd30887bc3e8fe72d3b5 [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
initial.commit94958cf2008-07-26 22:42:52 +000022import optparse
23import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000024import random
initial.commit94958cf2008-07-26 22:42:52 +000025import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000026import select
agl@chromium.orge2f47042012-03-19 16:23:30 +000027import SocketServer
agl@chromium.org26ffa202012-03-19 17:18:07 +000028import socket
agl@chromium.orge2f47042012-03-19 16:23:30 +000029import sys
agl@chromium.org26ffa202012-03-19 17:18:07 +000030import struct
initial.commit94958cf2008-07-26 22:42:52 +000031import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000032import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000033import urlparse
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000034import warnings
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000035import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000036
37# Ignore deprecation warnings, they make our output more cluttered.
38warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000039
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000040import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000041import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000042import tlslite
43import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000044
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000045try:
46 import hashlib
47 _new_md5 = hashlib.md5
48except ImportError:
49 import md5
50 _new_md5 = md5.new
51
dpranke@chromium.org70049b72011-10-14 00:38:18 +000052try:
53 import json
54except ImportError:
55 import simplejson as json
56
davidben@chromium.org06fcf202010-09-22 18:15:23 +000057if sys.platform == 'win32':
58 import msvcrt
59
maruel@chromium.org756cf982009-03-05 12:46:38 +000060SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000061SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000062SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000063SERVER_TCP_ECHO = 3
64SERVER_UDP_ECHO = 4
initial.commit94958cf2008-07-26 22:42:52 +000065
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000066# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000067debug_output = sys.stderr
68def debug(str):
69 debug_output.write(str + "\n")
70 debug_output.flush()
71
agl@chromium.orgf9e66792011-12-12 22:22:19 +000072class RecordingSSLSessionCache(object):
73 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
74 lookups and inserts in order to test session cache behaviours."""
75
76 def __init__(self):
77 self.log = []
78
79 def __getitem__(self, sessionID):
80 self.log.append(('lookup', sessionID))
81 raise KeyError()
82
83 def __setitem__(self, sessionID, session):
84 self.log.append(('insert', sessionID))
85
erikwright@chromium.org847ef282012-02-22 16:41:10 +000086
87class ClientRestrictingServerMixIn:
88 """Implements verify_request to limit connections to our configured IP
89 address."""
90
91 def verify_request(self, request, client_address):
92 return client_address[0] == self.server_address[0]
93
94
initial.commit94958cf2008-07-26 22:42:52 +000095class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +000096 """This is a specialization of BaseHTTPServer to allow it
initial.commit94958cf2008-07-26 22:42:52 +000097 to be exited cleanly (by setting its "stop" member to True)."""
98
99 def serve_forever(self):
100 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +0000101 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +0000102 while not self.stop:
103 self.handle_request()
104 self.socket.close()
105
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000106
107class HTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
agl@chromium.org26ffa202012-03-19 17:18:07 +0000108 """This is a specialization of StoppableHTTPerver that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000109 verification."""
110
111 pass
112
113
114class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
115 ClientRestrictingServerMixIn,
116 StoppableHTTPServer):
agl@chromium.org26ffa202012-03-19 17:18:07 +0000117 """This is a specialization of StoppableHTTPerver that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000118 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000119
agl@chromium.org26ffa202012-03-19 17:18:07 +0000120 def __init__(self, server_address, request_hander_class, cert_path,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000121 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
122 record_resume_info):
agl@chromium.org26ffa202012-03-19 17:18:07 +0000123 s = open(cert_path).read()
124 self.cert_chain = tlslite.api.X509CertChain().parseChain(s)
125 s = open(cert_path).read()
126 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000127 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000128 self.ssl_client_cas = []
129 for ca_file in ssl_client_cas:
130 s = open(ca_file).read()
131 x509 = tlslite.api.X509()
132 x509.parse(s)
133 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000134 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
135 if ssl_bulk_ciphers is not None:
136 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000137
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000138 if record_resume_info:
139 # If record_resume_info is true then we'll replace the session cache with
140 # an object that records the lookups and inserts that it sees.
141 self.session_cache = RecordingSSLSessionCache()
142 else:
143 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000144 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
145
146 def handshake(self, tlsConnection):
147 """Creates the SSL connection."""
148 try:
149 tlsConnection.handshakeServer(certChain=self.cert_chain,
150 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000151 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000152 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000153 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000154 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +0000155 tlsConnection.ignoreAbruptClose = True
156 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000157 except tlslite.api.TLSAbruptCloseError:
158 # Ignore abrupt close.
159 return True
initial.commit94958cf2008-07-26 22:42:52 +0000160 except tlslite.api.TLSError, error:
161 print "Handshake failure:", str(error)
162 return False
163
akalin@chromium.org154bb132010-11-12 02:20:27 +0000164
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000165class SyncHTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000166 """An HTTP server that handles sync commands."""
167
168 def __init__(self, server_address, request_handler_class):
169 # We import here to avoid pulling in chromiumsync's dependencies
170 # unless strictly necessary.
171 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000172 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000173 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000174 self._sync_handler = chromiumsync.TestServer()
175 self._xmpp_socket_map = {}
176 self._xmpp_server = xmppserver.XmppServer(
177 self._xmpp_socket_map, ('localhost', 0))
178 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000179 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000180
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000181 def GetXmppServer(self):
182 return self._xmpp_server
183
akalin@chromium.org154bb132010-11-12 02:20:27 +0000184 def HandleCommand(self, query, raw_request):
185 return self._sync_handler.HandleCommand(query, raw_request)
186
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000187 def HandleRequestNoBlock(self):
188 """Handles a single request.
189
190 Copied from SocketServer._handle_request_noblock().
191 """
192 try:
193 request, client_address = self.get_request()
194 except socket.error:
195 return
196 if self.verify_request(request, client_address):
197 try:
198 self.process_request(request, client_address)
199 except:
200 self.handle_error(request, client_address)
201 self.close_request(request)
202
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000203 def SetAuthenticated(self, auth_valid):
204 self.authenticated = auth_valid
205
206 def GetAuthenticated(self):
207 return self.authenticated
208
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000209 def serve_forever(self):
210 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
211 """
212
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000213 def HandleXmppSocket(fd, socket_map, handler):
214 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000215
216 Adapted from asyncore.read() et al.
217 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000218 xmpp_connection = socket_map.get(fd)
219 # This could happen if a previous handler call caused fd to get
220 # removed from socket_map.
221 if xmpp_connection is None:
222 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000223 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000224 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000225 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
226 raise
227 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000228 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000229
230 while True:
231 read_fds = [ self.fileno() ]
232 write_fds = []
233 exceptional_fds = []
234
235 for fd, xmpp_connection in self._xmpp_socket_map.items():
236 is_r = xmpp_connection.readable()
237 is_w = xmpp_connection.writable()
238 if is_r:
239 read_fds.append(fd)
240 if is_w:
241 write_fds.append(fd)
242 if is_r or is_w:
243 exceptional_fds.append(fd)
244
245 try:
246 read_fds, write_fds, exceptional_fds = (
247 select.select(read_fds, write_fds, exceptional_fds))
248 except select.error, err:
249 if err.args[0] != errno.EINTR:
250 raise
251 else:
252 continue
253
254 for fd in read_fds:
255 if fd == self.fileno():
256 self.HandleRequestNoBlock()
257 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000258 HandleXmppSocket(fd, self._xmpp_socket_map,
259 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000260
261 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000262 HandleXmppSocket(fd, self._xmpp_socket_map,
263 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000264
265 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000266 HandleXmppSocket(fd, self._xmpp_socket_map,
267 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000268
akalin@chromium.org154bb132010-11-12 02:20:27 +0000269
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000270class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
271 """This is a specialization of FTPServer that adds client verification."""
272
273 pass
274
275
276class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000277 """A TCP echo server that echoes back what it has received."""
278
279 def server_bind(self):
280 """Override server_bind to store the server name."""
281 SocketServer.TCPServer.server_bind(self)
282 host, port = self.socket.getsockname()[:2]
283 self.server_name = socket.getfqdn(host)
284 self.server_port = port
285
286 def serve_forever(self):
287 self.stop = False
288 self.nonce_time = None
289 while not self.stop:
290 self.handle_request()
291 self.socket.close()
292
293
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000294class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000295 """A UDP echo server that echoes back what it has received."""
296
297 def server_bind(self):
298 """Override server_bind to store the server name."""
299 SocketServer.UDPServer.server_bind(self)
300 host, port = self.socket.getsockname()[:2]
301 self.server_name = socket.getfqdn(host)
302 self.server_port = port
303
304 def serve_forever(self):
305 self.stop = False
306 self.nonce_time = None
307 while not self.stop:
308 self.handle_request()
309 self.socket.close()
310
311
akalin@chromium.org154bb132010-11-12 02:20:27 +0000312class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
313
314 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000315 connect_handlers, get_handlers, head_handlers, post_handlers,
316 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000317 self._connect_handlers = connect_handlers
318 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000319 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000320 self._post_handlers = post_handlers
321 self._put_handlers = put_handlers
322 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
323 self, request, client_address, socket_server)
324
325 def log_request(self, *args, **kwargs):
326 # Disable request logging to declutter test log output.
327 pass
328
329 def _ShouldHandleRequest(self, handler_name):
330 """Determines if the path can be handled by the handler.
331
332 We consider a handler valid if the path begins with the
333 handler name. It can optionally be followed by "?*", "/*".
334 """
335
336 pattern = re.compile('%s($|\?|/).*' % handler_name)
337 return pattern.match(self.path)
338
339 def do_CONNECT(self):
340 for handler in self._connect_handlers:
341 if handler():
342 return
343
344 def do_GET(self):
345 for handler in self._get_handlers:
346 if handler():
347 return
348
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000349 def do_HEAD(self):
350 for handler in self._head_handlers:
351 if handler():
352 return
353
akalin@chromium.org154bb132010-11-12 02:20:27 +0000354 def do_POST(self):
355 for handler in self._post_handlers:
356 if handler():
357 return
358
359 def do_PUT(self):
360 for handler in self._put_handlers:
361 if handler():
362 return
363
364
365class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000366
367 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000368 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000369 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000370 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000371 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000372 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000373 self.NoCacheMaxAgeTimeHandler,
374 self.NoCacheTimeHandler,
375 self.CacheTimeHandler,
376 self.CacheExpiresHandler,
377 self.CacheProxyRevalidateHandler,
378 self.CachePrivateHandler,
379 self.CachePublicHandler,
380 self.CacheSMaxAgeHandler,
381 self.CacheMustRevalidateHandler,
382 self.CacheMustRevalidateMaxAgeHandler,
383 self.CacheNoStoreHandler,
384 self.CacheNoStoreMaxAgeHandler,
385 self.CacheNoTransformHandler,
386 self.DownloadHandler,
387 self.DownloadFinishHandler,
388 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000389 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000390 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000391 self.ZipFileHandler,
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000392 self.GDataAuthHandler,
393 self.GDataDocumentsFeedQueryHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000394 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000395 self.SetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000396 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000397 self.AuthBasicHandler,
398 self.AuthDigestHandler,
399 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000400 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000401 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000402 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000403 self.ServerRedirectHandler,
404 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000405 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000406 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000407 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000408 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000409 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000410 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000411 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000412 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000413 self.DeviceManagementHandler,
414 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000415 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000416 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000417 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000418 head_handlers = [
419 self.FileHandler,
420 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000421
maruel@google.come250a9b2009-03-10 17:39:46 +0000422 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000423 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000424 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000425 'gif': 'image/gif',
426 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000427 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000428 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000429 'pdf' : 'application/pdf',
430 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000431 }
initial.commit94958cf2008-07-26 22:42:52 +0000432 self._default_mime_type = 'text/html'
433
akalin@chromium.org154bb132010-11-12 02:20:27 +0000434 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000435 connect_handlers, get_handlers, head_handlers,
436 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000437
initial.commit94958cf2008-07-26 22:42:52 +0000438 def GetMIMETypeFromName(self, file_name):
439 """Returns the mime type for the specified file_name. So far it only looks
440 at the file extension."""
441
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000442 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000443 if len(extension) == 0:
444 # no extension.
445 return self._default_mime_type
446
ericroman@google.comc17ca532009-05-07 03:51:05 +0000447 # extension starts with a dot, so we need to remove it
448 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000449
initial.commit94958cf2008-07-26 22:42:52 +0000450 def NoCacheMaxAgeTimeHandler(self):
451 """This request handler yields a page with the title set to the current
452 system time, and no caching requested."""
453
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000454 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000455 return False
456
457 self.send_response(200)
458 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000459 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000460 self.end_headers()
461
maruel@google.come250a9b2009-03-10 17:39:46 +0000462 self.wfile.write('<html><head><title>%s</title></head></html>' %
463 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000464
465 return True
466
467 def NoCacheTimeHandler(self):
468 """This request handler yields a page with the title set to the current
469 system time, and no caching requested."""
470
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000471 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000472 return False
473
474 self.send_response(200)
475 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000476 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000477 self.end_headers()
478
maruel@google.come250a9b2009-03-10 17:39:46 +0000479 self.wfile.write('<html><head><title>%s</title></head></html>' %
480 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000481
482 return True
483
484 def CacheTimeHandler(self):
485 """This request handler yields a page with the title set to the current
486 system time, and allows caching for one minute."""
487
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000488 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000489 return False
490
491 self.send_response(200)
492 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000493 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000494 self.end_headers()
495
maruel@google.come250a9b2009-03-10 17:39:46 +0000496 self.wfile.write('<html><head><title>%s</title></head></html>' %
497 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000498
499 return True
500
501 def CacheExpiresHandler(self):
502 """This request handler yields a page with the title set to the current
503 system time, and set the page to expire on 1 Jan 2099."""
504
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000505 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000506 return False
507
508 self.send_response(200)
509 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000510 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000511 self.end_headers()
512
maruel@google.come250a9b2009-03-10 17:39:46 +0000513 self.wfile.write('<html><head><title>%s</title></head></html>' %
514 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000515
516 return True
517
518 def CacheProxyRevalidateHandler(self):
519 """This request handler yields a page with the title set to the current
520 system time, and allows caching for 60 seconds"""
521
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000522 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000523 return False
524
525 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000526 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000527 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
528 self.end_headers()
529
maruel@google.come250a9b2009-03-10 17:39:46 +0000530 self.wfile.write('<html><head><title>%s</title></head></html>' %
531 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000532
533 return True
534
535 def CachePrivateHandler(self):
536 """This request handler yields a page with the title set to the current
537 system time, and allows caching for 5 seconds."""
538
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000539 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000540 return False
541
542 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000543 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000544 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000545 self.end_headers()
546
maruel@google.come250a9b2009-03-10 17:39:46 +0000547 self.wfile.write('<html><head><title>%s</title></head></html>' %
548 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000549
550 return True
551
552 def CachePublicHandler(self):
553 """This request handler yields a page with the title set to the current
554 system time, and allows caching for 5 seconds."""
555
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000556 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000557 return False
558
559 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000560 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000561 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000562 self.end_headers()
563
maruel@google.come250a9b2009-03-10 17:39:46 +0000564 self.wfile.write('<html><head><title>%s</title></head></html>' %
565 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000566
567 return True
568
569 def CacheSMaxAgeHandler(self):
570 """This request handler yields a page with the title set to the current
571 system time, and does not allow for caching."""
572
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000573 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000574 return False
575
576 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000577 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000578 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
579 self.end_headers()
580
maruel@google.come250a9b2009-03-10 17:39:46 +0000581 self.wfile.write('<html><head><title>%s</title></head></html>' %
582 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000583
584 return True
585
586 def CacheMustRevalidateHandler(self):
587 """This request handler yields a page with the title set to the current
588 system time, and does not allow caching."""
589
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000590 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000591 return False
592
593 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000594 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000595 self.send_header('Cache-Control', 'must-revalidate')
596 self.end_headers()
597
maruel@google.come250a9b2009-03-10 17:39:46 +0000598 self.wfile.write('<html><head><title>%s</title></head></html>' %
599 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000600
601 return True
602
603 def CacheMustRevalidateMaxAgeHandler(self):
604 """This request handler yields a page with the title set to the current
605 system time, and does not allow caching event though max-age of 60
606 seconds is specified."""
607
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000608 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000609 return False
610
611 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000612 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000613 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
614 self.end_headers()
615
maruel@google.come250a9b2009-03-10 17:39:46 +0000616 self.wfile.write('<html><head><title>%s</title></head></html>' %
617 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000618
619 return True
620
initial.commit94958cf2008-07-26 22:42:52 +0000621 def CacheNoStoreHandler(self):
622 """This request handler yields a page with the title set to the current
623 system time, and does not allow the page to be stored."""
624
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000625 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000626 return False
627
628 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000629 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000630 self.send_header('Cache-Control', 'no-store')
631 self.end_headers()
632
maruel@google.come250a9b2009-03-10 17:39:46 +0000633 self.wfile.write('<html><head><title>%s</title></head></html>' %
634 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000635
636 return True
637
638 def CacheNoStoreMaxAgeHandler(self):
639 """This request handler yields a page with the title set to the current
640 system time, and does not allow the page to be stored even though max-age
641 of 60 seconds is specified."""
642
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000643 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000644 return False
645
646 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000647 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000648 self.send_header('Cache-Control', 'max-age=60, no-store')
649 self.end_headers()
650
maruel@google.come250a9b2009-03-10 17:39:46 +0000651 self.wfile.write('<html><head><title>%s</title></head></html>' %
652 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000653
654 return True
655
656
657 def CacheNoTransformHandler(self):
658 """This request handler yields a page with the title set to the current
659 system time, and does not allow the content to transformed during
660 user-agent caching"""
661
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000662 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000663 return False
664
665 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000666 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000667 self.send_header('Cache-Control', 'no-transform')
668 self.end_headers()
669
maruel@google.come250a9b2009-03-10 17:39:46 +0000670 self.wfile.write('<html><head><title>%s</title></head></html>' %
671 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000672
673 return True
674
675 def EchoHeader(self):
676 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000677 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000678
ananta@chromium.org56812d02011-04-07 17:52:05 +0000679 """This function echoes back the value of a specific request header"""
680 """while allowing caching for 16 hours."""
681 def EchoHeaderCache(self):
682 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000683
684 def EchoHeaderHelper(self, echo_header):
685 """This function echoes back the value of the request header passed in."""
686 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000687 return False
688
689 query_char = self.path.find('?')
690 if query_char != -1:
691 header_name = self.path[query_char+1:]
692
693 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000694 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000695 if echo_header == '/echoheadercache':
696 self.send_header('Cache-control', 'max-age=60000')
697 else:
698 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000699 # insert a vary header to properly indicate that the cachability of this
700 # request is subject to value of the request header being echoed.
701 if len(header_name) > 0:
702 self.send_header('Vary', header_name)
703 self.end_headers()
704
705 if len(header_name) > 0:
706 self.wfile.write(self.headers.getheader(header_name))
707
708 return True
709
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000710 def ReadRequestBody(self):
711 """This function reads the body of the current HTTP request, handling
712 both plain and chunked transfer encoded requests."""
713
714 if self.headers.getheader('transfer-encoding') != 'chunked':
715 length = int(self.headers.getheader('content-length'))
716 return self.rfile.read(length)
717
718 # Read the request body as chunks.
719 body = ""
720 while True:
721 line = self.rfile.readline()
722 length = int(line, 16)
723 if length == 0:
724 self.rfile.readline()
725 break
726 body += self.rfile.read(length)
727 self.rfile.read(2)
728 return body
729
initial.commit94958cf2008-07-26 22:42:52 +0000730 def EchoHandler(self):
731 """This handler just echoes back the payload of the request, for testing
732 form submission."""
733
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000734 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000735 return False
736
737 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000738 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000739 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000740 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000741 return True
742
743 def EchoTitleHandler(self):
744 """This handler is like Echo, but sets the page title to the request."""
745
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000746 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000747 return False
748
749 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000750 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000751 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000752 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000753 self.wfile.write('<html><head><title>')
754 self.wfile.write(request)
755 self.wfile.write('</title></head></html>')
756 return True
757
758 def EchoAllHandler(self):
759 """This handler yields a (more) human-readable page listing information
760 about the request header & contents."""
761
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000762 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000763 return False
764
765 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000766 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000767 self.end_headers()
768 self.wfile.write('<html><head><style>'
769 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
770 '</style></head><body>'
771 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000772 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000773 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000774
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000775 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000776 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000777 params = cgi.parse_qs(qs, keep_blank_values=1)
778
779 for param in params:
780 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000781
782 self.wfile.write('</pre>')
783
784 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
785
786 self.wfile.write('</body></html>')
787 return True
788
789 def DownloadHandler(self):
790 """This handler sends a downloadable file with or without reporting
791 the size (6K)."""
792
793 if self.path.startswith("/download-unknown-size"):
794 send_length = False
795 elif self.path.startswith("/download-known-size"):
796 send_length = True
797 else:
798 return False
799
800 #
801 # The test which uses this functionality is attempting to send
802 # small chunks of data to the client. Use a fairly large buffer
803 # so that we'll fill chrome's IO buffer enough to force it to
804 # actually write the data.
805 # See also the comments in the client-side of this test in
806 # download_uitest.cc
807 #
808 size_chunk1 = 35*1024
809 size_chunk2 = 10*1024
810
811 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000812 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000813 self.send_header('Cache-Control', 'max-age=0')
814 if send_length:
815 self.send_header('Content-Length', size_chunk1 + size_chunk2)
816 self.end_headers()
817
818 # First chunk of data:
819 self.wfile.write("*" * size_chunk1)
820 self.wfile.flush()
821
822 # handle requests until one of them clears this flag.
823 self.server.waitForDownload = True
824 while self.server.waitForDownload:
825 self.server.handle_request()
826
827 # Second chunk of data:
828 self.wfile.write("*" * size_chunk2)
829 return True
830
831 def DownloadFinishHandler(self):
832 """This handler just tells the server to finish the current download."""
833
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000834 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000835 return False
836
837 self.server.waitForDownload = False
838 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000839 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000840 self.send_header('Cache-Control', 'max-age=0')
841 self.end_headers()
842 return True
843
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000844 def _ReplaceFileData(self, data, query_parameters):
845 """Replaces matching substrings in a file.
846
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000847 If the 'replace_text' URL query parameter is present, it is expected to be
848 of the form old_text:new_text, which indicates that any old_text strings in
849 the file are replaced with new_text. Multiple 'replace_text' parameters may
850 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000851
852 If the parameters are not present, |data| is returned.
853 """
854 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000855 replace_text_values = query_dict.get('replace_text', [])
856 for replace_text_value in replace_text_values:
857 replace_text_args = replace_text_value.split(':')
858 if len(replace_text_args) != 2:
859 raise ValueError(
860 'replace_text must be of form old_text:new_text. Actual value: %s' %
861 replace_text_value)
862 old_text_b64, new_text_b64 = replace_text_args
863 old_text = base64.urlsafe_b64decode(old_text_b64)
864 new_text = base64.urlsafe_b64decode(new_text_b64)
865 data = data.replace(old_text, new_text)
866 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000867
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000868 def ZipFileHandler(self):
869 """This handler sends the contents of the requested file in compressed form.
870 Can pass in a parameter that specifies that the content length be
871 C - the compressed size (OK),
872 U - the uncompressed size (Non-standard, but handled),
873 S - less than compressed (OK because we keep going),
874 M - larger than compressed but less than uncompressed (an error),
875 L - larger than uncompressed (an error)
876 Example: compressedfiles/Picture_1.doc?C
877 """
878
879 prefix = "/compressedfiles/"
880 if not self.path.startswith(prefix):
881 return False
882
883 # Consume a request body if present.
884 if self.command == 'POST' or self.command == 'PUT' :
885 self.ReadRequestBody()
886
887 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
888
889 if not query in ('C', 'U', 'S', 'M', 'L'):
890 return False
891
892 sub_path = url_path[len(prefix):]
893 entries = sub_path.split('/')
894 file_path = os.path.join(self.server.data_dir, *entries)
895 if os.path.isdir(file_path):
896 file_path = os.path.join(file_path, 'index.html')
897
898 if not os.path.isfile(file_path):
899 print "File not found " + sub_path + " full path:" + file_path
900 self.send_error(404)
901 return True
902
903 f = open(file_path, "rb")
904 data = f.read()
905 uncompressed_len = len(data)
906 f.close()
907
908 # Compress the data.
909 data = zlib.compress(data)
910 compressed_len = len(data)
911
912 content_length = compressed_len
913 if query == 'U':
914 content_length = uncompressed_len
915 elif query == 'S':
916 content_length = compressed_len / 2
917 elif query == 'M':
918 content_length = (compressed_len + uncompressed_len) / 2
919 elif query == 'L':
920 content_length = compressed_len + uncompressed_len
921
922 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000923 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000924 self.send_header('Content-encoding', 'deflate')
925 self.send_header('Connection', 'close')
926 self.send_header('Content-Length', content_length)
927 self.send_header('ETag', '\'' + file_path + '\'')
928 self.end_headers()
929
930 self.wfile.write(data)
931
932 return True
933
initial.commit94958cf2008-07-26 22:42:52 +0000934 def FileHandler(self):
935 """This handler sends the contents of the requested file. Wow, it's like
936 a real webserver!"""
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000937 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000938 if not self.path.startswith(prefix):
939 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000940 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000941
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000942 def PostOnlyFileHandler(self):
943 """This handler sends the contents of the requested file on a POST."""
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000944 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000945 if not self.path.startswith(prefix):
946 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000947 return self._FileHandlerHelper(prefix)
948
949 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000950 request_body = ''
951 if self.command == 'POST' or self.command == 'PUT':
952 # Consume a request body if present.
953 request_body = self.ReadRequestBody()
954
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000955 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000956 query_dict = cgi.parse_qs(query)
957
958 expected_body = query_dict.get('expected_body', [])
959 if expected_body and request_body not in expected_body:
960 self.send_response(404)
961 self.end_headers()
962 self.wfile.write('')
963 return True
964
965 expected_headers = query_dict.get('expected_headers', [])
966 for expected_header in expected_headers:
967 header_name, expected_value = expected_header.split(':')
968 if self.headers.getheader(header_name) != expected_value:
969 self.send_response(404)
970 self.end_headers()
971 self.wfile.write('')
972 return True
973
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000974 sub_path = url_path[len(prefix):]
975 entries = sub_path.split('/')
976 file_path = os.path.join(self.server.data_dir, *entries)
977 if os.path.isdir(file_path):
978 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000979
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000980 if not os.path.isfile(file_path):
981 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000982 self.send_error(404)
983 return True
984
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000985 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000986 data = f.read()
987 f.close()
988
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000989 data = self._ReplaceFileData(data, query)
990
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000991 old_protocol_version = self.protocol_version
992
initial.commit94958cf2008-07-26 22:42:52 +0000993 # If file.mock-http-headers exists, it contains the headers we
994 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000995 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000996 if os.path.isfile(headers_path):
997 f = open(headers_path, "r")
998
999 # "HTTP/1.1 200 OK"
1000 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001001 http_major, http_minor, status_code = re.findall(
1002 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1003 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001004 self.send_response(int(status_code))
1005
1006 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001007 header_values = re.findall('(\S+):\s*(.*)', line)
1008 if len(header_values) > 0:
1009 # "name: value"
1010 name, value = header_values[0]
1011 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001012 f.close()
1013 else:
1014 # Could be more generic once we support mime-type sniffing, but for
1015 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001016
1017 range = self.headers.get('Range')
1018 if range and range.startswith('bytes='):
shishir@chromium.orge6444822011-12-09 02:45:44 +00001019 # Note this doesn't handle all valid byte range values (i.e. left
1020 # open ended ones), just enough for what we needed so far.
jam@chromium.org41550782010-11-17 23:47:50 +00001021 range = range[6:].split('-')
1022 start = int(range[0])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001023 if range[1]:
1024 end = int(range[1])
1025 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001026 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001027
1028 self.send_response(206)
1029 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
1030 str(len(data))
1031 self.send_header('Content-Range', content_range)
1032 data = data[start: end + 1]
1033 else:
1034 self.send_response(200)
1035
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001036 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001037 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001038 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001039 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001040 self.end_headers()
1041
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001042 if (self.command != 'HEAD'):
1043 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001044
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001045 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001046 return True
1047
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001048 def SetCookieHandler(self):
1049 """This handler just sets a cookie, for testing cookie handling."""
1050
1051 if not self._ShouldHandleRequest("/set-cookie"):
1052 return False
1053
1054 query_char = self.path.find('?')
1055 if query_char != -1:
1056 cookie_values = self.path[query_char + 1:].split('&')
1057 else:
1058 cookie_values = ("",)
1059 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001060 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001061 for cookie_value in cookie_values:
1062 self.send_header('Set-Cookie', '%s' % cookie_value)
1063 self.end_headers()
1064 for cookie_value in cookie_values:
1065 self.wfile.write('%s' % cookie_value)
1066 return True
1067
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001068 def SetHeaderHandler(self):
1069 """This handler sets a response header. Parameters are in the
1070 key%3A%20value&key2%3A%20value2 format."""
1071
1072 if not self._ShouldHandleRequest("/set-header"):
1073 return False
1074
1075 query_char = self.path.find('?')
1076 if query_char != -1:
1077 headers_values = self.path[query_char + 1:].split('&')
1078 else:
1079 headers_values = ("",)
1080 self.send_response(200)
1081 self.send_header('Content-Type', 'text/html')
1082 for header_value in headers_values:
1083 header_value = urllib.unquote(header_value)
1084 (key, value) = header_value.split(': ', 1)
1085 self.send_header(key, value)
1086 self.end_headers()
1087 for header_value in headers_values:
1088 self.wfile.write('%s' % header_value)
1089 return True
1090
initial.commit94958cf2008-07-26 22:42:52 +00001091 def AuthBasicHandler(self):
1092 """This handler tests 'Basic' authentication. It just sends a page with
1093 title 'user/pass' if you succeed."""
1094
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001095 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001096 return False
1097
1098 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001099 expected_password = 'secret'
1100 realm = 'testrealm'
1101 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001102
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001103 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1104 query_params = cgi.parse_qs(query, True)
1105 if 'set-cookie-if-challenged' in query_params:
1106 set_cookie_if_challenged = True
1107 if 'password' in query_params:
1108 expected_password = query_params['password'][0]
1109 if 'realm' in query_params:
1110 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001111
initial.commit94958cf2008-07-26 22:42:52 +00001112 auth = self.headers.getheader('authorization')
1113 try:
1114 if not auth:
1115 raise Exception('no auth')
1116 b64str = re.findall(r'Basic (\S+)', auth)[0]
1117 userpass = base64.b64decode(b64str)
1118 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001119 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001120 raise Exception('wrong password')
1121 except Exception, e:
1122 # Authentication failed.
1123 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001124 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001125 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001126 if set_cookie_if_challenged:
1127 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001128 self.end_headers()
1129 self.wfile.write('<html><head>')
1130 self.wfile.write('<title>Denied: %s</title>' % e)
1131 self.wfile.write('</head><body>')
1132 self.wfile.write('auth=%s<p>' % auth)
1133 self.wfile.write('b64str=%s<p>' % b64str)
1134 self.wfile.write('username: %s<p>' % username)
1135 self.wfile.write('userpass: %s<p>' % userpass)
1136 self.wfile.write('password: %s<p>' % password)
1137 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1138 self.wfile.write('</body></html>')
1139 return True
1140
1141 # Authentication successful. (Return a cachable response to allow for
1142 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001143 old_protocol_version = self.protocol_version
1144 self.protocol_version = "HTTP/1.1"
1145
initial.commit94958cf2008-07-26 22:42:52 +00001146 if_none_match = self.headers.getheader('if-none-match')
1147 if if_none_match == "abc":
1148 self.send_response(304)
1149 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001150 elif url_path.endswith(".gif"):
1151 # Using chrome/test/data/google/logo.gif as the test image
1152 test_image_path = ['google', 'logo.gif']
1153 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1154 if not os.path.isfile(gif_path):
1155 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001156 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001157 return True
1158
1159 f = open(gif_path, "rb")
1160 data = f.read()
1161 f.close()
1162
1163 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001164 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001165 self.send_header('Cache-control', 'max-age=60000')
1166 self.send_header('Etag', 'abc')
1167 self.end_headers()
1168 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001169 else:
1170 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001171 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001172 self.send_header('Cache-control', 'max-age=60000')
1173 self.send_header('Etag', 'abc')
1174 self.end_headers()
1175 self.wfile.write('<html><head>')
1176 self.wfile.write('<title>%s/%s</title>' % (username, password))
1177 self.wfile.write('</head><body>')
1178 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001179 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001180 self.wfile.write('</body></html>')
1181
rvargas@google.com54453b72011-05-19 01:11:11 +00001182 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001183 return True
1184
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001185 def GDataAuthHandler(self):
1186 """This handler verifies the Authentication header for GData requests."""
1187 if not self.server.gdata_auth_token:
1188 # --auth-token is not specified, not the test case for GData.
1189 return False
1190
1191 if not self._ShouldHandleRequest('/files/chromeos/gdata'):
1192 return False
1193
1194 if 'GData-Version' not in self.headers:
1195 self.send_error(httplib.BAD_REQUEST, 'GData-Version header is missing.')
1196 return True
1197
1198 if 'Authorization' not in self.headers:
1199 self.send_error(httplib.UNAUTHORIZED)
1200 return True
1201
1202 field_prefix = 'Bearer '
1203 authorization = self.headers['Authorization']
1204 if not authorization.startswith(field_prefix):
1205 self.send_error(httplib.UNAUTHORIZED)
1206 return True
1207
1208 code = authorization[len(field_prefix):]
1209 if code != self.server.gdata_auth_token:
1210 self.send_error(httplib.UNAUTHORIZED)
1211 return True
1212
1213 return False
1214
1215 def GDataDocumentsFeedQueryHandler(self):
1216 """This handler verifies if required parameters are properly
1217 specified for the GData DocumentsFeed request."""
1218 if not self.server.gdata_auth_token:
1219 # --auth-token is not specified, not the test case for GData.
1220 return False
1221
1222 if not self._ShouldHandleRequest('/files/chromeos/gdata/root_feed.json'):
1223 return False
1224
1225 (path, question, query_params) = self.path.partition('?')
1226 self.query_params = urlparse.parse_qs(query_params)
1227
1228 if 'v' not in self.query_params:
1229 self.send_error(httplib.BAD_REQUEST, 'v is not specified.')
1230 return True
1231 elif 'alt' not in self.query_params or self.query_params['alt'] != ['json']:
1232 # currently our GData client only uses JSON format.
1233 self.send_error(httplib.BAD_REQUEST, 'alt parameter is wrong.')
1234 return True
1235
1236 return False
1237
tonyg@chromium.org75054202010-03-31 22:06:10 +00001238 def GetNonce(self, force_reset=False):
1239 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001240
tonyg@chromium.org75054202010-03-31 22:06:10 +00001241 This is a fake implementation. A real implementation would only use a given
1242 nonce a single time (hence the name n-once). However, for the purposes of
1243 unittesting, we don't care about the security of the nonce.
1244
1245 Args:
1246 force_reset: Iff set, the nonce will be changed. Useful for testing the
1247 "stale" response.
1248 """
1249 if force_reset or not self.server.nonce_time:
1250 self.server.nonce_time = time.time()
1251 return _new_md5('privatekey%s%d' %
1252 (self.path, self.server.nonce_time)).hexdigest()
1253
1254 def AuthDigestHandler(self):
1255 """This handler tests 'Digest' authentication.
1256
1257 It just sends a page with title 'user/pass' if you succeed.
1258
1259 A stale response is sent iff "stale" is present in the request path.
1260 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001261 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001262 return False
1263
tonyg@chromium.org75054202010-03-31 22:06:10 +00001264 stale = 'stale' in self.path
1265 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001266 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001267 password = 'secret'
1268 realm = 'testrealm'
1269
1270 auth = self.headers.getheader('authorization')
1271 pairs = {}
1272 try:
1273 if not auth:
1274 raise Exception('no auth')
1275 if not auth.startswith('Digest'):
1276 raise Exception('not digest')
1277 # Pull out all the name="value" pairs as a dictionary.
1278 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1279
1280 # Make sure it's all valid.
1281 if pairs['nonce'] != nonce:
1282 raise Exception('wrong nonce')
1283 if pairs['opaque'] != opaque:
1284 raise Exception('wrong opaque')
1285
1286 # Check the 'response' value and make sure it matches our magic hash.
1287 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001288 hash_a1 = _new_md5(
1289 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001290 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001291 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001292 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001293 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1294 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001295 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001296
1297 if pairs['response'] != response:
1298 raise Exception('wrong password')
1299 except Exception, e:
1300 # Authentication failed.
1301 self.send_response(401)
1302 hdr = ('Digest '
1303 'realm="%s", '
1304 'domain="/", '
1305 'qop="auth", '
1306 'algorithm=MD5, '
1307 'nonce="%s", '
1308 'opaque="%s"') % (realm, nonce, opaque)
1309 if stale:
1310 hdr += ', stale="TRUE"'
1311 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001312 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001313 self.end_headers()
1314 self.wfile.write('<html><head>')
1315 self.wfile.write('<title>Denied: %s</title>' % e)
1316 self.wfile.write('</head><body>')
1317 self.wfile.write('auth=%s<p>' % auth)
1318 self.wfile.write('pairs=%s<p>' % pairs)
1319 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1320 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1321 self.wfile.write('</body></html>')
1322 return True
1323
1324 # Authentication successful.
1325 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001326 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001327 self.end_headers()
1328 self.wfile.write('<html><head>')
1329 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1330 self.wfile.write('</head><body>')
1331 self.wfile.write('auth=%s<p>' % auth)
1332 self.wfile.write('pairs=%s<p>' % pairs)
1333 self.wfile.write('</body></html>')
1334
1335 return True
1336
1337 def SlowServerHandler(self):
1338 """Wait for the user suggested time before responding. The syntax is
1339 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001340 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001341 return False
1342 query_char = self.path.find('?')
1343 wait_sec = 1.0
1344 if query_char >= 0:
1345 try:
1346 wait_sec = int(self.path[query_char + 1:])
1347 except ValueError:
1348 pass
1349 time.sleep(wait_sec)
1350 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001351 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001352 self.end_headers()
1353 self.wfile.write("waited %d seconds" % wait_sec)
1354 return True
1355
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001356 def ChunkedServerHandler(self):
1357 """Send chunked response. Allows to specify chunks parameters:
1358 - waitBeforeHeaders - ms to wait before sending headers
1359 - waitBetweenChunks - ms to wait between chunks
1360 - chunkSize - size of each chunk in bytes
1361 - chunksNumber - number of chunks
1362 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1363 waits one second, then sends headers and five chunks five bytes each."""
1364 if not self._ShouldHandleRequest("/chunked"):
1365 return False
1366 query_char = self.path.find('?')
1367 chunkedSettings = {'waitBeforeHeaders' : 0,
1368 'waitBetweenChunks' : 0,
1369 'chunkSize' : 5,
1370 'chunksNumber' : 5}
1371 if query_char >= 0:
1372 params = self.path[query_char + 1:].split('&')
1373 for param in params:
1374 keyValue = param.split('=')
1375 if len(keyValue) == 2:
1376 try:
1377 chunkedSettings[keyValue[0]] = int(keyValue[1])
1378 except ValueError:
1379 pass
1380 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1381 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1382 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001383 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001384 self.send_header('Connection', 'close')
1385 self.send_header('Transfer-Encoding', 'chunked')
1386 self.end_headers()
1387 # Chunked encoding: sending all chunks, then final zero-length chunk and
1388 # then final CRLF.
1389 for i in range(0, chunkedSettings['chunksNumber']):
1390 if i > 0:
1391 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1392 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1393 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1394 self.sendChunkHelp('')
1395 return True
1396
initial.commit94958cf2008-07-26 22:42:52 +00001397 def ContentTypeHandler(self):
1398 """Returns a string of html with the given content type. E.g.,
1399 /contenttype?text/css returns an html file with the Content-Type
1400 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001401 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001402 return False
1403 query_char = self.path.find('?')
1404 content_type = self.path[query_char + 1:].strip()
1405 if not content_type:
1406 content_type = 'text/html'
1407 self.send_response(200)
1408 self.send_header('Content-Type', content_type)
1409 self.end_headers()
1410 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1411 return True
1412
creis@google.com2f4f6a42011-03-25 19:44:19 +00001413 def NoContentHandler(self):
1414 """Returns a 204 No Content response."""
1415 if not self._ShouldHandleRequest("/nocontent"):
1416 return False
1417 self.send_response(204)
1418 self.end_headers()
1419 return True
1420
initial.commit94958cf2008-07-26 22:42:52 +00001421 def ServerRedirectHandler(self):
1422 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001423 '/server-redirect?http://foo.bar/asdf' to redirect to
1424 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001425
1426 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001427 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001428 return False
1429
1430 query_char = self.path.find('?')
1431 if query_char < 0 or len(self.path) <= query_char + 1:
1432 self.sendRedirectHelp(test_name)
1433 return True
1434 dest = self.path[query_char + 1:]
1435
1436 self.send_response(301) # moved permanently
1437 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001438 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001439 self.end_headers()
1440 self.wfile.write('<html><head>')
1441 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1442
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001443 return True
initial.commit94958cf2008-07-26 22:42:52 +00001444
1445 def ClientRedirectHandler(self):
1446 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001447 '/client-redirect?http://foo.bar/asdf' to redirect to
1448 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001449
1450 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001451 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001452 return False
1453
1454 query_char = self.path.find('?');
1455 if query_char < 0 or len(self.path) <= query_char + 1:
1456 self.sendRedirectHelp(test_name)
1457 return True
1458 dest = self.path[query_char + 1:]
1459
1460 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001461 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001462 self.end_headers()
1463 self.wfile.write('<html><head>')
1464 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1465 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1466
1467 return True
1468
tony@chromium.org03266982010-03-05 03:18:42 +00001469 def MultipartHandler(self):
1470 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001471 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001472 if not self._ShouldHandleRequest(test_name):
1473 return False
1474
1475 num_frames = 10
1476 bound = '12345'
1477 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001478 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001479 'multipart/x-mixed-replace;boundary=' + bound)
1480 self.end_headers()
1481
1482 for i in xrange(num_frames):
1483 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001484 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001485 self.wfile.write('<title>page ' + str(i) + '</title>')
1486 self.wfile.write('page ' + str(i))
1487
1488 self.wfile.write('--' + bound + '--')
1489 return True
1490
tony@chromium.org4cb88302011-09-27 22:13:49 +00001491 def MultipartSlowHandler(self):
1492 """Send a multipart response (3 text/html pages) with a slight delay
1493 between each page. This is similar to how some pages show status using
1494 multipart."""
1495 test_name = '/multipart-slow'
1496 if not self._ShouldHandleRequest(test_name):
1497 return False
1498
1499 num_frames = 3
1500 bound = '12345'
1501 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001502 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001503 'multipart/x-mixed-replace;boundary=' + bound)
1504 self.end_headers()
1505
1506 for i in xrange(num_frames):
1507 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001508 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001509 time.sleep(0.25)
1510 if i == 2:
1511 self.wfile.write('<title>PASS</title>')
1512 else:
1513 self.wfile.write('<title>page ' + str(i) + '</title>')
1514 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1515
1516 self.wfile.write('--' + bound + '--')
1517 return True
1518
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001519 def GetSSLSessionCacheHandler(self):
1520 """Send a reply containing a log of the session cache operations."""
1521
1522 if not self._ShouldHandleRequest('/ssl-session-cache'):
1523 return False
1524
1525 self.send_response(200)
1526 self.send_header('Content-Type', 'text/plain')
1527 self.end_headers()
1528 try:
1529 for (action, sessionID) in self.server.session_cache.log:
1530 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
1531 except AttributeError, e:
1532 self.wfile.write('Pass --https-record-resume in order to use' +
1533 ' this request')
1534 return True
1535
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001536 def CloseSocketHandler(self):
1537 """Closes the socket without sending anything."""
1538
1539 if not self._ShouldHandleRequest('/close-socket'):
1540 return False
1541
1542 self.wfile.close()
1543 return True
1544
initial.commit94958cf2008-07-26 22:42:52 +00001545 def DefaultResponseHandler(self):
1546 """This is the catch-all response handler for requests that aren't handled
1547 by one of the special handlers above.
1548 Note that we specify the content-length as without it the https connection
1549 is not closed properly (and the browser keeps expecting data)."""
1550
1551 contents = "Default response given for path: " + self.path
1552 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001553 self.send_header('Content-Type', 'text/html')
1554 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001555 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001556 if (self.command != 'HEAD'):
1557 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001558 return True
1559
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001560 def RedirectConnectHandler(self):
1561 """Sends a redirect to the CONNECT request for www.redirect.com. This
1562 response is not specified by the RFC, so the browser should not follow
1563 the redirect."""
1564
1565 if (self.path.find("www.redirect.com") < 0):
1566 return False
1567
1568 dest = "http://www.destination.com/foo.js"
1569
1570 self.send_response(302) # moved temporarily
1571 self.send_header('Location', dest)
1572 self.send_header('Connection', 'close')
1573 self.end_headers()
1574 return True
1575
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001576 def ServerAuthConnectHandler(self):
1577 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1578 response doesn't make sense because the proxy server cannot request
1579 server authentication."""
1580
1581 if (self.path.find("www.server-auth.com") < 0):
1582 return False
1583
1584 challenge = 'Basic realm="WallyWorld"'
1585
1586 self.send_response(401) # unauthorized
1587 self.send_header('WWW-Authenticate', challenge)
1588 self.send_header('Connection', 'close')
1589 self.end_headers()
1590 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001591
1592 def DefaultConnectResponseHandler(self):
1593 """This is the catch-all response handler for CONNECT requests that aren't
1594 handled by one of the special handlers above. Real Web servers respond
1595 with 400 to CONNECT requests."""
1596
1597 contents = "Your client has issued a malformed or illegal request."
1598 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001599 self.send_header('Content-Type', 'text/html')
1600 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001601 self.end_headers()
1602 self.wfile.write(contents)
1603 return True
1604
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001605 def DeviceManagementHandler(self):
1606 """Delegates to the device management service used for cloud policy."""
1607 if not self._ShouldHandleRequest("/device_management"):
1608 return False
1609
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001610 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001611
1612 if not self.server._device_management_handler:
1613 import device_management
1614 policy_path = os.path.join(self.server.data_dir, 'device_management')
1615 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001616 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001617 self.server.policy_keys,
1618 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001619
1620 http_response, raw_reply = (
1621 self.server._device_management_handler.HandleRequest(self.path,
1622 self.headers,
1623 raw_request))
1624 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001625 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001626 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001627 self.end_headers()
1628 self.wfile.write(raw_reply)
1629 return True
1630
initial.commit94958cf2008-07-26 22:42:52 +00001631 # called by the redirect handling function when there is no parameter
1632 def sendRedirectHelp(self, redirect_name):
1633 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001634 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001635 self.end_headers()
1636 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1637 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1638 self.wfile.write('</body></html>')
1639
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001640 # called by chunked handling function
1641 def sendChunkHelp(self, chunk):
1642 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1643 self.wfile.write('%X\r\n' % len(chunk))
1644 self.wfile.write(chunk)
1645 self.wfile.write('\r\n')
1646
akalin@chromium.org154bb132010-11-12 02:20:27 +00001647
1648class SyncPageHandler(BasePageHandler):
1649 """Handler for the main HTTP sync server."""
1650
1651 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001652 get_handlers = [self.ChromiumSyncTimeHandler,
1653 self.ChromiumSyncMigrationOpHandler,
1654 self.ChromiumSyncCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001655 self.ChromiumSyncDisableNotificationsOpHandler,
1656 self.ChromiumSyncEnableNotificationsOpHandler,
1657 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001658 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001659 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001660 self.ChromiumSyncErrorOpHandler,
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001661 self.ChromiumSyncSyncTabsOpHandler,
1662 self.ChromiumSyncCreateSyncedBookmarksOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001663
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001664 post_handlers = [self.ChromiumSyncCommandHandler,
1665 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001666 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001667 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001668 post_handlers, [])
1669
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001670
akalin@chromium.org154bb132010-11-12 02:20:27 +00001671 def ChromiumSyncTimeHandler(self):
1672 """Handle Chromium sync .../time requests.
1673
1674 The syncer sometimes checks server reachability by examining /time.
1675 """
1676 test_name = "/chromiumsync/time"
1677 if not self._ShouldHandleRequest(test_name):
1678 return False
1679
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001680 # Chrome hates it if we send a response before reading the request.
1681 if self.headers.getheader('content-length'):
1682 length = int(self.headers.getheader('content-length'))
1683 raw_request = self.rfile.read(length)
1684
akalin@chromium.org154bb132010-11-12 02:20:27 +00001685 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001686 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001687 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001688 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001689 return True
1690
1691 def ChromiumSyncCommandHandler(self):
1692 """Handle a chromiumsync command arriving via http.
1693
1694 This covers all sync protocol commands: authentication, getupdates, and
1695 commit.
1696 """
1697 test_name = "/chromiumsync/command"
1698 if not self._ShouldHandleRequest(test_name):
1699 return False
1700
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001701 length = int(self.headers.getheader('content-length'))
1702 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001703 http_response = 200
1704 raw_reply = None
1705 if not self.server.GetAuthenticated():
1706 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001707 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1708 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001709 else:
1710 http_response, raw_reply = self.server.HandleCommand(
1711 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001712
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001713 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001714 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001715 if http_response == 401:
1716 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001717 self.end_headers()
1718 self.wfile.write(raw_reply)
1719 return True
1720
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001721 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001722 test_name = "/chromiumsync/migrate"
1723 if not self._ShouldHandleRequest(test_name):
1724 return False
1725
1726 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1727 self.path)
1728 self.send_response(http_response)
1729 self.send_header('Content-Type', 'text/html')
1730 self.send_header('Content-Length', len(raw_reply))
1731 self.end_headers()
1732 self.wfile.write(raw_reply)
1733 return True
1734
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001735 def ChromiumSyncCredHandler(self):
1736 test_name = "/chromiumsync/cred"
1737 if not self._ShouldHandleRequest(test_name):
1738 return False
1739 try:
1740 query = urlparse.urlparse(self.path)[4]
1741 cred_valid = urlparse.parse_qs(query)['valid']
1742 if cred_valid[0] == 'True':
1743 self.server.SetAuthenticated(True)
1744 else:
1745 self.server.SetAuthenticated(False)
1746 except:
1747 self.server.SetAuthenticated(False)
1748
1749 http_response = 200
1750 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1751 self.send_response(http_response)
1752 self.send_header('Content-Type', 'text/html')
1753 self.send_header('Content-Length', len(raw_reply))
1754 self.end_headers()
1755 self.wfile.write(raw_reply)
1756 return True
1757
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001758 def ChromiumSyncDisableNotificationsOpHandler(self):
1759 test_name = "/chromiumsync/disablenotifications"
1760 if not self._ShouldHandleRequest(test_name):
1761 return False
1762 self.server.GetXmppServer().DisableNotifications()
1763 result = 200
1764 raw_reply = ('<html><title>Notifications disabled</title>'
1765 '<H1>Notifications disabled</H1></html>')
1766 self.send_response(result)
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
1773 def ChromiumSyncEnableNotificationsOpHandler(self):
1774 test_name = "/chromiumsync/enablenotifications"
1775 if not self._ShouldHandleRequest(test_name):
1776 return False
1777 self.server.GetXmppServer().EnableNotifications()
1778 result = 200
1779 raw_reply = ('<html><title>Notifications enabled</title>'
1780 '<H1>Notifications enabled</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 ChromiumSyncSendNotificationOpHandler(self):
1789 test_name = "/chromiumsync/sendnotification"
1790 if not self._ShouldHandleRequest(test_name):
1791 return False
1792 query = urlparse.urlparse(self.path)[4]
1793 query_params = urlparse.parse_qs(query)
1794 channel = ''
1795 data = ''
1796 if 'channel' in query_params:
1797 channel = query_params['channel'][0]
1798 if 'data' in query_params:
1799 data = query_params['data'][0]
1800 self.server.GetXmppServer().SendNotification(channel, data)
1801 result = 200
1802 raw_reply = ('<html><title>Notification sent</title>'
1803 '<H1>Notification sent with channel "%s" '
1804 'and data "%s"</H1></html>'
1805 % (channel, data))
1806 self.send_response(result)
1807 self.send_header('Content-Type', 'text/html')
1808 self.send_header('Content-Length', len(raw_reply))
1809 self.end_headers()
1810 self.wfile.write(raw_reply)
1811 return True;
1812
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001813 def ChromiumSyncBirthdayErrorOpHandler(self):
1814 test_name = "/chromiumsync/birthdayerror"
1815 if not self._ShouldHandleRequest(test_name):
1816 return False
1817 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1818 self.send_response(result)
1819 self.send_header('Content-Type', 'text/html')
1820 self.send_header('Content-Length', len(raw_reply))
1821 self.end_headers()
1822 self.wfile.write(raw_reply)
1823 return True;
1824
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001825 def ChromiumSyncTransientErrorOpHandler(self):
1826 test_name = "/chromiumsync/transienterror"
1827 if not self._ShouldHandleRequest(test_name):
1828 return False
1829 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1830 self.send_response(result)
1831 self.send_header('Content-Type', 'text/html')
1832 self.send_header('Content-Length', len(raw_reply))
1833 self.end_headers()
1834 self.wfile.write(raw_reply)
1835 return True;
1836
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001837 def ChromiumSyncErrorOpHandler(self):
1838 test_name = "/chromiumsync/error"
1839 if not self._ShouldHandleRequest(test_name):
1840 return False
1841 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1842 self.path)
1843 self.send_response(result)
1844 self.send_header('Content-Type', 'text/html')
1845 self.send_header('Content-Length', len(raw_reply))
1846 self.end_headers()
1847 self.wfile.write(raw_reply)
1848 return True;
1849
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001850 def ChromiumSyncSyncTabsOpHandler(self):
1851 test_name = "/chromiumsync/synctabs"
1852 if not self._ShouldHandleRequest(test_name):
1853 return False
1854 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1855 self.send_response(result)
1856 self.send_header('Content-Type', 'text/html')
1857 self.send_header('Content-Length', len(raw_reply))
1858 self.end_headers()
1859 self.wfile.write(raw_reply)
1860 return True;
1861
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001862 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1863 test_name = "/chromiumsync/createsyncedbookmarks"
1864 if not self._ShouldHandleRequest(test_name):
1865 return False
1866 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1867 self.send_response(result)
1868 self.send_header('Content-Type', 'text/html')
1869 self.send_header('Content-Length', len(raw_reply))
1870 self.end_headers()
1871 self.wfile.write(raw_reply)
1872 return True;
1873
akalin@chromium.org154bb132010-11-12 02:20:27 +00001874
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001875def MakeDataDir():
1876 if options.data_dir:
1877 if not os.path.isdir(options.data_dir):
1878 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1879 return None
1880 my_data_dir = options.data_dir
1881 else:
1882 # Create the default path to our data dir, relative to the exe dir.
1883 my_data_dir = os.path.dirname(sys.argv[0])
1884 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001885 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001886
1887 #TODO(ibrar): Must use Find* funtion defined in google\tools
1888 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1889
1890 return my_data_dir
1891
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001892
1893class TCPEchoHandler(SocketServer.BaseRequestHandler):
1894 """The RequestHandler class for TCP echo server.
1895
1896 It is instantiated once per connection to the server, and overrides the
1897 handle() method to implement communication to the client.
1898 """
1899
1900 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001901 """Handles the request from the client and constructs a response."""
1902
1903 data = self.request.recv(65536).strip()
1904 # Verify the "echo request" message received from the client. Send back
1905 # "echo response" message if "echo request" message is valid.
1906 try:
1907 return_data = echo_message.GetEchoResponseData(data)
1908 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001909 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001910 except ValueError:
1911 return
1912
1913 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001914
1915
1916class UDPEchoHandler(SocketServer.BaseRequestHandler):
1917 """The RequestHandler class for UDP echo server.
1918
1919 It is instantiated once per connection to the server, and overrides the
1920 handle() method to implement communication to the client.
1921 """
1922
1923 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001924 """Handles the request from the client and constructs a response."""
1925
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001926 data = self.request[0].strip()
1927 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001928 # Verify the "echo request" message received from the client. Send back
1929 # "echo response" message if "echo request" message is valid.
1930 try:
1931 return_data = echo_message.GetEchoResponseData(data)
1932 if not return_data:
1933 return
1934 except ValueError:
1935 return
1936 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001937
1938
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001939class FileMultiplexer:
1940 def __init__(self, fd1, fd2) :
1941 self.__fd1 = fd1
1942 self.__fd2 = fd2
1943
1944 def __del__(self) :
1945 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1946 self.__fd1.close()
1947 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1948 self.__fd2.close()
1949
1950 def write(self, text) :
1951 self.__fd1.write(text)
1952 self.__fd2.write(text)
1953
1954 def flush(self) :
1955 self.__fd1.flush()
1956 self.__fd2.flush()
1957
initial.commit94958cf2008-07-26 22:42:52 +00001958def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001959 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001960 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001961 if options.log_to_console:
1962 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1963 else:
1964 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001965
1966 port = options.port
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001967 host = options.host
initial.commit94958cf2008-07-26 22:42:52 +00001968
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001969 server_data = {}
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001970 server_data['host'] = host
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001971
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001972 if options.server_type == SERVER_HTTP:
agl@chromium.org26ffa202012-03-19 17:18:07 +00001973 if options.cert:
1974 # let's make sure the cert file exists.
1975 if not os.path.isfile(options.cert):
1976 print 'specified server cert file not found: ' + options.cert + \
1977 ' exiting...'
1978 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001979 for ca_cert in options.ssl_client_ca:
1980 if not os.path.isfile(ca_cert):
1981 print 'specified trusted client CA file not found: ' + ca_cert + \
1982 ' exiting...'
1983 return
agl@chromium.org26ffa202012-03-19 17:18:07 +00001984 server = HTTPSServer((host, port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001985 options.ssl_client_auth, options.ssl_client_ca,
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001986 options.ssl_bulk_cipher, options.record_resume)
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001987 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001988 else:
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001989 server = HTTPServer((host, port), TestPageHandler)
1990 print 'HTTP server started on %s:%d...' % (host, server.server_port)
erikkay@google.com70397b62008-12-30 21:49:21 +00001991
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001992 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001993 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001994 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001995 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001996 server.policy_keys = options.policy_keys
1997 server.policy_user = options.policy_user
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001998 server.gdata_auth_token = options.auth_token
akalin@chromium.org154bb132010-11-12 02:20:27 +00001999 elif options.server_type == SERVER_SYNC:
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002000 server = SyncHTTPServer((host, port), SyncPageHandler)
akalin@chromium.org154bb132010-11-12 02:20:27 +00002001 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00002002 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2003 server_data['port'] = server.server_port
2004 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002005 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002006 # Used for generating the key (randomly) that encodes the "echo request"
2007 # message.
2008 random.seed()
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002009 server = TCPEchoServer((host, port), TCPEchoHandler)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002010 print 'Echo TCP server started on port %d...' % server.server_port
2011 server_data['port'] = server.server_port
2012 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002013 # Used for generating the key (randomly) that encodes the "echo request"
2014 # message.
2015 random.seed()
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002016 server = UDPEchoServer((host, port), UDPEchoHandler)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002017 print 'Echo UDP server started on port %d...' % server.server_port
2018 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002019 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00002020 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002021 my_data_dir = MakeDataDir()
2022
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002023 # Instantiate a dummy authorizer for managing 'virtual' users
2024 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2025
2026 # Define a new user having full r/w permissions and a read-only
2027 # anonymous user
2028 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2029
2030 authorizer.add_anonymous(my_data_dir)
2031
2032 # Instantiate FTP handler class
2033 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2034 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002035
2036 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00002037 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2038 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002039
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002040 # Instantiate FTP server class and listen to address:port
2041 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00002042 server_data['port'] = server.socket.getsockname()[1]
2043 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00002044
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002045 # Notify the parent that we've started. (BaseServer subclasses
2046 # bind their sockets on construction.)
2047 if options.startup_pipe is not None:
dpranke@chromium.org70049b72011-10-14 00:38:18 +00002048 server_data_json = json.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00002049 server_data_len = len(server_data_json)
2050 print 'sending server_data: %s (%d bytes)' % (
2051 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002052 if sys.platform == 'win32':
2053 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
2054 else:
2055 fd = options.startup_pipe
2056 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00002057 # First write the data length as an unsigned 4-byte value. This
2058 # is _not_ using network byte ordering since the other end of the
2059 # pipe is on the same machine.
2060 startup_pipe.write(struct.pack('=L', server_data_len))
2061 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002062 startup_pipe.close()
2063
initial.commit94958cf2008-07-26 22:42:52 +00002064 try:
2065 server.serve_forever()
2066 except KeyboardInterrupt:
2067 print 'shutting down server'
2068 server.stop = True
2069
2070if __name__ == '__main__':
2071 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002072 option_parser.add_option("-f", '--ftp', action='store_const',
2073 const=SERVER_FTP, default=SERVER_HTTP,
2074 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00002075 help='start up an FTP server.')
2076 option_parser.add_option('', '--sync', action='store_const',
2077 const=SERVER_SYNC, default=SERVER_HTTP,
2078 dest='server_type',
2079 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002080 option_parser.add_option('', '--tcp-echo', action='store_const',
2081 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2082 dest='server_type',
2083 help='start up a tcp echo server.')
2084 option_parser.add_option('', '--udp-echo', action='store_const',
2085 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2086 dest='server_type',
2087 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00002088 option_parser.add_option('', '--log-to-console', action='store_const',
2089 const=True, default=False,
2090 dest='log_to_console',
2091 help='Enables or disables sys.stdout logging to '
2092 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00002093 option_parser.add_option('', '--port', default='0', type='int',
2094 help='Port used by the server. If unspecified, the '
2095 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002096 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00002097 help='Directory from which to read the files.')
agl@chromium.org26ffa202012-03-19 17:18:07 +00002098 option_parser.add_option('', '--https', dest='cert',
2099 help='Specify that https should be used, specify '
2100 'the path to the cert containing the private key '
2101 'the server should use.')
agl@chromium.orgf9e66792011-12-12 22:22:19 +00002102 option_parser.add_option('', '--https-record-resume', dest='record_resume',
2103 const=True, default=False, action='store_const',
2104 help='Record resumption cache events rather than'
2105 ' resuming as normal. Allows the use of the'
2106 ' /ssl-session-cache request')
davidben@chromium.org31282a12010-08-07 01:10:02 +00002107 option_parser.add_option('', '--ssl-client-auth', action='store_true',
2108 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00002109 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
2110 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00002111 'should include the CA named in the subject of '
2112 'the DER-encoded certificate contained in the '
2113 'specified file. This option may appear multiple '
2114 'times, indicating multiple CA names should be '
2115 'sent in the request.')
2116 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
2117 help='Specify the bulk encryption algorithm(s)'
2118 'that will be accepted by the SSL server. Valid '
2119 'values are "aes256", "aes128", "3des", "rc4". If '
2120 'omitted, all algorithms will be used. This '
2121 'option may appear multiple times, indicating '
2122 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00002123 option_parser.add_option('', '--file-root-url', default='/files/',
2124 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002125 option_parser.add_option('', '--startup-pipe', type='int',
2126 dest='startup_pipe',
2127 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002128 option_parser.add_option('', '--policy-key', action='append',
2129 dest='policy_keys',
2130 help='Specify a path to a PEM-encoded private key '
2131 'to use for policy signing. May be specified '
2132 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00002133 'the server. If ther server has multiple keys, it '
2134 'will rotate through them in at each request a '
2135 'round-robin fashion. The server will generate a '
2136 'random key if none is specified on the command '
2137 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002138 option_parser.add_option('', '--policy-user', default='user@example.com',
2139 dest='policy_user',
2140 help='Specify the user name the server should '
2141 'report back to the client as the user owning the '
2142 'token used for making the policy request.')
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002143 option_parser.add_option('', '--host', default='127.0.0.1',
2144 dest='host',
2145 help='Hostname or IP upon which the server will '
2146 'listen. Client connections will also only be '
2147 'allowed from this address.')
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00002148 option_parser.add_option('', '--auth-token', dest='auth_token',
2149 help='Specify the auth token which should be used'
2150 'in the authorization header for GData.')
initial.commit94958cf2008-07-26 22:42:52 +00002151 options, args = option_parser.parse_args()
2152
2153 sys.exit(main(options, args))