blob: c079b4c514d72991832f582e7aa9a8dd761bbb15 [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.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
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
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000039import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000040import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000041import tlslite
42import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000043
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000044try:
45 import hashlib
46 _new_md5 = hashlib.md5
47except ImportError:
48 import md5
49 _new_md5 = md5.new
50
davidben@chromium.org06fcf202010-09-22 18:15:23 +000051if sys.platform == 'win32':
52 import msvcrt
53
maruel@chromium.org756cf982009-03-05 12:46:38 +000054SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000055SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000056SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000057SERVER_TCP_ECHO = 3
58SERVER_UDP_ECHO = 4
initial.commit94958cf2008-07-26 22:42:52 +000059
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000060# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000061debug_output = sys.stderr
62def debug(str):
63 debug_output.write(str + "\n")
64 debug_output.flush()
65
66class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
67 """This is a specialization of of BaseHTTPServer to allow it
68 to be exited cleanly (by setting its "stop" member to True)."""
69
70 def serve_forever(self):
71 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000072 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000073 while not self.stop:
74 self.handle_request()
75 self.socket.close()
76
77class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
78 """This is a specialization of StoppableHTTPerver that add https support."""
79
davidben@chromium.org31282a12010-08-07 01:10:02 +000080 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000081 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers):
initial.commit94958cf2008-07-26 22:42:52 +000082 s = open(cert_path).read()
83 x509 = tlslite.api.X509()
84 x509.parse(s)
85 self.cert_chain = tlslite.api.X509CertChain([x509])
86 s = open(cert_path).read()
87 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000088 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000089 self.ssl_client_cas = []
90 for ca_file in ssl_client_cas:
91 s = open(ca_file).read()
92 x509 = tlslite.api.X509()
93 x509.parse(s)
94 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000095 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
96 if ssl_bulk_ciphers is not None:
97 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +000098
99 self.session_cache = tlslite.api.SessionCache()
100 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
101
102 def handshake(self, tlsConnection):
103 """Creates the SSL connection."""
104 try:
105 tlsConnection.handshakeServer(certChain=self.cert_chain,
106 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000107 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000108 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000109 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000110 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +0000111 tlsConnection.ignoreAbruptClose = True
112 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000113 except tlslite.api.TLSAbruptCloseError:
114 # Ignore abrupt close.
115 return True
initial.commit94958cf2008-07-26 22:42:52 +0000116 except tlslite.api.TLSError, error:
117 print "Handshake failure:", str(error)
118 return False
119
akalin@chromium.org154bb132010-11-12 02:20:27 +0000120
121class SyncHTTPServer(StoppableHTTPServer):
122 """An HTTP server that handles sync commands."""
123
124 def __init__(self, server_address, request_handler_class):
125 # We import here to avoid pulling in chromiumsync's dependencies
126 # unless strictly necessary.
127 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000128 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000129 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000130 self._sync_handler = chromiumsync.TestServer()
131 self._xmpp_socket_map = {}
132 self._xmpp_server = xmppserver.XmppServer(
133 self._xmpp_socket_map, ('localhost', 0))
134 self.xmpp_port = self._xmpp_server.getsockname()[1]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000135
136 def HandleCommand(self, query, raw_request):
137 return self._sync_handler.HandleCommand(query, raw_request)
138
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000139 def HandleRequestNoBlock(self):
140 """Handles a single request.
141
142 Copied from SocketServer._handle_request_noblock().
143 """
144 try:
145 request, client_address = self.get_request()
146 except socket.error:
147 return
148 if self.verify_request(request, client_address):
149 try:
150 self.process_request(request, client_address)
151 except:
152 self.handle_error(request, client_address)
153 self.close_request(request)
154
155 def serve_forever(self):
156 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
157 """
158
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000159 def HandleXmppSocket(fd, socket_map, handler):
160 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000161
162 Adapted from asyncore.read() et al.
163 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000164 xmpp_connection = socket_map.get(fd)
165 # This could happen if a previous handler call caused fd to get
166 # removed from socket_map.
167 if xmpp_connection is None:
168 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000169 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000170 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000171 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
172 raise
173 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000174 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000175
176 while True:
177 read_fds = [ self.fileno() ]
178 write_fds = []
179 exceptional_fds = []
180
181 for fd, xmpp_connection in self._xmpp_socket_map.items():
182 is_r = xmpp_connection.readable()
183 is_w = xmpp_connection.writable()
184 if is_r:
185 read_fds.append(fd)
186 if is_w:
187 write_fds.append(fd)
188 if is_r or is_w:
189 exceptional_fds.append(fd)
190
191 try:
192 read_fds, write_fds, exceptional_fds = (
193 select.select(read_fds, write_fds, exceptional_fds))
194 except select.error, err:
195 if err.args[0] != errno.EINTR:
196 raise
197 else:
198 continue
199
200 for fd in read_fds:
201 if fd == self.fileno():
202 self.HandleRequestNoBlock()
203 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000204 HandleXmppSocket(fd, self._xmpp_socket_map,
205 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000206
207 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000208 HandleXmppSocket(fd, self._xmpp_socket_map,
209 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000210
211 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000212 HandleXmppSocket(fd, self._xmpp_socket_map,
213 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000214
akalin@chromium.org154bb132010-11-12 02:20:27 +0000215
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000216class TCPEchoServer(SocketServer.TCPServer):
217 """A TCP echo server that echoes back what it has received."""
218
219 def server_bind(self):
220 """Override server_bind to store the server name."""
221 SocketServer.TCPServer.server_bind(self)
222 host, port = self.socket.getsockname()[:2]
223 self.server_name = socket.getfqdn(host)
224 self.server_port = port
225
226 def serve_forever(self):
227 self.stop = False
228 self.nonce_time = None
229 while not self.stop:
230 self.handle_request()
231 self.socket.close()
232
233
234class UDPEchoServer(SocketServer.UDPServer):
235 """A UDP echo server that echoes back what it has received."""
236
237 def server_bind(self):
238 """Override server_bind to store the server name."""
239 SocketServer.UDPServer.server_bind(self)
240 host, port = self.socket.getsockname()[:2]
241 self.server_name = socket.getfqdn(host)
242 self.server_port = port
243
244 def serve_forever(self):
245 self.stop = False
246 self.nonce_time = None
247 while not self.stop:
248 self.handle_request()
249 self.socket.close()
250
251
akalin@chromium.org154bb132010-11-12 02:20:27 +0000252class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
253
254 def __init__(self, request, client_address, socket_server,
255 connect_handlers, get_handlers, post_handlers, put_handlers):
256 self._connect_handlers = connect_handlers
257 self._get_handlers = get_handlers
258 self._post_handlers = post_handlers
259 self._put_handlers = put_handlers
260 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
261 self, request, client_address, socket_server)
262
263 def log_request(self, *args, **kwargs):
264 # Disable request logging to declutter test log output.
265 pass
266
267 def _ShouldHandleRequest(self, handler_name):
268 """Determines if the path can be handled by the handler.
269
270 We consider a handler valid if the path begins with the
271 handler name. It can optionally be followed by "?*", "/*".
272 """
273
274 pattern = re.compile('%s($|\?|/).*' % handler_name)
275 return pattern.match(self.path)
276
277 def do_CONNECT(self):
278 for handler in self._connect_handlers:
279 if handler():
280 return
281
282 def do_GET(self):
283 for handler in self._get_handlers:
284 if handler():
285 return
286
287 def do_POST(self):
288 for handler in self._post_handlers:
289 if handler():
290 return
291
292 def do_PUT(self):
293 for handler in self._put_handlers:
294 if handler():
295 return
296
297
298class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000299
300 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000301 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000302 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000303 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000304 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000305 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000306 self.NoCacheMaxAgeTimeHandler,
307 self.NoCacheTimeHandler,
308 self.CacheTimeHandler,
309 self.CacheExpiresHandler,
310 self.CacheProxyRevalidateHandler,
311 self.CachePrivateHandler,
312 self.CachePublicHandler,
313 self.CacheSMaxAgeHandler,
314 self.CacheMustRevalidateHandler,
315 self.CacheMustRevalidateMaxAgeHandler,
316 self.CacheNoStoreHandler,
317 self.CacheNoStoreMaxAgeHandler,
318 self.CacheNoTransformHandler,
319 self.DownloadHandler,
320 self.DownloadFinishHandler,
321 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000322 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000323 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000324 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000325 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000326 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000327 self.AuthBasicHandler,
328 self.AuthDigestHandler,
329 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000330 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000331 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000332 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000333 self.ServerRedirectHandler,
334 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000335 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000336 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000337 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000338 self.EchoTitleHandler,
339 self.EchoAllHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000340 self.EchoHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000341 self.DeviceManagementHandler] + get_handlers
342 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000343 self.EchoTitleHandler,
344 self.EchoAllHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000345 self.EchoHandler] + get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000346
maruel@google.come250a9b2009-03-10 17:39:46 +0000347 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000348 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000349 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000350 'gif': 'image/gif',
351 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000352 'jpg' : 'image/jpeg',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000353 'pdf' : 'application/pdf',
354 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000355 }
initial.commit94958cf2008-07-26 22:42:52 +0000356 self._default_mime_type = 'text/html'
357
akalin@chromium.org154bb132010-11-12 02:20:27 +0000358 BasePageHandler.__init__(self, request, client_address, socket_server,
359 connect_handlers, get_handlers, post_handlers,
360 put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000361
initial.commit94958cf2008-07-26 22:42:52 +0000362 def GetMIMETypeFromName(self, file_name):
363 """Returns the mime type for the specified file_name. So far it only looks
364 at the file extension."""
365
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000366 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000367 if len(extension) == 0:
368 # no extension.
369 return self._default_mime_type
370
ericroman@google.comc17ca532009-05-07 03:51:05 +0000371 # extension starts with a dot, so we need to remove it
372 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000373
initial.commit94958cf2008-07-26 22:42:52 +0000374 def NoCacheMaxAgeTimeHandler(self):
375 """This request handler yields a page with the title set to the current
376 system time, and no caching requested."""
377
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000378 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000379 return False
380
381 self.send_response(200)
382 self.send_header('Cache-Control', 'max-age=0')
383 self.send_header('Content-type', 'text/html')
384 self.end_headers()
385
maruel@google.come250a9b2009-03-10 17:39:46 +0000386 self.wfile.write('<html><head><title>%s</title></head></html>' %
387 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000388
389 return True
390
391 def NoCacheTimeHandler(self):
392 """This request handler yields a page with the title set to the current
393 system time, and no caching requested."""
394
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000395 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000396 return False
397
398 self.send_response(200)
399 self.send_header('Cache-Control', 'no-cache')
400 self.send_header('Content-type', 'text/html')
401 self.end_headers()
402
maruel@google.come250a9b2009-03-10 17:39:46 +0000403 self.wfile.write('<html><head><title>%s</title></head></html>' %
404 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000405
406 return True
407
408 def CacheTimeHandler(self):
409 """This request handler yields a page with the title set to the current
410 system time, and allows caching for one minute."""
411
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000412 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000413 return False
414
415 self.send_response(200)
416 self.send_header('Cache-Control', 'max-age=60')
417 self.send_header('Content-type', 'text/html')
418 self.end_headers()
419
maruel@google.come250a9b2009-03-10 17:39:46 +0000420 self.wfile.write('<html><head><title>%s</title></head></html>' %
421 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000422
423 return True
424
425 def CacheExpiresHandler(self):
426 """This request handler yields a page with the title set to the current
427 system time, and set the page to expire on 1 Jan 2099."""
428
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000429 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000430 return False
431
432 self.send_response(200)
433 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
434 self.send_header('Content-type', 'text/html')
435 self.end_headers()
436
maruel@google.come250a9b2009-03-10 17:39:46 +0000437 self.wfile.write('<html><head><title>%s</title></head></html>' %
438 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000439
440 return True
441
442 def CacheProxyRevalidateHandler(self):
443 """This request handler yields a page with the title set to the current
444 system time, and allows caching for 60 seconds"""
445
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000446 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000447 return False
448
449 self.send_response(200)
450 self.send_header('Content-type', 'text/html')
451 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
452 self.end_headers()
453
maruel@google.come250a9b2009-03-10 17:39:46 +0000454 self.wfile.write('<html><head><title>%s</title></head></html>' %
455 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000456
457 return True
458
459 def CachePrivateHandler(self):
460 """This request handler yields a page with the title set to the current
461 system time, and allows caching for 5 seconds."""
462
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000463 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000464 return False
465
466 self.send_response(200)
467 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000468 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000469 self.end_headers()
470
maruel@google.come250a9b2009-03-10 17:39:46 +0000471 self.wfile.write('<html><head><title>%s</title></head></html>' %
472 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000473
474 return True
475
476 def CachePublicHandler(self):
477 """This request handler yields a page with the title set to the current
478 system time, and allows caching for 5 seconds."""
479
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000480 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000481 return False
482
483 self.send_response(200)
484 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000485 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000486 self.end_headers()
487
maruel@google.come250a9b2009-03-10 17:39:46 +0000488 self.wfile.write('<html><head><title>%s</title></head></html>' %
489 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000490
491 return True
492
493 def CacheSMaxAgeHandler(self):
494 """This request handler yields a page with the title set to the current
495 system time, and does not allow for caching."""
496
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000497 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000498 return False
499
500 self.send_response(200)
501 self.send_header('Content-type', 'text/html')
502 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
503 self.end_headers()
504
maruel@google.come250a9b2009-03-10 17:39:46 +0000505 self.wfile.write('<html><head><title>%s</title></head></html>' %
506 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000507
508 return True
509
510 def CacheMustRevalidateHandler(self):
511 """This request handler yields a page with the title set to the current
512 system time, and does not allow caching."""
513
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000514 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000515 return False
516
517 self.send_response(200)
518 self.send_header('Content-type', 'text/html')
519 self.send_header('Cache-Control', 'must-revalidate')
520 self.end_headers()
521
maruel@google.come250a9b2009-03-10 17:39:46 +0000522 self.wfile.write('<html><head><title>%s</title></head></html>' %
523 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000524
525 return True
526
527 def CacheMustRevalidateMaxAgeHandler(self):
528 """This request handler yields a page with the title set to the current
529 system time, and does not allow caching event though max-age of 60
530 seconds is specified."""
531
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000532 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000533 return False
534
535 self.send_response(200)
536 self.send_header('Content-type', 'text/html')
537 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
538 self.end_headers()
539
maruel@google.come250a9b2009-03-10 17:39:46 +0000540 self.wfile.write('<html><head><title>%s</title></head></html>' %
541 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000542
543 return True
544
initial.commit94958cf2008-07-26 22:42:52 +0000545 def CacheNoStoreHandler(self):
546 """This request handler yields a page with the title set to the current
547 system time, and does not allow the page to be stored."""
548
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000549 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000550 return False
551
552 self.send_response(200)
553 self.send_header('Content-type', 'text/html')
554 self.send_header('Cache-Control', 'no-store')
555 self.end_headers()
556
maruel@google.come250a9b2009-03-10 17:39:46 +0000557 self.wfile.write('<html><head><title>%s</title></head></html>' %
558 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000559
560 return True
561
562 def CacheNoStoreMaxAgeHandler(self):
563 """This request handler yields a page with the title set to the current
564 system time, and does not allow the page to be stored even though max-age
565 of 60 seconds is specified."""
566
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000567 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000568 return False
569
570 self.send_response(200)
571 self.send_header('Content-type', 'text/html')
572 self.send_header('Cache-Control', 'max-age=60, no-store')
573 self.end_headers()
574
maruel@google.come250a9b2009-03-10 17:39:46 +0000575 self.wfile.write('<html><head><title>%s</title></head></html>' %
576 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000577
578 return True
579
580
581 def CacheNoTransformHandler(self):
582 """This request handler yields a page with the title set to the current
583 system time, and does not allow the content to transformed during
584 user-agent caching"""
585
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000586 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000587 return False
588
589 self.send_response(200)
590 self.send_header('Content-type', 'text/html')
591 self.send_header('Cache-Control', 'no-transform')
592 self.end_headers()
593
maruel@google.come250a9b2009-03-10 17:39:46 +0000594 self.wfile.write('<html><head><title>%s</title></head></html>' %
595 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000596
597 return True
598
599 def EchoHeader(self):
600 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000601 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000602
ananta@chromium.org56812d02011-04-07 17:52:05 +0000603 """This function echoes back the value of a specific request header"""
604 """while allowing caching for 16 hours."""
605 def EchoHeaderCache(self):
606 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000607
608 def EchoHeaderHelper(self, echo_header):
609 """This function echoes back the value of the request header passed in."""
610 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000611 return False
612
613 query_char = self.path.find('?')
614 if query_char != -1:
615 header_name = self.path[query_char+1:]
616
617 self.send_response(200)
618 self.send_header('Content-type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000619 if echo_header == '/echoheadercache':
620 self.send_header('Cache-control', 'max-age=60000')
621 else:
622 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000623 # insert a vary header to properly indicate that the cachability of this
624 # request is subject to value of the request header being echoed.
625 if len(header_name) > 0:
626 self.send_header('Vary', header_name)
627 self.end_headers()
628
629 if len(header_name) > 0:
630 self.wfile.write(self.headers.getheader(header_name))
631
632 return True
633
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000634 def ReadRequestBody(self):
635 """This function reads the body of the current HTTP request, handling
636 both plain and chunked transfer encoded requests."""
637
638 if self.headers.getheader('transfer-encoding') != 'chunked':
639 length = int(self.headers.getheader('content-length'))
640 return self.rfile.read(length)
641
642 # Read the request body as chunks.
643 body = ""
644 while True:
645 line = self.rfile.readline()
646 length = int(line, 16)
647 if length == 0:
648 self.rfile.readline()
649 break
650 body += self.rfile.read(length)
651 self.rfile.read(2)
652 return body
653
initial.commit94958cf2008-07-26 22:42:52 +0000654 def EchoHandler(self):
655 """This handler just echoes back the payload of the request, for testing
656 form submission."""
657
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000658 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000659 return False
660
661 self.send_response(200)
662 self.send_header('Content-type', 'text/html')
663 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000664 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000665 return True
666
667 def EchoTitleHandler(self):
668 """This handler is like Echo, but sets the page title to the request."""
669
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000670 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000671 return False
672
673 self.send_response(200)
674 self.send_header('Content-type', 'text/html')
675 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000676 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000677 self.wfile.write('<html><head><title>')
678 self.wfile.write(request)
679 self.wfile.write('</title></head></html>')
680 return True
681
682 def EchoAllHandler(self):
683 """This handler yields a (more) human-readable page listing information
684 about the request header & contents."""
685
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000686 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000687 return False
688
689 self.send_response(200)
690 self.send_header('Content-type', 'text/html')
691 self.end_headers()
692 self.wfile.write('<html><head><style>'
693 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
694 '</style></head><body>'
695 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000696 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000697 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000698
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000699 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000700 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000701 params = cgi.parse_qs(qs, keep_blank_values=1)
702
703 for param in params:
704 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000705
706 self.wfile.write('</pre>')
707
708 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
709
710 self.wfile.write('</body></html>')
711 return True
712
713 def DownloadHandler(self):
714 """This handler sends a downloadable file with or without reporting
715 the size (6K)."""
716
717 if self.path.startswith("/download-unknown-size"):
718 send_length = False
719 elif self.path.startswith("/download-known-size"):
720 send_length = True
721 else:
722 return False
723
724 #
725 # The test which uses this functionality is attempting to send
726 # small chunks of data to the client. Use a fairly large buffer
727 # so that we'll fill chrome's IO buffer enough to force it to
728 # actually write the data.
729 # See also the comments in the client-side of this test in
730 # download_uitest.cc
731 #
732 size_chunk1 = 35*1024
733 size_chunk2 = 10*1024
734
735 self.send_response(200)
736 self.send_header('Content-type', 'application/octet-stream')
737 self.send_header('Cache-Control', 'max-age=0')
738 if send_length:
739 self.send_header('Content-Length', size_chunk1 + size_chunk2)
740 self.end_headers()
741
742 # First chunk of data:
743 self.wfile.write("*" * size_chunk1)
744 self.wfile.flush()
745
746 # handle requests until one of them clears this flag.
747 self.server.waitForDownload = True
748 while self.server.waitForDownload:
749 self.server.handle_request()
750
751 # Second chunk of data:
752 self.wfile.write("*" * size_chunk2)
753 return True
754
755 def DownloadFinishHandler(self):
756 """This handler just tells the server to finish the current download."""
757
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000758 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000759 return False
760
761 self.server.waitForDownload = False
762 self.send_response(200)
763 self.send_header('Content-type', 'text/html')
764 self.send_header('Cache-Control', 'max-age=0')
765 self.end_headers()
766 return True
767
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000768 def _ReplaceFileData(self, data, query_parameters):
769 """Replaces matching substrings in a file.
770
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000771 If the 'replace_text' URL query parameter is present, it is expected to be
772 of the form old_text:new_text, which indicates that any old_text strings in
773 the file are replaced with new_text. Multiple 'replace_text' parameters may
774 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000775
776 If the parameters are not present, |data| is returned.
777 """
778 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000779 replace_text_values = query_dict.get('replace_text', [])
780 for replace_text_value in replace_text_values:
781 replace_text_args = replace_text_value.split(':')
782 if len(replace_text_args) != 2:
783 raise ValueError(
784 'replace_text must be of form old_text:new_text. Actual value: %s' %
785 replace_text_value)
786 old_text_b64, new_text_b64 = replace_text_args
787 old_text = base64.urlsafe_b64decode(old_text_b64)
788 new_text = base64.urlsafe_b64decode(new_text_b64)
789 data = data.replace(old_text, new_text)
790 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000791
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000792 def ZipFileHandler(self):
793 """This handler sends the contents of the requested file in compressed form.
794 Can pass in a parameter that specifies that the content length be
795 C - the compressed size (OK),
796 U - the uncompressed size (Non-standard, but handled),
797 S - less than compressed (OK because we keep going),
798 M - larger than compressed but less than uncompressed (an error),
799 L - larger than uncompressed (an error)
800 Example: compressedfiles/Picture_1.doc?C
801 """
802
803 prefix = "/compressedfiles/"
804 if not self.path.startswith(prefix):
805 return False
806
807 # Consume a request body if present.
808 if self.command == 'POST' or self.command == 'PUT' :
809 self.ReadRequestBody()
810
811 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
812
813 if not query in ('C', 'U', 'S', 'M', 'L'):
814 return False
815
816 sub_path = url_path[len(prefix):]
817 entries = sub_path.split('/')
818 file_path = os.path.join(self.server.data_dir, *entries)
819 if os.path.isdir(file_path):
820 file_path = os.path.join(file_path, 'index.html')
821
822 if not os.path.isfile(file_path):
823 print "File not found " + sub_path + " full path:" + file_path
824 self.send_error(404)
825 return True
826
827 f = open(file_path, "rb")
828 data = f.read()
829 uncompressed_len = len(data)
830 f.close()
831
832 # Compress the data.
833 data = zlib.compress(data)
834 compressed_len = len(data)
835
836 content_length = compressed_len
837 if query == 'U':
838 content_length = uncompressed_len
839 elif query == 'S':
840 content_length = compressed_len / 2
841 elif query == 'M':
842 content_length = (compressed_len + uncompressed_len) / 2
843 elif query == 'L':
844 content_length = compressed_len + uncompressed_len
845
846 self.send_response(200)
847 self.send_header('Content-type', 'application/msword')
848 self.send_header('Content-encoding', 'deflate')
849 self.send_header('Connection', 'close')
850 self.send_header('Content-Length', content_length)
851 self.send_header('ETag', '\'' + file_path + '\'')
852 self.end_headers()
853
854 self.wfile.write(data)
855
856 return True
857
initial.commit94958cf2008-07-26 22:42:52 +0000858 def FileHandler(self):
859 """This handler sends the contents of the requested file. Wow, it's like
860 a real webserver!"""
861
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000862 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000863 if not self.path.startswith(prefix):
864 return False
865
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000866 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000867 if self.command == 'POST' or self.command == 'PUT' :
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000868 self.ReadRequestBody()
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000869
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000870 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
871 sub_path = url_path[len(prefix):]
872 entries = sub_path.split('/')
873 file_path = os.path.join(self.server.data_dir, *entries)
874 if os.path.isdir(file_path):
875 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000876
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000877 if not os.path.isfile(file_path):
878 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000879 self.send_error(404)
880 return True
881
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000882 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000883 data = f.read()
884 f.close()
885
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000886 data = self._ReplaceFileData(data, query)
887
initial.commit94958cf2008-07-26 22:42:52 +0000888 # If file.mock-http-headers exists, it contains the headers we
889 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000890 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000891 if os.path.isfile(headers_path):
892 f = open(headers_path, "r")
893
894 # "HTTP/1.1 200 OK"
895 response = f.readline()
896 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
897 self.send_response(int(status_code))
898
899 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000900 header_values = re.findall('(\S+):\s*(.*)', line)
901 if len(header_values) > 0:
902 # "name: value"
903 name, value = header_values[0]
904 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000905 f.close()
906 else:
907 # Could be more generic once we support mime-type sniffing, but for
908 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000909
910 range = self.headers.get('Range')
911 if range and range.startswith('bytes='):
912 # Note this doesn't handle all valid byte range values (i.e. open ended
913 # ones), just enough for what we needed so far.
914 range = range[6:].split('-')
915 start = int(range[0])
916 end = int(range[1])
917
918 self.send_response(206)
919 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
920 str(len(data))
921 self.send_header('Content-Range', content_range)
922 data = data[start: end + 1]
923 else:
924 self.send_response(200)
925
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000926 self.send_header('Content-type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000927 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000928 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000929 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000930 self.end_headers()
931
932 self.wfile.write(data)
933
934 return True
935
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000936 def SetCookieHandler(self):
937 """This handler just sets a cookie, for testing cookie handling."""
938
939 if not self._ShouldHandleRequest("/set-cookie"):
940 return False
941
942 query_char = self.path.find('?')
943 if query_char != -1:
944 cookie_values = self.path[query_char + 1:].split('&')
945 else:
946 cookie_values = ("",)
947 self.send_response(200)
948 self.send_header('Content-type', 'text/html')
949 for cookie_value in cookie_values:
950 self.send_header('Set-Cookie', '%s' % cookie_value)
951 self.end_headers()
952 for cookie_value in cookie_values:
953 self.wfile.write('%s' % cookie_value)
954 return True
955
initial.commit94958cf2008-07-26 22:42:52 +0000956 def AuthBasicHandler(self):
957 """This handler tests 'Basic' authentication. It just sends a page with
958 title 'user/pass' if you succeed."""
959
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000960 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000961 return False
962
963 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000964 expected_password = 'secret'
965 realm = 'testrealm'
966 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +0000967
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000968 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
969 query_params = cgi.parse_qs(query, True)
970 if 'set-cookie-if-challenged' in query_params:
971 set_cookie_if_challenged = True
972 if 'password' in query_params:
973 expected_password = query_params['password'][0]
974 if 'realm' in query_params:
975 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +0000976
initial.commit94958cf2008-07-26 22:42:52 +0000977 auth = self.headers.getheader('authorization')
978 try:
979 if not auth:
980 raise Exception('no auth')
981 b64str = re.findall(r'Basic (\S+)', auth)[0]
982 userpass = base64.b64decode(b64str)
983 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000984 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +0000985 raise Exception('wrong password')
986 except Exception, e:
987 # Authentication failed.
988 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000989 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
initial.commit94958cf2008-07-26 22:42:52 +0000990 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000991 if set_cookie_if_challenged:
992 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000993 self.end_headers()
994 self.wfile.write('<html><head>')
995 self.wfile.write('<title>Denied: %s</title>' % e)
996 self.wfile.write('</head><body>')
997 self.wfile.write('auth=%s<p>' % auth)
998 self.wfile.write('b64str=%s<p>' % b64str)
999 self.wfile.write('username: %s<p>' % username)
1000 self.wfile.write('userpass: %s<p>' % userpass)
1001 self.wfile.write('password: %s<p>' % password)
1002 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1003 self.wfile.write('</body></html>')
1004 return True
1005
1006 # Authentication successful. (Return a cachable response to allow for
1007 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001008 old_protocol_version = self.protocol_version
1009 self.protocol_version = "HTTP/1.1"
1010
initial.commit94958cf2008-07-26 22:42:52 +00001011 if_none_match = self.headers.getheader('if-none-match')
1012 if if_none_match == "abc":
1013 self.send_response(304)
1014 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001015 elif url_path.endswith(".gif"):
1016 # Using chrome/test/data/google/logo.gif as the test image
1017 test_image_path = ['google', 'logo.gif']
1018 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1019 if not os.path.isfile(gif_path):
1020 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001021 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001022 return True
1023
1024 f = open(gif_path, "rb")
1025 data = f.read()
1026 f.close()
1027
1028 self.send_response(200)
1029 self.send_header('Content-type', 'image/gif')
1030 self.send_header('Cache-control', 'max-age=60000')
1031 self.send_header('Etag', 'abc')
1032 self.end_headers()
1033 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001034 else:
1035 self.send_response(200)
1036 self.send_header('Content-type', 'text/html')
1037 self.send_header('Cache-control', 'max-age=60000')
1038 self.send_header('Etag', 'abc')
1039 self.end_headers()
1040 self.wfile.write('<html><head>')
1041 self.wfile.write('<title>%s/%s</title>' % (username, password))
1042 self.wfile.write('</head><body>')
1043 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001044 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001045 self.wfile.write('</body></html>')
1046
rvargas@google.com54453b72011-05-19 01:11:11 +00001047 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001048 return True
1049
tonyg@chromium.org75054202010-03-31 22:06:10 +00001050 def GetNonce(self, force_reset=False):
1051 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001052
tonyg@chromium.org75054202010-03-31 22:06:10 +00001053 This is a fake implementation. A real implementation would only use a given
1054 nonce a single time (hence the name n-once). However, for the purposes of
1055 unittesting, we don't care about the security of the nonce.
1056
1057 Args:
1058 force_reset: Iff set, the nonce will be changed. Useful for testing the
1059 "stale" response.
1060 """
1061 if force_reset or not self.server.nonce_time:
1062 self.server.nonce_time = time.time()
1063 return _new_md5('privatekey%s%d' %
1064 (self.path, self.server.nonce_time)).hexdigest()
1065
1066 def AuthDigestHandler(self):
1067 """This handler tests 'Digest' authentication.
1068
1069 It just sends a page with title 'user/pass' if you succeed.
1070
1071 A stale response is sent iff "stale" is present in the request path.
1072 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001073 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001074 return False
1075
tonyg@chromium.org75054202010-03-31 22:06:10 +00001076 stale = 'stale' in self.path
1077 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001078 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001079 password = 'secret'
1080 realm = 'testrealm'
1081
1082 auth = self.headers.getheader('authorization')
1083 pairs = {}
1084 try:
1085 if not auth:
1086 raise Exception('no auth')
1087 if not auth.startswith('Digest'):
1088 raise Exception('not digest')
1089 # Pull out all the name="value" pairs as a dictionary.
1090 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1091
1092 # Make sure it's all valid.
1093 if pairs['nonce'] != nonce:
1094 raise Exception('wrong nonce')
1095 if pairs['opaque'] != opaque:
1096 raise Exception('wrong opaque')
1097
1098 # Check the 'response' value and make sure it matches our magic hash.
1099 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001100 hash_a1 = _new_md5(
1101 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001102 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001103 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001104 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001105 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1106 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001107 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001108
1109 if pairs['response'] != response:
1110 raise Exception('wrong password')
1111 except Exception, e:
1112 # Authentication failed.
1113 self.send_response(401)
1114 hdr = ('Digest '
1115 'realm="%s", '
1116 'domain="/", '
1117 'qop="auth", '
1118 'algorithm=MD5, '
1119 'nonce="%s", '
1120 'opaque="%s"') % (realm, nonce, opaque)
1121 if stale:
1122 hdr += ', stale="TRUE"'
1123 self.send_header('WWW-Authenticate', hdr)
1124 self.send_header('Content-type', 'text/html')
1125 self.end_headers()
1126 self.wfile.write('<html><head>')
1127 self.wfile.write('<title>Denied: %s</title>' % e)
1128 self.wfile.write('</head><body>')
1129 self.wfile.write('auth=%s<p>' % auth)
1130 self.wfile.write('pairs=%s<p>' % pairs)
1131 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1132 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1133 self.wfile.write('</body></html>')
1134 return True
1135
1136 # Authentication successful.
1137 self.send_response(200)
1138 self.send_header('Content-type', 'text/html')
1139 self.end_headers()
1140 self.wfile.write('<html><head>')
1141 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1142 self.wfile.write('</head><body>')
1143 self.wfile.write('auth=%s<p>' % auth)
1144 self.wfile.write('pairs=%s<p>' % pairs)
1145 self.wfile.write('</body></html>')
1146
1147 return True
1148
1149 def SlowServerHandler(self):
1150 """Wait for the user suggested time before responding. The syntax is
1151 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001152 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001153 return False
1154 query_char = self.path.find('?')
1155 wait_sec = 1.0
1156 if query_char >= 0:
1157 try:
1158 wait_sec = int(self.path[query_char + 1:])
1159 except ValueError:
1160 pass
1161 time.sleep(wait_sec)
1162 self.send_response(200)
1163 self.send_header('Content-type', 'text/plain')
1164 self.end_headers()
1165 self.wfile.write("waited %d seconds" % wait_sec)
1166 return True
1167
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001168 def ChunkedServerHandler(self):
1169 """Send chunked response. Allows to specify chunks parameters:
1170 - waitBeforeHeaders - ms to wait before sending headers
1171 - waitBetweenChunks - ms to wait between chunks
1172 - chunkSize - size of each chunk in bytes
1173 - chunksNumber - number of chunks
1174 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1175 waits one second, then sends headers and five chunks five bytes each."""
1176 if not self._ShouldHandleRequest("/chunked"):
1177 return False
1178 query_char = self.path.find('?')
1179 chunkedSettings = {'waitBeforeHeaders' : 0,
1180 'waitBetweenChunks' : 0,
1181 'chunkSize' : 5,
1182 'chunksNumber' : 5}
1183 if query_char >= 0:
1184 params = self.path[query_char + 1:].split('&')
1185 for param in params:
1186 keyValue = param.split('=')
1187 if len(keyValue) == 2:
1188 try:
1189 chunkedSettings[keyValue[0]] = int(keyValue[1])
1190 except ValueError:
1191 pass
1192 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1193 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1194 self.send_response(200)
1195 self.send_header('Content-type', 'text/plain')
1196 self.send_header('Connection', 'close')
1197 self.send_header('Transfer-Encoding', 'chunked')
1198 self.end_headers()
1199 # Chunked encoding: sending all chunks, then final zero-length chunk and
1200 # then final CRLF.
1201 for i in range(0, chunkedSettings['chunksNumber']):
1202 if i > 0:
1203 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1204 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1205 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1206 self.sendChunkHelp('')
1207 return True
1208
initial.commit94958cf2008-07-26 22:42:52 +00001209 def ContentTypeHandler(self):
1210 """Returns a string of html with the given content type. E.g.,
1211 /contenttype?text/css returns an html file with the Content-Type
1212 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001213 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001214 return False
1215 query_char = self.path.find('?')
1216 content_type = self.path[query_char + 1:].strip()
1217 if not content_type:
1218 content_type = 'text/html'
1219 self.send_response(200)
1220 self.send_header('Content-Type', content_type)
1221 self.end_headers()
1222 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1223 return True
1224
creis@google.com2f4f6a42011-03-25 19:44:19 +00001225 def NoContentHandler(self):
1226 """Returns a 204 No Content response."""
1227 if not self._ShouldHandleRequest("/nocontent"):
1228 return False
1229 self.send_response(204)
1230 self.end_headers()
1231 return True
1232
initial.commit94958cf2008-07-26 22:42:52 +00001233 def ServerRedirectHandler(self):
1234 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001235 '/server-redirect?http://foo.bar/asdf' to redirect to
1236 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001237
1238 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001239 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001240 return False
1241
1242 query_char = self.path.find('?')
1243 if query_char < 0 or len(self.path) <= query_char + 1:
1244 self.sendRedirectHelp(test_name)
1245 return True
1246 dest = self.path[query_char + 1:]
1247
1248 self.send_response(301) # moved permanently
1249 self.send_header('Location', dest)
1250 self.send_header('Content-type', 'text/html')
1251 self.end_headers()
1252 self.wfile.write('<html><head>')
1253 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1254
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001255 return True
initial.commit94958cf2008-07-26 22:42:52 +00001256
1257 def ClientRedirectHandler(self):
1258 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001259 '/client-redirect?http://foo.bar/asdf' to redirect to
1260 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001261
1262 test_name = "/client-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(200)
1273 self.send_header('Content-type', 'text/html')
1274 self.end_headers()
1275 self.wfile.write('<html><head>')
1276 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1277 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1278
1279 return True
1280
tony@chromium.org03266982010-03-05 03:18:42 +00001281 def MultipartHandler(self):
1282 """Send a multipart response (10 text/html pages)."""
1283 test_name = "/multipart"
1284 if not self._ShouldHandleRequest(test_name):
1285 return False
1286
1287 num_frames = 10
1288 bound = '12345'
1289 self.send_response(200)
1290 self.send_header('Content-type',
1291 'multipart/x-mixed-replace;boundary=' + bound)
1292 self.end_headers()
1293
1294 for i in xrange(num_frames):
1295 self.wfile.write('--' + bound + '\r\n')
1296 self.wfile.write('Content-type: text/html\r\n\r\n')
1297 self.wfile.write('<title>page ' + str(i) + '</title>')
1298 self.wfile.write('page ' + str(i))
1299
1300 self.wfile.write('--' + bound + '--')
1301 return True
1302
initial.commit94958cf2008-07-26 22:42:52 +00001303 def DefaultResponseHandler(self):
1304 """This is the catch-all response handler for requests that aren't handled
1305 by one of the special handlers above.
1306 Note that we specify the content-length as without it the https connection
1307 is not closed properly (and the browser keeps expecting data)."""
1308
1309 contents = "Default response given for path: " + self.path
1310 self.send_response(200)
1311 self.send_header('Content-type', 'text/html')
1312 self.send_header("Content-Length", len(contents))
1313 self.end_headers()
1314 self.wfile.write(contents)
1315 return True
1316
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001317 def RedirectConnectHandler(self):
1318 """Sends a redirect to the CONNECT request for www.redirect.com. This
1319 response is not specified by the RFC, so the browser should not follow
1320 the redirect."""
1321
1322 if (self.path.find("www.redirect.com") < 0):
1323 return False
1324
1325 dest = "http://www.destination.com/foo.js"
1326
1327 self.send_response(302) # moved temporarily
1328 self.send_header('Location', dest)
1329 self.send_header('Connection', 'close')
1330 self.end_headers()
1331 return True
1332
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001333 def ServerAuthConnectHandler(self):
1334 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1335 response doesn't make sense because the proxy server cannot request
1336 server authentication."""
1337
1338 if (self.path.find("www.server-auth.com") < 0):
1339 return False
1340
1341 challenge = 'Basic realm="WallyWorld"'
1342
1343 self.send_response(401) # unauthorized
1344 self.send_header('WWW-Authenticate', challenge)
1345 self.send_header('Connection', 'close')
1346 self.end_headers()
1347 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001348
1349 def DefaultConnectResponseHandler(self):
1350 """This is the catch-all response handler for CONNECT requests that aren't
1351 handled by one of the special handlers above. Real Web servers respond
1352 with 400 to CONNECT requests."""
1353
1354 contents = "Your client has issued a malformed or illegal request."
1355 self.send_response(400) # bad request
1356 self.send_header('Content-type', 'text/html')
1357 self.send_header("Content-Length", len(contents))
1358 self.end_headers()
1359 self.wfile.write(contents)
1360 return True
1361
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001362 def DeviceManagementHandler(self):
1363 """Delegates to the device management service used for cloud policy."""
1364 if not self._ShouldHandleRequest("/device_management"):
1365 return False
1366
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001367 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001368
1369 if not self.server._device_management_handler:
1370 import device_management
1371 policy_path = os.path.join(self.server.data_dir, 'device_management')
1372 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001373 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001374 self.server.policy_keys,
1375 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001376
1377 http_response, raw_reply = (
1378 self.server._device_management_handler.HandleRequest(self.path,
1379 self.headers,
1380 raw_request))
1381 self.send_response(http_response)
1382 self.end_headers()
1383 self.wfile.write(raw_reply)
1384 return True
1385
initial.commit94958cf2008-07-26 22:42:52 +00001386 # called by the redirect handling function when there is no parameter
1387 def sendRedirectHelp(self, redirect_name):
1388 self.send_response(200)
1389 self.send_header('Content-type', 'text/html')
1390 self.end_headers()
1391 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1392 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1393 self.wfile.write('</body></html>')
1394
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001395 # called by chunked handling function
1396 def sendChunkHelp(self, chunk):
1397 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1398 self.wfile.write('%X\r\n' % len(chunk))
1399 self.wfile.write(chunk)
1400 self.wfile.write('\r\n')
1401
akalin@chromium.org154bb132010-11-12 02:20:27 +00001402
1403class SyncPageHandler(BasePageHandler):
1404 """Handler for the main HTTP sync server."""
1405
1406 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001407 get_handlers = [self.ChromiumSyncMigrationOpHandler,
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001408 self.ChromiumSyncTimeHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001409 self.ChromiumSyncBirthdayErrorOpHandler,
1410 self.ChromiumSyncTransientErrorOpHandler]
1411
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001412 post_handlers = [self.ChromiumSyncCommandHandler,
1413 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001414 BasePageHandler.__init__(self, request, client_address,
1415 sync_http_server, [], get_handlers,
1416 post_handlers, [])
1417
1418 def ChromiumSyncTimeHandler(self):
1419 """Handle Chromium sync .../time requests.
1420
1421 The syncer sometimes checks server reachability by examining /time.
1422 """
1423 test_name = "/chromiumsync/time"
1424 if not self._ShouldHandleRequest(test_name):
1425 return False
1426
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001427 # Chrome hates it if we send a response before reading the request.
1428 if self.headers.getheader('content-length'):
1429 length = int(self.headers.getheader('content-length'))
1430 raw_request = self.rfile.read(length)
1431
akalin@chromium.org154bb132010-11-12 02:20:27 +00001432 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001433 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001434 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001435 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001436 return True
1437
1438 def ChromiumSyncCommandHandler(self):
1439 """Handle a chromiumsync command arriving via http.
1440
1441 This covers all sync protocol commands: authentication, getupdates, and
1442 commit.
1443 """
1444 test_name = "/chromiumsync/command"
1445 if not self._ShouldHandleRequest(test_name):
1446 return False
1447
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001448 length = int(self.headers.getheader('content-length'))
1449 raw_request = self.rfile.read(length)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001450
1451 http_response, raw_reply = self.server.HandleCommand(
1452 self.path, raw_request)
1453 self.send_response(http_response)
1454 self.end_headers()
1455 self.wfile.write(raw_reply)
1456 return True
1457
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001458 def ChromiumSyncMigrationOpHandler(self):
1459 """Handle a chromiumsync test-op command arriving via http.
1460 """
1461 test_name = "/chromiumsync/migrate"
1462 if not self._ShouldHandleRequest(test_name):
1463 return False
1464
1465 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1466 self.path)
1467 self.send_response(http_response)
1468 self.send_header('Content-Type', 'text/html')
1469 self.send_header('Content-Length', len(raw_reply))
1470 self.end_headers()
1471 self.wfile.write(raw_reply)
1472 return True
1473
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001474 def ChromiumSyncBirthdayErrorOpHandler(self):
1475 test_name = "/chromiumsync/birthdayerror"
1476 if not self._ShouldHandleRequest(test_name):
1477 return False
1478 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1479 self.send_response(result)
1480 self.send_header('Content-Type', 'text/html')
1481 self.send_header('Content-Length', len(raw_reply))
1482 self.end_headers()
1483 self.wfile.write(raw_reply)
1484 return True;
1485
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001486 def ChromiumSyncTransientErrorOpHandler(self):
1487 test_name = "/chromiumsync/transienterror"
1488 if not self._ShouldHandleRequest(test_name):
1489 return False
1490 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1491 self.send_response(result)
1492 self.send_header('Content-Type', 'text/html')
1493 self.send_header('Content-Length', len(raw_reply))
1494 self.end_headers()
1495 self.wfile.write(raw_reply)
1496 return True;
1497
akalin@chromium.org154bb132010-11-12 02:20:27 +00001498
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001499def MakeDataDir():
1500 if options.data_dir:
1501 if not os.path.isdir(options.data_dir):
1502 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1503 return None
1504 my_data_dir = options.data_dir
1505 else:
1506 # Create the default path to our data dir, relative to the exe dir.
1507 my_data_dir = os.path.dirname(sys.argv[0])
1508 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001509 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001510
1511 #TODO(ibrar): Must use Find* funtion defined in google\tools
1512 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1513
1514 return my_data_dir
1515
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001516
1517class TCPEchoHandler(SocketServer.BaseRequestHandler):
1518 """The RequestHandler class for TCP echo server.
1519
1520 It is instantiated once per connection to the server, and overrides the
1521 handle() method to implement communication to the client.
1522 """
1523
1524 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001525 """Handles the request from the client and constructs a response."""
1526
1527 data = self.request.recv(65536).strip()
1528 # Verify the "echo request" message received from the client. Send back
1529 # "echo response" message if "echo request" message is valid.
1530 try:
1531 return_data = echo_message.GetEchoResponseData(data)
1532 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001533 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001534 except ValueError:
1535 return
1536
1537 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001538
1539
1540class UDPEchoHandler(SocketServer.BaseRequestHandler):
1541 """The RequestHandler class for UDP echo server.
1542
1543 It is instantiated once per connection to the server, and overrides the
1544 handle() method to implement communication to the client.
1545 """
1546
1547 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001548 """Handles the request from the client and constructs a response."""
1549
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001550 data = self.request[0].strip()
1551 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001552 # Verify the "echo request" message received from the client. Send back
1553 # "echo response" message if "echo request" message is valid.
1554 try:
1555 return_data = echo_message.GetEchoResponseData(data)
1556 if not return_data:
1557 return
1558 except ValueError:
1559 return
1560 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001561
1562
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001563class FileMultiplexer:
1564 def __init__(self, fd1, fd2) :
1565 self.__fd1 = fd1
1566 self.__fd2 = fd2
1567
1568 def __del__(self) :
1569 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1570 self.__fd1.close()
1571 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1572 self.__fd2.close()
1573
1574 def write(self, text) :
1575 self.__fd1.write(text)
1576 self.__fd2.write(text)
1577
1578 def flush(self) :
1579 self.__fd1.flush()
1580 self.__fd2.flush()
1581
initial.commit94958cf2008-07-26 22:42:52 +00001582def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001583 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001584 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001585 if options.log_to_console:
1586 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1587 else:
1588 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001589
1590 port = options.port
1591
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001592 server_data = {}
1593
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001594 if options.server_type == SERVER_HTTP:
1595 if options.cert:
1596 # let's make sure the cert file exists.
1597 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001598 print 'specified server cert file not found: ' + options.cert + \
1599 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001600 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001601 for ca_cert in options.ssl_client_ca:
1602 if not os.path.isfile(ca_cert):
1603 print 'specified trusted client CA file not found: ' + ca_cert + \
1604 ' exiting...'
1605 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001606 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001607 options.ssl_client_auth, options.ssl_client_ca,
1608 options.ssl_bulk_cipher)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001609 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001610 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001611 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001612 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001613
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001614 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001615 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001616 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001617 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001618 server.policy_keys = options.policy_keys
1619 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001620 elif options.server_type == SERVER_SYNC:
1621 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1622 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001623 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1624 server_data['port'] = server.server_port
1625 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001626 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001627 # Used for generating the key (randomly) that encodes the "echo request"
1628 # message.
1629 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001630 server = TCPEchoServer(('127.0.0.1', port), TCPEchoHandler)
1631 print 'Echo TCP server started on port %d...' % server.server_port
1632 server_data['port'] = server.server_port
1633 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001634 # Used for generating the key (randomly) that encodes the "echo request"
1635 # message.
1636 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001637 server = UDPEchoServer(('127.0.0.1', port), UDPEchoHandler)
1638 print 'Echo UDP server started on port %d...' % server.server_port
1639 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001640 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001641 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001642 my_data_dir = MakeDataDir()
1643
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001644 # Instantiate a dummy authorizer for managing 'virtual' users
1645 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1646
1647 # Define a new user having full r/w permissions and a read-only
1648 # anonymous user
1649 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1650
1651 authorizer.add_anonymous(my_data_dir)
1652
1653 # Instantiate FTP handler class
1654 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1655 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001656
1657 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001658 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1659 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001660
1661 # Instantiate FTP server class and listen to 127.0.0.1:port
1662 address = ('127.0.0.1', port)
1663 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001664 server_data['port'] = server.socket.getsockname()[1]
1665 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001666
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001667 # Notify the parent that we've started. (BaseServer subclasses
1668 # bind their sockets on construction.)
1669 if options.startup_pipe is not None:
akalin@chromium.org73b7b5c2010-11-26 19:42:26 +00001670 server_data_json = simplejson.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001671 server_data_len = len(server_data_json)
1672 print 'sending server_data: %s (%d bytes)' % (
1673 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001674 if sys.platform == 'win32':
1675 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1676 else:
1677 fd = options.startup_pipe
1678 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001679 # First write the data length as an unsigned 4-byte value. This
1680 # is _not_ using network byte ordering since the other end of the
1681 # pipe is on the same machine.
1682 startup_pipe.write(struct.pack('=L', server_data_len))
1683 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001684 startup_pipe.close()
1685
initial.commit94958cf2008-07-26 22:42:52 +00001686 try:
1687 server.serve_forever()
1688 except KeyboardInterrupt:
1689 print 'shutting down server'
1690 server.stop = True
1691
1692if __name__ == '__main__':
1693 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001694 option_parser.add_option("-f", '--ftp', action='store_const',
1695 const=SERVER_FTP, default=SERVER_HTTP,
1696 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001697 help='start up an FTP server.')
1698 option_parser.add_option('', '--sync', action='store_const',
1699 const=SERVER_SYNC, default=SERVER_HTTP,
1700 dest='server_type',
1701 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001702 option_parser.add_option('', '--tcp-echo', action='store_const',
1703 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1704 dest='server_type',
1705 help='start up a tcp echo server.')
1706 option_parser.add_option('', '--udp-echo', action='store_const',
1707 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
1708 dest='server_type',
1709 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001710 option_parser.add_option('', '--log-to-console', action='store_const',
1711 const=True, default=False,
1712 dest='log_to_console',
1713 help='Enables or disables sys.stdout logging to '
1714 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001715 option_parser.add_option('', '--port', default='0', type='int',
1716 help='Port used by the server. If unspecified, the '
1717 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001718 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001719 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001720 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001721 help='Specify that https should be used, specify '
1722 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001723 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001724 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1725 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001726 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1727 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001728 'should include the CA named in the subject of '
1729 'the DER-encoded certificate contained in the '
1730 'specified file. This option may appear multiple '
1731 'times, indicating multiple CA names should be '
1732 'sent in the request.')
1733 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1734 help='Specify the bulk encryption algorithm(s)'
1735 'that will be accepted by the SSL server. Valid '
1736 'values are "aes256", "aes128", "3des", "rc4". If '
1737 'omitted, all algorithms will be used. This '
1738 'option may appear multiple times, indicating '
1739 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001740 option_parser.add_option('', '--file-root-url', default='/files/',
1741 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001742 option_parser.add_option('', '--startup-pipe', type='int',
1743 dest='startup_pipe',
1744 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001745 option_parser.add_option('', '--policy-key', action='append',
1746 dest='policy_keys',
1747 help='Specify a path to a PEM-encoded private key '
1748 'to use for policy signing. May be specified '
1749 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00001750 'the server. If ther server has multiple keys, it '
1751 'will rotate through them in at each request a '
1752 'round-robin fashion. The server will generate a '
1753 'random key if none is specified on the command '
1754 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001755 option_parser.add_option('', '--policy-user', default='user@example.com',
1756 dest='policy_user',
1757 help='Specify the user name the server should '
1758 'report back to the client as the user owning the '
1759 'token used for making the policy request.')
initial.commit94958cf2008-07-26 22:42:52 +00001760 options, args = option_parser.parse_args()
1761
1762 sys.exit(main(options, args))