blob: c8fab2bba065bfa5f1e2e52acb7421a83a6f9618 [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
6"""This is a simple HTTP server used for testing Chrome.
7
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
23import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000024import select
akalin@chromium.org18e34882010-11-26 07:10:41 +000025import simplejson
initial.commit94958cf2008-07-26 22:42:52 +000026import SocketServer
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import socket
initial.commit94958cf2008-07-26 22:42:52 +000028import sys
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000029import struct
initial.commit94958cf2008-07-26 22:42:52 +000030import time
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000031import urlparse
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000032import warnings
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000033import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000034
35# Ignore deprecation warnings, they make our output more cluttered.
36warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000037
38import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000039import tlslite
40import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000041
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000042try:
43 import hashlib
44 _new_md5 = hashlib.md5
45except ImportError:
46 import md5
47 _new_md5 = md5.new
48
davidben@chromium.org06fcf202010-09-22 18:15:23 +000049if sys.platform == 'win32':
50 import msvcrt
51
maruel@chromium.org756cf982009-03-05 12:46:38 +000052SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000053SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000054SERVER_SYNC = 2
initial.commit94958cf2008-07-26 22:42:52 +000055
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000056# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000057debug_output = sys.stderr
58def debug(str):
59 debug_output.write(str + "\n")
60 debug_output.flush()
61
62class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
63 """This is a specialization of of BaseHTTPServer to allow it
64 to be exited cleanly (by setting its "stop" member to True)."""
65
66 def serve_forever(self):
67 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000068 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000069 while not self.stop:
70 self.handle_request()
71 self.socket.close()
72
73class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
74 """This is a specialization of StoppableHTTPerver that add https support."""
75
davidben@chromium.org31282a12010-08-07 01:10:02 +000076 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000077 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers):
initial.commit94958cf2008-07-26 22:42:52 +000078 s = open(cert_path).read()
79 x509 = tlslite.api.X509()
80 x509.parse(s)
81 self.cert_chain = tlslite.api.X509CertChain([x509])
82 s = open(cert_path).read()
83 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000084 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000085 self.ssl_client_cas = []
86 for ca_file in ssl_client_cas:
87 s = open(ca_file).read()
88 x509 = tlslite.api.X509()
89 x509.parse(s)
90 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000091 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
92 if ssl_bulk_ciphers is not None:
93 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +000094
95 self.session_cache = tlslite.api.SessionCache()
96 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
97
98 def handshake(self, tlsConnection):
99 """Creates the SSL connection."""
100 try:
101 tlsConnection.handshakeServer(certChain=self.cert_chain,
102 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000103 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000104 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000105 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000106 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +0000107 tlsConnection.ignoreAbruptClose = True
108 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000109 except tlslite.api.TLSAbruptCloseError:
110 # Ignore abrupt close.
111 return True
initial.commit94958cf2008-07-26 22:42:52 +0000112 except tlslite.api.TLSError, error:
113 print "Handshake failure:", str(error)
114 return False
115
akalin@chromium.org154bb132010-11-12 02:20:27 +0000116
117class SyncHTTPServer(StoppableHTTPServer):
118 """An HTTP server that handles sync commands."""
119
120 def __init__(self, server_address, request_handler_class):
121 # We import here to avoid pulling in chromiumsync's dependencies
122 # unless strictly necessary.
123 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000124 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000125 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000126 self._sync_handler = chromiumsync.TestServer()
127 self._xmpp_socket_map = {}
128 self._xmpp_server = xmppserver.XmppServer(
129 self._xmpp_socket_map, ('localhost', 0))
130 self.xmpp_port = self._xmpp_server.getsockname()[1]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000131
132 def HandleCommand(self, query, raw_request):
133 return self._sync_handler.HandleCommand(query, raw_request)
134
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000135 def HandleRequestNoBlock(self):
136 """Handles a single request.
137
138 Copied from SocketServer._handle_request_noblock().
139 """
140 try:
141 request, client_address = self.get_request()
142 except socket.error:
143 return
144 if self.verify_request(request, client_address):
145 try:
146 self.process_request(request, client_address)
147 except:
148 self.handle_error(request, client_address)
149 self.close_request(request)
150
151 def serve_forever(self):
152 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
153 """
154
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000155 def HandleXmppSocket(fd, socket_map, handler):
156 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000157
158 Adapted from asyncore.read() et al.
159 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000160 xmpp_connection = socket_map.get(fd)
161 # This could happen if a previous handler call caused fd to get
162 # removed from socket_map.
163 if xmpp_connection is None:
164 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000165 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000166 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000167 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
168 raise
169 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000170 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000171
172 while True:
173 read_fds = [ self.fileno() ]
174 write_fds = []
175 exceptional_fds = []
176
177 for fd, xmpp_connection in self._xmpp_socket_map.items():
178 is_r = xmpp_connection.readable()
179 is_w = xmpp_connection.writable()
180 if is_r:
181 read_fds.append(fd)
182 if is_w:
183 write_fds.append(fd)
184 if is_r or is_w:
185 exceptional_fds.append(fd)
186
187 try:
188 read_fds, write_fds, exceptional_fds = (
189 select.select(read_fds, write_fds, exceptional_fds))
190 except select.error, err:
191 if err.args[0] != errno.EINTR:
192 raise
193 else:
194 continue
195
196 for fd in read_fds:
197 if fd == self.fileno():
198 self.HandleRequestNoBlock()
199 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000200 HandleXmppSocket(fd, self._xmpp_socket_map,
201 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000202
203 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000204 HandleXmppSocket(fd, self._xmpp_socket_map,
205 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000206
207 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000208 HandleXmppSocket(fd, self._xmpp_socket_map,
209 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000210
akalin@chromium.org154bb132010-11-12 02:20:27 +0000211
212class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
213
214 def __init__(self, request, client_address, socket_server,
215 connect_handlers, get_handlers, post_handlers, put_handlers):
216 self._connect_handlers = connect_handlers
217 self._get_handlers = get_handlers
218 self._post_handlers = post_handlers
219 self._put_handlers = put_handlers
220 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
221 self, request, client_address, socket_server)
222
223 def log_request(self, *args, **kwargs):
224 # Disable request logging to declutter test log output.
225 pass
226
227 def _ShouldHandleRequest(self, handler_name):
228 """Determines if the path can be handled by the handler.
229
230 We consider a handler valid if the path begins with the
231 handler name. It can optionally be followed by "?*", "/*".
232 """
233
234 pattern = re.compile('%s($|\?|/).*' % handler_name)
235 return pattern.match(self.path)
236
237 def do_CONNECT(self):
238 for handler in self._connect_handlers:
239 if handler():
240 return
241
242 def do_GET(self):
243 for handler in self._get_handlers:
244 if handler():
245 return
246
247 def do_POST(self):
248 for handler in self._post_handlers:
249 if handler():
250 return
251
252 def do_PUT(self):
253 for handler in self._put_handlers:
254 if handler():
255 return
256
257
258class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000259
260 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000261 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000262 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000263 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000264 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000265 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000266 self.NoCacheMaxAgeTimeHandler,
267 self.NoCacheTimeHandler,
268 self.CacheTimeHandler,
269 self.CacheExpiresHandler,
270 self.CacheProxyRevalidateHandler,
271 self.CachePrivateHandler,
272 self.CachePublicHandler,
273 self.CacheSMaxAgeHandler,
274 self.CacheMustRevalidateHandler,
275 self.CacheMustRevalidateMaxAgeHandler,
276 self.CacheNoStoreHandler,
277 self.CacheNoStoreMaxAgeHandler,
278 self.CacheNoTransformHandler,
279 self.DownloadHandler,
280 self.DownloadFinishHandler,
281 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000282 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000283 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000284 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000285 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000286 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000287 self.AuthBasicHandler,
288 self.AuthDigestHandler,
289 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000290 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000291 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000292 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000293 self.ServerRedirectHandler,
294 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000295 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000296 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000297 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000298 self.EchoTitleHandler,
299 self.EchoAllHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000300 self.EchoHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000301 self.DeviceManagementHandler] + get_handlers
302 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000303 self.EchoTitleHandler,
304 self.EchoAllHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000305 self.EchoHandler] + get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000306
maruel@google.come250a9b2009-03-10 17:39:46 +0000307 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000308 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000309 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000310 'gif': 'image/gif',
311 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000312 'jpg' : 'image/jpeg',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000313 'pdf' : 'application/pdf',
314 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000315 }
initial.commit94958cf2008-07-26 22:42:52 +0000316 self._default_mime_type = 'text/html'
317
akalin@chromium.org154bb132010-11-12 02:20:27 +0000318 BasePageHandler.__init__(self, request, client_address, socket_server,
319 connect_handlers, get_handlers, post_handlers,
320 put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000321
initial.commit94958cf2008-07-26 22:42:52 +0000322 def GetMIMETypeFromName(self, file_name):
323 """Returns the mime type for the specified file_name. So far it only looks
324 at the file extension."""
325
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000326 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000327 if len(extension) == 0:
328 # no extension.
329 return self._default_mime_type
330
ericroman@google.comc17ca532009-05-07 03:51:05 +0000331 # extension starts with a dot, so we need to remove it
332 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000333
initial.commit94958cf2008-07-26 22:42:52 +0000334 def NoCacheMaxAgeTimeHandler(self):
335 """This request handler yields a page with the title set to the current
336 system time, and no caching requested."""
337
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000338 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000339 return False
340
341 self.send_response(200)
342 self.send_header('Cache-Control', 'max-age=0')
343 self.send_header('Content-type', 'text/html')
344 self.end_headers()
345
maruel@google.come250a9b2009-03-10 17:39:46 +0000346 self.wfile.write('<html><head><title>%s</title></head></html>' %
347 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000348
349 return True
350
351 def NoCacheTimeHandler(self):
352 """This request handler yields a page with the title set to the current
353 system time, and no caching requested."""
354
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000355 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000356 return False
357
358 self.send_response(200)
359 self.send_header('Cache-Control', 'no-cache')
360 self.send_header('Content-type', 'text/html')
361 self.end_headers()
362
maruel@google.come250a9b2009-03-10 17:39:46 +0000363 self.wfile.write('<html><head><title>%s</title></head></html>' %
364 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000365
366 return True
367
368 def CacheTimeHandler(self):
369 """This request handler yields a page with the title set to the current
370 system time, and allows caching for one minute."""
371
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000372 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000373 return False
374
375 self.send_response(200)
376 self.send_header('Cache-Control', 'max-age=60')
377 self.send_header('Content-type', 'text/html')
378 self.end_headers()
379
maruel@google.come250a9b2009-03-10 17:39:46 +0000380 self.wfile.write('<html><head><title>%s</title></head></html>' %
381 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000382
383 return True
384
385 def CacheExpiresHandler(self):
386 """This request handler yields a page with the title set to the current
387 system time, and set the page to expire on 1 Jan 2099."""
388
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000389 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000390 return False
391
392 self.send_response(200)
393 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
394 self.send_header('Content-type', 'text/html')
395 self.end_headers()
396
maruel@google.come250a9b2009-03-10 17:39:46 +0000397 self.wfile.write('<html><head><title>%s</title></head></html>' %
398 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000399
400 return True
401
402 def CacheProxyRevalidateHandler(self):
403 """This request handler yields a page with the title set to the current
404 system time, and allows caching for 60 seconds"""
405
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000406 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000407 return False
408
409 self.send_response(200)
410 self.send_header('Content-type', 'text/html')
411 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
412 self.end_headers()
413
maruel@google.come250a9b2009-03-10 17:39:46 +0000414 self.wfile.write('<html><head><title>%s</title></head></html>' %
415 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000416
417 return True
418
419 def CachePrivateHandler(self):
420 """This request handler yields a page with the title set to the current
421 system time, and allows caching for 5 seconds."""
422
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000423 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000424 return False
425
426 self.send_response(200)
427 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000428 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000429 self.end_headers()
430
maruel@google.come250a9b2009-03-10 17:39:46 +0000431 self.wfile.write('<html><head><title>%s</title></head></html>' %
432 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000433
434 return True
435
436 def CachePublicHandler(self):
437 """This request handler yields a page with the title set to the current
438 system time, and allows caching for 5 seconds."""
439
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000440 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000441 return False
442
443 self.send_response(200)
444 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000445 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000446 self.end_headers()
447
maruel@google.come250a9b2009-03-10 17:39:46 +0000448 self.wfile.write('<html><head><title>%s</title></head></html>' %
449 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000450
451 return True
452
453 def CacheSMaxAgeHandler(self):
454 """This request handler yields a page with the title set to the current
455 system time, and does not allow for caching."""
456
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000457 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000458 return False
459
460 self.send_response(200)
461 self.send_header('Content-type', 'text/html')
462 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
463 self.end_headers()
464
maruel@google.come250a9b2009-03-10 17:39:46 +0000465 self.wfile.write('<html><head><title>%s</title></head></html>' %
466 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000467
468 return True
469
470 def CacheMustRevalidateHandler(self):
471 """This request handler yields a page with the title set to the current
472 system time, and does not allow caching."""
473
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000474 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000475 return False
476
477 self.send_response(200)
478 self.send_header('Content-type', 'text/html')
479 self.send_header('Cache-Control', 'must-revalidate')
480 self.end_headers()
481
maruel@google.come250a9b2009-03-10 17:39:46 +0000482 self.wfile.write('<html><head><title>%s</title></head></html>' %
483 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000484
485 return True
486
487 def CacheMustRevalidateMaxAgeHandler(self):
488 """This request handler yields a page with the title set to the current
489 system time, and does not allow caching event though max-age of 60
490 seconds is specified."""
491
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000492 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000493 return False
494
495 self.send_response(200)
496 self.send_header('Content-type', 'text/html')
497 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
498 self.end_headers()
499
maruel@google.come250a9b2009-03-10 17:39:46 +0000500 self.wfile.write('<html><head><title>%s</title></head></html>' %
501 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000502
503 return True
504
initial.commit94958cf2008-07-26 22:42:52 +0000505 def CacheNoStoreHandler(self):
506 """This request handler yields a page with the title set to the current
507 system time, and does not allow the page to be stored."""
508
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000509 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000510 return False
511
512 self.send_response(200)
513 self.send_header('Content-type', 'text/html')
514 self.send_header('Cache-Control', 'no-store')
515 self.end_headers()
516
maruel@google.come250a9b2009-03-10 17:39:46 +0000517 self.wfile.write('<html><head><title>%s</title></head></html>' %
518 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000519
520 return True
521
522 def CacheNoStoreMaxAgeHandler(self):
523 """This request handler yields a page with the title set to the current
524 system time, and does not allow the page to be stored even though max-age
525 of 60 seconds is specified."""
526
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000527 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000528 return False
529
530 self.send_response(200)
531 self.send_header('Content-type', 'text/html')
532 self.send_header('Cache-Control', 'max-age=60, no-store')
533 self.end_headers()
534
maruel@google.come250a9b2009-03-10 17:39:46 +0000535 self.wfile.write('<html><head><title>%s</title></head></html>' %
536 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000537
538 return True
539
540
541 def CacheNoTransformHandler(self):
542 """This request handler yields a page with the title set to the current
543 system time, and does not allow the content to transformed during
544 user-agent caching"""
545
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000546 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000547 return False
548
549 self.send_response(200)
550 self.send_header('Content-type', 'text/html')
551 self.send_header('Cache-Control', 'no-transform')
552 self.end_headers()
553
maruel@google.come250a9b2009-03-10 17:39:46 +0000554 self.wfile.write('<html><head><title>%s</title></head></html>' %
555 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000556
557 return True
558
559 def EchoHeader(self):
560 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000561 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000562
ananta@chromium.org56812d02011-04-07 17:52:05 +0000563 """This function echoes back the value of a specific request header"""
564 """while allowing caching for 16 hours."""
565 def EchoHeaderCache(self):
566 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000567
568 def EchoHeaderHelper(self, echo_header):
569 """This function echoes back the value of the request header passed in."""
570 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000571 return False
572
573 query_char = self.path.find('?')
574 if query_char != -1:
575 header_name = self.path[query_char+1:]
576
577 self.send_response(200)
578 self.send_header('Content-type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000579 if echo_header == '/echoheadercache':
580 self.send_header('Cache-control', 'max-age=60000')
581 else:
582 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000583 # insert a vary header to properly indicate that the cachability of this
584 # request is subject to value of the request header being echoed.
585 if len(header_name) > 0:
586 self.send_header('Vary', header_name)
587 self.end_headers()
588
589 if len(header_name) > 0:
590 self.wfile.write(self.headers.getheader(header_name))
591
592 return True
593
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000594 def ReadRequestBody(self):
595 """This function reads the body of the current HTTP request, handling
596 both plain and chunked transfer encoded requests."""
597
598 if self.headers.getheader('transfer-encoding') != 'chunked':
599 length = int(self.headers.getheader('content-length'))
600 return self.rfile.read(length)
601
602 # Read the request body as chunks.
603 body = ""
604 while True:
605 line = self.rfile.readline()
606 length = int(line, 16)
607 if length == 0:
608 self.rfile.readline()
609 break
610 body += self.rfile.read(length)
611 self.rfile.read(2)
612 return body
613
initial.commit94958cf2008-07-26 22:42:52 +0000614 def EchoHandler(self):
615 """This handler just echoes back the payload of the request, for testing
616 form submission."""
617
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000618 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000619 return False
620
621 self.send_response(200)
622 self.send_header('Content-type', 'text/html')
623 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000624 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000625 return True
626
627 def EchoTitleHandler(self):
628 """This handler is like Echo, but sets the page title to the request."""
629
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000630 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000631 return False
632
633 self.send_response(200)
634 self.send_header('Content-type', 'text/html')
635 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000636 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000637 self.wfile.write('<html><head><title>')
638 self.wfile.write(request)
639 self.wfile.write('</title></head></html>')
640 return True
641
642 def EchoAllHandler(self):
643 """This handler yields a (more) human-readable page listing information
644 about the request header & contents."""
645
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000646 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000647 return False
648
649 self.send_response(200)
650 self.send_header('Content-type', 'text/html')
651 self.end_headers()
652 self.wfile.write('<html><head><style>'
653 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
654 '</style></head><body>'
655 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000656 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000657 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000658
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000659 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000660 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000661 params = cgi.parse_qs(qs, keep_blank_values=1)
662
663 for param in params:
664 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000665
666 self.wfile.write('</pre>')
667
668 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
669
670 self.wfile.write('</body></html>')
671 return True
672
673 def DownloadHandler(self):
674 """This handler sends a downloadable file with or without reporting
675 the size (6K)."""
676
677 if self.path.startswith("/download-unknown-size"):
678 send_length = False
679 elif self.path.startswith("/download-known-size"):
680 send_length = True
681 else:
682 return False
683
684 #
685 # The test which uses this functionality is attempting to send
686 # small chunks of data to the client. Use a fairly large buffer
687 # so that we'll fill chrome's IO buffer enough to force it to
688 # actually write the data.
689 # See also the comments in the client-side of this test in
690 # download_uitest.cc
691 #
692 size_chunk1 = 35*1024
693 size_chunk2 = 10*1024
694
695 self.send_response(200)
696 self.send_header('Content-type', 'application/octet-stream')
697 self.send_header('Cache-Control', 'max-age=0')
698 if send_length:
699 self.send_header('Content-Length', size_chunk1 + size_chunk2)
700 self.end_headers()
701
702 # First chunk of data:
703 self.wfile.write("*" * size_chunk1)
704 self.wfile.flush()
705
706 # handle requests until one of them clears this flag.
707 self.server.waitForDownload = True
708 while self.server.waitForDownload:
709 self.server.handle_request()
710
711 # Second chunk of data:
712 self.wfile.write("*" * size_chunk2)
713 return True
714
715 def DownloadFinishHandler(self):
716 """This handler just tells the server to finish the current download."""
717
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000718 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000719 return False
720
721 self.server.waitForDownload = False
722 self.send_response(200)
723 self.send_header('Content-type', 'text/html')
724 self.send_header('Cache-Control', 'max-age=0')
725 self.end_headers()
726 return True
727
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000728 def _ReplaceFileData(self, data, query_parameters):
729 """Replaces matching substrings in a file.
730
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000731 If the 'replace_text' URL query parameter is present, it is expected to be
732 of the form old_text:new_text, which indicates that any old_text strings in
733 the file are replaced with new_text. Multiple 'replace_text' parameters may
734 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000735
736 If the parameters are not present, |data| is returned.
737 """
738 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000739 replace_text_values = query_dict.get('replace_text', [])
740 for replace_text_value in replace_text_values:
741 replace_text_args = replace_text_value.split(':')
742 if len(replace_text_args) != 2:
743 raise ValueError(
744 'replace_text must be of form old_text:new_text. Actual value: %s' %
745 replace_text_value)
746 old_text_b64, new_text_b64 = replace_text_args
747 old_text = base64.urlsafe_b64decode(old_text_b64)
748 new_text = base64.urlsafe_b64decode(new_text_b64)
749 data = data.replace(old_text, new_text)
750 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000751
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000752 def ZipFileHandler(self):
753 """This handler sends the contents of the requested file in compressed form.
754 Can pass in a parameter that specifies that the content length be
755 C - the compressed size (OK),
756 U - the uncompressed size (Non-standard, but handled),
757 S - less than compressed (OK because we keep going),
758 M - larger than compressed but less than uncompressed (an error),
759 L - larger than uncompressed (an error)
760 Example: compressedfiles/Picture_1.doc?C
761 """
762
763 prefix = "/compressedfiles/"
764 if not self.path.startswith(prefix):
765 return False
766
767 # Consume a request body if present.
768 if self.command == 'POST' or self.command == 'PUT' :
769 self.ReadRequestBody()
770
771 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
772
773 if not query in ('C', 'U', 'S', 'M', 'L'):
774 return False
775
776 sub_path = url_path[len(prefix):]
777 entries = sub_path.split('/')
778 file_path = os.path.join(self.server.data_dir, *entries)
779 if os.path.isdir(file_path):
780 file_path = os.path.join(file_path, 'index.html')
781
782 if not os.path.isfile(file_path):
783 print "File not found " + sub_path + " full path:" + file_path
784 self.send_error(404)
785 return True
786
787 f = open(file_path, "rb")
788 data = f.read()
789 uncompressed_len = len(data)
790 f.close()
791
792 # Compress the data.
793 data = zlib.compress(data)
794 compressed_len = len(data)
795
796 content_length = compressed_len
797 if query == 'U':
798 content_length = uncompressed_len
799 elif query == 'S':
800 content_length = compressed_len / 2
801 elif query == 'M':
802 content_length = (compressed_len + uncompressed_len) / 2
803 elif query == 'L':
804 content_length = compressed_len + uncompressed_len
805
806 self.send_response(200)
807 self.send_header('Content-type', 'application/msword')
808 self.send_header('Content-encoding', 'deflate')
809 self.send_header('Connection', 'close')
810 self.send_header('Content-Length', content_length)
811 self.send_header('ETag', '\'' + file_path + '\'')
812 self.end_headers()
813
814 self.wfile.write(data)
815
816 return True
817
initial.commit94958cf2008-07-26 22:42:52 +0000818 def FileHandler(self):
819 """This handler sends the contents of the requested file. Wow, it's like
820 a real webserver!"""
821
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000822 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000823 if not self.path.startswith(prefix):
824 return False
825
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000826 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000827 if self.command == 'POST' or self.command == 'PUT' :
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000828 self.ReadRequestBody()
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000829
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000830 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
831 sub_path = url_path[len(prefix):]
832 entries = sub_path.split('/')
833 file_path = os.path.join(self.server.data_dir, *entries)
834 if os.path.isdir(file_path):
835 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000836
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000837 if not os.path.isfile(file_path):
838 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000839 self.send_error(404)
840 return True
841
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000842 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000843 data = f.read()
844 f.close()
845
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000846 data = self._ReplaceFileData(data, query)
847
initial.commit94958cf2008-07-26 22:42:52 +0000848 # If file.mock-http-headers exists, it contains the headers we
849 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000850 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000851 if os.path.isfile(headers_path):
852 f = open(headers_path, "r")
853
854 # "HTTP/1.1 200 OK"
855 response = f.readline()
856 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
857 self.send_response(int(status_code))
858
859 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000860 header_values = re.findall('(\S+):\s*(.*)', line)
861 if len(header_values) > 0:
862 # "name: value"
863 name, value = header_values[0]
864 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000865 f.close()
866 else:
867 # Could be more generic once we support mime-type sniffing, but for
868 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000869
870 range = self.headers.get('Range')
871 if range and range.startswith('bytes='):
872 # Note this doesn't handle all valid byte range values (i.e. open ended
873 # ones), just enough for what we needed so far.
874 range = range[6:].split('-')
875 start = int(range[0])
876 end = int(range[1])
877
878 self.send_response(206)
879 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
880 str(len(data))
881 self.send_header('Content-Range', content_range)
882 data = data[start: end + 1]
883 else:
884 self.send_response(200)
885
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000886 self.send_header('Content-type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000887 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000888 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000889 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000890 self.end_headers()
891
892 self.wfile.write(data)
893
894 return True
895
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000896 def SetCookieHandler(self):
897 """This handler just sets a cookie, for testing cookie handling."""
898
899 if not self._ShouldHandleRequest("/set-cookie"):
900 return False
901
902 query_char = self.path.find('?')
903 if query_char != -1:
904 cookie_values = self.path[query_char + 1:].split('&')
905 else:
906 cookie_values = ("",)
907 self.send_response(200)
908 self.send_header('Content-type', 'text/html')
909 for cookie_value in cookie_values:
910 self.send_header('Set-Cookie', '%s' % cookie_value)
911 self.end_headers()
912 for cookie_value in cookie_values:
913 self.wfile.write('%s' % cookie_value)
914 return True
915
initial.commit94958cf2008-07-26 22:42:52 +0000916 def AuthBasicHandler(self):
917 """This handler tests 'Basic' authentication. It just sends a page with
918 title 'user/pass' if you succeed."""
919
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000920 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000921 return False
922
923 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000924 expected_password = 'secret'
925 realm = 'testrealm'
926 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +0000927
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000928 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
929 query_params = cgi.parse_qs(query, True)
930 if 'set-cookie-if-challenged' in query_params:
931 set_cookie_if_challenged = True
932 if 'password' in query_params:
933 expected_password = query_params['password'][0]
934 if 'realm' in query_params:
935 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +0000936
initial.commit94958cf2008-07-26 22:42:52 +0000937 auth = self.headers.getheader('authorization')
938 try:
939 if not auth:
940 raise Exception('no auth')
941 b64str = re.findall(r'Basic (\S+)', auth)[0]
942 userpass = base64.b64decode(b64str)
943 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000944 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +0000945 raise Exception('wrong password')
946 except Exception, e:
947 # Authentication failed.
948 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000949 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
initial.commit94958cf2008-07-26 22:42:52 +0000950 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000951 if set_cookie_if_challenged:
952 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000953 self.end_headers()
954 self.wfile.write('<html><head>')
955 self.wfile.write('<title>Denied: %s</title>' % e)
956 self.wfile.write('</head><body>')
957 self.wfile.write('auth=%s<p>' % auth)
958 self.wfile.write('b64str=%s<p>' % b64str)
959 self.wfile.write('username: %s<p>' % username)
960 self.wfile.write('userpass: %s<p>' % userpass)
961 self.wfile.write('password: %s<p>' % password)
962 self.wfile.write('You sent:<br>%s<p>' % self.headers)
963 self.wfile.write('</body></html>')
964 return True
965
966 # Authentication successful. (Return a cachable response to allow for
967 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +0000968 old_protocol_version = self.protocol_version
969 self.protocol_version = "HTTP/1.1"
970
initial.commit94958cf2008-07-26 22:42:52 +0000971 if_none_match = self.headers.getheader('if-none-match')
972 if if_none_match == "abc":
973 self.send_response(304)
974 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000975 elif url_path.endswith(".gif"):
976 # Using chrome/test/data/google/logo.gif as the test image
977 test_image_path = ['google', 'logo.gif']
978 gif_path = os.path.join(self.server.data_dir, *test_image_path)
979 if not os.path.isfile(gif_path):
980 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +0000981 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000982 return True
983
984 f = open(gif_path, "rb")
985 data = f.read()
986 f.close()
987
988 self.send_response(200)
989 self.send_header('Content-type', 'image/gif')
990 self.send_header('Cache-control', 'max-age=60000')
991 self.send_header('Etag', 'abc')
992 self.end_headers()
993 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000994 else:
995 self.send_response(200)
996 self.send_header('Content-type', 'text/html')
997 self.send_header('Cache-control', 'max-age=60000')
998 self.send_header('Etag', 'abc')
999 self.end_headers()
1000 self.wfile.write('<html><head>')
1001 self.wfile.write('<title>%s/%s</title>' % (username, password))
1002 self.wfile.write('</head><body>')
1003 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001004 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001005 self.wfile.write('</body></html>')
1006
rvargas@google.com54453b72011-05-19 01:11:11 +00001007 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001008 return True
1009
tonyg@chromium.org75054202010-03-31 22:06:10 +00001010 def GetNonce(self, force_reset=False):
1011 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001012
tonyg@chromium.org75054202010-03-31 22:06:10 +00001013 This is a fake implementation. A real implementation would only use a given
1014 nonce a single time (hence the name n-once). However, for the purposes of
1015 unittesting, we don't care about the security of the nonce.
1016
1017 Args:
1018 force_reset: Iff set, the nonce will be changed. Useful for testing the
1019 "stale" response.
1020 """
1021 if force_reset or not self.server.nonce_time:
1022 self.server.nonce_time = time.time()
1023 return _new_md5('privatekey%s%d' %
1024 (self.path, self.server.nonce_time)).hexdigest()
1025
1026 def AuthDigestHandler(self):
1027 """This handler tests 'Digest' authentication.
1028
1029 It just sends a page with title 'user/pass' if you succeed.
1030
1031 A stale response is sent iff "stale" is present in the request path.
1032 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001033 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001034 return False
1035
tonyg@chromium.org75054202010-03-31 22:06:10 +00001036 stale = 'stale' in self.path
1037 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001038 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001039 password = 'secret'
1040 realm = 'testrealm'
1041
1042 auth = self.headers.getheader('authorization')
1043 pairs = {}
1044 try:
1045 if not auth:
1046 raise Exception('no auth')
1047 if not auth.startswith('Digest'):
1048 raise Exception('not digest')
1049 # Pull out all the name="value" pairs as a dictionary.
1050 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1051
1052 # Make sure it's all valid.
1053 if pairs['nonce'] != nonce:
1054 raise Exception('wrong nonce')
1055 if pairs['opaque'] != opaque:
1056 raise Exception('wrong opaque')
1057
1058 # Check the 'response' value and make sure it matches our magic hash.
1059 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001060 hash_a1 = _new_md5(
1061 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001062 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001063 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001064 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001065 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1066 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001067 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001068
1069 if pairs['response'] != response:
1070 raise Exception('wrong password')
1071 except Exception, e:
1072 # Authentication failed.
1073 self.send_response(401)
1074 hdr = ('Digest '
1075 'realm="%s", '
1076 'domain="/", '
1077 'qop="auth", '
1078 'algorithm=MD5, '
1079 'nonce="%s", '
1080 'opaque="%s"') % (realm, nonce, opaque)
1081 if stale:
1082 hdr += ', stale="TRUE"'
1083 self.send_header('WWW-Authenticate', hdr)
1084 self.send_header('Content-type', 'text/html')
1085 self.end_headers()
1086 self.wfile.write('<html><head>')
1087 self.wfile.write('<title>Denied: %s</title>' % e)
1088 self.wfile.write('</head><body>')
1089 self.wfile.write('auth=%s<p>' % auth)
1090 self.wfile.write('pairs=%s<p>' % pairs)
1091 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1092 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1093 self.wfile.write('</body></html>')
1094 return True
1095
1096 # Authentication successful.
1097 self.send_response(200)
1098 self.send_header('Content-type', 'text/html')
1099 self.end_headers()
1100 self.wfile.write('<html><head>')
1101 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1102 self.wfile.write('</head><body>')
1103 self.wfile.write('auth=%s<p>' % auth)
1104 self.wfile.write('pairs=%s<p>' % pairs)
1105 self.wfile.write('</body></html>')
1106
1107 return True
1108
1109 def SlowServerHandler(self):
1110 """Wait for the user suggested time before responding. The syntax is
1111 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001112 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001113 return False
1114 query_char = self.path.find('?')
1115 wait_sec = 1.0
1116 if query_char >= 0:
1117 try:
1118 wait_sec = int(self.path[query_char + 1:])
1119 except ValueError:
1120 pass
1121 time.sleep(wait_sec)
1122 self.send_response(200)
1123 self.send_header('Content-type', 'text/plain')
1124 self.end_headers()
1125 self.wfile.write("waited %d seconds" % wait_sec)
1126 return True
1127
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001128 def ChunkedServerHandler(self):
1129 """Send chunked response. Allows to specify chunks parameters:
1130 - waitBeforeHeaders - ms to wait before sending headers
1131 - waitBetweenChunks - ms to wait between chunks
1132 - chunkSize - size of each chunk in bytes
1133 - chunksNumber - number of chunks
1134 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1135 waits one second, then sends headers and five chunks five bytes each."""
1136 if not self._ShouldHandleRequest("/chunked"):
1137 return False
1138 query_char = self.path.find('?')
1139 chunkedSettings = {'waitBeforeHeaders' : 0,
1140 'waitBetweenChunks' : 0,
1141 'chunkSize' : 5,
1142 'chunksNumber' : 5}
1143 if query_char >= 0:
1144 params = self.path[query_char + 1:].split('&')
1145 for param in params:
1146 keyValue = param.split('=')
1147 if len(keyValue) == 2:
1148 try:
1149 chunkedSettings[keyValue[0]] = int(keyValue[1])
1150 except ValueError:
1151 pass
1152 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1153 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1154 self.send_response(200)
1155 self.send_header('Content-type', 'text/plain')
1156 self.send_header('Connection', 'close')
1157 self.send_header('Transfer-Encoding', 'chunked')
1158 self.end_headers()
1159 # Chunked encoding: sending all chunks, then final zero-length chunk and
1160 # then final CRLF.
1161 for i in range(0, chunkedSettings['chunksNumber']):
1162 if i > 0:
1163 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1164 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1165 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1166 self.sendChunkHelp('')
1167 return True
1168
initial.commit94958cf2008-07-26 22:42:52 +00001169 def ContentTypeHandler(self):
1170 """Returns a string of html with the given content type. E.g.,
1171 /contenttype?text/css returns an html file with the Content-Type
1172 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001173 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001174 return False
1175 query_char = self.path.find('?')
1176 content_type = self.path[query_char + 1:].strip()
1177 if not content_type:
1178 content_type = 'text/html'
1179 self.send_response(200)
1180 self.send_header('Content-Type', content_type)
1181 self.end_headers()
1182 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1183 return True
1184
creis@google.com2f4f6a42011-03-25 19:44:19 +00001185 def NoContentHandler(self):
1186 """Returns a 204 No Content response."""
1187 if not self._ShouldHandleRequest("/nocontent"):
1188 return False
1189 self.send_response(204)
1190 self.end_headers()
1191 return True
1192
initial.commit94958cf2008-07-26 22:42:52 +00001193 def ServerRedirectHandler(self):
1194 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001195 '/server-redirect?http://foo.bar/asdf' to redirect to
1196 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001197
1198 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001199 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001200 return False
1201
1202 query_char = self.path.find('?')
1203 if query_char < 0 or len(self.path) <= query_char + 1:
1204 self.sendRedirectHelp(test_name)
1205 return True
1206 dest = self.path[query_char + 1:]
1207
1208 self.send_response(301) # moved permanently
1209 self.send_header('Location', dest)
1210 self.send_header('Content-type', 'text/html')
1211 self.end_headers()
1212 self.wfile.write('<html><head>')
1213 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1214
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001215 return True
initial.commit94958cf2008-07-26 22:42:52 +00001216
1217 def ClientRedirectHandler(self):
1218 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001219 '/client-redirect?http://foo.bar/asdf' to redirect to
1220 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001221
1222 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001223 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001224 return False
1225
1226 query_char = self.path.find('?');
1227 if query_char < 0 or len(self.path) <= query_char + 1:
1228 self.sendRedirectHelp(test_name)
1229 return True
1230 dest = self.path[query_char + 1:]
1231
1232 self.send_response(200)
1233 self.send_header('Content-type', 'text/html')
1234 self.end_headers()
1235 self.wfile.write('<html><head>')
1236 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1237 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1238
1239 return True
1240
tony@chromium.org03266982010-03-05 03:18:42 +00001241 def MultipartHandler(self):
1242 """Send a multipart response (10 text/html pages)."""
1243 test_name = "/multipart"
1244 if not self._ShouldHandleRequest(test_name):
1245 return False
1246
1247 num_frames = 10
1248 bound = '12345'
1249 self.send_response(200)
1250 self.send_header('Content-type',
1251 'multipart/x-mixed-replace;boundary=' + bound)
1252 self.end_headers()
1253
1254 for i in xrange(num_frames):
1255 self.wfile.write('--' + bound + '\r\n')
1256 self.wfile.write('Content-type: text/html\r\n\r\n')
1257 self.wfile.write('<title>page ' + str(i) + '</title>')
1258 self.wfile.write('page ' + str(i))
1259
1260 self.wfile.write('--' + bound + '--')
1261 return True
1262
initial.commit94958cf2008-07-26 22:42:52 +00001263 def DefaultResponseHandler(self):
1264 """This is the catch-all response handler for requests that aren't handled
1265 by one of the special handlers above.
1266 Note that we specify the content-length as without it the https connection
1267 is not closed properly (and the browser keeps expecting data)."""
1268
1269 contents = "Default response given for path: " + self.path
1270 self.send_response(200)
1271 self.send_header('Content-type', 'text/html')
1272 self.send_header("Content-Length", len(contents))
1273 self.end_headers()
1274 self.wfile.write(contents)
1275 return True
1276
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001277 def RedirectConnectHandler(self):
1278 """Sends a redirect to the CONNECT request for www.redirect.com. This
1279 response is not specified by the RFC, so the browser should not follow
1280 the redirect."""
1281
1282 if (self.path.find("www.redirect.com") < 0):
1283 return False
1284
1285 dest = "http://www.destination.com/foo.js"
1286
1287 self.send_response(302) # moved temporarily
1288 self.send_header('Location', dest)
1289 self.send_header('Connection', 'close')
1290 self.end_headers()
1291 return True
1292
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001293 def ServerAuthConnectHandler(self):
1294 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1295 response doesn't make sense because the proxy server cannot request
1296 server authentication."""
1297
1298 if (self.path.find("www.server-auth.com") < 0):
1299 return False
1300
1301 challenge = 'Basic realm="WallyWorld"'
1302
1303 self.send_response(401) # unauthorized
1304 self.send_header('WWW-Authenticate', challenge)
1305 self.send_header('Connection', 'close')
1306 self.end_headers()
1307 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001308
1309 def DefaultConnectResponseHandler(self):
1310 """This is the catch-all response handler for CONNECT requests that aren't
1311 handled by one of the special handlers above. Real Web servers respond
1312 with 400 to CONNECT requests."""
1313
1314 contents = "Your client has issued a malformed or illegal request."
1315 self.send_response(400) # bad request
1316 self.send_header('Content-type', 'text/html')
1317 self.send_header("Content-Length", len(contents))
1318 self.end_headers()
1319 self.wfile.write(contents)
1320 return True
1321
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001322 def DeviceManagementHandler(self):
1323 """Delegates to the device management service used for cloud policy."""
1324 if not self._ShouldHandleRequest("/device_management"):
1325 return False
1326
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001327 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001328
1329 if not self.server._device_management_handler:
1330 import device_management
1331 policy_path = os.path.join(self.server.data_dir, 'device_management')
1332 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001333 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001334 self.server.policy_keys,
1335 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001336
1337 http_response, raw_reply = (
1338 self.server._device_management_handler.HandleRequest(self.path,
1339 self.headers,
1340 raw_request))
1341 self.send_response(http_response)
1342 self.end_headers()
1343 self.wfile.write(raw_reply)
1344 return True
1345
initial.commit94958cf2008-07-26 22:42:52 +00001346 # called by the redirect handling function when there is no parameter
1347 def sendRedirectHelp(self, redirect_name):
1348 self.send_response(200)
1349 self.send_header('Content-type', 'text/html')
1350 self.end_headers()
1351 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1352 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1353 self.wfile.write('</body></html>')
1354
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001355 # called by chunked handling function
1356 def sendChunkHelp(self, chunk):
1357 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1358 self.wfile.write('%X\r\n' % len(chunk))
1359 self.wfile.write(chunk)
1360 self.wfile.write('\r\n')
1361
akalin@chromium.org154bb132010-11-12 02:20:27 +00001362
1363class SyncPageHandler(BasePageHandler):
1364 """Handler for the main HTTP sync server."""
1365
1366 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001367 get_handlers = [self.ChromiumSyncMigrationOpHandler,
1368 self.ChromiumSyncTimeHandler]
1369 post_handlers = [self.ChromiumSyncCommandHandler,
1370 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001371 BasePageHandler.__init__(self, request, client_address,
1372 sync_http_server, [], get_handlers,
1373 post_handlers, [])
1374
1375 def ChromiumSyncTimeHandler(self):
1376 """Handle Chromium sync .../time requests.
1377
1378 The syncer sometimes checks server reachability by examining /time.
1379 """
1380 test_name = "/chromiumsync/time"
1381 if not self._ShouldHandleRequest(test_name):
1382 return False
1383
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001384 # Chrome hates it if we send a response before reading the request.
1385 if self.headers.getheader('content-length'):
1386 length = int(self.headers.getheader('content-length'))
1387 raw_request = self.rfile.read(length)
1388
akalin@chromium.org154bb132010-11-12 02:20:27 +00001389 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001390 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001391 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001392 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001393 return True
1394
1395 def ChromiumSyncCommandHandler(self):
1396 """Handle a chromiumsync command arriving via http.
1397
1398 This covers all sync protocol commands: authentication, getupdates, and
1399 commit.
1400 """
1401 test_name = "/chromiumsync/command"
1402 if not self._ShouldHandleRequest(test_name):
1403 return False
1404
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001405 length = int(self.headers.getheader('content-length'))
1406 raw_request = self.rfile.read(length)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001407
1408 http_response, raw_reply = self.server.HandleCommand(
1409 self.path, raw_request)
1410 self.send_response(http_response)
1411 self.end_headers()
1412 self.wfile.write(raw_reply)
1413 return True
1414
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001415 def ChromiumSyncMigrationOpHandler(self):
1416 """Handle a chromiumsync test-op command arriving via http.
1417 """
1418 test_name = "/chromiumsync/migrate"
1419 if not self._ShouldHandleRequest(test_name):
1420 return False
1421
1422 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1423 self.path)
1424 self.send_response(http_response)
1425 self.send_header('Content-Type', 'text/html')
1426 self.send_header('Content-Length', len(raw_reply))
1427 self.end_headers()
1428 self.wfile.write(raw_reply)
1429 return True
1430
akalin@chromium.org154bb132010-11-12 02:20:27 +00001431
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001432def MakeDataDir():
1433 if options.data_dir:
1434 if not os.path.isdir(options.data_dir):
1435 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1436 return None
1437 my_data_dir = options.data_dir
1438 else:
1439 # Create the default path to our data dir, relative to the exe dir.
1440 my_data_dir = os.path.dirname(sys.argv[0])
1441 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001442 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001443
1444 #TODO(ibrar): Must use Find* funtion defined in google\tools
1445 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1446
1447 return my_data_dir
1448
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001449class FileMultiplexer:
1450 def __init__(self, fd1, fd2) :
1451 self.__fd1 = fd1
1452 self.__fd2 = fd2
1453
1454 def __del__(self) :
1455 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1456 self.__fd1.close()
1457 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1458 self.__fd2.close()
1459
1460 def write(self, text) :
1461 self.__fd1.write(text)
1462 self.__fd2.write(text)
1463
1464 def flush(self) :
1465 self.__fd1.flush()
1466 self.__fd2.flush()
1467
initial.commit94958cf2008-07-26 22:42:52 +00001468def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001469 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001470 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001471 if options.log_to_console:
1472 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1473 else:
1474 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001475
1476 port = options.port
1477
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001478 server_data = {}
1479
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001480 if options.server_type == SERVER_HTTP:
1481 if options.cert:
1482 # let's make sure the cert file exists.
1483 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001484 print 'specified server cert file not found: ' + options.cert + \
1485 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001486 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001487 for ca_cert in options.ssl_client_ca:
1488 if not os.path.isfile(ca_cert):
1489 print 'specified trusted client CA file not found: ' + ca_cert + \
1490 ' exiting...'
1491 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001492 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001493 options.ssl_client_auth, options.ssl_client_ca,
1494 options.ssl_bulk_cipher)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001495 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001496 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001497 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001498 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001499
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001500 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001501 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001502 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001503 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001504 server.policy_keys = options.policy_keys
1505 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001506 elif options.server_type == SERVER_SYNC:
1507 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1508 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001509 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1510 server_data['port'] = server.server_port
1511 server_data['xmpp_port'] = server.xmpp_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001512 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001513 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001514 my_data_dir = MakeDataDir()
1515
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001516 # Instantiate a dummy authorizer for managing 'virtual' users
1517 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1518
1519 # Define a new user having full r/w permissions and a read-only
1520 # anonymous user
1521 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1522
1523 authorizer.add_anonymous(my_data_dir)
1524
1525 # Instantiate FTP handler class
1526 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1527 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001528
1529 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001530 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1531 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001532
1533 # Instantiate FTP server class and listen to 127.0.0.1:port
1534 address = ('127.0.0.1', port)
1535 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001536 server_data['port'] = server.socket.getsockname()[1]
1537 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001538
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001539 # Notify the parent that we've started. (BaseServer subclasses
1540 # bind their sockets on construction.)
1541 if options.startup_pipe is not None:
akalin@chromium.org73b7b5c2010-11-26 19:42:26 +00001542 server_data_json = simplejson.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001543 server_data_len = len(server_data_json)
1544 print 'sending server_data: %s (%d bytes)' % (
1545 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001546 if sys.platform == 'win32':
1547 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1548 else:
1549 fd = options.startup_pipe
1550 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001551 # First write the data length as an unsigned 4-byte value. This
1552 # is _not_ using network byte ordering since the other end of the
1553 # pipe is on the same machine.
1554 startup_pipe.write(struct.pack('=L', server_data_len))
1555 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001556 startup_pipe.close()
1557
initial.commit94958cf2008-07-26 22:42:52 +00001558 try:
1559 server.serve_forever()
1560 except KeyboardInterrupt:
1561 print 'shutting down server'
1562 server.stop = True
1563
1564if __name__ == '__main__':
1565 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001566 option_parser.add_option("-f", '--ftp', action='store_const',
1567 const=SERVER_FTP, default=SERVER_HTTP,
1568 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001569 help='start up an FTP server.')
1570 option_parser.add_option('', '--sync', action='store_const',
1571 const=SERVER_SYNC, default=SERVER_HTTP,
1572 dest='server_type',
1573 help='start up a sync server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001574 option_parser.add_option('', '--log-to-console', action='store_const',
1575 const=True, default=False,
1576 dest='log_to_console',
1577 help='Enables or disables sys.stdout logging to '
1578 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001579 option_parser.add_option('', '--port', default='0', type='int',
1580 help='Port used by the server. If unspecified, the '
1581 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001582 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001583 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001584 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001585 help='Specify that https should be used, specify '
1586 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001587 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001588 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1589 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001590 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1591 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001592 'should include the CA named in the subject of '
1593 'the DER-encoded certificate contained in the '
1594 'specified file. This option may appear multiple '
1595 'times, indicating multiple CA names should be '
1596 'sent in the request.')
1597 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1598 help='Specify the bulk encryption algorithm(s)'
1599 'that will be accepted by the SSL server. Valid '
1600 'values are "aes256", "aes128", "3des", "rc4". If '
1601 'omitted, all algorithms will be used. This '
1602 'option may appear multiple times, indicating '
1603 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001604 option_parser.add_option('', '--file-root-url', default='/files/',
1605 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001606 option_parser.add_option('', '--startup-pipe', type='int',
1607 dest='startup_pipe',
1608 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001609 option_parser.add_option('', '--policy-key', action='append',
1610 dest='policy_keys',
1611 help='Specify a path to a PEM-encoded private key '
1612 'to use for policy signing. May be specified '
1613 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00001614 'the server. If ther server has multiple keys, it '
1615 'will rotate through them in at each request a '
1616 'round-robin fashion. The server will generate a '
1617 'random key if none is specified on the command '
1618 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001619 option_parser.add_option('', '--policy-user', default='user@example.com',
1620 dest='policy_user',
1621 help='Specify the user name the server should '
1622 'report back to the client as the user owning the '
1623 'token used for making the policy request.')
initial.commit94958cf2008-07-26 22:42:52 +00001624 options, args = option_parser.parse_args()
1625
1626 sys.exit(main(options, args))