OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright 2013 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """This is a simple HTTP/FTP/SYNC/TCP/UDP/ server used for testing Chrome. | 6 """This is a simple HTTP/FTP/TCP/UDP/BASIC_AUTH_PROXY/WEBSOCKET server used for |
| 7 testing Chrome. |
7 | 8 |
8 It supports several test URLs, as specified by the handlers in TestPageHandler. | 9 It supports several test URLs, as specified by the handlers in TestPageHandler. |
9 By default, it listens on an ephemeral port and sends the port number back to | 10 By default, it listens on an ephemeral port and sends the port number back to |
10 the originating process over a pipe. The originating process can specify an | 11 the originating process over a pipe. The originating process can specify an |
11 explicit port if necessary. | 12 explicit port if necessary. |
12 It can use https if you specify the flag --https=CERT where CERT is the path | 13 It can use https if you specify the flag --https=CERT where CERT is the path |
13 to a pem file containing the certificate and private key that should be used. | 14 to a pem file containing the certificate and private key that should be used. |
14 """ | 15 """ |
15 | 16 |
16 import asyncore | |
17 import base64 | 17 import base64 |
18 import BaseHTTPServer | 18 import BaseHTTPServer |
19 import cgi | 19 import cgi |
20 import errno | |
21 import hashlib | 20 import hashlib |
22 import json | |
23 import logging | 21 import logging |
24 import minica | 22 import minica |
25 import os | 23 import os |
26 import random | 24 import random |
27 import re | 25 import re |
28 import select | 26 import select |
29 import socket | 27 import socket |
30 import SocketServer | 28 import SocketServer |
31 import struct | 29 import struct |
32 import sys | 30 import sys |
33 import threading | 31 import threading |
34 import time | 32 import time |
35 import urllib | 33 import urllib |
36 import urlparse | 34 import urlparse |
37 import zlib | 35 import zlib |
38 | 36 |
39 import echo_message | 37 import echo_message |
40 import pyftpdlib.ftpserver | 38 import pyftpdlib.ftpserver |
41 import testserver_base | 39 import testserver_base |
42 import tlslite | 40 import tlslite |
43 import tlslite.api | 41 import tlslite.api |
44 | 42 |
45 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | 43 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) |
46 sys.path.insert( | 44 sys.path.insert( |
47 0, os.path.join(BASE_DIR, '..', '..', '..', 'third_party/pywebsocket/src')) | 45 0, os.path.join(BASE_DIR, '..', '..', '..', 'third_party/pywebsocket/src')) |
48 from mod_pywebsocket.standalone import WebSocketServer | 46 from mod_pywebsocket.standalone import WebSocketServer |
49 | 47 |
50 SERVER_HTTP = 0 | 48 SERVER_HTTP = 0 |
51 SERVER_FTP = 1 | 49 SERVER_FTP = 1 |
52 SERVER_SYNC = 2 | 50 SERVER_TCP_ECHO = 2 |
53 SERVER_TCP_ECHO = 3 | 51 SERVER_UDP_ECHO = 3 |
54 SERVER_UDP_ECHO = 4 | 52 SERVER_BASIC_AUTH_PROXY = 4 |
55 SERVER_BASIC_AUTH_PROXY = 5 | 53 SERVER_WEBSOCKET = 5 |
56 SERVER_WEBSOCKET = 6 | |
57 | 54 |
58 # Default request queue size for WebSocketServer. | 55 # Default request queue size for WebSocketServer. |
59 _DEFAULT_REQUEST_QUEUE_SIZE = 128 | 56 _DEFAULT_REQUEST_QUEUE_SIZE = 128 |
60 | 57 |
61 | 58 |
62 # Using debug() seems to cause hangs on XP: see http://crbug.com/64515 . | |
63 debug_output = sys.stderr | |
64 def debug(string): | |
65 debug_output.write(string + "\n") | |
66 debug_output.flush() | |
67 | |
68 | |
69 class WebSocketOptions: | 59 class WebSocketOptions: |
70 """Holds options for WebSocketServer.""" | 60 """Holds options for WebSocketServer.""" |
71 | 61 |
72 def __init__(self, host, port, data_dir): | 62 def __init__(self, host, port, data_dir): |
73 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE | 63 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE |
74 self.server_host = host | 64 self.server_host = host |
75 self.port = port | 65 self.port = port |
76 self.websock_handlers = data_dir | 66 self.websock_handlers = data_dir |
77 self.scan_dir = None | 67 self.scan_dir = None |
78 self.allow_handlers_outside_root_dir = False | 68 self.allow_handlers_outside_root_dir = False |
(...skipping 19 matching lines...) Expand all Loading... |
98 self.log = [] | 88 self.log = [] |
99 | 89 |
100 def __getitem__(self, sessionID): | 90 def __getitem__(self, sessionID): |
101 self.log.append(('lookup', sessionID)) | 91 self.log.append(('lookup', sessionID)) |
102 raise KeyError() | 92 raise KeyError() |
103 | 93 |
104 def __setitem__(self, sessionID, session): | 94 def __setitem__(self, sessionID, session): |
105 self.log.append(('insert', sessionID)) | 95 self.log.append(('insert', sessionID)) |
106 | 96 |
107 | 97 |
108 class ClientRestrictingServerMixIn: | 98 class HTTPServer(testserver_base.ClientRestrictingServerMixIn, |
109 """Implements verify_request to limit connections to our configured IP | 99 testserver_base.BrokenPipeHandlerMixIn, |
110 address.""" | 100 testserver_base.StoppableHTTPServer): |
111 | |
112 def verify_request(self, _request, client_address): | |
113 return client_address[0] == self.server_address[0] | |
114 | |
115 | |
116 class BrokenPipeHandlerMixIn: | |
117 """Allows the server to deal with "broken pipe" errors (which happen if the | |
118 browser quits with outstanding requests, like for the favicon). This mix-in | |
119 requires the class to derive from SocketServer.BaseServer and not override its | |
120 handle_error() method. """ | |
121 | |
122 def handle_error(self, request, client_address): | |
123 value = sys.exc_info()[1] | |
124 if isinstance(value, socket.error): | |
125 err = value.args[0] | |
126 if sys.platform in ('win32', 'cygwin'): | |
127 # "An established connection was aborted by the software in your host." | |
128 pipe_err = 10053 | |
129 else: | |
130 pipe_err = errno.EPIPE | |
131 if err == pipe_err: | |
132 print "testserver.py: Broken pipe" | |
133 return | |
134 SocketServer.BaseServer.handle_error(self, request, client_address) | |
135 | |
136 | |
137 class StoppableHTTPServer(BaseHTTPServer.HTTPServer): | |
138 """This is a specialization of BaseHTTPServer to allow it | |
139 to be exited cleanly (by setting its "stop" member to True).""" | |
140 | |
141 def serve_forever(self): | |
142 self.stop = False | |
143 self.nonce_time = None | |
144 while not self.stop: | |
145 self.handle_request() | |
146 self.socket.close() | |
147 | |
148 | |
149 class HTTPServer(ClientRestrictingServerMixIn, | |
150 BrokenPipeHandlerMixIn, | |
151 StoppableHTTPServer): | |
152 """This is a specialization of StoppableHTTPServer that adds client | 101 """This is a specialization of StoppableHTTPServer that adds client |
153 verification.""" | 102 verification.""" |
154 | 103 |
155 pass | 104 pass |
156 | 105 |
157 class OCSPServer(ClientRestrictingServerMixIn, | 106 class OCSPServer(testserver_base.ClientRestrictingServerMixIn, |
158 BrokenPipeHandlerMixIn, | 107 testserver_base.BrokenPipeHandlerMixIn, |
159 BaseHTTPServer.HTTPServer): | 108 BaseHTTPServer.HTTPServer): |
160 """This is a specialization of HTTPServer that serves an | 109 """This is a specialization of HTTPServer that serves an |
161 OCSP response""" | 110 OCSP response""" |
162 | 111 |
163 def serve_forever_on_thread(self): | 112 def serve_forever_on_thread(self): |
164 self.thread = threading.Thread(target = self.serve_forever, | 113 self.thread = threading.Thread(target = self.serve_forever, |
165 name = "OCSPServerThread") | 114 name = "OCSPServerThread") |
166 self.thread.start() | 115 self.thread.start() |
167 | 116 |
168 def stop_serving(self): | 117 def stop_serving(self): |
169 self.shutdown() | 118 self.shutdown() |
170 self.thread.join() | 119 self.thread.join() |
171 | 120 |
172 | 121 |
173 class HTTPSServer(tlslite.api.TLSSocketServerMixIn, | 122 class HTTPSServer(tlslite.api.TLSSocketServerMixIn, |
174 ClientRestrictingServerMixIn, | 123 testserver_base.ClientRestrictingServerMixIn, |
175 BrokenPipeHandlerMixIn, | 124 testserver_base.BrokenPipeHandlerMixIn, |
176 StoppableHTTPServer): | 125 testserver_base.StoppableHTTPServer): |
177 """This is a specialization of StoppableHTTPServer that add https support and | 126 """This is a specialization of StoppableHTTPServer that add https support and |
178 client verification.""" | 127 client verification.""" |
179 | 128 |
180 def __init__(self, server_address, request_hander_class, pem_cert_and_key, | 129 def __init__(self, server_address, request_hander_class, pem_cert_and_key, |
181 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers, | 130 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers, |
182 record_resume_info, tls_intolerant): | 131 record_resume_info, tls_intolerant): |
183 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key) | 132 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key) |
184 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True) | 133 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True) |
185 self.ssl_client_auth = ssl_client_auth | 134 self.ssl_client_auth = ssl_client_auth |
186 self.ssl_client_cas = [] | 135 self.ssl_client_cas = [] |
187 self.tls_intolerant = tls_intolerant | 136 self.tls_intolerant = tls_intolerant |
188 | 137 |
189 for ca_file in ssl_client_cas: | 138 for ca_file in ssl_client_cas: |
190 s = open(ca_file).read() | 139 s = open(ca_file).read() |
191 x509 = tlslite.api.X509() | 140 x509 = tlslite.api.X509() |
192 x509.parse(s) | 141 x509.parse(s) |
193 self.ssl_client_cas.append(x509.subject) | 142 self.ssl_client_cas.append(x509.subject) |
194 self.ssl_handshake_settings = tlslite.api.HandshakeSettings() | 143 self.ssl_handshake_settings = tlslite.api.HandshakeSettings() |
195 if ssl_bulk_ciphers is not None: | 144 if ssl_bulk_ciphers is not None: |
196 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers | 145 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers |
197 | 146 |
198 if record_resume_info: | 147 if record_resume_info: |
199 # If record_resume_info is true then we'll replace the session cache with | 148 # If record_resume_info is true then we'll replace the session cache with |
200 # an object that records the lookups and inserts that it sees. | 149 # an object that records the lookups and inserts that it sees. |
201 self.session_cache = RecordingSSLSessionCache() | 150 self.session_cache = RecordingSSLSessionCache() |
202 else: | 151 else: |
203 self.session_cache = tlslite.api.SessionCache() | 152 self.session_cache = tlslite.api.SessionCache() |
204 StoppableHTTPServer.__init__(self, server_address, request_hander_class) | 153 testserver_base.StoppableHTTPServer.__init__(self, |
| 154 server_address, |
| 155 request_hander_class) |
205 | 156 |
206 def handshake(self, tlsConnection): | 157 def handshake(self, tlsConnection): |
207 """Creates the SSL connection.""" | 158 """Creates the SSL connection.""" |
208 | 159 |
209 try: | 160 try: |
210 tlsConnection.handshakeServer(certChain=self.cert_chain, | 161 tlsConnection.handshakeServer(certChain=self.cert_chain, |
211 privateKey=self.private_key, | 162 privateKey=self.private_key, |
212 sessionCache=self.session_cache, | 163 sessionCache=self.session_cache, |
213 reqCert=self.ssl_client_auth, | 164 reqCert=self.ssl_client_auth, |
214 settings=self.ssl_handshake_settings, | 165 settings=self.ssl_handshake_settings, |
215 reqCAs=self.ssl_client_cas, | 166 reqCAs=self.ssl_client_cas, |
216 tlsIntolerant=self.tls_intolerant) | 167 tlsIntolerant=self.tls_intolerant) |
217 tlsConnection.ignoreAbruptClose = True | 168 tlsConnection.ignoreAbruptClose = True |
218 return True | 169 return True |
219 except tlslite.api.TLSAbruptCloseError: | 170 except tlslite.api.TLSAbruptCloseError: |
220 # Ignore abrupt close. | 171 # Ignore abrupt close. |
221 return True | 172 return True |
222 except tlslite.api.TLSError, error: | 173 except tlslite.api.TLSError, error: |
223 print "Handshake failure:", str(error) | 174 print "Handshake failure:", str(error) |
224 return False | 175 return False |
225 | 176 |
226 | 177 |
227 class SyncHTTPServer(ClientRestrictingServerMixIn, | 178 class FTPServer(testserver_base.ClientRestrictingServerMixIn, |
228 BrokenPipeHandlerMixIn, | 179 pyftpdlib.ftpserver.FTPServer): |
229 StoppableHTTPServer): | |
230 """An HTTP server that handles sync commands.""" | |
231 | |
232 def __init__(self, server_address, xmpp_port, request_handler_class): | |
233 # We import here to avoid pulling in chromiumsync's dependencies | |
234 # unless strictly necessary. | |
235 import chromiumsync | |
236 import xmppserver | |
237 StoppableHTTPServer.__init__(self, server_address, request_handler_class) | |
238 self._sync_handler = chromiumsync.TestServer() | |
239 self._xmpp_socket_map = {} | |
240 self._xmpp_server = xmppserver.XmppServer( | |
241 self._xmpp_socket_map, ('localhost', xmpp_port)) | |
242 self.xmpp_port = self._xmpp_server.getsockname()[1] | |
243 self.authenticated = True | |
244 | |
245 def GetXmppServer(self): | |
246 return self._xmpp_server | |
247 | |
248 def HandleCommand(self, query, raw_request): | |
249 return self._sync_handler.HandleCommand(query, raw_request) | |
250 | |
251 def HandleRequestNoBlock(self): | |
252 """Handles a single request. | |
253 | |
254 Copied from SocketServer._handle_request_noblock(). | |
255 """ | |
256 | |
257 try: | |
258 request, client_address = self.get_request() | |
259 except socket.error: | |
260 return | |
261 if self.verify_request(request, client_address): | |
262 try: | |
263 self.process_request(request, client_address) | |
264 except Exception: | |
265 self.handle_error(request, client_address) | |
266 self.close_request(request) | |
267 | |
268 def SetAuthenticated(self, auth_valid): | |
269 self.authenticated = auth_valid | |
270 | |
271 def GetAuthenticated(self): | |
272 return self.authenticated | |
273 | |
274 def serve_forever(self): | |
275 """This is a merge of asyncore.loop() and SocketServer.serve_forever(). | |
276 """ | |
277 | |
278 def HandleXmppSocket(fd, socket_map, handler): | |
279 """Runs the handler for the xmpp connection for fd. | |
280 | |
281 Adapted from asyncore.read() et al. | |
282 """ | |
283 | |
284 xmpp_connection = socket_map.get(fd) | |
285 # This could happen if a previous handler call caused fd to get | |
286 # removed from socket_map. | |
287 if xmpp_connection is None: | |
288 return | |
289 try: | |
290 handler(xmpp_connection) | |
291 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit): | |
292 raise | |
293 except: | |
294 xmpp_connection.handle_error() | |
295 | |
296 while True: | |
297 read_fds = [ self.fileno() ] | |
298 write_fds = [] | |
299 exceptional_fds = [] | |
300 | |
301 for fd, xmpp_connection in self._xmpp_socket_map.items(): | |
302 is_r = xmpp_connection.readable() | |
303 is_w = xmpp_connection.writable() | |
304 if is_r: | |
305 read_fds.append(fd) | |
306 if is_w: | |
307 write_fds.append(fd) | |
308 if is_r or is_w: | |
309 exceptional_fds.append(fd) | |
310 | |
311 try: | |
312 read_fds, write_fds, exceptional_fds = ( | |
313 select.select(read_fds, write_fds, exceptional_fds)) | |
314 except select.error, err: | |
315 if err.args[0] != errno.EINTR: | |
316 raise | |
317 else: | |
318 continue | |
319 | |
320 for fd in read_fds: | |
321 if fd == self.fileno(): | |
322 self.HandleRequestNoBlock() | |
323 continue | |
324 HandleXmppSocket(fd, self._xmpp_socket_map, | |
325 asyncore.dispatcher.handle_read_event) | |
326 | |
327 for fd in write_fds: | |
328 HandleXmppSocket(fd, self._xmpp_socket_map, | |
329 asyncore.dispatcher.handle_write_event) | |
330 | |
331 for fd in exceptional_fds: | |
332 HandleXmppSocket(fd, self._xmpp_socket_map, | |
333 asyncore.dispatcher.handle_expt_event) | |
334 | |
335 | |
336 class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer): | |
337 """This is a specialization of FTPServer that adds client verification.""" | 180 """This is a specialization of FTPServer that adds client verification.""" |
338 | 181 |
339 pass | 182 pass |
340 | 183 |
341 | 184 |
342 class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer): | 185 class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn, |
| 186 SocketServer.TCPServer): |
343 """A TCP echo server that echoes back what it has received.""" | 187 """A TCP echo server that echoes back what it has received.""" |
344 | 188 |
345 def server_bind(self): | 189 def server_bind(self): |
346 """Override server_bind to store the server name.""" | 190 """Override server_bind to store the server name.""" |
347 | 191 |
348 SocketServer.TCPServer.server_bind(self) | 192 SocketServer.TCPServer.server_bind(self) |
349 host, port = self.socket.getsockname()[:2] | 193 host, port = self.socket.getsockname()[:2] |
350 self.server_name = socket.getfqdn(host) | 194 self.server_name = socket.getfqdn(host) |
351 self.server_port = port | 195 self.server_port = port |
352 | 196 |
353 def serve_forever(self): | 197 def serve_forever(self): |
354 self.stop = False | 198 self.stop = False |
355 self.nonce_time = None | 199 self.nonce_time = None |
356 while not self.stop: | 200 while not self.stop: |
357 self.handle_request() | 201 self.handle_request() |
358 self.socket.close() | 202 self.socket.close() |
359 | 203 |
360 | 204 |
361 class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer): | 205 class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn, |
| 206 SocketServer.UDPServer): |
362 """A UDP echo server that echoes back what it has received.""" | 207 """A UDP echo server that echoes back what it has received.""" |
363 | 208 |
364 def server_bind(self): | 209 def server_bind(self): |
365 """Override server_bind to store the server name.""" | 210 """Override server_bind to store the server name.""" |
366 | 211 |
367 SocketServer.UDPServer.server_bind(self) | 212 SocketServer.UDPServer.server_bind(self) |
368 host, port = self.socket.getsockname()[:2] | 213 host, port = self.socket.getsockname()[:2] |
369 self.server_name = socket.getfqdn(host) | 214 self.server_name = socket.getfqdn(host) |
370 self.server_port = port | 215 self.server_port = port |
371 | 216 |
372 def serve_forever(self): | 217 def serve_forever(self): |
373 self.stop = False | 218 self.stop = False |
374 self.nonce_time = None | 219 self.nonce_time = None |
375 while not self.stop: | 220 while not self.stop: |
376 self.handle_request() | 221 self.handle_request() |
377 self.socket.close() | 222 self.socket.close() |
378 | 223 |
379 | 224 |
380 class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler): | 225 class TestPageHandler(testserver_base.BasePageHandler): |
381 | |
382 def __init__(self, request, client_address, socket_server, | |
383 connect_handlers, get_handlers, head_handlers, post_handlers, | |
384 put_handlers): | |
385 self._connect_handlers = connect_handlers | |
386 self._get_handlers = get_handlers | |
387 self._head_handlers = head_handlers | |
388 self._post_handlers = post_handlers | |
389 self._put_handlers = put_handlers | |
390 BaseHTTPServer.BaseHTTPRequestHandler.__init__( | |
391 self, request, client_address, socket_server) | |
392 | |
393 def log_request(self, *args, **kwargs): | |
394 # Disable request logging to declutter test log output. | |
395 pass | |
396 | |
397 def _ShouldHandleRequest(self, handler_name): | |
398 """Determines if the path can be handled by the handler. | |
399 | |
400 We consider a handler valid if the path begins with the | |
401 handler name. It can optionally be followed by "?*", "/*". | |
402 """ | |
403 | |
404 pattern = re.compile('%s($|\?|/).*' % handler_name) | |
405 return pattern.match(self.path) | |
406 | |
407 def do_CONNECT(self): | |
408 for handler in self._connect_handlers: | |
409 if handler(): | |
410 return | |
411 | |
412 def do_GET(self): | |
413 for handler in self._get_handlers: | |
414 if handler(): | |
415 return | |
416 | |
417 def do_HEAD(self): | |
418 for handler in self._head_handlers: | |
419 if handler(): | |
420 return | |
421 | |
422 def do_POST(self): | |
423 for handler in self._post_handlers: | |
424 if handler(): | |
425 return | |
426 | |
427 def do_PUT(self): | |
428 for handler in self._put_handlers: | |
429 if handler(): | |
430 return | |
431 | |
432 | |
433 class TestPageHandler(BasePageHandler): | |
434 | 226 |
435 def __init__(self, request, client_address, socket_server): | 227 def __init__(self, request, client_address, socket_server): |
436 connect_handlers = [ | 228 connect_handlers = [ |
437 self.RedirectConnectHandler, | 229 self.RedirectConnectHandler, |
438 self.ServerAuthConnectHandler, | 230 self.ServerAuthConnectHandler, |
439 self.DefaultConnectResponseHandler] | 231 self.DefaultConnectResponseHandler] |
440 get_handlers = [ | 232 get_handlers = [ |
441 self.NoCacheMaxAgeTimeHandler, | 233 self.NoCacheMaxAgeTimeHandler, |
442 self.NoCacheTimeHandler, | 234 self.NoCacheTimeHandler, |
443 self.CacheTimeHandler, | 235 self.CacheTimeHandler, |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
494 'gif': 'image/gif', | 286 'gif': 'image/gif', |
495 'jpeg' : 'image/jpeg', | 287 'jpeg' : 'image/jpeg', |
496 'jpg' : 'image/jpeg', | 288 'jpg' : 'image/jpeg', |
497 'json': 'application/json', | 289 'json': 'application/json', |
498 'pdf' : 'application/pdf', | 290 'pdf' : 'application/pdf', |
499 'wav' : 'audio/wav', | 291 'wav' : 'audio/wav', |
500 'xml' : 'text/xml' | 292 'xml' : 'text/xml' |
501 } | 293 } |
502 self._default_mime_type = 'text/html' | 294 self._default_mime_type = 'text/html' |
503 | 295 |
504 BasePageHandler.__init__(self, request, client_address, socket_server, | 296 testserver_base.BasePageHandler.__init__(self, request, client_address, |
505 connect_handlers, get_handlers, head_handlers, | 297 socket_server, connect_handlers, |
506 post_handlers, put_handlers) | 298 get_handlers, head_handlers, |
| 299 post_handlers, put_handlers) |
507 | 300 |
508 def GetMIMETypeFromName(self, file_name): | 301 def GetMIMETypeFromName(self, file_name): |
509 """Returns the mime type for the specified file_name. So far it only looks | 302 """Returns the mime type for the specified file_name. So far it only looks |
510 at the file extension.""" | 303 at the file extension.""" |
511 | 304 |
512 (_shortname, extension) = os.path.splitext(file_name.split("?")[0]) | 305 (_shortname, extension) = os.path.splitext(file_name.split("?")[0]) |
513 if len(extension) == 0: | 306 if len(extension) == 0: |
514 # no extension. | 307 # no extension. |
515 return self._default_mime_type | 308 return self._default_mime_type |
516 | 309 |
(...skipping 1306 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1823 self.wfile.write('</body></html>') | 1616 self.wfile.write('</body></html>') |
1824 | 1617 |
1825 # called by chunked handling function | 1618 # called by chunked handling function |
1826 def sendChunkHelp(self, chunk): | 1619 def sendChunkHelp(self, chunk): |
1827 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF | 1620 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF |
1828 self.wfile.write('%X\r\n' % len(chunk)) | 1621 self.wfile.write('%X\r\n' % len(chunk)) |
1829 self.wfile.write(chunk) | 1622 self.wfile.write(chunk) |
1830 self.wfile.write('\r\n') | 1623 self.wfile.write('\r\n') |
1831 | 1624 |
1832 | 1625 |
1833 class SyncPageHandler(BasePageHandler): | 1626 class OCSPHandler(testserver_base.BasePageHandler): |
1834 """Handler for the main HTTP sync server.""" | |
1835 | |
1836 def __init__(self, request, client_address, sync_http_server): | |
1837 get_handlers = [self.ChromiumSyncTimeHandler, | |
1838 self.ChromiumSyncMigrationOpHandler, | |
1839 self.ChromiumSyncCredHandler, | |
1840 self.ChromiumSyncXmppCredHandler, | |
1841 self.ChromiumSyncDisableNotificationsOpHandler, | |
1842 self.ChromiumSyncEnableNotificationsOpHandler, | |
1843 self.ChromiumSyncSendNotificationOpHandler, | |
1844 self.ChromiumSyncBirthdayErrorOpHandler, | |
1845 self.ChromiumSyncTransientErrorOpHandler, | |
1846 self.ChromiumSyncErrorOpHandler, | |
1847 self.ChromiumSyncSyncTabFaviconsOpHandler, | |
1848 self.ChromiumSyncCreateSyncedBookmarksOpHandler, | |
1849 self.ChromiumSyncEnableKeystoreEncryptionOpHandler, | |
1850 self.ChromiumSyncRotateKeystoreKeysOpHandler] | |
1851 | |
1852 post_handlers = [self.ChromiumSyncCommandHandler, | |
1853 self.ChromiumSyncTimeHandler] | |
1854 BasePageHandler.__init__(self, request, client_address, | |
1855 sync_http_server, [], get_handlers, [], | |
1856 post_handlers, []) | |
1857 | |
1858 | |
1859 def ChromiumSyncTimeHandler(self): | |
1860 """Handle Chromium sync .../time requests. | |
1861 | |
1862 The syncer sometimes checks server reachability by examining /time. | |
1863 """ | |
1864 | |
1865 test_name = "/chromiumsync/time" | |
1866 if not self._ShouldHandleRequest(test_name): | |
1867 return False | |
1868 | |
1869 # Chrome hates it if we send a response before reading the request. | |
1870 if self.headers.getheader('content-length'): | |
1871 length = int(self.headers.getheader('content-length')) | |
1872 _raw_request = self.rfile.read(length) | |
1873 | |
1874 self.send_response(200) | |
1875 self.send_header('Content-Type', 'text/plain') | |
1876 self.end_headers() | |
1877 self.wfile.write('0123456789') | |
1878 return True | |
1879 | |
1880 def ChromiumSyncCommandHandler(self): | |
1881 """Handle a chromiumsync command arriving via http. | |
1882 | |
1883 This covers all sync protocol commands: authentication, getupdates, and | |
1884 commit. | |
1885 """ | |
1886 | |
1887 test_name = "/chromiumsync/command" | |
1888 if not self._ShouldHandleRequest(test_name): | |
1889 return False | |
1890 | |
1891 length = int(self.headers.getheader('content-length')) | |
1892 raw_request = self.rfile.read(length) | |
1893 http_response = 200 | |
1894 raw_reply = None | |
1895 if not self.server.GetAuthenticated(): | |
1896 http_response = 401 | |
1897 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % ( | |
1898 self.server.server_address[0]) | |
1899 else: | |
1900 http_response, raw_reply = self.server.HandleCommand( | |
1901 self.path, raw_request) | |
1902 | |
1903 ### Now send the response to the client. ### | |
1904 self.send_response(http_response) | |
1905 if http_response == 401: | |
1906 self.send_header('www-Authenticate', challenge) | |
1907 self.end_headers() | |
1908 self.wfile.write(raw_reply) | |
1909 return True | |
1910 | |
1911 def ChromiumSyncMigrationOpHandler(self): | |
1912 test_name = "/chromiumsync/migrate" | |
1913 if not self._ShouldHandleRequest(test_name): | |
1914 return False | |
1915 | |
1916 http_response, raw_reply = self.server._sync_handler.HandleMigrate( | |
1917 self.path) | |
1918 self.send_response(http_response) | |
1919 self.send_header('Content-Type', 'text/html') | |
1920 self.send_header('Content-Length', len(raw_reply)) | |
1921 self.end_headers() | |
1922 self.wfile.write(raw_reply) | |
1923 return True | |
1924 | |
1925 def ChromiumSyncCredHandler(self): | |
1926 test_name = "/chromiumsync/cred" | |
1927 if not self._ShouldHandleRequest(test_name): | |
1928 return False | |
1929 try: | |
1930 query = urlparse.urlparse(self.path)[4] | |
1931 cred_valid = urlparse.parse_qs(query)['valid'] | |
1932 if cred_valid[0] == 'True': | |
1933 self.server.SetAuthenticated(True) | |
1934 else: | |
1935 self.server.SetAuthenticated(False) | |
1936 except Exception: | |
1937 self.server.SetAuthenticated(False) | |
1938 | |
1939 http_response = 200 | |
1940 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated() | |
1941 self.send_response(http_response) | |
1942 self.send_header('Content-Type', 'text/html') | |
1943 self.send_header('Content-Length', len(raw_reply)) | |
1944 self.end_headers() | |
1945 self.wfile.write(raw_reply) | |
1946 return True | |
1947 | |
1948 def ChromiumSyncXmppCredHandler(self): | |
1949 test_name = "/chromiumsync/xmppcred" | |
1950 if not self._ShouldHandleRequest(test_name): | |
1951 return False | |
1952 xmpp_server = self.server.GetXmppServer() | |
1953 try: | |
1954 query = urlparse.urlparse(self.path)[4] | |
1955 cred_valid = urlparse.parse_qs(query)['valid'] | |
1956 if cred_valid[0] == 'True': | |
1957 xmpp_server.SetAuthenticated(True) | |
1958 else: | |
1959 xmpp_server.SetAuthenticated(False) | |
1960 except: | |
1961 xmpp_server.SetAuthenticated(False) | |
1962 | |
1963 http_response = 200 | |
1964 raw_reply = 'XMPP Authenticated: %s ' % xmpp_server.GetAuthenticated() | |
1965 self.send_response(http_response) | |
1966 self.send_header('Content-Type', 'text/html') | |
1967 self.send_header('Content-Length', len(raw_reply)) | |
1968 self.end_headers() | |
1969 self.wfile.write(raw_reply) | |
1970 return True | |
1971 | |
1972 def ChromiumSyncDisableNotificationsOpHandler(self): | |
1973 test_name = "/chromiumsync/disablenotifications" | |
1974 if not self._ShouldHandleRequest(test_name): | |
1975 return False | |
1976 self.server.GetXmppServer().DisableNotifications() | |
1977 result = 200 | |
1978 raw_reply = ('<html><title>Notifications disabled</title>' | |
1979 '<H1>Notifications disabled</H1></html>') | |
1980 self.send_response(result) | |
1981 self.send_header('Content-Type', 'text/html') | |
1982 self.send_header('Content-Length', len(raw_reply)) | |
1983 self.end_headers() | |
1984 self.wfile.write(raw_reply) | |
1985 return True | |
1986 | |
1987 def ChromiumSyncEnableNotificationsOpHandler(self): | |
1988 test_name = "/chromiumsync/enablenotifications" | |
1989 if not self._ShouldHandleRequest(test_name): | |
1990 return False | |
1991 self.server.GetXmppServer().EnableNotifications() | |
1992 result = 200 | |
1993 raw_reply = ('<html><title>Notifications enabled</title>' | |
1994 '<H1>Notifications enabled</H1></html>') | |
1995 self.send_response(result) | |
1996 self.send_header('Content-Type', 'text/html') | |
1997 self.send_header('Content-Length', len(raw_reply)) | |
1998 self.end_headers() | |
1999 self.wfile.write(raw_reply) | |
2000 return True | |
2001 | |
2002 def ChromiumSyncSendNotificationOpHandler(self): | |
2003 test_name = "/chromiumsync/sendnotification" | |
2004 if not self._ShouldHandleRequest(test_name): | |
2005 return False | |
2006 query = urlparse.urlparse(self.path)[4] | |
2007 query_params = urlparse.parse_qs(query) | |
2008 channel = '' | |
2009 data = '' | |
2010 if 'channel' in query_params: | |
2011 channel = query_params['channel'][0] | |
2012 if 'data' in query_params: | |
2013 data = query_params['data'][0] | |
2014 self.server.GetXmppServer().SendNotification(channel, data) | |
2015 result = 200 | |
2016 raw_reply = ('<html><title>Notification sent</title>' | |
2017 '<H1>Notification sent with channel "%s" ' | |
2018 'and data "%s"</H1></html>' | |
2019 % (channel, data)) | |
2020 self.send_response(result) | |
2021 self.send_header('Content-Type', 'text/html') | |
2022 self.send_header('Content-Length', len(raw_reply)) | |
2023 self.end_headers() | |
2024 self.wfile.write(raw_reply) | |
2025 return True | |
2026 | |
2027 def ChromiumSyncBirthdayErrorOpHandler(self): | |
2028 test_name = "/chromiumsync/birthdayerror" | |
2029 if not self._ShouldHandleRequest(test_name): | |
2030 return False | |
2031 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError() | |
2032 self.send_response(result) | |
2033 self.send_header('Content-Type', 'text/html') | |
2034 self.send_header('Content-Length', len(raw_reply)) | |
2035 self.end_headers() | |
2036 self.wfile.write(raw_reply) | |
2037 return True | |
2038 | |
2039 def ChromiumSyncTransientErrorOpHandler(self): | |
2040 test_name = "/chromiumsync/transienterror" | |
2041 if not self._ShouldHandleRequest(test_name): | |
2042 return False | |
2043 result, raw_reply = self.server._sync_handler.HandleSetTransientError() | |
2044 self.send_response(result) | |
2045 self.send_header('Content-Type', 'text/html') | |
2046 self.send_header('Content-Length', len(raw_reply)) | |
2047 self.end_headers() | |
2048 self.wfile.write(raw_reply) | |
2049 return True | |
2050 | |
2051 def ChromiumSyncErrorOpHandler(self): | |
2052 test_name = "/chromiumsync/error" | |
2053 if not self._ShouldHandleRequest(test_name): | |
2054 return False | |
2055 result, raw_reply = self.server._sync_handler.HandleSetInducedError( | |
2056 self.path) | |
2057 self.send_response(result) | |
2058 self.send_header('Content-Type', 'text/html') | |
2059 self.send_header('Content-Length', len(raw_reply)) | |
2060 self.end_headers() | |
2061 self.wfile.write(raw_reply) | |
2062 return True | |
2063 | |
2064 def ChromiumSyncSyncTabFaviconsOpHandler(self): | |
2065 test_name = "/chromiumsync/synctabfavicons" | |
2066 if not self._ShouldHandleRequest(test_name): | |
2067 return False | |
2068 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons() | |
2069 self.send_response(result) | |
2070 self.send_header('Content-Type', 'text/html') | |
2071 self.send_header('Content-Length', len(raw_reply)) | |
2072 self.end_headers() | |
2073 self.wfile.write(raw_reply) | |
2074 return True | |
2075 | |
2076 def ChromiumSyncCreateSyncedBookmarksOpHandler(self): | |
2077 test_name = "/chromiumsync/createsyncedbookmarks" | |
2078 if not self._ShouldHandleRequest(test_name): | |
2079 return False | |
2080 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks() | |
2081 self.send_response(result) | |
2082 self.send_header('Content-Type', 'text/html') | |
2083 self.send_header('Content-Length', len(raw_reply)) | |
2084 self.end_headers() | |
2085 self.wfile.write(raw_reply) | |
2086 return True | |
2087 | |
2088 def ChromiumSyncEnableKeystoreEncryptionOpHandler(self): | |
2089 test_name = "/chromiumsync/enablekeystoreencryption" | |
2090 if not self._ShouldHandleRequest(test_name): | |
2091 return False | |
2092 result, raw_reply = ( | |
2093 self.server._sync_handler.HandleEnableKeystoreEncryption()) | |
2094 self.send_response(result) | |
2095 self.send_header('Content-Type', 'text/html') | |
2096 self.send_header('Content-Length', len(raw_reply)) | |
2097 self.end_headers() | |
2098 self.wfile.write(raw_reply) | |
2099 return True | |
2100 | |
2101 def ChromiumSyncRotateKeystoreKeysOpHandler(self): | |
2102 test_name = "/chromiumsync/rotatekeystorekeys" | |
2103 if not self._ShouldHandleRequest(test_name): | |
2104 return False | |
2105 result, raw_reply = ( | |
2106 self.server._sync_handler.HandleRotateKeystoreKeys()) | |
2107 self.send_response(result) | |
2108 self.send_header('Content-Type', 'text/html') | |
2109 self.send_header('Content-Length', len(raw_reply)) | |
2110 self.end_headers() | |
2111 self.wfile.write(raw_reply) | |
2112 return True | |
2113 | |
2114 | |
2115 class OCSPHandler(BasePageHandler): | |
2116 def __init__(self, request, client_address, socket_server): | 1627 def __init__(self, request, client_address, socket_server): |
2117 handlers = [self.OCSPResponse] | 1628 handlers = [self.OCSPResponse] |
2118 self.ocsp_response = socket_server.ocsp_response | 1629 self.ocsp_response = socket_server.ocsp_response |
2119 BasePageHandler.__init__(self, request, client_address, socket_server, | 1630 testserver_base.BasePageHandler.__init__(self, request, client_address, |
2120 [], handlers, [], handlers, []) | 1631 socket_server, [], handlers, [], |
| 1632 handlers, []) |
2121 | 1633 |
2122 def OCSPResponse(self): | 1634 def OCSPResponse(self): |
2123 self.send_response(200) | 1635 self.send_response(200) |
2124 self.send_header('Content-Type', 'application/ocsp-response') | 1636 self.send_header('Content-Type', 'application/ocsp-response') |
2125 self.send_header('Content-Length', str(len(self.ocsp_response))) | 1637 self.send_header('Content-Length', str(len(self.ocsp_response))) |
2126 self.end_headers() | 1638 self.end_headers() |
2127 | 1639 |
2128 self.wfile.write(self.ocsp_response) | 1640 self.wfile.write(self.ocsp_response) |
2129 | 1641 |
2130 | 1642 |
(...skipping 258 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2389 raise testserver_base.OptionError( | 1901 raise testserver_base.OptionError( |
2390 'one trusted client CA file should be specified') | 1902 'one trusted client CA file should be specified') |
2391 if not os.path.isfile(self.options.ssl_client_ca[0]): | 1903 if not os.path.isfile(self.options.ssl_client_ca[0]): |
2392 raise testserver_base.OptionError( | 1904 raise testserver_base.OptionError( |
2393 'specified trusted client CA file not found: ' + | 1905 'specified trusted client CA file not found: ' + |
2394 self.options.ssl_client_ca[0] + ' exiting...') | 1906 self.options.ssl_client_ca[0] + ' exiting...') |
2395 websocket_options.tls_client_ca = self.options.ssl_client_ca[0] | 1907 websocket_options.tls_client_ca = self.options.ssl_client_ca[0] |
2396 server = WebSocketServer(websocket_options) | 1908 server = WebSocketServer(websocket_options) |
2397 print 'WebSocket server started on %s:%d...' % (host, server.server_port) | 1909 print 'WebSocket server started on %s:%d...' % (host, server.server_port) |
2398 server_data['port'] = server.server_port | 1910 server_data['port'] = server.server_port |
2399 elif self.options.server_type == SERVER_SYNC: | |
2400 xmpp_port = self.options.xmpp_port | |
2401 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler) | |
2402 print 'Sync HTTP server started on port %d...' % server.server_port | |
2403 print 'Sync XMPP server started on port %d...' % server.xmpp_port | |
2404 server_data['port'] = server.server_port | |
2405 server_data['xmpp_port'] = server.xmpp_port | |
2406 elif self.options.server_type == SERVER_TCP_ECHO: | 1911 elif self.options.server_type == SERVER_TCP_ECHO: |
2407 # Used for generating the key (randomly) that encodes the "echo request" | 1912 # Used for generating the key (randomly) that encodes the "echo request" |
2408 # message. | 1913 # message. |
2409 random.seed() | 1914 random.seed() |
2410 server = TCPEchoServer((host, port), TCPEchoHandler) | 1915 server = TCPEchoServer((host, port), TCPEchoHandler) |
2411 print 'Echo TCP server started on port %d...' % server.server_port | 1916 print 'Echo TCP server started on port %d...' % server.server_port |
2412 server_data['port'] = server.server_port | 1917 server_data['port'] = server.server_port |
2413 elif self.options.server_type == SERVER_UDP_ECHO: | 1918 elif self.options.server_type == SERVER_UDP_ECHO: |
2414 # Used for generating the key (randomly) that encodes the "echo request" | 1919 # Used for generating the key (randomly) that encodes the "echo request" |
2415 # message. | 1920 # message. |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2459 | 1964 |
2460 if self.__ocsp_server: | 1965 if self.__ocsp_server: |
2461 self.__ocsp_server.stop_serving() | 1966 self.__ocsp_server.stop_serving() |
2462 | 1967 |
2463 def add_options(self): | 1968 def add_options(self): |
2464 testserver_base.TestServerRunner.add_options(self) | 1969 testserver_base.TestServerRunner.add_options(self) |
2465 self.option_parser.add_option('-f', '--ftp', action='store_const', | 1970 self.option_parser.add_option('-f', '--ftp', action='store_const', |
2466 const=SERVER_FTP, default=SERVER_HTTP, | 1971 const=SERVER_FTP, default=SERVER_HTTP, |
2467 dest='server_type', | 1972 dest='server_type', |
2468 help='start up an FTP server.') | 1973 help='start up an FTP server.') |
2469 self.option_parser.add_option('--sync', action='store_const', | |
2470 const=SERVER_SYNC, default=SERVER_HTTP, | |
2471 dest='server_type', | |
2472 help='start up a sync server.') | |
2473 self.option_parser.add_option('--tcp-echo', action='store_const', | 1974 self.option_parser.add_option('--tcp-echo', action='store_const', |
2474 const=SERVER_TCP_ECHO, default=SERVER_HTTP, | 1975 const=SERVER_TCP_ECHO, default=SERVER_HTTP, |
2475 dest='server_type', | 1976 dest='server_type', |
2476 help='start up a tcp echo server.') | 1977 help='start up a tcp echo server.') |
2477 self.option_parser.add_option('--udp-echo', action='store_const', | 1978 self.option_parser.add_option('--udp-echo', action='store_const', |
2478 const=SERVER_UDP_ECHO, default=SERVER_HTTP, | 1979 const=SERVER_UDP_ECHO, default=SERVER_HTTP, |
2479 dest='server_type', | 1980 dest='server_type', |
2480 help='start up a udp echo server.') | 1981 help='start up a udp echo server.') |
2481 self.option_parser.add_option('--basic-auth-proxy', action='store_const', | 1982 self.option_parser.add_option('--basic-auth-proxy', action='store_const', |
2482 const=SERVER_BASIC_AUTH_PROXY, | 1983 const=SERVER_BASIC_AUTH_PROXY, |
2483 default=SERVER_HTTP, dest='server_type', | 1984 default=SERVER_HTTP, dest='server_type', |
2484 help='start up a proxy server which requires ' | 1985 help='start up a proxy server which requires ' |
2485 'basic authentication.') | 1986 'basic authentication.') |
2486 self.option_parser.add_option('--websocket', action='store_const', | 1987 self.option_parser.add_option('--websocket', action='store_const', |
2487 const=SERVER_WEBSOCKET, default=SERVER_HTTP, | 1988 const=SERVER_WEBSOCKET, default=SERVER_HTTP, |
2488 dest='server_type', | 1989 dest='server_type', |
2489 help='start up a WebSocket server.') | 1990 help='start up a WebSocket server.') |
2490 self.option_parser.add_option('--xmpp-port', default='0', type='int', | |
2491 help='Port used by the XMPP server. If ' | |
2492 'unspecified, the XMPP server will listen on ' | |
2493 'an ephemeral port.') | |
2494 self.option_parser.add_option('--data-dir', dest='data_dir', | |
2495 help='Directory from which to read the ' | |
2496 'files.') | |
2497 self.option_parser.add_option('--https', action='store_true', | 1991 self.option_parser.add_option('--https', action='store_true', |
2498 dest='https', help='Specify that https ' | 1992 dest='https', help='Specify that https ' |
2499 'should be used.') | 1993 'should be used.') |
2500 self.option_parser.add_option('--cert-and-key-file', | 1994 self.option_parser.add_option('--cert-and-key-file', |
2501 dest='cert_and_key_file', help='specify the ' | 1995 dest='cert_and_key_file', help='specify the ' |
2502 'path to the file containing the certificate ' | 1996 'path to the file containing the certificate ' |
2503 'and private key for the server in PEM ' | 1997 'and private key for the server in PEM ' |
2504 'format') | 1998 'format') |
2505 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok', | 1999 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok', |
2506 help='The type of OCSP response generated ' | 2000 help='The type of OCSP response generated ' |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2549 'load multipe keys into the server. If the ' | 2043 'load multipe keys into the server. If the ' |
2550 'server has multiple keys, it will rotate ' | 2044 'server has multiple keys, it will rotate ' |
2551 'through them in at each request a ' | 2045 'through them in at each request a ' |
2552 'round-robin fashion. The server will ' | 2046 'round-robin fashion. The server will ' |
2553 'generate a random key if none is specified ' | 2047 'generate a random key if none is specified ' |
2554 'on the command line.') | 2048 'on the command line.') |
2555 | 2049 |
2556 | 2050 |
2557 if __name__ == '__main__': | 2051 if __name__ == '__main__': |
2558 sys.exit(ServerRunner().main()) | 2052 sys.exit(ServerRunner().main()) |
OLD | NEW |