blob: bc7f5e188e881c165232a18232d37cfd3e7d9757 [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00002# Copyright (c) 2011 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
initial.commit94958cf2008-07-26 22:42:52 +000021import optparse
22import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000023import random
initial.commit94958cf2008-07-26 22:42:52 +000024import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000025import select
initial.commit94958cf2008-07-26 22:42:52 +000026import SocketServer
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import socket
initial.commit94958cf2008-07-26 22:42:52 +000028import sys
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000029import struct
initial.commit94958cf2008-07-26 22:42:52 +000030import time
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000031import urlparse
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000032import warnings
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000033import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000034
35# Ignore deprecation warnings, they make our output more cluttered.
36warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000037
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000038import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000039import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000040import tlslite
41import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000042
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000043try:
44 import hashlib
45 _new_md5 = hashlib.md5
46except ImportError:
47 import md5
48 _new_md5 = md5.new
49
dpranke@chromium.org70049b72011-10-14 00:38:18 +000050try:
51 import json
52except ImportError:
53 import simplejson as json
54
davidben@chromium.org06fcf202010-09-22 18:15:23 +000055if sys.platform == 'win32':
56 import msvcrt
57
maruel@chromium.org756cf982009-03-05 12:46:38 +000058SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000059SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000060SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000061SERVER_TCP_ECHO = 3
62SERVER_UDP_ECHO = 4
initial.commit94958cf2008-07-26 22:42:52 +000063
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000064# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000065debug_output = sys.stderr
66def debug(str):
67 debug_output.write(str + "\n")
68 debug_output.flush()
69
70class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
71 """This is a specialization of of BaseHTTPServer to allow it
72 to be exited cleanly (by setting its "stop" member to True)."""
73
74 def serve_forever(self):
75 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000076 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000077 while not self.stop:
78 self.handle_request()
79 self.socket.close()
80
81class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
82 """This is a specialization of StoppableHTTPerver that add https support."""
83
davidben@chromium.org31282a12010-08-07 01:10:02 +000084 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000085 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers):
initial.commit94958cf2008-07-26 22:42:52 +000086 s = open(cert_path).read()
87 x509 = tlslite.api.X509()
88 x509.parse(s)
89 self.cert_chain = tlslite.api.X509CertChain([x509])
90 s = open(cert_path).read()
91 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000092 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000093 self.ssl_client_cas = []
94 for ca_file in ssl_client_cas:
95 s = open(ca_file).read()
96 x509 = tlslite.api.X509()
97 x509.parse(s)
98 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000099 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
100 if ssl_bulk_ciphers is not None:
101 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000102
103 self.session_cache = tlslite.api.SessionCache()
104 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
105
106 def handshake(self, tlsConnection):
107 """Creates the SSL connection."""
108 try:
109 tlsConnection.handshakeServer(certChain=self.cert_chain,
110 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000111 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000112 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000113 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000114 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +0000115 tlsConnection.ignoreAbruptClose = True
116 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000117 except tlslite.api.TLSAbruptCloseError:
118 # Ignore abrupt close.
119 return True
initial.commit94958cf2008-07-26 22:42:52 +0000120 except tlslite.api.TLSError, error:
121 print "Handshake failure:", str(error)
122 return False
123
akalin@chromium.org154bb132010-11-12 02:20:27 +0000124
125class SyncHTTPServer(StoppableHTTPServer):
126 """An HTTP server that handles sync commands."""
127
128 def __init__(self, server_address, request_handler_class):
129 # We import here to avoid pulling in chromiumsync's dependencies
130 # unless strictly necessary.
131 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000132 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000133 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000134 self._sync_handler = chromiumsync.TestServer()
135 self._xmpp_socket_map = {}
136 self._xmpp_server = xmppserver.XmppServer(
137 self._xmpp_socket_map, ('localhost', 0))
138 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000139 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000140
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000141 def GetXmppServer(self):
142 return self._xmpp_server
143
akalin@chromium.org154bb132010-11-12 02:20:27 +0000144 def HandleCommand(self, query, raw_request):
145 return self._sync_handler.HandleCommand(query, raw_request)
146
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000147 def HandleRequestNoBlock(self):
148 """Handles a single request.
149
150 Copied from SocketServer._handle_request_noblock().
151 """
152 try:
153 request, client_address = self.get_request()
154 except socket.error:
155 return
156 if self.verify_request(request, client_address):
157 try:
158 self.process_request(request, client_address)
159 except:
160 self.handle_error(request, client_address)
161 self.close_request(request)
162
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000163 def SetAuthenticated(self, auth_valid):
164 self.authenticated = auth_valid
165
166 def GetAuthenticated(self):
167 return self.authenticated
168
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000169 def serve_forever(self):
170 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
171 """
172
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000173 def HandleXmppSocket(fd, socket_map, handler):
174 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000175
176 Adapted from asyncore.read() et al.
177 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000178 xmpp_connection = socket_map.get(fd)
179 # This could happen if a previous handler call caused fd to get
180 # removed from socket_map.
181 if xmpp_connection is None:
182 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000183 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000184 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000185 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
186 raise
187 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000188 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000189
190 while True:
191 read_fds = [ self.fileno() ]
192 write_fds = []
193 exceptional_fds = []
194
195 for fd, xmpp_connection in self._xmpp_socket_map.items():
196 is_r = xmpp_connection.readable()
197 is_w = xmpp_connection.writable()
198 if is_r:
199 read_fds.append(fd)
200 if is_w:
201 write_fds.append(fd)
202 if is_r or is_w:
203 exceptional_fds.append(fd)
204
205 try:
206 read_fds, write_fds, exceptional_fds = (
207 select.select(read_fds, write_fds, exceptional_fds))
208 except select.error, err:
209 if err.args[0] != errno.EINTR:
210 raise
211 else:
212 continue
213
214 for fd in read_fds:
215 if fd == self.fileno():
216 self.HandleRequestNoBlock()
217 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000218 HandleXmppSocket(fd, self._xmpp_socket_map,
219 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000220
221 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000222 HandleXmppSocket(fd, self._xmpp_socket_map,
223 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000224
225 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000226 HandleXmppSocket(fd, self._xmpp_socket_map,
227 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000228
akalin@chromium.org154bb132010-11-12 02:20:27 +0000229
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000230class TCPEchoServer(SocketServer.TCPServer):
231 """A TCP echo server that echoes back what it has received."""
232
233 def server_bind(self):
234 """Override server_bind to store the server name."""
235 SocketServer.TCPServer.server_bind(self)
236 host, port = self.socket.getsockname()[:2]
237 self.server_name = socket.getfqdn(host)
238 self.server_port = port
239
240 def serve_forever(self):
241 self.stop = False
242 self.nonce_time = None
243 while not self.stop:
244 self.handle_request()
245 self.socket.close()
246
247
248class UDPEchoServer(SocketServer.UDPServer):
249 """A UDP echo server that echoes back what it has received."""
250
251 def server_bind(self):
252 """Override server_bind to store the server name."""
253 SocketServer.UDPServer.server_bind(self)
254 host, port = self.socket.getsockname()[:2]
255 self.server_name = socket.getfqdn(host)
256 self.server_port = port
257
258 def serve_forever(self):
259 self.stop = False
260 self.nonce_time = None
261 while not self.stop:
262 self.handle_request()
263 self.socket.close()
264
265
akalin@chromium.org154bb132010-11-12 02:20:27 +0000266class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
267
268 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000269 connect_handlers, get_handlers, head_handlers, post_handlers,
270 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000271 self._connect_handlers = connect_handlers
272 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000273 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000274 self._post_handlers = post_handlers
275 self._put_handlers = put_handlers
276 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
277 self, request, client_address, socket_server)
278
279 def log_request(self, *args, **kwargs):
280 # Disable request logging to declutter test log output.
281 pass
282
283 def _ShouldHandleRequest(self, handler_name):
284 """Determines if the path can be handled by the handler.
285
286 We consider a handler valid if the path begins with the
287 handler name. It can optionally be followed by "?*", "/*".
288 """
289
290 pattern = re.compile('%s($|\?|/).*' % handler_name)
291 return pattern.match(self.path)
292
293 def do_CONNECT(self):
294 for handler in self._connect_handlers:
295 if handler():
296 return
297
298 def do_GET(self):
299 for handler in self._get_handlers:
300 if handler():
301 return
302
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000303 def do_HEAD(self):
304 for handler in self._head_handlers:
305 if handler():
306 return
307
akalin@chromium.org154bb132010-11-12 02:20:27 +0000308 def do_POST(self):
309 for handler in self._post_handlers:
310 if handler():
311 return
312
313 def do_PUT(self):
314 for handler in self._put_handlers:
315 if handler():
316 return
317
318
319class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000320
321 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000322 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000323 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000324 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000325 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000326 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000327 self.NoCacheMaxAgeTimeHandler,
328 self.NoCacheTimeHandler,
329 self.CacheTimeHandler,
330 self.CacheExpiresHandler,
331 self.CacheProxyRevalidateHandler,
332 self.CachePrivateHandler,
333 self.CachePublicHandler,
334 self.CacheSMaxAgeHandler,
335 self.CacheMustRevalidateHandler,
336 self.CacheMustRevalidateMaxAgeHandler,
337 self.CacheNoStoreHandler,
338 self.CacheNoStoreMaxAgeHandler,
339 self.CacheNoTransformHandler,
340 self.DownloadHandler,
341 self.DownloadFinishHandler,
342 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000343 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000344 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000345 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000346 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000347 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000348 self.AuthBasicHandler,
349 self.AuthDigestHandler,
350 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000351 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000352 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000353 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000354 self.ServerRedirectHandler,
355 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000356 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000357 self.MultipartSlowHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000358 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000359 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000360 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000361 self.EchoHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000362 self.DeviceManagementHandler] + get_handlers
363 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000364 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000365 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000366 head_handlers = [
367 self.FileHandler,
368 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000369
maruel@google.come250a9b2009-03-10 17:39:46 +0000370 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000371 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000372 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000373 'gif': 'image/gif',
374 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000375 'jpg' : 'image/jpeg',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000376 'pdf' : 'application/pdf',
377 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000378 }
initial.commit94958cf2008-07-26 22:42:52 +0000379 self._default_mime_type = 'text/html'
380
akalin@chromium.org154bb132010-11-12 02:20:27 +0000381 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000382 connect_handlers, get_handlers, head_handlers,
383 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000384
initial.commit94958cf2008-07-26 22:42:52 +0000385 def GetMIMETypeFromName(self, file_name):
386 """Returns the mime type for the specified file_name. So far it only looks
387 at the file extension."""
388
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000389 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000390 if len(extension) == 0:
391 # no extension.
392 return self._default_mime_type
393
ericroman@google.comc17ca532009-05-07 03:51:05 +0000394 # extension starts with a dot, so we need to remove it
395 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000396
initial.commit94958cf2008-07-26 22:42:52 +0000397 def NoCacheMaxAgeTimeHandler(self):
398 """This request handler yields a page with the title set to the current
399 system time, and no caching requested."""
400
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000401 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000402 return False
403
404 self.send_response(200)
405 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000406 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000407 self.end_headers()
408
maruel@google.come250a9b2009-03-10 17:39:46 +0000409 self.wfile.write('<html><head><title>%s</title></head></html>' %
410 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000411
412 return True
413
414 def NoCacheTimeHandler(self):
415 """This request handler yields a page with the title set to the current
416 system time, and no caching requested."""
417
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000418 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000419 return False
420
421 self.send_response(200)
422 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000423 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000424 self.end_headers()
425
maruel@google.come250a9b2009-03-10 17:39:46 +0000426 self.wfile.write('<html><head><title>%s</title></head></html>' %
427 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000428
429 return True
430
431 def CacheTimeHandler(self):
432 """This request handler yields a page with the title set to the current
433 system time, and allows caching for one minute."""
434
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000435 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000436 return False
437
438 self.send_response(200)
439 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000440 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000441 self.end_headers()
442
maruel@google.come250a9b2009-03-10 17:39:46 +0000443 self.wfile.write('<html><head><title>%s</title></head></html>' %
444 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000445
446 return True
447
448 def CacheExpiresHandler(self):
449 """This request handler yields a page with the title set to the current
450 system time, and set the page to expire on 1 Jan 2099."""
451
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000452 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000453 return False
454
455 self.send_response(200)
456 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000457 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000458 self.end_headers()
459
maruel@google.come250a9b2009-03-10 17:39:46 +0000460 self.wfile.write('<html><head><title>%s</title></head></html>' %
461 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000462
463 return True
464
465 def CacheProxyRevalidateHandler(self):
466 """This request handler yields a page with the title set to the current
467 system time, and allows caching for 60 seconds"""
468
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000469 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000470 return False
471
472 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000473 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000474 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
475 self.end_headers()
476
maruel@google.come250a9b2009-03-10 17:39:46 +0000477 self.wfile.write('<html><head><title>%s</title></head></html>' %
478 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000479
480 return True
481
482 def CachePrivateHandler(self):
483 """This request handler yields a page with the title set to the current
484 system time, and allows caching for 5 seconds."""
485
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000486 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000487 return False
488
489 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000490 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000491 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000492 self.end_headers()
493
maruel@google.come250a9b2009-03-10 17:39:46 +0000494 self.wfile.write('<html><head><title>%s</title></head></html>' %
495 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000496
497 return True
498
499 def CachePublicHandler(self):
500 """This request handler yields a page with the title set to the current
501 system time, and allows caching for 5 seconds."""
502
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000503 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000504 return False
505
506 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000507 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000508 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000509 self.end_headers()
510
maruel@google.come250a9b2009-03-10 17:39:46 +0000511 self.wfile.write('<html><head><title>%s</title></head></html>' %
512 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000513
514 return True
515
516 def CacheSMaxAgeHandler(self):
517 """This request handler yields a page with the title set to the current
518 system time, and does not allow for caching."""
519
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000520 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000521 return False
522
523 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000524 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000525 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
526 self.end_headers()
527
maruel@google.come250a9b2009-03-10 17:39:46 +0000528 self.wfile.write('<html><head><title>%s</title></head></html>' %
529 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000530
531 return True
532
533 def CacheMustRevalidateHandler(self):
534 """This request handler yields a page with the title set to the current
535 system time, and does not allow caching."""
536
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000537 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000538 return False
539
540 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000541 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000542 self.send_header('Cache-Control', 'must-revalidate')
543 self.end_headers()
544
maruel@google.come250a9b2009-03-10 17:39:46 +0000545 self.wfile.write('<html><head><title>%s</title></head></html>' %
546 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000547
548 return True
549
550 def CacheMustRevalidateMaxAgeHandler(self):
551 """This request handler yields a page with the title set to the current
552 system time, and does not allow caching event though max-age of 60
553 seconds is specified."""
554
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000555 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000556 return False
557
558 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000559 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000560 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
561 self.end_headers()
562
maruel@google.come250a9b2009-03-10 17:39:46 +0000563 self.wfile.write('<html><head><title>%s</title></head></html>' %
564 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000565
566 return True
567
initial.commit94958cf2008-07-26 22:42:52 +0000568 def CacheNoStoreHandler(self):
569 """This request handler yields a page with the title set to the current
570 system time, and does not allow the page to be stored."""
571
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000572 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000573 return False
574
575 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000576 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000577 self.send_header('Cache-Control', 'no-store')
578 self.end_headers()
579
maruel@google.come250a9b2009-03-10 17:39:46 +0000580 self.wfile.write('<html><head><title>%s</title></head></html>' %
581 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000582
583 return True
584
585 def CacheNoStoreMaxAgeHandler(self):
586 """This request handler yields a page with the title set to the current
587 system time, and does not allow the page to be stored even though max-age
588 of 60 seconds is specified."""
589
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000590 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
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', 'max-age=60, no-store')
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
604 def CacheNoTransformHandler(self):
605 """This request handler yields a page with the title set to the current
606 system time, and does not allow the content to transformed during
607 user-agent caching"""
608
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000609 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000610 return False
611
612 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000613 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000614 self.send_header('Cache-Control', 'no-transform')
615 self.end_headers()
616
maruel@google.come250a9b2009-03-10 17:39:46 +0000617 self.wfile.write('<html><head><title>%s</title></head></html>' %
618 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000619
620 return True
621
622 def EchoHeader(self):
623 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000624 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000625
ananta@chromium.org56812d02011-04-07 17:52:05 +0000626 """This function echoes back the value of a specific request header"""
627 """while allowing caching for 16 hours."""
628 def EchoHeaderCache(self):
629 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000630
631 def EchoHeaderHelper(self, echo_header):
632 """This function echoes back the value of the request header passed in."""
633 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000634 return False
635
636 query_char = self.path.find('?')
637 if query_char != -1:
638 header_name = self.path[query_char+1:]
639
640 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000641 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000642 if echo_header == '/echoheadercache':
643 self.send_header('Cache-control', 'max-age=60000')
644 else:
645 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000646 # insert a vary header to properly indicate that the cachability of this
647 # request is subject to value of the request header being echoed.
648 if len(header_name) > 0:
649 self.send_header('Vary', header_name)
650 self.end_headers()
651
652 if len(header_name) > 0:
653 self.wfile.write(self.headers.getheader(header_name))
654
655 return True
656
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000657 def ReadRequestBody(self):
658 """This function reads the body of the current HTTP request, handling
659 both plain and chunked transfer encoded requests."""
660
661 if self.headers.getheader('transfer-encoding') != 'chunked':
662 length = int(self.headers.getheader('content-length'))
663 return self.rfile.read(length)
664
665 # Read the request body as chunks.
666 body = ""
667 while True:
668 line = self.rfile.readline()
669 length = int(line, 16)
670 if length == 0:
671 self.rfile.readline()
672 break
673 body += self.rfile.read(length)
674 self.rfile.read(2)
675 return body
676
initial.commit94958cf2008-07-26 22:42:52 +0000677 def EchoHandler(self):
678 """This handler just echoes back the payload of the request, for testing
679 form submission."""
680
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000681 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000682 return False
683
684 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000685 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000686 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000687 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000688 return True
689
690 def EchoTitleHandler(self):
691 """This handler is like Echo, but sets the page title to the request."""
692
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000693 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000694 return False
695
696 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000697 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000698 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000699 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000700 self.wfile.write('<html><head><title>')
701 self.wfile.write(request)
702 self.wfile.write('</title></head></html>')
703 return True
704
705 def EchoAllHandler(self):
706 """This handler yields a (more) human-readable page listing information
707 about the request header & contents."""
708
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000709 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000710 return False
711
712 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000713 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000714 self.end_headers()
715 self.wfile.write('<html><head><style>'
716 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
717 '</style></head><body>'
718 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000719 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000720 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000721
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000722 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000723 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000724 params = cgi.parse_qs(qs, keep_blank_values=1)
725
726 for param in params:
727 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000728
729 self.wfile.write('</pre>')
730
731 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
732
733 self.wfile.write('</body></html>')
734 return True
735
736 def DownloadHandler(self):
737 """This handler sends a downloadable file with or without reporting
738 the size (6K)."""
739
740 if self.path.startswith("/download-unknown-size"):
741 send_length = False
742 elif self.path.startswith("/download-known-size"):
743 send_length = True
744 else:
745 return False
746
747 #
748 # The test which uses this functionality is attempting to send
749 # small chunks of data to the client. Use a fairly large buffer
750 # so that we'll fill chrome's IO buffer enough to force it to
751 # actually write the data.
752 # See also the comments in the client-side of this test in
753 # download_uitest.cc
754 #
755 size_chunk1 = 35*1024
756 size_chunk2 = 10*1024
757
758 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000759 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000760 self.send_header('Cache-Control', 'max-age=0')
761 if send_length:
762 self.send_header('Content-Length', size_chunk1 + size_chunk2)
763 self.end_headers()
764
765 # First chunk of data:
766 self.wfile.write("*" * size_chunk1)
767 self.wfile.flush()
768
769 # handle requests until one of them clears this flag.
770 self.server.waitForDownload = True
771 while self.server.waitForDownload:
772 self.server.handle_request()
773
774 # Second chunk of data:
775 self.wfile.write("*" * size_chunk2)
776 return True
777
778 def DownloadFinishHandler(self):
779 """This handler just tells the server to finish the current download."""
780
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000781 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000782 return False
783
784 self.server.waitForDownload = False
785 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000786 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000787 self.send_header('Cache-Control', 'max-age=0')
788 self.end_headers()
789 return True
790
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000791 def _ReplaceFileData(self, data, query_parameters):
792 """Replaces matching substrings in a file.
793
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000794 If the 'replace_text' URL query parameter is present, it is expected to be
795 of the form old_text:new_text, which indicates that any old_text strings in
796 the file are replaced with new_text. Multiple 'replace_text' parameters may
797 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000798
799 If the parameters are not present, |data| is returned.
800 """
801 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000802 replace_text_values = query_dict.get('replace_text', [])
803 for replace_text_value in replace_text_values:
804 replace_text_args = replace_text_value.split(':')
805 if len(replace_text_args) != 2:
806 raise ValueError(
807 'replace_text must be of form old_text:new_text. Actual value: %s' %
808 replace_text_value)
809 old_text_b64, new_text_b64 = replace_text_args
810 old_text = base64.urlsafe_b64decode(old_text_b64)
811 new_text = base64.urlsafe_b64decode(new_text_b64)
812 data = data.replace(old_text, new_text)
813 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000814
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000815 def ZipFileHandler(self):
816 """This handler sends the contents of the requested file in compressed form.
817 Can pass in a parameter that specifies that the content length be
818 C - the compressed size (OK),
819 U - the uncompressed size (Non-standard, but handled),
820 S - less than compressed (OK because we keep going),
821 M - larger than compressed but less than uncompressed (an error),
822 L - larger than uncompressed (an error)
823 Example: compressedfiles/Picture_1.doc?C
824 """
825
826 prefix = "/compressedfiles/"
827 if not self.path.startswith(prefix):
828 return False
829
830 # Consume a request body if present.
831 if self.command == 'POST' or self.command == 'PUT' :
832 self.ReadRequestBody()
833
834 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
835
836 if not query in ('C', 'U', 'S', 'M', 'L'):
837 return False
838
839 sub_path = url_path[len(prefix):]
840 entries = sub_path.split('/')
841 file_path = os.path.join(self.server.data_dir, *entries)
842 if os.path.isdir(file_path):
843 file_path = os.path.join(file_path, 'index.html')
844
845 if not os.path.isfile(file_path):
846 print "File not found " + sub_path + " full path:" + file_path
847 self.send_error(404)
848 return True
849
850 f = open(file_path, "rb")
851 data = f.read()
852 uncompressed_len = len(data)
853 f.close()
854
855 # Compress the data.
856 data = zlib.compress(data)
857 compressed_len = len(data)
858
859 content_length = compressed_len
860 if query == 'U':
861 content_length = uncompressed_len
862 elif query == 'S':
863 content_length = compressed_len / 2
864 elif query == 'M':
865 content_length = (compressed_len + uncompressed_len) / 2
866 elif query == 'L':
867 content_length = compressed_len + uncompressed_len
868
869 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000870 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000871 self.send_header('Content-encoding', 'deflate')
872 self.send_header('Connection', 'close')
873 self.send_header('Content-Length', content_length)
874 self.send_header('ETag', '\'' + file_path + '\'')
875 self.end_headers()
876
877 self.wfile.write(data)
878
879 return True
880
initial.commit94958cf2008-07-26 22:42:52 +0000881 def FileHandler(self):
882 """This handler sends the contents of the requested file. Wow, it's like
883 a real webserver!"""
884
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000885 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000886 if not self.path.startswith(prefix):
887 return False
888
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000889 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000890 if self.command == 'POST' or self.command == 'PUT' :
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000891 self.ReadRequestBody()
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000892
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000893 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
894 sub_path = url_path[len(prefix):]
895 entries = sub_path.split('/')
896 file_path = os.path.join(self.server.data_dir, *entries)
897 if os.path.isdir(file_path):
898 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000899
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000900 if not os.path.isfile(file_path):
901 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000902 self.send_error(404)
903 return True
904
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000905 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000906 data = f.read()
907 f.close()
908
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000909 data = self._ReplaceFileData(data, query)
910
initial.commit94958cf2008-07-26 22:42:52 +0000911 # If file.mock-http-headers exists, it contains the headers we
912 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000913 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000914 if os.path.isfile(headers_path):
915 f = open(headers_path, "r")
916
917 # "HTTP/1.1 200 OK"
918 response = f.readline()
919 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
920 self.send_response(int(status_code))
921
922 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000923 header_values = re.findall('(\S+):\s*(.*)', line)
924 if len(header_values) > 0:
925 # "name: value"
926 name, value = header_values[0]
927 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000928 f.close()
929 else:
930 # Could be more generic once we support mime-type sniffing, but for
931 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000932
933 range = self.headers.get('Range')
934 if range and range.startswith('bytes='):
935 # Note this doesn't handle all valid byte range values (i.e. open ended
936 # ones), just enough for what we needed so far.
937 range = range[6:].split('-')
938 start = int(range[0])
939 end = int(range[1])
940
941 self.send_response(206)
942 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
943 str(len(data))
944 self.send_header('Content-Range', content_range)
945 data = data[start: end + 1]
946 else:
947 self.send_response(200)
948
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000949 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000950 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000951 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000952 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000953 self.end_headers()
954
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000955 if (self.command != 'HEAD'):
956 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000957
958 return True
959
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000960 def SetCookieHandler(self):
961 """This handler just sets a cookie, for testing cookie handling."""
962
963 if not self._ShouldHandleRequest("/set-cookie"):
964 return False
965
966 query_char = self.path.find('?')
967 if query_char != -1:
968 cookie_values = self.path[query_char + 1:].split('&')
969 else:
970 cookie_values = ("",)
971 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000972 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000973 for cookie_value in cookie_values:
974 self.send_header('Set-Cookie', '%s' % cookie_value)
975 self.end_headers()
976 for cookie_value in cookie_values:
977 self.wfile.write('%s' % cookie_value)
978 return True
979
initial.commit94958cf2008-07-26 22:42:52 +0000980 def AuthBasicHandler(self):
981 """This handler tests 'Basic' authentication. It just sends a page with
982 title 'user/pass' if you succeed."""
983
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000984 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000985 return False
986
987 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000988 expected_password = 'secret'
989 realm = 'testrealm'
990 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +0000991
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000992 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
993 query_params = cgi.parse_qs(query, True)
994 if 'set-cookie-if-challenged' in query_params:
995 set_cookie_if_challenged = True
996 if 'password' in query_params:
997 expected_password = query_params['password'][0]
998 if 'realm' in query_params:
999 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001000
initial.commit94958cf2008-07-26 22:42:52 +00001001 auth = self.headers.getheader('authorization')
1002 try:
1003 if not auth:
1004 raise Exception('no auth')
1005 b64str = re.findall(r'Basic (\S+)', auth)[0]
1006 userpass = base64.b64decode(b64str)
1007 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001008 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001009 raise Exception('wrong password')
1010 except Exception, e:
1011 # Authentication failed.
1012 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001013 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001014 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001015 if set_cookie_if_challenged:
1016 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001017 self.end_headers()
1018 self.wfile.write('<html><head>')
1019 self.wfile.write('<title>Denied: %s</title>' % e)
1020 self.wfile.write('</head><body>')
1021 self.wfile.write('auth=%s<p>' % auth)
1022 self.wfile.write('b64str=%s<p>' % b64str)
1023 self.wfile.write('username: %s<p>' % username)
1024 self.wfile.write('userpass: %s<p>' % userpass)
1025 self.wfile.write('password: %s<p>' % password)
1026 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1027 self.wfile.write('</body></html>')
1028 return True
1029
1030 # Authentication successful. (Return a cachable response to allow for
1031 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001032 old_protocol_version = self.protocol_version
1033 self.protocol_version = "HTTP/1.1"
1034
initial.commit94958cf2008-07-26 22:42:52 +00001035 if_none_match = self.headers.getheader('if-none-match')
1036 if if_none_match == "abc":
1037 self.send_response(304)
1038 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001039 elif url_path.endswith(".gif"):
1040 # Using chrome/test/data/google/logo.gif as the test image
1041 test_image_path = ['google', 'logo.gif']
1042 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1043 if not os.path.isfile(gif_path):
1044 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001045 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001046 return True
1047
1048 f = open(gif_path, "rb")
1049 data = f.read()
1050 f.close()
1051
1052 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001053 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001054 self.send_header('Cache-control', 'max-age=60000')
1055 self.send_header('Etag', 'abc')
1056 self.end_headers()
1057 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001058 else:
1059 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001060 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001061 self.send_header('Cache-control', 'max-age=60000')
1062 self.send_header('Etag', 'abc')
1063 self.end_headers()
1064 self.wfile.write('<html><head>')
1065 self.wfile.write('<title>%s/%s</title>' % (username, password))
1066 self.wfile.write('</head><body>')
1067 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001068 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001069 self.wfile.write('</body></html>')
1070
rvargas@google.com54453b72011-05-19 01:11:11 +00001071 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001072 return True
1073
tonyg@chromium.org75054202010-03-31 22:06:10 +00001074 def GetNonce(self, force_reset=False):
1075 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001076
tonyg@chromium.org75054202010-03-31 22:06:10 +00001077 This is a fake implementation. A real implementation would only use a given
1078 nonce a single time (hence the name n-once). However, for the purposes of
1079 unittesting, we don't care about the security of the nonce.
1080
1081 Args:
1082 force_reset: Iff set, the nonce will be changed. Useful for testing the
1083 "stale" response.
1084 """
1085 if force_reset or not self.server.nonce_time:
1086 self.server.nonce_time = time.time()
1087 return _new_md5('privatekey%s%d' %
1088 (self.path, self.server.nonce_time)).hexdigest()
1089
1090 def AuthDigestHandler(self):
1091 """This handler tests 'Digest' authentication.
1092
1093 It just sends a page with title 'user/pass' if you succeed.
1094
1095 A stale response is sent iff "stale" is present in the request path.
1096 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001097 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001098 return False
1099
tonyg@chromium.org75054202010-03-31 22:06:10 +00001100 stale = 'stale' in self.path
1101 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001102 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001103 password = 'secret'
1104 realm = 'testrealm'
1105
1106 auth = self.headers.getheader('authorization')
1107 pairs = {}
1108 try:
1109 if not auth:
1110 raise Exception('no auth')
1111 if not auth.startswith('Digest'):
1112 raise Exception('not digest')
1113 # Pull out all the name="value" pairs as a dictionary.
1114 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1115
1116 # Make sure it's all valid.
1117 if pairs['nonce'] != nonce:
1118 raise Exception('wrong nonce')
1119 if pairs['opaque'] != opaque:
1120 raise Exception('wrong opaque')
1121
1122 # Check the 'response' value and make sure it matches our magic hash.
1123 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001124 hash_a1 = _new_md5(
1125 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001126 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001127 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001128 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001129 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1130 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001131 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001132
1133 if pairs['response'] != response:
1134 raise Exception('wrong password')
1135 except Exception, e:
1136 # Authentication failed.
1137 self.send_response(401)
1138 hdr = ('Digest '
1139 'realm="%s", '
1140 'domain="/", '
1141 'qop="auth", '
1142 'algorithm=MD5, '
1143 'nonce="%s", '
1144 'opaque="%s"') % (realm, nonce, opaque)
1145 if stale:
1146 hdr += ', stale="TRUE"'
1147 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001148 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001149 self.end_headers()
1150 self.wfile.write('<html><head>')
1151 self.wfile.write('<title>Denied: %s</title>' % e)
1152 self.wfile.write('</head><body>')
1153 self.wfile.write('auth=%s<p>' % auth)
1154 self.wfile.write('pairs=%s<p>' % pairs)
1155 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1156 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1157 self.wfile.write('</body></html>')
1158 return True
1159
1160 # Authentication successful.
1161 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001162 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001163 self.end_headers()
1164 self.wfile.write('<html><head>')
1165 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1166 self.wfile.write('</head><body>')
1167 self.wfile.write('auth=%s<p>' % auth)
1168 self.wfile.write('pairs=%s<p>' % pairs)
1169 self.wfile.write('</body></html>')
1170
1171 return True
1172
1173 def SlowServerHandler(self):
1174 """Wait for the user suggested time before responding. The syntax is
1175 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001176 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001177 return False
1178 query_char = self.path.find('?')
1179 wait_sec = 1.0
1180 if query_char >= 0:
1181 try:
1182 wait_sec = int(self.path[query_char + 1:])
1183 except ValueError:
1184 pass
1185 time.sleep(wait_sec)
1186 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001187 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001188 self.end_headers()
1189 self.wfile.write("waited %d seconds" % wait_sec)
1190 return True
1191
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001192 def ChunkedServerHandler(self):
1193 """Send chunked response. Allows to specify chunks parameters:
1194 - waitBeforeHeaders - ms to wait before sending headers
1195 - waitBetweenChunks - ms to wait between chunks
1196 - chunkSize - size of each chunk in bytes
1197 - chunksNumber - number of chunks
1198 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1199 waits one second, then sends headers and five chunks five bytes each."""
1200 if not self._ShouldHandleRequest("/chunked"):
1201 return False
1202 query_char = self.path.find('?')
1203 chunkedSettings = {'waitBeforeHeaders' : 0,
1204 'waitBetweenChunks' : 0,
1205 'chunkSize' : 5,
1206 'chunksNumber' : 5}
1207 if query_char >= 0:
1208 params = self.path[query_char + 1:].split('&')
1209 for param in params:
1210 keyValue = param.split('=')
1211 if len(keyValue) == 2:
1212 try:
1213 chunkedSettings[keyValue[0]] = int(keyValue[1])
1214 except ValueError:
1215 pass
1216 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1217 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1218 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001219 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001220 self.send_header('Connection', 'close')
1221 self.send_header('Transfer-Encoding', 'chunked')
1222 self.end_headers()
1223 # Chunked encoding: sending all chunks, then final zero-length chunk and
1224 # then final CRLF.
1225 for i in range(0, chunkedSettings['chunksNumber']):
1226 if i > 0:
1227 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1228 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1229 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1230 self.sendChunkHelp('')
1231 return True
1232
initial.commit94958cf2008-07-26 22:42:52 +00001233 def ContentTypeHandler(self):
1234 """Returns a string of html with the given content type. E.g.,
1235 /contenttype?text/css returns an html file with the Content-Type
1236 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001237 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001238 return False
1239 query_char = self.path.find('?')
1240 content_type = self.path[query_char + 1:].strip()
1241 if not content_type:
1242 content_type = 'text/html'
1243 self.send_response(200)
1244 self.send_header('Content-Type', content_type)
1245 self.end_headers()
1246 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1247 return True
1248
creis@google.com2f4f6a42011-03-25 19:44:19 +00001249 def NoContentHandler(self):
1250 """Returns a 204 No Content response."""
1251 if not self._ShouldHandleRequest("/nocontent"):
1252 return False
1253 self.send_response(204)
1254 self.end_headers()
1255 return True
1256
initial.commit94958cf2008-07-26 22:42:52 +00001257 def ServerRedirectHandler(self):
1258 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001259 '/server-redirect?http://foo.bar/asdf' to redirect to
1260 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001261
1262 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001263 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001264 return False
1265
1266 query_char = self.path.find('?')
1267 if query_char < 0 or len(self.path) <= query_char + 1:
1268 self.sendRedirectHelp(test_name)
1269 return True
1270 dest = self.path[query_char + 1:]
1271
1272 self.send_response(301) # moved permanently
1273 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001274 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001275 self.end_headers()
1276 self.wfile.write('<html><head>')
1277 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1278
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001279 return True
initial.commit94958cf2008-07-26 22:42:52 +00001280
1281 def ClientRedirectHandler(self):
1282 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001283 '/client-redirect?http://foo.bar/asdf' to redirect to
1284 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001285
1286 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001287 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001288 return False
1289
1290 query_char = self.path.find('?');
1291 if query_char < 0 or len(self.path) <= query_char + 1:
1292 self.sendRedirectHelp(test_name)
1293 return True
1294 dest = self.path[query_char + 1:]
1295
1296 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001297 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001298 self.end_headers()
1299 self.wfile.write('<html><head>')
1300 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1301 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1302
1303 return True
1304
tony@chromium.org03266982010-03-05 03:18:42 +00001305 def MultipartHandler(self):
1306 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001307 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001308 if not self._ShouldHandleRequest(test_name):
1309 return False
1310
1311 num_frames = 10
1312 bound = '12345'
1313 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001314 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001315 'multipart/x-mixed-replace;boundary=' + bound)
1316 self.end_headers()
1317
1318 for i in xrange(num_frames):
1319 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001320 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001321 self.wfile.write('<title>page ' + str(i) + '</title>')
1322 self.wfile.write('page ' + str(i))
1323
1324 self.wfile.write('--' + bound + '--')
1325 return True
1326
tony@chromium.org4cb88302011-09-27 22:13:49 +00001327 def MultipartSlowHandler(self):
1328 """Send a multipart response (3 text/html pages) with a slight delay
1329 between each page. This is similar to how some pages show status using
1330 multipart."""
1331 test_name = '/multipart-slow'
1332 if not self._ShouldHandleRequest(test_name):
1333 return False
1334
1335 num_frames = 3
1336 bound = '12345'
1337 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001338 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001339 'multipart/x-mixed-replace;boundary=' + bound)
1340 self.end_headers()
1341
1342 for i in xrange(num_frames):
1343 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001344 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001345 time.sleep(0.25)
1346 if i == 2:
1347 self.wfile.write('<title>PASS</title>')
1348 else:
1349 self.wfile.write('<title>page ' + str(i) + '</title>')
1350 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1351
1352 self.wfile.write('--' + bound + '--')
1353 return True
1354
initial.commit94958cf2008-07-26 22:42:52 +00001355 def DefaultResponseHandler(self):
1356 """This is the catch-all response handler for requests that aren't handled
1357 by one of the special handlers above.
1358 Note that we specify the content-length as without it the https connection
1359 is not closed properly (and the browser keeps expecting data)."""
1360
1361 contents = "Default response given for path: " + self.path
1362 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001363 self.send_header('Content-Type', 'text/html')
1364 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001365 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001366 if (self.command != 'HEAD'):
1367 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001368 return True
1369
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001370 def RedirectConnectHandler(self):
1371 """Sends a redirect to the CONNECT request for www.redirect.com. This
1372 response is not specified by the RFC, so the browser should not follow
1373 the redirect."""
1374
1375 if (self.path.find("www.redirect.com") < 0):
1376 return False
1377
1378 dest = "http://www.destination.com/foo.js"
1379
1380 self.send_response(302) # moved temporarily
1381 self.send_header('Location', dest)
1382 self.send_header('Connection', 'close')
1383 self.end_headers()
1384 return True
1385
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001386 def ServerAuthConnectHandler(self):
1387 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1388 response doesn't make sense because the proxy server cannot request
1389 server authentication."""
1390
1391 if (self.path.find("www.server-auth.com") < 0):
1392 return False
1393
1394 challenge = 'Basic realm="WallyWorld"'
1395
1396 self.send_response(401) # unauthorized
1397 self.send_header('WWW-Authenticate', challenge)
1398 self.send_header('Connection', 'close')
1399 self.end_headers()
1400 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001401
1402 def DefaultConnectResponseHandler(self):
1403 """This is the catch-all response handler for CONNECT requests that aren't
1404 handled by one of the special handlers above. Real Web servers respond
1405 with 400 to CONNECT requests."""
1406
1407 contents = "Your client has issued a malformed or illegal request."
1408 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001409 self.send_header('Content-Type', 'text/html')
1410 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001411 self.end_headers()
1412 self.wfile.write(contents)
1413 return True
1414
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001415 def DeviceManagementHandler(self):
1416 """Delegates to the device management service used for cloud policy."""
1417 if not self._ShouldHandleRequest("/device_management"):
1418 return False
1419
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001420 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001421
1422 if not self.server._device_management_handler:
1423 import device_management
1424 policy_path = os.path.join(self.server.data_dir, 'device_management')
1425 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001426 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001427 self.server.policy_keys,
1428 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001429
1430 http_response, raw_reply = (
1431 self.server._device_management_handler.HandleRequest(self.path,
1432 self.headers,
1433 raw_request))
1434 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001435 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001436 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001437 self.end_headers()
1438 self.wfile.write(raw_reply)
1439 return True
1440
initial.commit94958cf2008-07-26 22:42:52 +00001441 # called by the redirect handling function when there is no parameter
1442 def sendRedirectHelp(self, redirect_name):
1443 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001444 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001445 self.end_headers()
1446 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1447 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1448 self.wfile.write('</body></html>')
1449
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001450 # called by chunked handling function
1451 def sendChunkHelp(self, chunk):
1452 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1453 self.wfile.write('%X\r\n' % len(chunk))
1454 self.wfile.write(chunk)
1455 self.wfile.write('\r\n')
1456
akalin@chromium.org154bb132010-11-12 02:20:27 +00001457
1458class SyncPageHandler(BasePageHandler):
1459 """Handler for the main HTTP sync server."""
1460
1461 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001462 get_handlers = [self.ChromiumSyncMigrationOpHandler,
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001463 self.ChromiumSyncTimeHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001464 self.ChromiumSyncDisableNotificationsOpHandler,
1465 self.ChromiumSyncEnableNotificationsOpHandler,
1466 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001467 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001468 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001469 self.ChromiumSyncSyncTabsOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001470 self.ChromiumSyncErrorOpHandler,
1471 self.ChromiumSyncCredHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001472
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001473 post_handlers = [self.ChromiumSyncCommandHandler,
1474 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001475 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001476 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001477 post_handlers, [])
1478
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001479
akalin@chromium.org154bb132010-11-12 02:20:27 +00001480 def ChromiumSyncTimeHandler(self):
1481 """Handle Chromium sync .../time requests.
1482
1483 The syncer sometimes checks server reachability by examining /time.
1484 """
1485 test_name = "/chromiumsync/time"
1486 if not self._ShouldHandleRequest(test_name):
1487 return False
1488
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001489 # Chrome hates it if we send a response before reading the request.
1490 if self.headers.getheader('content-length'):
1491 length = int(self.headers.getheader('content-length'))
1492 raw_request = self.rfile.read(length)
1493
akalin@chromium.org154bb132010-11-12 02:20:27 +00001494 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001495 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001496 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001497 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001498 return True
1499
1500 def ChromiumSyncCommandHandler(self):
1501 """Handle a chromiumsync command arriving via http.
1502
1503 This covers all sync protocol commands: authentication, getupdates, and
1504 commit.
1505 """
1506 test_name = "/chromiumsync/command"
1507 if not self._ShouldHandleRequest(test_name):
1508 return False
1509
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001510 length = int(self.headers.getheader('content-length'))
1511 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001512 http_response = 200
1513 raw_reply = None
1514 if not self.server.GetAuthenticated():
1515 http_response = 401
1516 challenge = 'GoogleLogin realm="http://127.0.0.1", service="chromiumsync"'
1517 else:
1518 http_response, raw_reply = self.server.HandleCommand(
1519 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001520
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001521 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001522 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001523 if http_response == 401:
1524 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001525 self.end_headers()
1526 self.wfile.write(raw_reply)
1527 return True
1528
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001529 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001530 test_name = "/chromiumsync/migrate"
1531 if not self._ShouldHandleRequest(test_name):
1532 return False
1533
1534 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1535 self.path)
1536 self.send_response(http_response)
1537 self.send_header('Content-Type', 'text/html')
1538 self.send_header('Content-Length', len(raw_reply))
1539 self.end_headers()
1540 self.wfile.write(raw_reply)
1541 return True
1542
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001543 def ChromiumSyncCredHandler(self):
1544 test_name = "/chromiumsync/cred"
1545 if not self._ShouldHandleRequest(test_name):
1546 return False
1547 try:
1548 query = urlparse.urlparse(self.path)[4]
1549 cred_valid = urlparse.parse_qs(query)['valid']
1550 if cred_valid[0] == 'True':
1551 self.server.SetAuthenticated(True)
1552 else:
1553 self.server.SetAuthenticated(False)
1554 except:
1555 self.server.SetAuthenticated(False)
1556
1557 http_response = 200
1558 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1559 self.send_response(http_response)
1560 self.send_header('Content-Type', 'text/html')
1561 self.send_header('Content-Length', len(raw_reply))
1562 self.end_headers()
1563 self.wfile.write(raw_reply)
1564 return True
1565
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001566 def ChromiumSyncDisableNotificationsOpHandler(self):
1567 test_name = "/chromiumsync/disablenotifications"
1568 if not self._ShouldHandleRequest(test_name):
1569 return False
1570 self.server.GetXmppServer().DisableNotifications()
1571 result = 200
1572 raw_reply = ('<html><title>Notifications disabled</title>'
1573 '<H1>Notifications disabled</H1></html>')
1574 self.send_response(result)
1575 self.send_header('Content-Type', 'text/html')
1576 self.send_header('Content-Length', len(raw_reply))
1577 self.end_headers()
1578 self.wfile.write(raw_reply)
1579 return True;
1580
1581 def ChromiumSyncEnableNotificationsOpHandler(self):
1582 test_name = "/chromiumsync/enablenotifications"
1583 if not self._ShouldHandleRequest(test_name):
1584 return False
1585 self.server.GetXmppServer().EnableNotifications()
1586 result = 200
1587 raw_reply = ('<html><title>Notifications enabled</title>'
1588 '<H1>Notifications enabled</H1></html>')
1589 self.send_response(result)
1590 self.send_header('Content-Type', 'text/html')
1591 self.send_header('Content-Length', len(raw_reply))
1592 self.end_headers()
1593 self.wfile.write(raw_reply)
1594 return True;
1595
1596 def ChromiumSyncSendNotificationOpHandler(self):
1597 test_name = "/chromiumsync/sendnotification"
1598 if not self._ShouldHandleRequest(test_name):
1599 return False
1600 query = urlparse.urlparse(self.path)[4]
1601 query_params = urlparse.parse_qs(query)
1602 channel = ''
1603 data = ''
1604 if 'channel' in query_params:
1605 channel = query_params['channel'][0]
1606 if 'data' in query_params:
1607 data = query_params['data'][0]
1608 self.server.GetXmppServer().SendNotification(channel, data)
1609 result = 200
1610 raw_reply = ('<html><title>Notification sent</title>'
1611 '<H1>Notification sent with channel "%s" '
1612 'and data "%s"</H1></html>'
1613 % (channel, data))
1614 self.send_response(result)
1615 self.send_header('Content-Type', 'text/html')
1616 self.send_header('Content-Length', len(raw_reply))
1617 self.end_headers()
1618 self.wfile.write(raw_reply)
1619 return True;
1620
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001621 def ChromiumSyncBirthdayErrorOpHandler(self):
1622 test_name = "/chromiumsync/birthdayerror"
1623 if not self._ShouldHandleRequest(test_name):
1624 return False
1625 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1626 self.send_response(result)
1627 self.send_header('Content-Type', 'text/html')
1628 self.send_header('Content-Length', len(raw_reply))
1629 self.end_headers()
1630 self.wfile.write(raw_reply)
1631 return True;
1632
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001633 def ChromiumSyncTransientErrorOpHandler(self):
1634 test_name = "/chromiumsync/transienterror"
1635 if not self._ShouldHandleRequest(test_name):
1636 return False
1637 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1638 self.send_response(result)
1639 self.send_header('Content-Type', 'text/html')
1640 self.send_header('Content-Length', len(raw_reply))
1641 self.end_headers()
1642 self.wfile.write(raw_reply)
1643 return True;
1644
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001645 def ChromiumSyncErrorOpHandler(self):
1646 test_name = "/chromiumsync/error"
1647 if not self._ShouldHandleRequest(test_name):
1648 return False
1649 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1650 self.path)
1651 self.send_response(result)
1652 self.send_header('Content-Type', 'text/html')
1653 self.send_header('Content-Length', len(raw_reply))
1654 self.end_headers()
1655 self.wfile.write(raw_reply)
1656 return True;
1657
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001658 def ChromiumSyncSyncTabsOpHandler(self):
1659 test_name = "/chromiumsync/synctabs"
1660 if not self._ShouldHandleRequest(test_name):
1661 return False
1662 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1663 self.send_response(result)
1664 self.send_header('Content-Type', 'text/html')
1665 self.send_header('Content-Length', len(raw_reply))
1666 self.end_headers()
1667 self.wfile.write(raw_reply)
1668 return True;
1669
akalin@chromium.org154bb132010-11-12 02:20:27 +00001670
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001671def MakeDataDir():
1672 if options.data_dir:
1673 if not os.path.isdir(options.data_dir):
1674 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1675 return None
1676 my_data_dir = options.data_dir
1677 else:
1678 # Create the default path to our data dir, relative to the exe dir.
1679 my_data_dir = os.path.dirname(sys.argv[0])
1680 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001681 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001682
1683 #TODO(ibrar): Must use Find* funtion defined in google\tools
1684 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1685
1686 return my_data_dir
1687
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001688
1689class TCPEchoHandler(SocketServer.BaseRequestHandler):
1690 """The RequestHandler class for TCP echo server.
1691
1692 It is instantiated once per connection to the server, and overrides the
1693 handle() method to implement communication to the client.
1694 """
1695
1696 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001697 """Handles the request from the client and constructs a response."""
1698
1699 data = self.request.recv(65536).strip()
1700 # Verify the "echo request" message received from the client. Send back
1701 # "echo response" message if "echo request" message is valid.
1702 try:
1703 return_data = echo_message.GetEchoResponseData(data)
1704 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001705 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001706 except ValueError:
1707 return
1708
1709 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001710
1711
1712class UDPEchoHandler(SocketServer.BaseRequestHandler):
1713 """The RequestHandler class for UDP echo server.
1714
1715 It is instantiated once per connection to the server, and overrides the
1716 handle() method to implement communication to the client.
1717 """
1718
1719 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001720 """Handles the request from the client and constructs a response."""
1721
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001722 data = self.request[0].strip()
1723 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001724 # Verify the "echo request" message received from the client. Send back
1725 # "echo response" message if "echo request" message is valid.
1726 try:
1727 return_data = echo_message.GetEchoResponseData(data)
1728 if not return_data:
1729 return
1730 except ValueError:
1731 return
1732 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001733
1734
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001735class FileMultiplexer:
1736 def __init__(self, fd1, fd2) :
1737 self.__fd1 = fd1
1738 self.__fd2 = fd2
1739
1740 def __del__(self) :
1741 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1742 self.__fd1.close()
1743 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1744 self.__fd2.close()
1745
1746 def write(self, text) :
1747 self.__fd1.write(text)
1748 self.__fd2.write(text)
1749
1750 def flush(self) :
1751 self.__fd1.flush()
1752 self.__fd2.flush()
1753
initial.commit94958cf2008-07-26 22:42:52 +00001754def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001755 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001756 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001757 if options.log_to_console:
1758 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1759 else:
1760 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001761
1762 port = options.port
1763
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001764 server_data = {}
1765
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001766 if options.server_type == SERVER_HTTP:
1767 if options.cert:
1768 # let's make sure the cert file exists.
1769 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001770 print 'specified server cert file not found: ' + options.cert + \
1771 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001772 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001773 for ca_cert in options.ssl_client_ca:
1774 if not os.path.isfile(ca_cert):
1775 print 'specified trusted client CA file not found: ' + ca_cert + \
1776 ' exiting...'
1777 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001778 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001779 options.ssl_client_auth, options.ssl_client_ca,
1780 options.ssl_bulk_cipher)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001781 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001782 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001783 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001784 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001785
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001786 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001787 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001788 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001789 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001790 server.policy_keys = options.policy_keys
1791 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001792 elif options.server_type == SERVER_SYNC:
1793 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1794 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001795 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1796 server_data['port'] = server.server_port
1797 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001798 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001799 # Used for generating the key (randomly) that encodes the "echo request"
1800 # message.
1801 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001802 server = TCPEchoServer(('127.0.0.1', port), TCPEchoHandler)
1803 print 'Echo TCP server started on port %d...' % server.server_port
1804 server_data['port'] = server.server_port
1805 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001806 # Used for generating the key (randomly) that encodes the "echo request"
1807 # message.
1808 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001809 server = UDPEchoServer(('127.0.0.1', port), UDPEchoHandler)
1810 print 'Echo UDP server started on port %d...' % server.server_port
1811 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001812 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001813 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001814 my_data_dir = MakeDataDir()
1815
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001816 # Instantiate a dummy authorizer for managing 'virtual' users
1817 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1818
1819 # Define a new user having full r/w permissions and a read-only
1820 # anonymous user
1821 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1822
1823 authorizer.add_anonymous(my_data_dir)
1824
1825 # Instantiate FTP handler class
1826 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1827 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001828
1829 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001830 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1831 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001832
1833 # Instantiate FTP server class and listen to 127.0.0.1:port
1834 address = ('127.0.0.1', port)
1835 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001836 server_data['port'] = server.socket.getsockname()[1]
1837 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001838
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001839 # Notify the parent that we've started. (BaseServer subclasses
1840 # bind their sockets on construction.)
1841 if options.startup_pipe is not None:
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001842 server_data_json = json.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001843 server_data_len = len(server_data_json)
1844 print 'sending server_data: %s (%d bytes)' % (
1845 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001846 if sys.platform == 'win32':
1847 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1848 else:
1849 fd = options.startup_pipe
1850 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001851 # First write the data length as an unsigned 4-byte value. This
1852 # is _not_ using network byte ordering since the other end of the
1853 # pipe is on the same machine.
1854 startup_pipe.write(struct.pack('=L', server_data_len))
1855 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001856 startup_pipe.close()
1857
initial.commit94958cf2008-07-26 22:42:52 +00001858 try:
1859 server.serve_forever()
1860 except KeyboardInterrupt:
1861 print 'shutting down server'
1862 server.stop = True
1863
1864if __name__ == '__main__':
1865 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001866 option_parser.add_option("-f", '--ftp', action='store_const',
1867 const=SERVER_FTP, default=SERVER_HTTP,
1868 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001869 help='start up an FTP server.')
1870 option_parser.add_option('', '--sync', action='store_const',
1871 const=SERVER_SYNC, default=SERVER_HTTP,
1872 dest='server_type',
1873 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001874 option_parser.add_option('', '--tcp-echo', action='store_const',
1875 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1876 dest='server_type',
1877 help='start up a tcp echo server.')
1878 option_parser.add_option('', '--udp-echo', action='store_const',
1879 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
1880 dest='server_type',
1881 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001882 option_parser.add_option('', '--log-to-console', action='store_const',
1883 const=True, default=False,
1884 dest='log_to_console',
1885 help='Enables or disables sys.stdout logging to '
1886 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001887 option_parser.add_option('', '--port', default='0', type='int',
1888 help='Port used by the server. If unspecified, the '
1889 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001890 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001891 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001892 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001893 help='Specify that https should be used, specify '
1894 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001895 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001896 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1897 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001898 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1899 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001900 'should include the CA named in the subject of '
1901 'the DER-encoded certificate contained in the '
1902 'specified file. This option may appear multiple '
1903 'times, indicating multiple CA names should be '
1904 'sent in the request.')
1905 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1906 help='Specify the bulk encryption algorithm(s)'
1907 'that will be accepted by the SSL server. Valid '
1908 'values are "aes256", "aes128", "3des", "rc4". If '
1909 'omitted, all algorithms will be used. This '
1910 'option may appear multiple times, indicating '
1911 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001912 option_parser.add_option('', '--file-root-url', default='/files/',
1913 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001914 option_parser.add_option('', '--startup-pipe', type='int',
1915 dest='startup_pipe',
1916 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001917 option_parser.add_option('', '--policy-key', action='append',
1918 dest='policy_keys',
1919 help='Specify a path to a PEM-encoded private key '
1920 'to use for policy signing. May be specified '
1921 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00001922 'the server. If ther server has multiple keys, it '
1923 'will rotate through them in at each request a '
1924 'round-robin fashion. The server will generate a '
1925 'random key if none is specified on the command '
1926 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001927 option_parser.add_option('', '--policy-user', default='user@example.com',
1928 dest='policy_user',
1929 help='Specify the user name the server should '
1930 'report back to the client as the user owning the '
1931 'token used for making the policy request.')
initial.commit94958cf2008-07-26 22:42:52 +00001932 options, args = option_parser.parse_args()
1933
1934 sys.exit(main(options, args))