blob: 28405ffc17e871cabafb5d47ddf1c23c34af7745 [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
initial.commit94958cf2008-07-26 22:42:52 +000027import SocketServer
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000028import socket
initial.commit94958cf2008-07-26 22:42:52 +000029import sys
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +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):
108 """This is a specialization of StoppableHTTPerver that adds client
109 verification."""
110
111 pass
112
113
114class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
115 ClientRestrictingServerMixIn,
116 StoppableHTTPServer):
117 """This is a specialization of StoppableHTTPerver that add https support and
118 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000119
davidben@chromium.org31282a12010-08-07 01:10:02 +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):
initial.commit94958cf2008-07-26 22:42:52 +0000123 s = open(cert_path).read()
rsleevi@chromium.orge35b5fc2012-03-02 14:58:02 +0000124 self.cert_chain = tlslite.api.X509CertChain().parseChain(s)
initial.commit94958cf2008-07-26 22:42:52 +0000125 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:
1026 end = len(data)
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):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001652 get_handlers = [self.ChromiumSyncMigrationOpHandler,
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001653 self.ChromiumSyncTimeHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001654 self.ChromiumSyncDisableNotificationsOpHandler,
1655 self.ChromiumSyncEnableNotificationsOpHandler,
1656 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001657 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001658 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001659 self.ChromiumSyncSyncTabsOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001660 self.ChromiumSyncErrorOpHandler,
1661 self.ChromiumSyncCredHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001662
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001663 post_handlers = [self.ChromiumSyncCommandHandler,
1664 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001665 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001666 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001667 post_handlers, [])
1668
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001669
akalin@chromium.org154bb132010-11-12 02:20:27 +00001670 def ChromiumSyncTimeHandler(self):
1671 """Handle Chromium sync .../time requests.
1672
1673 The syncer sometimes checks server reachability by examining /time.
1674 """
1675 test_name = "/chromiumsync/time"
1676 if not self._ShouldHandleRequest(test_name):
1677 return False
1678
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001679 # Chrome hates it if we send a response before reading the request.
1680 if self.headers.getheader('content-length'):
1681 length = int(self.headers.getheader('content-length'))
1682 raw_request = self.rfile.read(length)
1683
akalin@chromium.org154bb132010-11-12 02:20:27 +00001684 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001685 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001686 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001687 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001688 return True
1689
1690 def ChromiumSyncCommandHandler(self):
1691 """Handle a chromiumsync command arriving via http.
1692
1693 This covers all sync protocol commands: authentication, getupdates, and
1694 commit.
1695 """
1696 test_name = "/chromiumsync/command"
1697 if not self._ShouldHandleRequest(test_name):
1698 return False
1699
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001700 length = int(self.headers.getheader('content-length'))
1701 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001702 http_response = 200
1703 raw_reply = None
1704 if not self.server.GetAuthenticated():
1705 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001706 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1707 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001708 else:
1709 http_response, raw_reply = self.server.HandleCommand(
1710 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001711
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001712 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001713 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001714 if http_response == 401:
1715 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001716 self.end_headers()
1717 self.wfile.write(raw_reply)
1718 return True
1719
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001720 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001721 test_name = "/chromiumsync/migrate"
1722 if not self._ShouldHandleRequest(test_name):
1723 return False
1724
1725 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1726 self.path)
1727 self.send_response(http_response)
1728 self.send_header('Content-Type', 'text/html')
1729 self.send_header('Content-Length', len(raw_reply))
1730 self.end_headers()
1731 self.wfile.write(raw_reply)
1732 return True
1733
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001734 def ChromiumSyncCredHandler(self):
1735 test_name = "/chromiumsync/cred"
1736 if not self._ShouldHandleRequest(test_name):
1737 return False
1738 try:
1739 query = urlparse.urlparse(self.path)[4]
1740 cred_valid = urlparse.parse_qs(query)['valid']
1741 if cred_valid[0] == 'True':
1742 self.server.SetAuthenticated(True)
1743 else:
1744 self.server.SetAuthenticated(False)
1745 except:
1746 self.server.SetAuthenticated(False)
1747
1748 http_response = 200
1749 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1750 self.send_response(http_response)
1751 self.send_header('Content-Type', 'text/html')
1752 self.send_header('Content-Length', len(raw_reply))
1753 self.end_headers()
1754 self.wfile.write(raw_reply)
1755 return True
1756
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001757 def ChromiumSyncDisableNotificationsOpHandler(self):
1758 test_name = "/chromiumsync/disablenotifications"
1759 if not self._ShouldHandleRequest(test_name):
1760 return False
1761 self.server.GetXmppServer().DisableNotifications()
1762 result = 200
1763 raw_reply = ('<html><title>Notifications disabled</title>'
1764 '<H1>Notifications disabled</H1></html>')
1765 self.send_response(result)
1766 self.send_header('Content-Type', 'text/html')
1767 self.send_header('Content-Length', len(raw_reply))
1768 self.end_headers()
1769 self.wfile.write(raw_reply)
1770 return True;
1771
1772 def ChromiumSyncEnableNotificationsOpHandler(self):
1773 test_name = "/chromiumsync/enablenotifications"
1774 if not self._ShouldHandleRequest(test_name):
1775 return False
1776 self.server.GetXmppServer().EnableNotifications()
1777 result = 200
1778 raw_reply = ('<html><title>Notifications enabled</title>'
1779 '<H1>Notifications enabled</H1></html>')
1780 self.send_response(result)
1781 self.send_header('Content-Type', 'text/html')
1782 self.send_header('Content-Length', len(raw_reply))
1783 self.end_headers()
1784 self.wfile.write(raw_reply)
1785 return True;
1786
1787 def ChromiumSyncSendNotificationOpHandler(self):
1788 test_name = "/chromiumsync/sendnotification"
1789 if not self._ShouldHandleRequest(test_name):
1790 return False
1791 query = urlparse.urlparse(self.path)[4]
1792 query_params = urlparse.parse_qs(query)
1793 channel = ''
1794 data = ''
1795 if 'channel' in query_params:
1796 channel = query_params['channel'][0]
1797 if 'data' in query_params:
1798 data = query_params['data'][0]
1799 self.server.GetXmppServer().SendNotification(channel, data)
1800 result = 200
1801 raw_reply = ('<html><title>Notification sent</title>'
1802 '<H1>Notification sent with channel "%s" '
1803 'and data "%s"</H1></html>'
1804 % (channel, data))
1805 self.send_response(result)
1806 self.send_header('Content-Type', 'text/html')
1807 self.send_header('Content-Length', len(raw_reply))
1808 self.end_headers()
1809 self.wfile.write(raw_reply)
1810 return True;
1811
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001812 def ChromiumSyncBirthdayErrorOpHandler(self):
1813 test_name = "/chromiumsync/birthdayerror"
1814 if not self._ShouldHandleRequest(test_name):
1815 return False
1816 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1817 self.send_response(result)
1818 self.send_header('Content-Type', 'text/html')
1819 self.send_header('Content-Length', len(raw_reply))
1820 self.end_headers()
1821 self.wfile.write(raw_reply)
1822 return True;
1823
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001824 def ChromiumSyncTransientErrorOpHandler(self):
1825 test_name = "/chromiumsync/transienterror"
1826 if not self._ShouldHandleRequest(test_name):
1827 return False
1828 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1829 self.send_response(result)
1830 self.send_header('Content-Type', 'text/html')
1831 self.send_header('Content-Length', len(raw_reply))
1832 self.end_headers()
1833 self.wfile.write(raw_reply)
1834 return True;
1835
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001836 def ChromiumSyncErrorOpHandler(self):
1837 test_name = "/chromiumsync/error"
1838 if not self._ShouldHandleRequest(test_name):
1839 return False
1840 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1841 self.path)
1842 self.send_response(result)
1843 self.send_header('Content-Type', 'text/html')
1844 self.send_header('Content-Length', len(raw_reply))
1845 self.end_headers()
1846 self.wfile.write(raw_reply)
1847 return True;
1848
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001849 def ChromiumSyncSyncTabsOpHandler(self):
1850 test_name = "/chromiumsync/synctabs"
1851 if not self._ShouldHandleRequest(test_name):
1852 return False
1853 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1854 self.send_response(result)
1855 self.send_header('Content-Type', 'text/html')
1856 self.send_header('Content-Length', len(raw_reply))
1857 self.end_headers()
1858 self.wfile.write(raw_reply)
1859 return True;
1860
akalin@chromium.org154bb132010-11-12 02:20:27 +00001861
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001862def MakeDataDir():
1863 if options.data_dir:
1864 if not os.path.isdir(options.data_dir):
1865 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1866 return None
1867 my_data_dir = options.data_dir
1868 else:
1869 # Create the default path to our data dir, relative to the exe dir.
1870 my_data_dir = os.path.dirname(sys.argv[0])
1871 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001872 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001873
1874 #TODO(ibrar): Must use Find* funtion defined in google\tools
1875 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1876
1877 return my_data_dir
1878
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001879
1880class TCPEchoHandler(SocketServer.BaseRequestHandler):
1881 """The RequestHandler class for TCP echo server.
1882
1883 It is instantiated once per connection to the server, and overrides the
1884 handle() method to implement communication to the client.
1885 """
1886
1887 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001888 """Handles the request from the client and constructs a response."""
1889
1890 data = self.request.recv(65536).strip()
1891 # Verify the "echo request" message received from the client. Send back
1892 # "echo response" message if "echo request" message is valid.
1893 try:
1894 return_data = echo_message.GetEchoResponseData(data)
1895 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001896 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001897 except ValueError:
1898 return
1899
1900 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001901
1902
1903class UDPEchoHandler(SocketServer.BaseRequestHandler):
1904 """The RequestHandler class for UDP echo server.
1905
1906 It is instantiated once per connection to the server, and overrides the
1907 handle() method to implement communication to the client.
1908 """
1909
1910 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001911 """Handles the request from the client and constructs a response."""
1912
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001913 data = self.request[0].strip()
1914 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001915 # Verify the "echo request" message received from the client. Send back
1916 # "echo response" message if "echo request" message is valid.
1917 try:
1918 return_data = echo_message.GetEchoResponseData(data)
1919 if not return_data:
1920 return
1921 except ValueError:
1922 return
1923 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001924
1925
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001926class FileMultiplexer:
1927 def __init__(self, fd1, fd2) :
1928 self.__fd1 = fd1
1929 self.__fd2 = fd2
1930
1931 def __del__(self) :
1932 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1933 self.__fd1.close()
1934 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1935 self.__fd2.close()
1936
1937 def write(self, text) :
1938 self.__fd1.write(text)
1939 self.__fd2.write(text)
1940
1941 def flush(self) :
1942 self.__fd1.flush()
1943 self.__fd2.flush()
1944
initial.commit94958cf2008-07-26 22:42:52 +00001945def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001946 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001947 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001948 if options.log_to_console:
1949 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1950 else:
1951 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001952
1953 port = options.port
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001954 host = options.host
initial.commit94958cf2008-07-26 22:42:52 +00001955
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001956 server_data = {}
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001957 server_data['host'] = host
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001958
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001959 if options.server_type == SERVER_HTTP:
1960 if options.cert:
1961 # let's make sure the cert file exists.
1962 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001963 print 'specified server cert file not found: ' + options.cert + \
1964 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001965 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001966 for ca_cert in options.ssl_client_ca:
1967 if not os.path.isfile(ca_cert):
1968 print 'specified trusted client CA file not found: ' + ca_cert + \
1969 ' exiting...'
1970 return
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001971 server = HTTPSServer((host, port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001972 options.ssl_client_auth, options.ssl_client_ca,
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001973 options.ssl_bulk_cipher, options.record_resume)
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001974 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001975 else:
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001976 server = HTTPServer((host, port), TestPageHandler)
1977 print 'HTTP server started on %s:%d...' % (host, server.server_port)
erikkay@google.com70397b62008-12-30 21:49:21 +00001978
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001979 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001980 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001981 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001982 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001983 server.policy_keys = options.policy_keys
1984 server.policy_user = options.policy_user
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001985 server.gdata_auth_token = options.auth_token
akalin@chromium.org154bb132010-11-12 02:20:27 +00001986 elif options.server_type == SERVER_SYNC:
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001987 server = SyncHTTPServer((host, port), SyncPageHandler)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001988 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001989 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1990 server_data['port'] = server.server_port
1991 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001992 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001993 # Used for generating the key (randomly) that encodes the "echo request"
1994 # message.
1995 random.seed()
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001996 server = TCPEchoServer((host, port), TCPEchoHandler)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001997 print 'Echo TCP server started on port %d...' % server.server_port
1998 server_data['port'] = server.server_port
1999 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002000 # Used for generating the key (randomly) that encodes the "echo request"
2001 # message.
2002 random.seed()
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002003 server = UDPEchoServer((host, port), UDPEchoHandler)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002004 print 'Echo UDP server started on port %d...' % server.server_port
2005 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002006 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00002007 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002008 my_data_dir = MakeDataDir()
2009
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002010 # Instantiate a dummy authorizer for managing 'virtual' users
2011 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2012
2013 # Define a new user having full r/w permissions and a read-only
2014 # anonymous user
2015 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2016
2017 authorizer.add_anonymous(my_data_dir)
2018
2019 # Instantiate FTP handler class
2020 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2021 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002022
2023 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00002024 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2025 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002026
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002027 # Instantiate FTP server class and listen to address:port
2028 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00002029 server_data['port'] = server.socket.getsockname()[1]
2030 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00002031
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002032 # Notify the parent that we've started. (BaseServer subclasses
2033 # bind their sockets on construction.)
2034 if options.startup_pipe is not None:
dpranke@chromium.org70049b72011-10-14 00:38:18 +00002035 server_data_json = json.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00002036 server_data_len = len(server_data_json)
2037 print 'sending server_data: %s (%d bytes)' % (
2038 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002039 if sys.platform == 'win32':
2040 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
2041 else:
2042 fd = options.startup_pipe
2043 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00002044 # First write the data length as an unsigned 4-byte value. This
2045 # is _not_ using network byte ordering since the other end of the
2046 # pipe is on the same machine.
2047 startup_pipe.write(struct.pack('=L', server_data_len))
2048 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002049 startup_pipe.close()
2050
initial.commit94958cf2008-07-26 22:42:52 +00002051 try:
2052 server.serve_forever()
2053 except KeyboardInterrupt:
2054 print 'shutting down server'
2055 server.stop = True
2056
2057if __name__ == '__main__':
2058 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002059 option_parser.add_option("-f", '--ftp', action='store_const',
2060 const=SERVER_FTP, default=SERVER_HTTP,
2061 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00002062 help='start up an FTP server.')
2063 option_parser.add_option('', '--sync', action='store_const',
2064 const=SERVER_SYNC, default=SERVER_HTTP,
2065 dest='server_type',
2066 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002067 option_parser.add_option('', '--tcp-echo', action='store_const',
2068 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2069 dest='server_type',
2070 help='start up a tcp echo server.')
2071 option_parser.add_option('', '--udp-echo', action='store_const',
2072 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2073 dest='server_type',
2074 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00002075 option_parser.add_option('', '--log-to-console', action='store_const',
2076 const=True, default=False,
2077 dest='log_to_console',
2078 help='Enables or disables sys.stdout logging to '
2079 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00002080 option_parser.add_option('', '--port', default='0', type='int',
2081 help='Port used by the server. If unspecified, the '
2082 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002083 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00002084 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002085 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00002086 help='Specify that https should be used, specify '
2087 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00002088 'the server should use.')
agl@chromium.orgf9e66792011-12-12 22:22:19 +00002089 option_parser.add_option('', '--https-record-resume', dest='record_resume',
2090 const=True, default=False, action='store_const',
2091 help='Record resumption cache events rather than'
2092 ' resuming as normal. Allows the use of the'
2093 ' /ssl-session-cache request')
davidben@chromium.org31282a12010-08-07 01:10:02 +00002094 option_parser.add_option('', '--ssl-client-auth', action='store_true',
2095 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00002096 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
2097 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00002098 'should include the CA named in the subject of '
2099 'the DER-encoded certificate contained in the '
2100 'specified file. This option may appear multiple '
2101 'times, indicating multiple CA names should be '
2102 'sent in the request.')
2103 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
2104 help='Specify the bulk encryption algorithm(s)'
2105 'that will be accepted by the SSL server. Valid '
2106 'values are "aes256", "aes128", "3des", "rc4". If '
2107 'omitted, all algorithms will be used. This '
2108 'option may appear multiple times, indicating '
2109 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00002110 option_parser.add_option('', '--file-root-url', default='/files/',
2111 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002112 option_parser.add_option('', '--startup-pipe', type='int',
2113 dest='startup_pipe',
2114 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002115 option_parser.add_option('', '--policy-key', action='append',
2116 dest='policy_keys',
2117 help='Specify a path to a PEM-encoded private key '
2118 'to use for policy signing. May be specified '
2119 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00002120 'the server. If ther server has multiple keys, it '
2121 'will rotate through them in at each request a '
2122 'round-robin fashion. The server will generate a '
2123 'random key if none is specified on the command '
2124 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002125 option_parser.add_option('', '--policy-user', default='user@example.com',
2126 dest='policy_user',
2127 help='Specify the user name the server should '
2128 'report back to the client as the user owning the '
2129 'token used for making the policy request.')
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002130 option_parser.add_option('', '--host', default='127.0.0.1',
2131 dest='host',
2132 help='Hostname or IP upon which the server will '
2133 'listen. Client connections will also only be '
2134 'allowed from this address.')
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00002135 option_parser.add_option('', '--auth-token', dest='auth_token',
2136 help='Specify the auth token which should be used'
2137 'in the authorization header for GData.')
initial.commit94958cf2008-07-26 22:42:52 +00002138 options, args = option_parser.parse_args()
2139
2140 sys.exit(main(options, args))