blob: c0da4ec372d5eb9920aa9e160da54d7e43b38025 [file] [log] [blame]
initial.commit94958cf2008-07-26 22:42:52 +00001#!/usr/bin/python2.4
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.orgfc70e5e2011-06-09 05:11:41 +00006"""This is a simple HTTP/FTP/SYNC/TCP ECHO/UDP ECHO/ server used for testing
7Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00008
9It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000010By default, it listens on an ephemeral port and sends the port number back to
11the originating process over a pipe. The originating process can specify an
12explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000013It can use https if you specify the flag --https=CERT where CERT is the path
14to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000015"""
16
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000017import asyncore
initial.commit94958cf2008-07-26 22:42:52 +000018import base64
19import BaseHTTPServer
20import cgi
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000021import errno
initial.commit94958cf2008-07-26 22:42:52 +000022import optparse
23import os
24import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000025import select
akalin@chromium.org18e34882010-11-26 07:10:41 +000026import simplejson
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
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000032import urlparse
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000033import warnings
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000034import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000035
36# Ignore deprecation warnings, they make our output more cluttered.
37warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000038
39import 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
davidben@chromium.org06fcf202010-09-22 18:15:23 +000050if sys.platform == 'win32':
51 import msvcrt
52
maruel@chromium.org756cf982009-03-05 12:46:38 +000053SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000054SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000055SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000056SERVER_TCP_ECHO = 3
57SERVER_UDP_ECHO = 4
initial.commit94958cf2008-07-26 22:42:52 +000058
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000059# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000060debug_output = sys.stderr
61def debug(str):
62 debug_output.write(str + "\n")
63 debug_output.flush()
64
65class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
66 """This is a specialization of of BaseHTTPServer to allow it
67 to be exited cleanly (by setting its "stop" member to True)."""
68
69 def serve_forever(self):
70 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000071 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000072 while not self.stop:
73 self.handle_request()
74 self.socket.close()
75
76class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
77 """This is a specialization of StoppableHTTPerver that add https support."""
78
davidben@chromium.org31282a12010-08-07 01:10:02 +000079 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000080 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers):
initial.commit94958cf2008-07-26 22:42:52 +000081 s = open(cert_path).read()
82 x509 = tlslite.api.X509()
83 x509.parse(s)
84 self.cert_chain = tlslite.api.X509CertChain([x509])
85 s = open(cert_path).read()
86 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000087 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000088 self.ssl_client_cas = []
89 for ca_file in ssl_client_cas:
90 s = open(ca_file).read()
91 x509 = tlslite.api.X509()
92 x509.parse(s)
93 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000094 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
95 if ssl_bulk_ciphers is not None:
96 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +000097
98 self.session_cache = tlslite.api.SessionCache()
99 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
100
101 def handshake(self, tlsConnection):
102 """Creates the SSL connection."""
103 try:
104 tlsConnection.handshakeServer(certChain=self.cert_chain,
105 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000106 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000107 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000108 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000109 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +0000110 tlsConnection.ignoreAbruptClose = True
111 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000112 except tlslite.api.TLSAbruptCloseError:
113 # Ignore abrupt close.
114 return True
initial.commit94958cf2008-07-26 22:42:52 +0000115 except tlslite.api.TLSError, error:
116 print "Handshake failure:", str(error)
117 return False
118
akalin@chromium.org154bb132010-11-12 02:20:27 +0000119
120class SyncHTTPServer(StoppableHTTPServer):
121 """An HTTP server that handles sync commands."""
122
123 def __init__(self, server_address, request_handler_class):
124 # We import here to avoid pulling in chromiumsync's dependencies
125 # unless strictly necessary.
126 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000127 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000128 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000129 self._sync_handler = chromiumsync.TestServer()
130 self._xmpp_socket_map = {}
131 self._xmpp_server = xmppserver.XmppServer(
132 self._xmpp_socket_map, ('localhost', 0))
133 self.xmpp_port = self._xmpp_server.getsockname()[1]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000134
135 def HandleCommand(self, query, raw_request):
136 return self._sync_handler.HandleCommand(query, raw_request)
137
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000138 def HandleRequestNoBlock(self):
139 """Handles a single request.
140
141 Copied from SocketServer._handle_request_noblock().
142 """
143 try:
144 request, client_address = self.get_request()
145 except socket.error:
146 return
147 if self.verify_request(request, client_address):
148 try:
149 self.process_request(request, client_address)
150 except:
151 self.handle_error(request, client_address)
152 self.close_request(request)
153
154 def serve_forever(self):
155 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
156 """
157
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000158 def HandleXmppSocket(fd, socket_map, handler):
159 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000160
161 Adapted from asyncore.read() et al.
162 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000163 xmpp_connection = socket_map.get(fd)
164 # This could happen if a previous handler call caused fd to get
165 # removed from socket_map.
166 if xmpp_connection is None:
167 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000168 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000169 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000170 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
171 raise
172 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000173 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000174
175 while True:
176 read_fds = [ self.fileno() ]
177 write_fds = []
178 exceptional_fds = []
179
180 for fd, xmpp_connection in self._xmpp_socket_map.items():
181 is_r = xmpp_connection.readable()
182 is_w = xmpp_connection.writable()
183 if is_r:
184 read_fds.append(fd)
185 if is_w:
186 write_fds.append(fd)
187 if is_r or is_w:
188 exceptional_fds.append(fd)
189
190 try:
191 read_fds, write_fds, exceptional_fds = (
192 select.select(read_fds, write_fds, exceptional_fds))
193 except select.error, err:
194 if err.args[0] != errno.EINTR:
195 raise
196 else:
197 continue
198
199 for fd in read_fds:
200 if fd == self.fileno():
201 self.HandleRequestNoBlock()
202 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000203 HandleXmppSocket(fd, self._xmpp_socket_map,
204 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000205
206 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000207 HandleXmppSocket(fd, self._xmpp_socket_map,
208 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000209
210 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000211 HandleXmppSocket(fd, self._xmpp_socket_map,
212 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000213
akalin@chromium.org154bb132010-11-12 02:20:27 +0000214
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000215class TCPEchoServer(SocketServer.TCPServer):
216 """A TCP echo server that echoes back what it has received."""
217
218 def server_bind(self):
219 """Override server_bind to store the server name."""
220 SocketServer.TCPServer.server_bind(self)
221 host, port = self.socket.getsockname()[:2]
222 self.server_name = socket.getfqdn(host)
223 self.server_port = port
224
225 def serve_forever(self):
226 self.stop = False
227 self.nonce_time = None
228 while not self.stop:
229 self.handle_request()
230 self.socket.close()
231
232
233class UDPEchoServer(SocketServer.UDPServer):
234 """A UDP echo server that echoes back what it has received."""
235
236 def server_bind(self):
237 """Override server_bind to store the server name."""
238 SocketServer.UDPServer.server_bind(self)
239 host, port = self.socket.getsockname()[:2]
240 self.server_name = socket.getfqdn(host)
241 self.server_port = port
242
243 def serve_forever(self):
244 self.stop = False
245 self.nonce_time = None
246 while not self.stop:
247 self.handle_request()
248 self.socket.close()
249
250
akalin@chromium.org154bb132010-11-12 02:20:27 +0000251class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
252
253 def __init__(self, request, client_address, socket_server,
254 connect_handlers, get_handlers, post_handlers, put_handlers):
255 self._connect_handlers = connect_handlers
256 self._get_handlers = get_handlers
257 self._post_handlers = post_handlers
258 self._put_handlers = put_handlers
259 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
260 self, request, client_address, socket_server)
261
262 def log_request(self, *args, **kwargs):
263 # Disable request logging to declutter test log output.
264 pass
265
266 def _ShouldHandleRequest(self, handler_name):
267 """Determines if the path can be handled by the handler.
268
269 We consider a handler valid if the path begins with the
270 handler name. It can optionally be followed by "?*", "/*".
271 """
272
273 pattern = re.compile('%s($|\?|/).*' % handler_name)
274 return pattern.match(self.path)
275
276 def do_CONNECT(self):
277 for handler in self._connect_handlers:
278 if handler():
279 return
280
281 def do_GET(self):
282 for handler in self._get_handlers:
283 if handler():
284 return
285
286 def do_POST(self):
287 for handler in self._post_handlers:
288 if handler():
289 return
290
291 def do_PUT(self):
292 for handler in self._put_handlers:
293 if handler():
294 return
295
296
297class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000298
299 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000300 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000301 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000302 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000303 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000304 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000305 self.NoCacheMaxAgeTimeHandler,
306 self.NoCacheTimeHandler,
307 self.CacheTimeHandler,
308 self.CacheExpiresHandler,
309 self.CacheProxyRevalidateHandler,
310 self.CachePrivateHandler,
311 self.CachePublicHandler,
312 self.CacheSMaxAgeHandler,
313 self.CacheMustRevalidateHandler,
314 self.CacheMustRevalidateMaxAgeHandler,
315 self.CacheNoStoreHandler,
316 self.CacheNoStoreMaxAgeHandler,
317 self.CacheNoTransformHandler,
318 self.DownloadHandler,
319 self.DownloadFinishHandler,
320 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000321 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000322 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000323 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000324 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000325 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000326 self.AuthBasicHandler,
327 self.AuthDigestHandler,
328 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000329 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000330 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000331 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000332 self.ServerRedirectHandler,
333 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000334 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000335 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000336 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000337 self.EchoTitleHandler,
338 self.EchoAllHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000339 self.EchoHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000340 self.DeviceManagementHandler] + get_handlers
341 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000342 self.EchoTitleHandler,
343 self.EchoAllHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000344 self.EchoHandler] + get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000345
maruel@google.come250a9b2009-03-10 17:39:46 +0000346 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000347 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000348 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000349 'gif': 'image/gif',
350 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000351 'jpg' : 'image/jpeg',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000352 'pdf' : 'application/pdf',
353 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000354 }
initial.commit94958cf2008-07-26 22:42:52 +0000355 self._default_mime_type = 'text/html'
356
akalin@chromium.org154bb132010-11-12 02:20:27 +0000357 BasePageHandler.__init__(self, request, client_address, socket_server,
358 connect_handlers, get_handlers, post_handlers,
359 put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000360
initial.commit94958cf2008-07-26 22:42:52 +0000361 def GetMIMETypeFromName(self, file_name):
362 """Returns the mime type for the specified file_name. So far it only looks
363 at the file extension."""
364
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000365 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000366 if len(extension) == 0:
367 # no extension.
368 return self._default_mime_type
369
ericroman@google.comc17ca532009-05-07 03:51:05 +0000370 # extension starts with a dot, so we need to remove it
371 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000372
initial.commit94958cf2008-07-26 22:42:52 +0000373 def NoCacheMaxAgeTimeHandler(self):
374 """This request handler yields a page with the title set to the current
375 system time, and no caching requested."""
376
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000377 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000378 return False
379
380 self.send_response(200)
381 self.send_header('Cache-Control', 'max-age=0')
382 self.send_header('Content-type', 'text/html')
383 self.end_headers()
384
maruel@google.come250a9b2009-03-10 17:39:46 +0000385 self.wfile.write('<html><head><title>%s</title></head></html>' %
386 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000387
388 return True
389
390 def NoCacheTimeHandler(self):
391 """This request handler yields a page with the title set to the current
392 system time, and no caching requested."""
393
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000394 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000395 return False
396
397 self.send_response(200)
398 self.send_header('Cache-Control', 'no-cache')
399 self.send_header('Content-type', 'text/html')
400 self.end_headers()
401
maruel@google.come250a9b2009-03-10 17:39:46 +0000402 self.wfile.write('<html><head><title>%s</title></head></html>' %
403 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000404
405 return True
406
407 def CacheTimeHandler(self):
408 """This request handler yields a page with the title set to the current
409 system time, and allows caching for one minute."""
410
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000411 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000412 return False
413
414 self.send_response(200)
415 self.send_header('Cache-Control', 'max-age=60')
416 self.send_header('Content-type', 'text/html')
417 self.end_headers()
418
maruel@google.come250a9b2009-03-10 17:39:46 +0000419 self.wfile.write('<html><head><title>%s</title></head></html>' %
420 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000421
422 return True
423
424 def CacheExpiresHandler(self):
425 """This request handler yields a page with the title set to the current
426 system time, and set the page to expire on 1 Jan 2099."""
427
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000428 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000429 return False
430
431 self.send_response(200)
432 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
433 self.send_header('Content-type', 'text/html')
434 self.end_headers()
435
maruel@google.come250a9b2009-03-10 17:39:46 +0000436 self.wfile.write('<html><head><title>%s</title></head></html>' %
437 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000438
439 return True
440
441 def CacheProxyRevalidateHandler(self):
442 """This request handler yields a page with the title set to the current
443 system time, and allows caching for 60 seconds"""
444
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000445 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000446 return False
447
448 self.send_response(200)
449 self.send_header('Content-type', 'text/html')
450 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
451 self.end_headers()
452
maruel@google.come250a9b2009-03-10 17:39:46 +0000453 self.wfile.write('<html><head><title>%s</title></head></html>' %
454 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000455
456 return True
457
458 def CachePrivateHandler(self):
459 """This request handler yields a page with the title set to the current
460 system time, and allows caching for 5 seconds."""
461
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000462 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000463 return False
464
465 self.send_response(200)
466 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000467 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000468 self.end_headers()
469
maruel@google.come250a9b2009-03-10 17:39:46 +0000470 self.wfile.write('<html><head><title>%s</title></head></html>' %
471 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000472
473 return True
474
475 def CachePublicHandler(self):
476 """This request handler yields a page with the title set to the current
477 system time, and allows caching for 5 seconds."""
478
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000479 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000480 return False
481
482 self.send_response(200)
483 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000484 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000485 self.end_headers()
486
maruel@google.come250a9b2009-03-10 17:39:46 +0000487 self.wfile.write('<html><head><title>%s</title></head></html>' %
488 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000489
490 return True
491
492 def CacheSMaxAgeHandler(self):
493 """This request handler yields a page with the title set to the current
494 system time, and does not allow for caching."""
495
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000496 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000497 return False
498
499 self.send_response(200)
500 self.send_header('Content-type', 'text/html')
501 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
502 self.end_headers()
503
maruel@google.come250a9b2009-03-10 17:39:46 +0000504 self.wfile.write('<html><head><title>%s</title></head></html>' %
505 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000506
507 return True
508
509 def CacheMustRevalidateHandler(self):
510 """This request handler yields a page with the title set to the current
511 system time, and does not allow caching."""
512
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000513 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000514 return False
515
516 self.send_response(200)
517 self.send_header('Content-type', 'text/html')
518 self.send_header('Cache-Control', 'must-revalidate')
519 self.end_headers()
520
maruel@google.come250a9b2009-03-10 17:39:46 +0000521 self.wfile.write('<html><head><title>%s</title></head></html>' %
522 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000523
524 return True
525
526 def CacheMustRevalidateMaxAgeHandler(self):
527 """This request handler yields a page with the title set to the current
528 system time, and does not allow caching event though max-age of 60
529 seconds is specified."""
530
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000531 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000532 return False
533
534 self.send_response(200)
535 self.send_header('Content-type', 'text/html')
536 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
537 self.end_headers()
538
maruel@google.come250a9b2009-03-10 17:39:46 +0000539 self.wfile.write('<html><head><title>%s</title></head></html>' %
540 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000541
542 return True
543
initial.commit94958cf2008-07-26 22:42:52 +0000544 def CacheNoStoreHandler(self):
545 """This request handler yields a page with the title set to the current
546 system time, and does not allow the page to be stored."""
547
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000548 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000549 return False
550
551 self.send_response(200)
552 self.send_header('Content-type', 'text/html')
553 self.send_header('Cache-Control', 'no-store')
554 self.end_headers()
555
maruel@google.come250a9b2009-03-10 17:39:46 +0000556 self.wfile.write('<html><head><title>%s</title></head></html>' %
557 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000558
559 return True
560
561 def CacheNoStoreMaxAgeHandler(self):
562 """This request handler yields a page with the title set to the current
563 system time, and does not allow the page to be stored even though max-age
564 of 60 seconds is specified."""
565
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000566 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000567 return False
568
569 self.send_response(200)
570 self.send_header('Content-type', 'text/html')
571 self.send_header('Cache-Control', 'max-age=60, no-store')
572 self.end_headers()
573
maruel@google.come250a9b2009-03-10 17:39:46 +0000574 self.wfile.write('<html><head><title>%s</title></head></html>' %
575 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000576
577 return True
578
579
580 def CacheNoTransformHandler(self):
581 """This request handler yields a page with the title set to the current
582 system time, and does not allow the content to transformed during
583 user-agent caching"""
584
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000585 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000586 return False
587
588 self.send_response(200)
589 self.send_header('Content-type', 'text/html')
590 self.send_header('Cache-Control', 'no-transform')
591 self.end_headers()
592
maruel@google.come250a9b2009-03-10 17:39:46 +0000593 self.wfile.write('<html><head><title>%s</title></head></html>' %
594 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000595
596 return True
597
598 def EchoHeader(self):
599 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000600 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000601
ananta@chromium.org56812d02011-04-07 17:52:05 +0000602 """This function echoes back the value of a specific request header"""
603 """while allowing caching for 16 hours."""
604 def EchoHeaderCache(self):
605 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000606
607 def EchoHeaderHelper(self, echo_header):
608 """This function echoes back the value of the request header passed in."""
609 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000610 return False
611
612 query_char = self.path.find('?')
613 if query_char != -1:
614 header_name = self.path[query_char+1:]
615
616 self.send_response(200)
617 self.send_header('Content-type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000618 if echo_header == '/echoheadercache':
619 self.send_header('Cache-control', 'max-age=60000')
620 else:
621 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000622 # insert a vary header to properly indicate that the cachability of this
623 # request is subject to value of the request header being echoed.
624 if len(header_name) > 0:
625 self.send_header('Vary', header_name)
626 self.end_headers()
627
628 if len(header_name) > 0:
629 self.wfile.write(self.headers.getheader(header_name))
630
631 return True
632
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000633 def ReadRequestBody(self):
634 """This function reads the body of the current HTTP request, handling
635 both plain and chunked transfer encoded requests."""
636
637 if self.headers.getheader('transfer-encoding') != 'chunked':
638 length = int(self.headers.getheader('content-length'))
639 return self.rfile.read(length)
640
641 # Read the request body as chunks.
642 body = ""
643 while True:
644 line = self.rfile.readline()
645 length = int(line, 16)
646 if length == 0:
647 self.rfile.readline()
648 break
649 body += self.rfile.read(length)
650 self.rfile.read(2)
651 return body
652
initial.commit94958cf2008-07-26 22:42:52 +0000653 def EchoHandler(self):
654 """This handler just echoes back the payload of the request, for testing
655 form submission."""
656
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000657 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000658 return False
659
660 self.send_response(200)
661 self.send_header('Content-type', 'text/html')
662 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000663 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000664 return True
665
666 def EchoTitleHandler(self):
667 """This handler is like Echo, but sets the page title to the request."""
668
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000669 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000670 return False
671
672 self.send_response(200)
673 self.send_header('Content-type', 'text/html')
674 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000675 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000676 self.wfile.write('<html><head><title>')
677 self.wfile.write(request)
678 self.wfile.write('</title></head></html>')
679 return True
680
681 def EchoAllHandler(self):
682 """This handler yields a (more) human-readable page listing information
683 about the request header & contents."""
684
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000685 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000686 return False
687
688 self.send_response(200)
689 self.send_header('Content-type', 'text/html')
690 self.end_headers()
691 self.wfile.write('<html><head><style>'
692 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
693 '</style></head><body>'
694 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000695 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000696 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000697
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000698 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000699 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000700 params = cgi.parse_qs(qs, keep_blank_values=1)
701
702 for param in params:
703 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000704
705 self.wfile.write('</pre>')
706
707 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
708
709 self.wfile.write('</body></html>')
710 return True
711
712 def DownloadHandler(self):
713 """This handler sends a downloadable file with or without reporting
714 the size (6K)."""
715
716 if self.path.startswith("/download-unknown-size"):
717 send_length = False
718 elif self.path.startswith("/download-known-size"):
719 send_length = True
720 else:
721 return False
722
723 #
724 # The test which uses this functionality is attempting to send
725 # small chunks of data to the client. Use a fairly large buffer
726 # so that we'll fill chrome's IO buffer enough to force it to
727 # actually write the data.
728 # See also the comments in the client-side of this test in
729 # download_uitest.cc
730 #
731 size_chunk1 = 35*1024
732 size_chunk2 = 10*1024
733
734 self.send_response(200)
735 self.send_header('Content-type', 'application/octet-stream')
736 self.send_header('Cache-Control', 'max-age=0')
737 if send_length:
738 self.send_header('Content-Length', size_chunk1 + size_chunk2)
739 self.end_headers()
740
741 # First chunk of data:
742 self.wfile.write("*" * size_chunk1)
743 self.wfile.flush()
744
745 # handle requests until one of them clears this flag.
746 self.server.waitForDownload = True
747 while self.server.waitForDownload:
748 self.server.handle_request()
749
750 # Second chunk of data:
751 self.wfile.write("*" * size_chunk2)
752 return True
753
754 def DownloadFinishHandler(self):
755 """This handler just tells the server to finish the current download."""
756
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000757 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000758 return False
759
760 self.server.waitForDownload = False
761 self.send_response(200)
762 self.send_header('Content-type', 'text/html')
763 self.send_header('Cache-Control', 'max-age=0')
764 self.end_headers()
765 return True
766
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000767 def _ReplaceFileData(self, data, query_parameters):
768 """Replaces matching substrings in a file.
769
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000770 If the 'replace_text' URL query parameter is present, it is expected to be
771 of the form old_text:new_text, which indicates that any old_text strings in
772 the file are replaced with new_text. Multiple 'replace_text' parameters may
773 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000774
775 If the parameters are not present, |data| is returned.
776 """
777 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000778 replace_text_values = query_dict.get('replace_text', [])
779 for replace_text_value in replace_text_values:
780 replace_text_args = replace_text_value.split(':')
781 if len(replace_text_args) != 2:
782 raise ValueError(
783 'replace_text must be of form old_text:new_text. Actual value: %s' %
784 replace_text_value)
785 old_text_b64, new_text_b64 = replace_text_args
786 old_text = base64.urlsafe_b64decode(old_text_b64)
787 new_text = base64.urlsafe_b64decode(new_text_b64)
788 data = data.replace(old_text, new_text)
789 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000790
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000791 def ZipFileHandler(self):
792 """This handler sends the contents of the requested file in compressed form.
793 Can pass in a parameter that specifies that the content length be
794 C - the compressed size (OK),
795 U - the uncompressed size (Non-standard, but handled),
796 S - less than compressed (OK because we keep going),
797 M - larger than compressed but less than uncompressed (an error),
798 L - larger than uncompressed (an error)
799 Example: compressedfiles/Picture_1.doc?C
800 """
801
802 prefix = "/compressedfiles/"
803 if not self.path.startswith(prefix):
804 return False
805
806 # Consume a request body if present.
807 if self.command == 'POST' or self.command == 'PUT' :
808 self.ReadRequestBody()
809
810 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
811
812 if not query in ('C', 'U', 'S', 'M', 'L'):
813 return False
814
815 sub_path = url_path[len(prefix):]
816 entries = sub_path.split('/')
817 file_path = os.path.join(self.server.data_dir, *entries)
818 if os.path.isdir(file_path):
819 file_path = os.path.join(file_path, 'index.html')
820
821 if not os.path.isfile(file_path):
822 print "File not found " + sub_path + " full path:" + file_path
823 self.send_error(404)
824 return True
825
826 f = open(file_path, "rb")
827 data = f.read()
828 uncompressed_len = len(data)
829 f.close()
830
831 # Compress the data.
832 data = zlib.compress(data)
833 compressed_len = len(data)
834
835 content_length = compressed_len
836 if query == 'U':
837 content_length = uncompressed_len
838 elif query == 'S':
839 content_length = compressed_len / 2
840 elif query == 'M':
841 content_length = (compressed_len + uncompressed_len) / 2
842 elif query == 'L':
843 content_length = compressed_len + uncompressed_len
844
845 self.send_response(200)
846 self.send_header('Content-type', 'application/msword')
847 self.send_header('Content-encoding', 'deflate')
848 self.send_header('Connection', 'close')
849 self.send_header('Content-Length', content_length)
850 self.send_header('ETag', '\'' + file_path + '\'')
851 self.end_headers()
852
853 self.wfile.write(data)
854
855 return True
856
initial.commit94958cf2008-07-26 22:42:52 +0000857 def FileHandler(self):
858 """This handler sends the contents of the requested file. Wow, it's like
859 a real webserver!"""
860
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000861 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000862 if not self.path.startswith(prefix):
863 return False
864
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000865 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000866 if self.command == 'POST' or self.command == 'PUT' :
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000867 self.ReadRequestBody()
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000868
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000869 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
870 sub_path = url_path[len(prefix):]
871 entries = sub_path.split('/')
872 file_path = os.path.join(self.server.data_dir, *entries)
873 if os.path.isdir(file_path):
874 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000875
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000876 if not os.path.isfile(file_path):
877 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000878 self.send_error(404)
879 return True
880
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000881 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000882 data = f.read()
883 f.close()
884
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000885 data = self._ReplaceFileData(data, query)
886
initial.commit94958cf2008-07-26 22:42:52 +0000887 # If file.mock-http-headers exists, it contains the headers we
888 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000889 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000890 if os.path.isfile(headers_path):
891 f = open(headers_path, "r")
892
893 # "HTTP/1.1 200 OK"
894 response = f.readline()
895 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
896 self.send_response(int(status_code))
897
898 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000899 header_values = re.findall('(\S+):\s*(.*)', line)
900 if len(header_values) > 0:
901 # "name: value"
902 name, value = header_values[0]
903 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000904 f.close()
905 else:
906 # Could be more generic once we support mime-type sniffing, but for
907 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000908
909 range = self.headers.get('Range')
910 if range and range.startswith('bytes='):
911 # Note this doesn't handle all valid byte range values (i.e. open ended
912 # ones), just enough for what we needed so far.
913 range = range[6:].split('-')
914 start = int(range[0])
915 end = int(range[1])
916
917 self.send_response(206)
918 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
919 str(len(data))
920 self.send_header('Content-Range', content_range)
921 data = data[start: end + 1]
922 else:
923 self.send_response(200)
924
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000925 self.send_header('Content-type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000926 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000927 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000928 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000929 self.end_headers()
930
931 self.wfile.write(data)
932
933 return True
934
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000935 def SetCookieHandler(self):
936 """This handler just sets a cookie, for testing cookie handling."""
937
938 if not self._ShouldHandleRequest("/set-cookie"):
939 return False
940
941 query_char = self.path.find('?')
942 if query_char != -1:
943 cookie_values = self.path[query_char + 1:].split('&')
944 else:
945 cookie_values = ("",)
946 self.send_response(200)
947 self.send_header('Content-type', 'text/html')
948 for cookie_value in cookie_values:
949 self.send_header('Set-Cookie', '%s' % cookie_value)
950 self.end_headers()
951 for cookie_value in cookie_values:
952 self.wfile.write('%s' % cookie_value)
953 return True
954
initial.commit94958cf2008-07-26 22:42:52 +0000955 def AuthBasicHandler(self):
956 """This handler tests 'Basic' authentication. It just sends a page with
957 title 'user/pass' if you succeed."""
958
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000959 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000960 return False
961
962 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000963 expected_password = 'secret'
964 realm = 'testrealm'
965 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +0000966
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000967 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
968 query_params = cgi.parse_qs(query, True)
969 if 'set-cookie-if-challenged' in query_params:
970 set_cookie_if_challenged = True
971 if 'password' in query_params:
972 expected_password = query_params['password'][0]
973 if 'realm' in query_params:
974 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +0000975
initial.commit94958cf2008-07-26 22:42:52 +0000976 auth = self.headers.getheader('authorization')
977 try:
978 if not auth:
979 raise Exception('no auth')
980 b64str = re.findall(r'Basic (\S+)', auth)[0]
981 userpass = base64.b64decode(b64str)
982 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000983 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +0000984 raise Exception('wrong password')
985 except Exception, e:
986 # Authentication failed.
987 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000988 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
initial.commit94958cf2008-07-26 22:42:52 +0000989 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000990 if set_cookie_if_challenged:
991 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000992 self.end_headers()
993 self.wfile.write('<html><head>')
994 self.wfile.write('<title>Denied: %s</title>' % e)
995 self.wfile.write('</head><body>')
996 self.wfile.write('auth=%s<p>' % auth)
997 self.wfile.write('b64str=%s<p>' % b64str)
998 self.wfile.write('username: %s<p>' % username)
999 self.wfile.write('userpass: %s<p>' % userpass)
1000 self.wfile.write('password: %s<p>' % password)
1001 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1002 self.wfile.write('</body></html>')
1003 return True
1004
1005 # Authentication successful. (Return a cachable response to allow for
1006 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001007 old_protocol_version = self.protocol_version
1008 self.protocol_version = "HTTP/1.1"
1009
initial.commit94958cf2008-07-26 22:42:52 +00001010 if_none_match = self.headers.getheader('if-none-match')
1011 if if_none_match == "abc":
1012 self.send_response(304)
1013 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001014 elif url_path.endswith(".gif"):
1015 # Using chrome/test/data/google/logo.gif as the test image
1016 test_image_path = ['google', 'logo.gif']
1017 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1018 if not os.path.isfile(gif_path):
1019 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001020 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001021 return True
1022
1023 f = open(gif_path, "rb")
1024 data = f.read()
1025 f.close()
1026
1027 self.send_response(200)
1028 self.send_header('Content-type', 'image/gif')
1029 self.send_header('Cache-control', 'max-age=60000')
1030 self.send_header('Etag', 'abc')
1031 self.end_headers()
1032 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001033 else:
1034 self.send_response(200)
1035 self.send_header('Content-type', 'text/html')
1036 self.send_header('Cache-control', 'max-age=60000')
1037 self.send_header('Etag', 'abc')
1038 self.end_headers()
1039 self.wfile.write('<html><head>')
1040 self.wfile.write('<title>%s/%s</title>' % (username, password))
1041 self.wfile.write('</head><body>')
1042 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001043 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001044 self.wfile.write('</body></html>')
1045
rvargas@google.com54453b72011-05-19 01:11:11 +00001046 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001047 return True
1048
tonyg@chromium.org75054202010-03-31 22:06:10 +00001049 def GetNonce(self, force_reset=False):
1050 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001051
tonyg@chromium.org75054202010-03-31 22:06:10 +00001052 This is a fake implementation. A real implementation would only use a given
1053 nonce a single time (hence the name n-once). However, for the purposes of
1054 unittesting, we don't care about the security of the nonce.
1055
1056 Args:
1057 force_reset: Iff set, the nonce will be changed. Useful for testing the
1058 "stale" response.
1059 """
1060 if force_reset or not self.server.nonce_time:
1061 self.server.nonce_time = time.time()
1062 return _new_md5('privatekey%s%d' %
1063 (self.path, self.server.nonce_time)).hexdigest()
1064
1065 def AuthDigestHandler(self):
1066 """This handler tests 'Digest' authentication.
1067
1068 It just sends a page with title 'user/pass' if you succeed.
1069
1070 A stale response is sent iff "stale" is present in the request path.
1071 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001072 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001073 return False
1074
tonyg@chromium.org75054202010-03-31 22:06:10 +00001075 stale = 'stale' in self.path
1076 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001077 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001078 password = 'secret'
1079 realm = 'testrealm'
1080
1081 auth = self.headers.getheader('authorization')
1082 pairs = {}
1083 try:
1084 if not auth:
1085 raise Exception('no auth')
1086 if not auth.startswith('Digest'):
1087 raise Exception('not digest')
1088 # Pull out all the name="value" pairs as a dictionary.
1089 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1090
1091 # Make sure it's all valid.
1092 if pairs['nonce'] != nonce:
1093 raise Exception('wrong nonce')
1094 if pairs['opaque'] != opaque:
1095 raise Exception('wrong opaque')
1096
1097 # Check the 'response' value and make sure it matches our magic hash.
1098 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001099 hash_a1 = _new_md5(
1100 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001101 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001102 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001103 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001104 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1105 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001106 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001107
1108 if pairs['response'] != response:
1109 raise Exception('wrong password')
1110 except Exception, e:
1111 # Authentication failed.
1112 self.send_response(401)
1113 hdr = ('Digest '
1114 'realm="%s", '
1115 'domain="/", '
1116 'qop="auth", '
1117 'algorithm=MD5, '
1118 'nonce="%s", '
1119 'opaque="%s"') % (realm, nonce, opaque)
1120 if stale:
1121 hdr += ', stale="TRUE"'
1122 self.send_header('WWW-Authenticate', hdr)
1123 self.send_header('Content-type', 'text/html')
1124 self.end_headers()
1125 self.wfile.write('<html><head>')
1126 self.wfile.write('<title>Denied: %s</title>' % e)
1127 self.wfile.write('</head><body>')
1128 self.wfile.write('auth=%s<p>' % auth)
1129 self.wfile.write('pairs=%s<p>' % pairs)
1130 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1131 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1132 self.wfile.write('</body></html>')
1133 return True
1134
1135 # Authentication successful.
1136 self.send_response(200)
1137 self.send_header('Content-type', 'text/html')
1138 self.end_headers()
1139 self.wfile.write('<html><head>')
1140 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1141 self.wfile.write('</head><body>')
1142 self.wfile.write('auth=%s<p>' % auth)
1143 self.wfile.write('pairs=%s<p>' % pairs)
1144 self.wfile.write('</body></html>')
1145
1146 return True
1147
1148 def SlowServerHandler(self):
1149 """Wait for the user suggested time before responding. The syntax is
1150 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001151 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001152 return False
1153 query_char = self.path.find('?')
1154 wait_sec = 1.0
1155 if query_char >= 0:
1156 try:
1157 wait_sec = int(self.path[query_char + 1:])
1158 except ValueError:
1159 pass
1160 time.sleep(wait_sec)
1161 self.send_response(200)
1162 self.send_header('Content-type', 'text/plain')
1163 self.end_headers()
1164 self.wfile.write("waited %d seconds" % wait_sec)
1165 return True
1166
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001167 def ChunkedServerHandler(self):
1168 """Send chunked response. Allows to specify chunks parameters:
1169 - waitBeforeHeaders - ms to wait before sending headers
1170 - waitBetweenChunks - ms to wait between chunks
1171 - chunkSize - size of each chunk in bytes
1172 - chunksNumber - number of chunks
1173 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1174 waits one second, then sends headers and five chunks five bytes each."""
1175 if not self._ShouldHandleRequest("/chunked"):
1176 return False
1177 query_char = self.path.find('?')
1178 chunkedSettings = {'waitBeforeHeaders' : 0,
1179 'waitBetweenChunks' : 0,
1180 'chunkSize' : 5,
1181 'chunksNumber' : 5}
1182 if query_char >= 0:
1183 params = self.path[query_char + 1:].split('&')
1184 for param in params:
1185 keyValue = param.split('=')
1186 if len(keyValue) == 2:
1187 try:
1188 chunkedSettings[keyValue[0]] = int(keyValue[1])
1189 except ValueError:
1190 pass
1191 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1192 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1193 self.send_response(200)
1194 self.send_header('Content-type', 'text/plain')
1195 self.send_header('Connection', 'close')
1196 self.send_header('Transfer-Encoding', 'chunked')
1197 self.end_headers()
1198 # Chunked encoding: sending all chunks, then final zero-length chunk and
1199 # then final CRLF.
1200 for i in range(0, chunkedSettings['chunksNumber']):
1201 if i > 0:
1202 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1203 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1204 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1205 self.sendChunkHelp('')
1206 return True
1207
initial.commit94958cf2008-07-26 22:42:52 +00001208 def ContentTypeHandler(self):
1209 """Returns a string of html with the given content type. E.g.,
1210 /contenttype?text/css returns an html file with the Content-Type
1211 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001212 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001213 return False
1214 query_char = self.path.find('?')
1215 content_type = self.path[query_char + 1:].strip()
1216 if not content_type:
1217 content_type = 'text/html'
1218 self.send_response(200)
1219 self.send_header('Content-Type', content_type)
1220 self.end_headers()
1221 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1222 return True
1223
creis@google.com2f4f6a42011-03-25 19:44:19 +00001224 def NoContentHandler(self):
1225 """Returns a 204 No Content response."""
1226 if not self._ShouldHandleRequest("/nocontent"):
1227 return False
1228 self.send_response(204)
1229 self.end_headers()
1230 return True
1231
initial.commit94958cf2008-07-26 22:42:52 +00001232 def ServerRedirectHandler(self):
1233 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001234 '/server-redirect?http://foo.bar/asdf' to redirect to
1235 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001236
1237 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001238 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001239 return False
1240
1241 query_char = self.path.find('?')
1242 if query_char < 0 or len(self.path) <= query_char + 1:
1243 self.sendRedirectHelp(test_name)
1244 return True
1245 dest = self.path[query_char + 1:]
1246
1247 self.send_response(301) # moved permanently
1248 self.send_header('Location', dest)
1249 self.send_header('Content-type', 'text/html')
1250 self.end_headers()
1251 self.wfile.write('<html><head>')
1252 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1253
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001254 return True
initial.commit94958cf2008-07-26 22:42:52 +00001255
1256 def ClientRedirectHandler(self):
1257 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001258 '/client-redirect?http://foo.bar/asdf' to redirect to
1259 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001260
1261 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001262 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001263 return False
1264
1265 query_char = self.path.find('?');
1266 if query_char < 0 or len(self.path) <= query_char + 1:
1267 self.sendRedirectHelp(test_name)
1268 return True
1269 dest = self.path[query_char + 1:]
1270
1271 self.send_response(200)
1272 self.send_header('Content-type', 'text/html')
1273 self.end_headers()
1274 self.wfile.write('<html><head>')
1275 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1276 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1277
1278 return True
1279
tony@chromium.org03266982010-03-05 03:18:42 +00001280 def MultipartHandler(self):
1281 """Send a multipart response (10 text/html pages)."""
1282 test_name = "/multipart"
1283 if not self._ShouldHandleRequest(test_name):
1284 return False
1285
1286 num_frames = 10
1287 bound = '12345'
1288 self.send_response(200)
1289 self.send_header('Content-type',
1290 'multipart/x-mixed-replace;boundary=' + bound)
1291 self.end_headers()
1292
1293 for i in xrange(num_frames):
1294 self.wfile.write('--' + bound + '\r\n')
1295 self.wfile.write('Content-type: text/html\r\n\r\n')
1296 self.wfile.write('<title>page ' + str(i) + '</title>')
1297 self.wfile.write('page ' + str(i))
1298
1299 self.wfile.write('--' + bound + '--')
1300 return True
1301
initial.commit94958cf2008-07-26 22:42:52 +00001302 def DefaultResponseHandler(self):
1303 """This is the catch-all response handler for requests that aren't handled
1304 by one of the special handlers above.
1305 Note that we specify the content-length as without it the https connection
1306 is not closed properly (and the browser keeps expecting data)."""
1307
1308 contents = "Default response given for path: " + self.path
1309 self.send_response(200)
1310 self.send_header('Content-type', 'text/html')
1311 self.send_header("Content-Length", len(contents))
1312 self.end_headers()
1313 self.wfile.write(contents)
1314 return True
1315
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001316 def RedirectConnectHandler(self):
1317 """Sends a redirect to the CONNECT request for www.redirect.com. This
1318 response is not specified by the RFC, so the browser should not follow
1319 the redirect."""
1320
1321 if (self.path.find("www.redirect.com") < 0):
1322 return False
1323
1324 dest = "http://www.destination.com/foo.js"
1325
1326 self.send_response(302) # moved temporarily
1327 self.send_header('Location', dest)
1328 self.send_header('Connection', 'close')
1329 self.end_headers()
1330 return True
1331
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001332 def ServerAuthConnectHandler(self):
1333 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1334 response doesn't make sense because the proxy server cannot request
1335 server authentication."""
1336
1337 if (self.path.find("www.server-auth.com") < 0):
1338 return False
1339
1340 challenge = 'Basic realm="WallyWorld"'
1341
1342 self.send_response(401) # unauthorized
1343 self.send_header('WWW-Authenticate', challenge)
1344 self.send_header('Connection', 'close')
1345 self.end_headers()
1346 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001347
1348 def DefaultConnectResponseHandler(self):
1349 """This is the catch-all response handler for CONNECT requests that aren't
1350 handled by one of the special handlers above. Real Web servers respond
1351 with 400 to CONNECT requests."""
1352
1353 contents = "Your client has issued a malformed or illegal request."
1354 self.send_response(400) # bad request
1355 self.send_header('Content-type', 'text/html')
1356 self.send_header("Content-Length", len(contents))
1357 self.end_headers()
1358 self.wfile.write(contents)
1359 return True
1360
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001361 def DeviceManagementHandler(self):
1362 """Delegates to the device management service used for cloud policy."""
1363 if not self._ShouldHandleRequest("/device_management"):
1364 return False
1365
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001366 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001367
1368 if not self.server._device_management_handler:
1369 import device_management
1370 policy_path = os.path.join(self.server.data_dir, 'device_management')
1371 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001372 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001373 self.server.policy_keys,
1374 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001375
1376 http_response, raw_reply = (
1377 self.server._device_management_handler.HandleRequest(self.path,
1378 self.headers,
1379 raw_request))
1380 self.send_response(http_response)
1381 self.end_headers()
1382 self.wfile.write(raw_reply)
1383 return True
1384
initial.commit94958cf2008-07-26 22:42:52 +00001385 # called by the redirect handling function when there is no parameter
1386 def sendRedirectHelp(self, redirect_name):
1387 self.send_response(200)
1388 self.send_header('Content-type', 'text/html')
1389 self.end_headers()
1390 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1391 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1392 self.wfile.write('</body></html>')
1393
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001394 # called by chunked handling function
1395 def sendChunkHelp(self, chunk):
1396 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1397 self.wfile.write('%X\r\n' % len(chunk))
1398 self.wfile.write(chunk)
1399 self.wfile.write('\r\n')
1400
akalin@chromium.org154bb132010-11-12 02:20:27 +00001401
1402class SyncPageHandler(BasePageHandler):
1403 """Handler for the main HTTP sync server."""
1404
1405 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001406 get_handlers = [self.ChromiumSyncMigrationOpHandler,
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001407 self.ChromiumSyncTimeHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001408 self.ChromiumSyncBirthdayErrorOpHandler,
1409 self.ChromiumSyncTransientErrorOpHandler]
1410
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001411 post_handlers = [self.ChromiumSyncCommandHandler,
1412 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001413 BasePageHandler.__init__(self, request, client_address,
1414 sync_http_server, [], get_handlers,
1415 post_handlers, [])
1416
1417 def ChromiumSyncTimeHandler(self):
1418 """Handle Chromium sync .../time requests.
1419
1420 The syncer sometimes checks server reachability by examining /time.
1421 """
1422 test_name = "/chromiumsync/time"
1423 if not self._ShouldHandleRequest(test_name):
1424 return False
1425
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001426 # Chrome hates it if we send a response before reading the request.
1427 if self.headers.getheader('content-length'):
1428 length = int(self.headers.getheader('content-length'))
1429 raw_request = self.rfile.read(length)
1430
akalin@chromium.org154bb132010-11-12 02:20:27 +00001431 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001432 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001433 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001434 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001435 return True
1436
1437 def ChromiumSyncCommandHandler(self):
1438 """Handle a chromiumsync command arriving via http.
1439
1440 This covers all sync protocol commands: authentication, getupdates, and
1441 commit.
1442 """
1443 test_name = "/chromiumsync/command"
1444 if not self._ShouldHandleRequest(test_name):
1445 return False
1446
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001447 length = int(self.headers.getheader('content-length'))
1448 raw_request = self.rfile.read(length)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001449
1450 http_response, raw_reply = self.server.HandleCommand(
1451 self.path, raw_request)
1452 self.send_response(http_response)
1453 self.end_headers()
1454 self.wfile.write(raw_reply)
1455 return True
1456
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001457 def ChromiumSyncMigrationOpHandler(self):
1458 """Handle a chromiumsync test-op command arriving via http.
1459 """
1460 test_name = "/chromiumsync/migrate"
1461 if not self._ShouldHandleRequest(test_name):
1462 return False
1463
1464 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1465 self.path)
1466 self.send_response(http_response)
1467 self.send_header('Content-Type', 'text/html')
1468 self.send_header('Content-Length', len(raw_reply))
1469 self.end_headers()
1470 self.wfile.write(raw_reply)
1471 return True
1472
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001473 def ChromiumSyncBirthdayErrorOpHandler(self):
1474 test_name = "/chromiumsync/birthdayerror"
1475 if not self._ShouldHandleRequest(test_name):
1476 return False
1477 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1478 self.send_response(result)
1479 self.send_header('Content-Type', 'text/html')
1480 self.send_header('Content-Length', len(raw_reply))
1481 self.end_headers()
1482 self.wfile.write(raw_reply)
1483 return True;
1484
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001485 def ChromiumSyncTransientErrorOpHandler(self):
1486 test_name = "/chromiumsync/transienterror"
1487 if not self._ShouldHandleRequest(test_name):
1488 return False
1489 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1490 self.send_response(result)
1491 self.send_header('Content-Type', 'text/html')
1492 self.send_header('Content-Length', len(raw_reply))
1493 self.end_headers()
1494 self.wfile.write(raw_reply)
1495 return True;
1496
akalin@chromium.org154bb132010-11-12 02:20:27 +00001497
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001498def MakeDataDir():
1499 if options.data_dir:
1500 if not os.path.isdir(options.data_dir):
1501 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1502 return None
1503 my_data_dir = options.data_dir
1504 else:
1505 # Create the default path to our data dir, relative to the exe dir.
1506 my_data_dir = os.path.dirname(sys.argv[0])
1507 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001508 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001509
1510 #TODO(ibrar): Must use Find* funtion defined in google\tools
1511 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1512
1513 return my_data_dir
1514
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001515
1516class TCPEchoHandler(SocketServer.BaseRequestHandler):
1517 """The RequestHandler class for TCP echo server.
1518
1519 It is instantiated once per connection to the server, and overrides the
1520 handle() method to implement communication to the client.
1521 """
1522
1523 def handle(self):
1524 data = self.request.recv(65536)
1525 if not data:
1526 return
1527 self.request.send(data)
1528
1529
1530class UDPEchoHandler(SocketServer.BaseRequestHandler):
1531 """The RequestHandler class for UDP echo server.
1532
1533 It is instantiated once per connection to the server, and overrides the
1534 handle() method to implement communication to the client.
1535 """
1536
1537 def handle(self):
1538 data = self.request[0].strip()
1539 socket = self.request[1]
1540 socket.sendto(data, self.client_address)
1541
1542
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001543class FileMultiplexer:
1544 def __init__(self, fd1, fd2) :
1545 self.__fd1 = fd1
1546 self.__fd2 = fd2
1547
1548 def __del__(self) :
1549 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1550 self.__fd1.close()
1551 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1552 self.__fd2.close()
1553
1554 def write(self, text) :
1555 self.__fd1.write(text)
1556 self.__fd2.write(text)
1557
1558 def flush(self) :
1559 self.__fd1.flush()
1560 self.__fd2.flush()
1561
initial.commit94958cf2008-07-26 22:42:52 +00001562def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001563 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001564 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001565 if options.log_to_console:
1566 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1567 else:
1568 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001569
1570 port = options.port
1571
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001572 server_data = {}
1573
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001574 if options.server_type == SERVER_HTTP:
1575 if options.cert:
1576 # let's make sure the cert file exists.
1577 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001578 print 'specified server cert file not found: ' + options.cert + \
1579 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001580 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001581 for ca_cert in options.ssl_client_ca:
1582 if not os.path.isfile(ca_cert):
1583 print 'specified trusted client CA file not found: ' + ca_cert + \
1584 ' exiting...'
1585 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001586 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001587 options.ssl_client_auth, options.ssl_client_ca,
1588 options.ssl_bulk_cipher)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001589 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001590 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001591 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001592 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001593
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001594 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001595 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001596 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001597 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001598 server.policy_keys = options.policy_keys
1599 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001600 elif options.server_type == SERVER_SYNC:
1601 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1602 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001603 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1604 server_data['port'] = server.server_port
1605 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001606 elif options.server_type == SERVER_TCP_ECHO:
1607 server = TCPEchoServer(('127.0.0.1', port), TCPEchoHandler)
1608 print 'Echo TCP server started on port %d...' % server.server_port
1609 server_data['port'] = server.server_port
1610 elif options.server_type == SERVER_UDP_ECHO:
1611 server = UDPEchoServer(('127.0.0.1', port), UDPEchoHandler)
1612 print 'Echo UDP server started on port %d...' % server.server_port
1613 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001614 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001615 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001616 my_data_dir = MakeDataDir()
1617
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001618 # Instantiate a dummy authorizer for managing 'virtual' users
1619 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1620
1621 # Define a new user having full r/w permissions and a read-only
1622 # anonymous user
1623 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1624
1625 authorizer.add_anonymous(my_data_dir)
1626
1627 # Instantiate FTP handler class
1628 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1629 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001630
1631 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001632 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1633 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001634
1635 # Instantiate FTP server class and listen to 127.0.0.1:port
1636 address = ('127.0.0.1', port)
1637 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001638 server_data['port'] = server.socket.getsockname()[1]
1639 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001640
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001641 # Notify the parent that we've started. (BaseServer subclasses
1642 # bind their sockets on construction.)
1643 if options.startup_pipe is not None:
akalin@chromium.org73b7b5c2010-11-26 19:42:26 +00001644 server_data_json = simplejson.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001645 server_data_len = len(server_data_json)
1646 print 'sending server_data: %s (%d bytes)' % (
1647 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001648 if sys.platform == 'win32':
1649 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1650 else:
1651 fd = options.startup_pipe
1652 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001653 # First write the data length as an unsigned 4-byte value. This
1654 # is _not_ using network byte ordering since the other end of the
1655 # pipe is on the same machine.
1656 startup_pipe.write(struct.pack('=L', server_data_len))
1657 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001658 startup_pipe.close()
1659
initial.commit94958cf2008-07-26 22:42:52 +00001660 try:
1661 server.serve_forever()
1662 except KeyboardInterrupt:
1663 print 'shutting down server'
1664 server.stop = True
1665
1666if __name__ == '__main__':
1667 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001668 option_parser.add_option("-f", '--ftp', action='store_const',
1669 const=SERVER_FTP, default=SERVER_HTTP,
1670 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001671 help='start up an FTP server.')
1672 option_parser.add_option('', '--sync', action='store_const',
1673 const=SERVER_SYNC, default=SERVER_HTTP,
1674 dest='server_type',
1675 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001676 option_parser.add_option('', '--tcp-echo', action='store_const',
1677 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1678 dest='server_type',
1679 help='start up a tcp echo server.')
1680 option_parser.add_option('', '--udp-echo', action='store_const',
1681 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
1682 dest='server_type',
1683 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001684 option_parser.add_option('', '--log-to-console', action='store_const',
1685 const=True, default=False,
1686 dest='log_to_console',
1687 help='Enables or disables sys.stdout logging to '
1688 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001689 option_parser.add_option('', '--port', default='0', type='int',
1690 help='Port used by the server. If unspecified, the '
1691 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001692 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001693 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001694 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001695 help='Specify that https should be used, specify '
1696 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001697 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001698 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1699 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001700 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1701 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001702 'should include the CA named in the subject of '
1703 'the DER-encoded certificate contained in the '
1704 'specified file. This option may appear multiple '
1705 'times, indicating multiple CA names should be '
1706 'sent in the request.')
1707 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1708 help='Specify the bulk encryption algorithm(s)'
1709 'that will be accepted by the SSL server. Valid '
1710 'values are "aes256", "aes128", "3des", "rc4". If '
1711 'omitted, all algorithms will be used. This '
1712 'option may appear multiple times, indicating '
1713 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001714 option_parser.add_option('', '--file-root-url', default='/files/',
1715 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001716 option_parser.add_option('', '--startup-pipe', type='int',
1717 dest='startup_pipe',
1718 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001719 option_parser.add_option('', '--policy-key', action='append',
1720 dest='policy_keys',
1721 help='Specify a path to a PEM-encoded private key '
1722 'to use for policy signing. May be specified '
1723 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00001724 'the server. If ther server has multiple keys, it '
1725 'will rotate through them in at each request a '
1726 'round-robin fashion. The server will generate a '
1727 'random key if none is specified on the command '
1728 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001729 option_parser.add_option('', '--policy-user', default='user@example.com',
1730 dest='policy_user',
1731 help='Specify the user name the server should '
1732 'report back to the client as the user owning the '
1733 'token used for making the policy request.')
initial.commit94958cf2008-07-26 22:42:52 +00001734 options, args = option_parser.parse_args()
1735
1736 sys.exit(main(options, args))