testserver.py revision 5821806d5e7f356e8fa4b058a389a808ea183019
1#!/usr/bin/env python 2# Copyright (c) 2012 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""This is a simple HTTP/FTP/SYNC/TCP/UDP/ server used for testing Chrome. 7 8It supports several test URLs, as specified by the handlers in TestPageHandler. 9By 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. 12It 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. 14""" 15 16import asyncore 17import base64 18import BaseHTTPServer 19import cgi 20import errno 21import hashlib 22import httplib 23import json 24import logging 25import minica 26import os 27import random 28import re 29import select 30import socket 31import SocketServer 32import sys 33import threading 34import time 35import urllib 36import urlparse 37import zlib 38 39import echo_message 40from mod_pywebsocket.standalone import WebSocketServer 41import pyftpdlib.ftpserver 42import testserver_base 43import tlslite 44import tlslite.api 45 46BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 47 48SERVER_HTTP = 0 49SERVER_FTP = 1 50SERVER_SYNC = 2 51SERVER_TCP_ECHO = 3 52SERVER_UDP_ECHO = 4 53SERVER_BASIC_AUTH_PROXY = 5 54SERVER_WEBSOCKET = 6 55 56# Default request queue size for WebSocketServer. 57_DEFAULT_REQUEST_QUEUE_SIZE = 128 58 59 60# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 . 61debug_output = sys.stderr 62def debug(string): 63 debug_output.write(string + "\n") 64 debug_output.flush() 65 66 67class WebSocketOptions: 68 """Holds options for WebSocketServer.""" 69 70 def __init__(self, host, port, data_dir): 71 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE 72 self.server_host = host 73 self.port = port 74 self.websock_handlers = data_dir 75 self.scan_dir = None 76 self.allow_handlers_outside_root_dir = False 77 self.websock_handlers_map_file = None 78 self.cgi_directories = [] 79 self.is_executable_method = None 80 self.allow_draft75 = False 81 self.strict = True 82 83 self.use_tls = False 84 self.private_key = None 85 self.certificate = None 86 self.tls_client_auth = False 87 self.tls_client_ca = None 88 self.use_basic_auth = False 89 90 91class RecordingSSLSessionCache(object): 92 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of 93 lookups and inserts in order to test session cache behaviours.""" 94 95 def __init__(self): 96 self.log = [] 97 98 def __getitem__(self, sessionID): 99 self.log.append(('lookup', sessionID)) 100 raise KeyError() 101 102 def __setitem__(self, sessionID, session): 103 self.log.append(('insert', sessionID)) 104 105 106class ClientRestrictingServerMixIn: 107 """Implements verify_request to limit connections to our configured IP 108 address.""" 109 110 def verify_request(self, _request, client_address): 111 return client_address[0] == self.server_address[0] 112 113 114class StoppableHTTPServer(BaseHTTPServer.HTTPServer): 115 """This is a specialization of BaseHTTPServer to allow it 116 to be exited cleanly (by setting its "stop" member to True).""" 117 118 def serve_forever(self): 119 self.stop = False 120 self.nonce_time = None 121 while not self.stop: 122 self.handle_request() 123 self.socket.close() 124 125 126class HTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer): 127 """This is a specialization of StoppableHTTPServer that adds client 128 verification.""" 129 130 pass 131 132class OCSPServer(ClientRestrictingServerMixIn, BaseHTTPServer.HTTPServer): 133 """This is a specialization of HTTPServer that serves an 134 OCSP response""" 135 136 def serve_forever_on_thread(self): 137 self.thread = threading.Thread(target = self.serve_forever, 138 name = "OCSPServerThread") 139 self.thread.start() 140 141 def stop_serving(self): 142 self.shutdown() 143 self.thread.join() 144 145 146class HTTPSServer(tlslite.api.TLSSocketServerMixIn, 147 ClientRestrictingServerMixIn, 148 StoppableHTTPServer): 149 """This is a specialization of StoppableHTTPServer that add https support and 150 client verification.""" 151 152 def __init__(self, server_address, request_hander_class, pem_cert_and_key, 153 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers, 154 record_resume_info, tls_intolerant): 155 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key) 156 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True) 157 self.ssl_client_auth = ssl_client_auth 158 self.ssl_client_cas = [] 159 self.tls_intolerant = tls_intolerant 160 161 for ca_file in ssl_client_cas: 162 s = open(ca_file).read() 163 x509 = tlslite.api.X509() 164 x509.parse(s) 165 self.ssl_client_cas.append(x509.subject) 166 self.ssl_handshake_settings = tlslite.api.HandshakeSettings() 167 if ssl_bulk_ciphers is not None: 168 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers 169 170 if record_resume_info: 171 # If record_resume_info is true then we'll replace the session cache with 172 # an object that records the lookups and inserts that it sees. 173 self.session_cache = RecordingSSLSessionCache() 174 else: 175 self.session_cache = tlslite.api.SessionCache() 176 StoppableHTTPServer.__init__(self, server_address, request_hander_class) 177 178 def handshake(self, tlsConnection): 179 """Creates the SSL connection.""" 180 181 try: 182 tlsConnection.handshakeServer(certChain=self.cert_chain, 183 privateKey=self.private_key, 184 sessionCache=self.session_cache, 185 reqCert=self.ssl_client_auth, 186 settings=self.ssl_handshake_settings, 187 reqCAs=self.ssl_client_cas, 188 tlsIntolerant=self.tls_intolerant) 189 tlsConnection.ignoreAbruptClose = True 190 return True 191 except tlslite.api.TLSAbruptCloseError: 192 # Ignore abrupt close. 193 return True 194 except tlslite.api.TLSError, error: 195 print "Handshake failure:", str(error) 196 return False 197 198 199class SyncHTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer): 200 """An HTTP server that handles sync commands.""" 201 202 def __init__(self, server_address, xmpp_port, request_handler_class): 203 # We import here to avoid pulling in chromiumsync's dependencies 204 # unless strictly necessary. 205 import chromiumsync 206 import xmppserver 207 StoppableHTTPServer.__init__(self, server_address, request_handler_class) 208 self._sync_handler = chromiumsync.TestServer() 209 self._xmpp_socket_map = {} 210 self._xmpp_server = xmppserver.XmppServer( 211 self._xmpp_socket_map, ('localhost', xmpp_port)) 212 self.xmpp_port = self._xmpp_server.getsockname()[1] 213 self.authenticated = True 214 215 def GetXmppServer(self): 216 return self._xmpp_server 217 218 def HandleCommand(self, query, raw_request): 219 return self._sync_handler.HandleCommand(query, raw_request) 220 221 def HandleRequestNoBlock(self): 222 """Handles a single request. 223 224 Copied from SocketServer._handle_request_noblock(). 225 """ 226 227 try: 228 request, client_address = self.get_request() 229 except socket.error: 230 return 231 if self.verify_request(request, client_address): 232 try: 233 self.process_request(request, client_address) 234 except Exception: 235 self.handle_error(request, client_address) 236 self.close_request(request) 237 238 def SetAuthenticated(self, auth_valid): 239 self.authenticated = auth_valid 240 241 def GetAuthenticated(self): 242 return self.authenticated 243 244 def serve_forever(self): 245 """This is a merge of asyncore.loop() and SocketServer.serve_forever(). 246 """ 247 248 def HandleXmppSocket(fd, socket_map, handler): 249 """Runs the handler for the xmpp connection for fd. 250 251 Adapted from asyncore.read() et al. 252 """ 253 254 xmpp_connection = socket_map.get(fd) 255 # This could happen if a previous handler call caused fd to get 256 # removed from socket_map. 257 if xmpp_connection is None: 258 return 259 try: 260 handler(xmpp_connection) 261 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit): 262 raise 263 except: 264 xmpp_connection.handle_error() 265 266 while True: 267 read_fds = [ self.fileno() ] 268 write_fds = [] 269 exceptional_fds = [] 270 271 for fd, xmpp_connection in self._xmpp_socket_map.items(): 272 is_r = xmpp_connection.readable() 273 is_w = xmpp_connection.writable() 274 if is_r: 275 read_fds.append(fd) 276 if is_w: 277 write_fds.append(fd) 278 if is_r or is_w: 279 exceptional_fds.append(fd) 280 281 try: 282 read_fds, write_fds, exceptional_fds = ( 283 select.select(read_fds, write_fds, exceptional_fds)) 284 except select.error, err: 285 if err.args[0] != errno.EINTR: 286 raise 287 else: 288 continue 289 290 for fd in read_fds: 291 if fd == self.fileno(): 292 self.HandleRequestNoBlock() 293 continue 294 HandleXmppSocket(fd, self._xmpp_socket_map, 295 asyncore.dispatcher.handle_read_event) 296 297 for fd in write_fds: 298 HandleXmppSocket(fd, self._xmpp_socket_map, 299 asyncore.dispatcher.handle_write_event) 300 301 for fd in exceptional_fds: 302 HandleXmppSocket(fd, self._xmpp_socket_map, 303 asyncore.dispatcher.handle_expt_event) 304 305 306class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer): 307 """This is a specialization of FTPServer that adds client verification.""" 308 309 pass 310 311 312class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer): 313 """A TCP echo server that echoes back what it has received.""" 314 315 def server_bind(self): 316 """Override server_bind to store the server name.""" 317 318 SocketServer.TCPServer.server_bind(self) 319 host, port = self.socket.getsockname()[:2] 320 self.server_name = socket.getfqdn(host) 321 self.server_port = port 322 323 def serve_forever(self): 324 self.stop = False 325 self.nonce_time = None 326 while not self.stop: 327 self.handle_request() 328 self.socket.close() 329 330 331class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer): 332 """A UDP echo server that echoes back what it has received.""" 333 334 def server_bind(self): 335 """Override server_bind to store the server name.""" 336 337 SocketServer.UDPServer.server_bind(self) 338 host, port = self.socket.getsockname()[:2] 339 self.server_name = socket.getfqdn(host) 340 self.server_port = port 341 342 def serve_forever(self): 343 self.stop = False 344 self.nonce_time = None 345 while not self.stop: 346 self.handle_request() 347 self.socket.close() 348 349 350class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler): 351 352 def __init__(self, request, client_address, socket_server, 353 connect_handlers, get_handlers, head_handlers, post_handlers, 354 put_handlers): 355 self._connect_handlers = connect_handlers 356 self._get_handlers = get_handlers 357 self._head_handlers = head_handlers 358 self._post_handlers = post_handlers 359 self._put_handlers = put_handlers 360 BaseHTTPServer.BaseHTTPRequestHandler.__init__( 361 self, request, client_address, socket_server) 362 363 def log_request(self, *args, **kwargs): 364 # Disable request logging to declutter test log output. 365 pass 366 367 def _ShouldHandleRequest(self, handler_name): 368 """Determines if the path can be handled by the handler. 369 370 We consider a handler valid if the path begins with the 371 handler name. It can optionally be followed by "?*", "/*". 372 """ 373 374 pattern = re.compile('%s($|\?|/).*' % handler_name) 375 return pattern.match(self.path) 376 377 def do_CONNECT(self): 378 for handler in self._connect_handlers: 379 if handler(): 380 return 381 382 def do_GET(self): 383 for handler in self._get_handlers: 384 if handler(): 385 return 386 387 def do_HEAD(self): 388 for handler in self._head_handlers: 389 if handler(): 390 return 391 392 def do_POST(self): 393 for handler in self._post_handlers: 394 if handler(): 395 return 396 397 def do_PUT(self): 398 for handler in self._put_handlers: 399 if handler(): 400 return 401 402 403class TestPageHandler(BasePageHandler): 404 405 def __init__(self, request, client_address, socket_server): 406 connect_handlers = [ 407 self.RedirectConnectHandler, 408 self.ServerAuthConnectHandler, 409 self.DefaultConnectResponseHandler] 410 get_handlers = [ 411 self.NoCacheMaxAgeTimeHandler, 412 self.NoCacheTimeHandler, 413 self.CacheTimeHandler, 414 self.CacheExpiresHandler, 415 self.CacheProxyRevalidateHandler, 416 self.CachePrivateHandler, 417 self.CachePublicHandler, 418 self.CacheSMaxAgeHandler, 419 self.CacheMustRevalidateHandler, 420 self.CacheMustRevalidateMaxAgeHandler, 421 self.CacheNoStoreHandler, 422 self.CacheNoStoreMaxAgeHandler, 423 self.CacheNoTransformHandler, 424 self.DownloadHandler, 425 self.DownloadFinishHandler, 426 self.EchoHeader, 427 self.EchoHeaderCache, 428 self.EchoAllHandler, 429 self.ZipFileHandler, 430 self.GDataAuthHandler, 431 self.GDataDocumentsFeedQueryHandler, 432 self.FileHandler, 433 self.SetCookieHandler, 434 self.SetManyCookiesHandler, 435 self.ExpectAndSetCookieHandler, 436 self.SetHeaderHandler, 437 self.AuthBasicHandler, 438 self.AuthDigestHandler, 439 self.SlowServerHandler, 440 self.ChunkedServerHandler, 441 self.ContentTypeHandler, 442 self.NoContentHandler, 443 self.ServerRedirectHandler, 444 self.ClientRedirectHandler, 445 self.MultipartHandler, 446 self.MultipartSlowHandler, 447 self.GetSSLSessionCacheHandler, 448 self.CloseSocketHandler, 449 self.DefaultResponseHandler] 450 post_handlers = [ 451 self.EchoTitleHandler, 452 self.EchoHandler, 453 self.DeviceManagementHandler, 454 self.PostOnlyFileHandler] + get_handlers 455 put_handlers = [ 456 self.EchoTitleHandler, 457 self.EchoHandler] + get_handlers 458 head_handlers = [ 459 self.FileHandler, 460 self.DefaultResponseHandler] 461 462 self._mime_types = { 463 'crx' : 'application/x-chrome-extension', 464 'exe' : 'application/octet-stream', 465 'gif': 'image/gif', 466 'jpeg' : 'image/jpeg', 467 'jpg' : 'image/jpeg', 468 'json': 'application/json', 469 'pdf' : 'application/pdf', 470 'xml' : 'text/xml' 471 } 472 self._default_mime_type = 'text/html' 473 474 BasePageHandler.__init__(self, request, client_address, socket_server, 475 connect_handlers, get_handlers, head_handlers, 476 post_handlers, put_handlers) 477 478 def GetMIMETypeFromName(self, file_name): 479 """Returns the mime type for the specified file_name. So far it only looks 480 at the file extension.""" 481 482 (_shortname, extension) = os.path.splitext(file_name.split("?")[0]) 483 if len(extension) == 0: 484 # no extension. 485 return self._default_mime_type 486 487 # extension starts with a dot, so we need to remove it 488 return self._mime_types.get(extension[1:], self._default_mime_type) 489 490 def NoCacheMaxAgeTimeHandler(self): 491 """This request handler yields a page with the title set to the current 492 system time, and no caching requested.""" 493 494 if not self._ShouldHandleRequest("/nocachetime/maxage"): 495 return False 496 497 self.send_response(200) 498 self.send_header('Cache-Control', 'max-age=0') 499 self.send_header('Content-Type', 'text/html') 500 self.end_headers() 501 502 self.wfile.write('<html><head><title>%s</title></head></html>' % 503 time.time()) 504 505 return True 506 507 def NoCacheTimeHandler(self): 508 """This request handler yields a page with the title set to the current 509 system time, and no caching requested.""" 510 511 if not self._ShouldHandleRequest("/nocachetime"): 512 return False 513 514 self.send_response(200) 515 self.send_header('Cache-Control', 'no-cache') 516 self.send_header('Content-Type', 'text/html') 517 self.end_headers() 518 519 self.wfile.write('<html><head><title>%s</title></head></html>' % 520 time.time()) 521 522 return True 523 524 def CacheTimeHandler(self): 525 """This request handler yields a page with the title set to the current 526 system time, and allows caching for one minute.""" 527 528 if not self._ShouldHandleRequest("/cachetime"): 529 return False 530 531 self.send_response(200) 532 self.send_header('Cache-Control', 'max-age=60') 533 self.send_header('Content-Type', 'text/html') 534 self.end_headers() 535 536 self.wfile.write('<html><head><title>%s</title></head></html>' % 537 time.time()) 538 539 return True 540 541 def CacheExpiresHandler(self): 542 """This request handler yields a page with the title set to the current 543 system time, and set the page to expire on 1 Jan 2099.""" 544 545 if not self._ShouldHandleRequest("/cache/expires"): 546 return False 547 548 self.send_response(200) 549 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT') 550 self.send_header('Content-Type', 'text/html') 551 self.end_headers() 552 553 self.wfile.write('<html><head><title>%s</title></head></html>' % 554 time.time()) 555 556 return True 557 558 def CacheProxyRevalidateHandler(self): 559 """This request handler yields a page with the title set to the current 560 system time, and allows caching for 60 seconds""" 561 562 if not self._ShouldHandleRequest("/cache/proxy-revalidate"): 563 return False 564 565 self.send_response(200) 566 self.send_header('Content-Type', 'text/html') 567 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate') 568 self.end_headers() 569 570 self.wfile.write('<html><head><title>%s</title></head></html>' % 571 time.time()) 572 573 return True 574 575 def CachePrivateHandler(self): 576 """This request handler yields a page with the title set to the current 577 system time, and allows caching for 5 seconds.""" 578 579 if not self._ShouldHandleRequest("/cache/private"): 580 return False 581 582 self.send_response(200) 583 self.send_header('Content-Type', 'text/html') 584 self.send_header('Cache-Control', 'max-age=3, private') 585 self.end_headers() 586 587 self.wfile.write('<html><head><title>%s</title></head></html>' % 588 time.time()) 589 590 return True 591 592 def CachePublicHandler(self): 593 """This request handler yields a page with the title set to the current 594 system time, and allows caching for 5 seconds.""" 595 596 if not self._ShouldHandleRequest("/cache/public"): 597 return False 598 599 self.send_response(200) 600 self.send_header('Content-Type', 'text/html') 601 self.send_header('Cache-Control', 'max-age=3, public') 602 self.end_headers() 603 604 self.wfile.write('<html><head><title>%s</title></head></html>' % 605 time.time()) 606 607 return True 608 609 def CacheSMaxAgeHandler(self): 610 """This request handler yields a page with the title set to the current 611 system time, and does not allow for caching.""" 612 613 if not self._ShouldHandleRequest("/cache/s-maxage"): 614 return False 615 616 self.send_response(200) 617 self.send_header('Content-Type', 'text/html') 618 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0') 619 self.end_headers() 620 621 self.wfile.write('<html><head><title>%s</title></head></html>' % 622 time.time()) 623 624 return True 625 626 def CacheMustRevalidateHandler(self): 627 """This request handler yields a page with the title set to the current 628 system time, and does not allow caching.""" 629 630 if not self._ShouldHandleRequest("/cache/must-revalidate"): 631 return False 632 633 self.send_response(200) 634 self.send_header('Content-Type', 'text/html') 635 self.send_header('Cache-Control', 'must-revalidate') 636 self.end_headers() 637 638 self.wfile.write('<html><head><title>%s</title></head></html>' % 639 time.time()) 640 641 return True 642 643 def CacheMustRevalidateMaxAgeHandler(self): 644 """This request handler yields a page with the title set to the current 645 system time, and does not allow caching event though max-age of 60 646 seconds is specified.""" 647 648 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"): 649 return False 650 651 self.send_response(200) 652 self.send_header('Content-Type', 'text/html') 653 self.send_header('Cache-Control', 'max-age=60, must-revalidate') 654 self.end_headers() 655 656 self.wfile.write('<html><head><title>%s</title></head></html>' % 657 time.time()) 658 659 return True 660 661 def CacheNoStoreHandler(self): 662 """This request handler yields a page with the title set to the current 663 system time, and does not allow the page to be stored.""" 664 665 if not self._ShouldHandleRequest("/cache/no-store"): 666 return False 667 668 self.send_response(200) 669 self.send_header('Content-Type', 'text/html') 670 self.send_header('Cache-Control', 'no-store') 671 self.end_headers() 672 673 self.wfile.write('<html><head><title>%s</title></head></html>' % 674 time.time()) 675 676 return True 677 678 def CacheNoStoreMaxAgeHandler(self): 679 """This request handler yields a page with the title set to the current 680 system time, and does not allow the page to be stored even though max-age 681 of 60 seconds is specified.""" 682 683 if not self._ShouldHandleRequest("/cache/no-store/max-age"): 684 return False 685 686 self.send_response(200) 687 self.send_header('Content-Type', 'text/html') 688 self.send_header('Cache-Control', 'max-age=60, no-store') 689 self.end_headers() 690 691 self.wfile.write('<html><head><title>%s</title></head></html>' % 692 time.time()) 693 694 return True 695 696 697 def CacheNoTransformHandler(self): 698 """This request handler yields a page with the title set to the current 699 system time, and does not allow the content to transformed during 700 user-agent caching""" 701 702 if not self._ShouldHandleRequest("/cache/no-transform"): 703 return False 704 705 self.send_response(200) 706 self.send_header('Content-Type', 'text/html') 707 self.send_header('Cache-Control', 'no-transform') 708 self.end_headers() 709 710 self.wfile.write('<html><head><title>%s</title></head></html>' % 711 time.time()) 712 713 return True 714 715 def EchoHeader(self): 716 """This handler echoes back the value of a specific request header.""" 717 718 return self.EchoHeaderHelper("/echoheader") 719 720 def EchoHeaderCache(self): 721 """This function echoes back the value of a specific request header while 722 allowing caching for 16 hours.""" 723 724 return self.EchoHeaderHelper("/echoheadercache") 725 726 def EchoHeaderHelper(self, echo_header): 727 """This function echoes back the value of the request header passed in.""" 728 729 if not self._ShouldHandleRequest(echo_header): 730 return False 731 732 query_char = self.path.find('?') 733 if query_char != -1: 734 header_name = self.path[query_char+1:] 735 736 self.send_response(200) 737 self.send_header('Content-Type', 'text/plain') 738 if echo_header == '/echoheadercache': 739 self.send_header('Cache-control', 'max-age=60000') 740 else: 741 self.send_header('Cache-control', 'no-cache') 742 # insert a vary header to properly indicate that the cachability of this 743 # request is subject to value of the request header being echoed. 744 if len(header_name) > 0: 745 self.send_header('Vary', header_name) 746 self.end_headers() 747 748 if len(header_name) > 0: 749 self.wfile.write(self.headers.getheader(header_name)) 750 751 return True 752 753 def ReadRequestBody(self): 754 """This function reads the body of the current HTTP request, handling 755 both plain and chunked transfer encoded requests.""" 756 757 if self.headers.getheader('transfer-encoding') != 'chunked': 758 length = int(self.headers.getheader('content-length')) 759 return self.rfile.read(length) 760 761 # Read the request body as chunks. 762 body = "" 763 while True: 764 line = self.rfile.readline() 765 length = int(line, 16) 766 if length == 0: 767 self.rfile.readline() 768 break 769 body += self.rfile.read(length) 770 self.rfile.read(2) 771 return body 772 773 def EchoHandler(self): 774 """This handler just echoes back the payload of the request, for testing 775 form submission.""" 776 777 if not self._ShouldHandleRequest("/echo"): 778 return False 779 780 self.send_response(200) 781 self.send_header('Content-Type', 'text/html') 782 self.end_headers() 783 self.wfile.write(self.ReadRequestBody()) 784 return True 785 786 def EchoTitleHandler(self): 787 """This handler is like Echo, but sets the page title to the request.""" 788 789 if not self._ShouldHandleRequest("/echotitle"): 790 return False 791 792 self.send_response(200) 793 self.send_header('Content-Type', 'text/html') 794 self.end_headers() 795 request = self.ReadRequestBody() 796 self.wfile.write('<html><head><title>') 797 self.wfile.write(request) 798 self.wfile.write('</title></head></html>') 799 return True 800 801 def EchoAllHandler(self): 802 """This handler yields a (more) human-readable page listing information 803 about the request header & contents.""" 804 805 if not self._ShouldHandleRequest("/echoall"): 806 return False 807 808 self.send_response(200) 809 self.send_header('Content-Type', 'text/html') 810 self.end_headers() 811 self.wfile.write('<html><head><style>' 812 'pre { border: 1px solid black; margin: 5px; padding: 5px }' 813 '</style></head><body>' 814 '<div style="float: right">' 815 '<a href="/echo">back to referring page</a></div>' 816 '<h1>Request Body:</h1><pre>') 817 818 if self.command == 'POST' or self.command == 'PUT': 819 qs = self.ReadRequestBody() 820 params = cgi.parse_qs(qs, keep_blank_values=1) 821 822 for param in params: 823 self.wfile.write('%s=%s\n' % (param, params[param][0])) 824 825 self.wfile.write('</pre>') 826 827 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers) 828 829 self.wfile.write('</body></html>') 830 return True 831 832 def DownloadHandler(self): 833 """This handler sends a downloadable file with or without reporting 834 the size (6K).""" 835 836 if self.path.startswith("/download-unknown-size"): 837 send_length = False 838 elif self.path.startswith("/download-known-size"): 839 send_length = True 840 else: 841 return False 842 843 # 844 # The test which uses this functionality is attempting to send 845 # small chunks of data to the client. Use a fairly large buffer 846 # so that we'll fill chrome's IO buffer enough to force it to 847 # actually write the data. 848 # See also the comments in the client-side of this test in 849 # download_uitest.cc 850 # 851 size_chunk1 = 35*1024 852 size_chunk2 = 10*1024 853 854 self.send_response(200) 855 self.send_header('Content-Type', 'application/octet-stream') 856 self.send_header('Cache-Control', 'max-age=0') 857 if send_length: 858 self.send_header('Content-Length', size_chunk1 + size_chunk2) 859 self.end_headers() 860 861 # First chunk of data: 862 self.wfile.write("*" * size_chunk1) 863 self.wfile.flush() 864 865 # handle requests until one of them clears this flag. 866 self.server.waitForDownload = True 867 while self.server.waitForDownload: 868 self.server.handle_request() 869 870 # Second chunk of data: 871 self.wfile.write("*" * size_chunk2) 872 return True 873 874 def DownloadFinishHandler(self): 875 """This handler just tells the server to finish the current download.""" 876 877 if not self._ShouldHandleRequest("/download-finish"): 878 return False 879 880 self.server.waitForDownload = False 881 self.send_response(200) 882 self.send_header('Content-Type', 'text/html') 883 self.send_header('Cache-Control', 'max-age=0') 884 self.end_headers() 885 return True 886 887 def _ReplaceFileData(self, data, query_parameters): 888 """Replaces matching substrings in a file. 889 890 If the 'replace_text' URL query parameter is present, it is expected to be 891 of the form old_text:new_text, which indicates that any old_text strings in 892 the file are replaced with new_text. Multiple 'replace_text' parameters may 893 be specified. 894 895 If the parameters are not present, |data| is returned. 896 """ 897 898 query_dict = cgi.parse_qs(query_parameters) 899 replace_text_values = query_dict.get('replace_text', []) 900 for replace_text_value in replace_text_values: 901 replace_text_args = replace_text_value.split(':') 902 if len(replace_text_args) != 2: 903 raise ValueError( 904 'replace_text must be of form old_text:new_text. Actual value: %s' % 905 replace_text_value) 906 old_text_b64, new_text_b64 = replace_text_args 907 old_text = base64.urlsafe_b64decode(old_text_b64) 908 new_text = base64.urlsafe_b64decode(new_text_b64) 909 data = data.replace(old_text, new_text) 910 return data 911 912 def ZipFileHandler(self): 913 """This handler sends the contents of the requested file in compressed form. 914 Can pass in a parameter that specifies that the content length be 915 C - the compressed size (OK), 916 U - the uncompressed size (Non-standard, but handled), 917 S - less than compressed (OK because we keep going), 918 M - larger than compressed but less than uncompressed (an error), 919 L - larger than uncompressed (an error) 920 Example: compressedfiles/Picture_1.doc?C 921 """ 922 923 prefix = "/compressedfiles/" 924 if not self.path.startswith(prefix): 925 return False 926 927 # Consume a request body if present. 928 if self.command == 'POST' or self.command == 'PUT' : 929 self.ReadRequestBody() 930 931 _, _, url_path, _, query, _ = urlparse.urlparse(self.path) 932 933 if not query in ('C', 'U', 'S', 'M', 'L'): 934 return False 935 936 sub_path = url_path[len(prefix):] 937 entries = sub_path.split('/') 938 file_path = os.path.join(self.server.data_dir, *entries) 939 if os.path.isdir(file_path): 940 file_path = os.path.join(file_path, 'index.html') 941 942 if not os.path.isfile(file_path): 943 print "File not found " + sub_path + " full path:" + file_path 944 self.send_error(404) 945 return True 946 947 f = open(file_path, "rb") 948 data = f.read() 949 uncompressed_len = len(data) 950 f.close() 951 952 # Compress the data. 953 data = zlib.compress(data) 954 compressed_len = len(data) 955 956 content_length = compressed_len 957 if query == 'U': 958 content_length = uncompressed_len 959 elif query == 'S': 960 content_length = compressed_len / 2 961 elif query == 'M': 962 content_length = (compressed_len + uncompressed_len) / 2 963 elif query == 'L': 964 content_length = compressed_len + uncompressed_len 965 966 self.send_response(200) 967 self.send_header('Content-Type', 'application/msword') 968 self.send_header('Content-encoding', 'deflate') 969 self.send_header('Connection', 'close') 970 self.send_header('Content-Length', content_length) 971 self.send_header('ETag', '\'' + file_path + '\'') 972 self.end_headers() 973 974 self.wfile.write(data) 975 976 return True 977 978 def FileHandler(self): 979 """This handler sends the contents of the requested file. Wow, it's like 980 a real webserver!""" 981 982 prefix = self.server.file_root_url 983 if not self.path.startswith(prefix): 984 return False 985 return self._FileHandlerHelper(prefix) 986 987 def PostOnlyFileHandler(self): 988 """This handler sends the contents of the requested file on a POST.""" 989 990 prefix = urlparse.urljoin(self.server.file_root_url, 'post/') 991 if not self.path.startswith(prefix): 992 return False 993 return self._FileHandlerHelper(prefix) 994 995 def _FileHandlerHelper(self, prefix): 996 request_body = '' 997 if self.command == 'POST' or self.command == 'PUT': 998 # Consume a request body if present. 999 request_body = self.ReadRequestBody() 1000 1001 _, _, url_path, _, query, _ = urlparse.urlparse(self.path) 1002 query_dict = cgi.parse_qs(query) 1003 1004 expected_body = query_dict.get('expected_body', []) 1005 if expected_body and request_body not in expected_body: 1006 self.send_response(404) 1007 self.end_headers() 1008 self.wfile.write('') 1009 return True 1010 1011 expected_headers = query_dict.get('expected_headers', []) 1012 for expected_header in expected_headers: 1013 header_name, expected_value = expected_header.split(':') 1014 if self.headers.getheader(header_name) != expected_value: 1015 self.send_response(404) 1016 self.end_headers() 1017 self.wfile.write('') 1018 return True 1019 1020 sub_path = url_path[len(prefix):] 1021 entries = sub_path.split('/') 1022 file_path = os.path.join(self.server.data_dir, *entries) 1023 if os.path.isdir(file_path): 1024 file_path = os.path.join(file_path, 'index.html') 1025 1026 if not os.path.isfile(file_path): 1027 print "File not found " + sub_path + " full path:" + file_path 1028 self.send_error(404) 1029 return True 1030 1031 f = open(file_path, "rb") 1032 data = f.read() 1033 f.close() 1034 1035 data = self._ReplaceFileData(data, query) 1036 1037 old_protocol_version = self.protocol_version 1038 1039 # If file.mock-http-headers exists, it contains the headers we 1040 # should send. Read them in and parse them. 1041 headers_path = file_path + '.mock-http-headers' 1042 if os.path.isfile(headers_path): 1043 f = open(headers_path, "r") 1044 1045 # "HTTP/1.1 200 OK" 1046 response = f.readline() 1047 http_major, http_minor, status_code = re.findall( 1048 'HTTP/(\d+).(\d+) (\d+)', response)[0] 1049 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor) 1050 self.send_response(int(status_code)) 1051 1052 for line in f: 1053 header_values = re.findall('(\S+):\s*(.*)', line) 1054 if len(header_values) > 0: 1055 # "name: value" 1056 name, value = header_values[0] 1057 self.send_header(name, value) 1058 f.close() 1059 else: 1060 # Could be more generic once we support mime-type sniffing, but for 1061 # now we need to set it explicitly. 1062 1063 range_header = self.headers.get('Range') 1064 if range_header and range_header.startswith('bytes='): 1065 # Note this doesn't handle all valid byte range_header values (i.e. 1066 # left open ended ones), just enough for what we needed so far. 1067 range_header = range_header[6:].split('-') 1068 start = int(range_header[0]) 1069 if range_header[1]: 1070 end = int(range_header[1]) 1071 else: 1072 end = len(data) - 1 1073 1074 self.send_response(206) 1075 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' + 1076 str(len(data))) 1077 self.send_header('Content-Range', content_range) 1078 data = data[start: end + 1] 1079 else: 1080 self.send_response(200) 1081 1082 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path)) 1083 self.send_header('Accept-Ranges', 'bytes') 1084 self.send_header('Content-Length', len(data)) 1085 self.send_header('ETag', '\'' + file_path + '\'') 1086 self.end_headers() 1087 1088 if (self.command != 'HEAD'): 1089 self.wfile.write(data) 1090 1091 self.protocol_version = old_protocol_version 1092 return True 1093 1094 def SetCookieHandler(self): 1095 """This handler just sets a cookie, for testing cookie handling.""" 1096 1097 if not self._ShouldHandleRequest("/set-cookie"): 1098 return False 1099 1100 query_char = self.path.find('?') 1101 if query_char != -1: 1102 cookie_values = self.path[query_char + 1:].split('&') 1103 else: 1104 cookie_values = ("",) 1105 self.send_response(200) 1106 self.send_header('Content-Type', 'text/html') 1107 for cookie_value in cookie_values: 1108 self.send_header('Set-Cookie', '%s' % cookie_value) 1109 self.end_headers() 1110 for cookie_value in cookie_values: 1111 self.wfile.write('%s' % cookie_value) 1112 return True 1113 1114 def SetManyCookiesHandler(self): 1115 """This handler just sets a given number of cookies, for testing handling 1116 of large numbers of cookies.""" 1117 1118 if not self._ShouldHandleRequest("/set-many-cookies"): 1119 return False 1120 1121 query_char = self.path.find('?') 1122 if query_char != -1: 1123 num_cookies = int(self.path[query_char + 1:]) 1124 else: 1125 num_cookies = 0 1126 self.send_response(200) 1127 self.send_header('', 'text/html') 1128 for _i in range(0, num_cookies): 1129 self.send_header('Set-Cookie', 'a=') 1130 self.end_headers() 1131 self.wfile.write('%d cookies were sent' % num_cookies) 1132 return True 1133 1134 def ExpectAndSetCookieHandler(self): 1135 """Expects some cookies to be sent, and if they are, sets more cookies. 1136 1137 The expect parameter specifies a required cookie. May be specified multiple 1138 times. 1139 The set parameter specifies a cookie to set if all required cookies are 1140 preset. May be specified multiple times. 1141 The data parameter specifies the response body data to be returned.""" 1142 1143 if not self._ShouldHandleRequest("/expect-and-set-cookie"): 1144 return False 1145 1146 _, _, _, _, query, _ = urlparse.urlparse(self.path) 1147 query_dict = cgi.parse_qs(query) 1148 cookies = set() 1149 if 'Cookie' in self.headers: 1150 cookie_header = self.headers.getheader('Cookie') 1151 cookies.update([s.strip() for s in cookie_header.split(';')]) 1152 got_all_expected_cookies = True 1153 for expected_cookie in query_dict.get('expect', []): 1154 if expected_cookie not in cookies: 1155 got_all_expected_cookies = False 1156 self.send_response(200) 1157 self.send_header('Content-Type', 'text/html') 1158 if got_all_expected_cookies: 1159 for cookie_value in query_dict.get('set', []): 1160 self.send_header('Set-Cookie', '%s' % cookie_value) 1161 self.end_headers() 1162 for data_value in query_dict.get('data', []): 1163 self.wfile.write(data_value) 1164 return True 1165 1166 def SetHeaderHandler(self): 1167 """This handler sets a response header. Parameters are in the 1168 key%3A%20value&key2%3A%20value2 format.""" 1169 1170 if not self._ShouldHandleRequest("/set-header"): 1171 return False 1172 1173 query_char = self.path.find('?') 1174 if query_char != -1: 1175 headers_values = self.path[query_char + 1:].split('&') 1176 else: 1177 headers_values = ("",) 1178 self.send_response(200) 1179 self.send_header('Content-Type', 'text/html') 1180 for header_value in headers_values: 1181 header_value = urllib.unquote(header_value) 1182 (key, value) = header_value.split(': ', 1) 1183 self.send_header(key, value) 1184 self.end_headers() 1185 for header_value in headers_values: 1186 self.wfile.write('%s' % header_value) 1187 return True 1188 1189 def AuthBasicHandler(self): 1190 """This handler tests 'Basic' authentication. It just sends a page with 1191 title 'user/pass' if you succeed.""" 1192 1193 if not self._ShouldHandleRequest("/auth-basic"): 1194 return False 1195 1196 username = userpass = password = b64str = "" 1197 expected_password = 'secret' 1198 realm = 'testrealm' 1199 set_cookie_if_challenged = False 1200 1201 _, _, url_path, _, query, _ = urlparse.urlparse(self.path) 1202 query_params = cgi.parse_qs(query, True) 1203 if 'set-cookie-if-challenged' in query_params: 1204 set_cookie_if_challenged = True 1205 if 'password' in query_params: 1206 expected_password = query_params['password'][0] 1207 if 'realm' in query_params: 1208 realm = query_params['realm'][0] 1209 1210 auth = self.headers.getheader('authorization') 1211 try: 1212 if not auth: 1213 raise Exception('no auth') 1214 b64str = re.findall(r'Basic (\S+)', auth)[0] 1215 userpass = base64.b64decode(b64str) 1216 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0] 1217 if password != expected_password: 1218 raise Exception('wrong password') 1219 except Exception, e: 1220 # Authentication failed. 1221 self.send_response(401) 1222 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm) 1223 self.send_header('Content-Type', 'text/html') 1224 if set_cookie_if_challenged: 1225 self.send_header('Set-Cookie', 'got_challenged=true') 1226 self.end_headers() 1227 self.wfile.write('<html><head>') 1228 self.wfile.write('<title>Denied: %s</title>' % e) 1229 self.wfile.write('</head><body>') 1230 self.wfile.write('auth=%s<p>' % auth) 1231 self.wfile.write('b64str=%s<p>' % b64str) 1232 self.wfile.write('username: %s<p>' % username) 1233 self.wfile.write('userpass: %s<p>' % userpass) 1234 self.wfile.write('password: %s<p>' % password) 1235 self.wfile.write('You sent:<br>%s<p>' % self.headers) 1236 self.wfile.write('</body></html>') 1237 return True 1238 1239 # Authentication successful. (Return a cachable response to allow for 1240 # testing cached pages that require authentication.) 1241 old_protocol_version = self.protocol_version 1242 self.protocol_version = "HTTP/1.1" 1243 1244 if_none_match = self.headers.getheader('if-none-match') 1245 if if_none_match == "abc": 1246 self.send_response(304) 1247 self.end_headers() 1248 elif url_path.endswith(".gif"): 1249 # Using chrome/test/data/google/logo.gif as the test image 1250 test_image_path = ['google', 'logo.gif'] 1251 gif_path = os.path.join(self.server.data_dir, *test_image_path) 1252 if not os.path.isfile(gif_path): 1253 self.send_error(404) 1254 self.protocol_version = old_protocol_version 1255 return True 1256 1257 f = open(gif_path, "rb") 1258 data = f.read() 1259 f.close() 1260 1261 self.send_response(200) 1262 self.send_header('Content-Type', 'image/gif') 1263 self.send_header('Cache-control', 'max-age=60000') 1264 self.send_header('Etag', 'abc') 1265 self.end_headers() 1266 self.wfile.write(data) 1267 else: 1268 self.send_response(200) 1269 self.send_header('Content-Type', 'text/html') 1270 self.send_header('Cache-control', 'max-age=60000') 1271 self.send_header('Etag', 'abc') 1272 self.end_headers() 1273 self.wfile.write('<html><head>') 1274 self.wfile.write('<title>%s/%s</title>' % (username, password)) 1275 self.wfile.write('</head><body>') 1276 self.wfile.write('auth=%s<p>' % auth) 1277 self.wfile.write('You sent:<br>%s<p>' % self.headers) 1278 self.wfile.write('</body></html>') 1279 1280 self.protocol_version = old_protocol_version 1281 return True 1282 1283 def GDataAuthHandler(self): 1284 """This handler verifies the Authentication header for GData requests.""" 1285 1286 if not self.server.gdata_auth_token: 1287 # --auth-token is not specified, not the test case for GData. 1288 return False 1289 1290 if not self._ShouldHandleRequest('/files/chromeos/gdata'): 1291 return False 1292 1293 if 'GData-Version' not in self.headers: 1294 self.send_error(httplib.BAD_REQUEST, 'GData-Version header is missing.') 1295 return True 1296 1297 if 'Authorization' not in self.headers: 1298 self.send_error(httplib.UNAUTHORIZED) 1299 return True 1300 1301 field_prefix = 'Bearer ' 1302 authorization = self.headers['Authorization'] 1303 if not authorization.startswith(field_prefix): 1304 self.send_error(httplib.UNAUTHORIZED) 1305 return True 1306 1307 code = authorization[len(field_prefix):] 1308 if code != self.server.gdata_auth_token: 1309 self.send_error(httplib.UNAUTHORIZED) 1310 return True 1311 1312 return False 1313 1314 def GDataDocumentsFeedQueryHandler(self): 1315 """This handler verifies if required parameters are properly 1316 specified for the GData DocumentsFeed request.""" 1317 1318 if not self.server.gdata_auth_token: 1319 # --auth-token is not specified, not the test case for GData. 1320 return False 1321 1322 if not self._ShouldHandleRequest('/files/chromeos/gdata/root_feed.json'): 1323 return False 1324 1325 (_path, _question, query_params) = self.path.partition('?') 1326 self.query_params = urlparse.parse_qs(query_params) 1327 1328 if 'v' not in self.query_params: 1329 self.send_error(httplib.BAD_REQUEST, 'v is not specified.') 1330 return True 1331 elif 'alt' not in self.query_params or self.query_params['alt'] != ['json']: 1332 # currently our GData client only uses JSON format. 1333 self.send_error(httplib.BAD_REQUEST, 'alt parameter is wrong.') 1334 return True 1335 1336 return False 1337 1338 def GetNonce(self, force_reset=False): 1339 """Returns a nonce that's stable per request path for the server's lifetime. 1340 This is a fake implementation. A real implementation would only use a given 1341 nonce a single time (hence the name n-once). However, for the purposes of 1342 unittesting, we don't care about the security of the nonce. 1343 1344 Args: 1345 force_reset: Iff set, the nonce will be changed. Useful for testing the 1346 "stale" response. 1347 """ 1348 1349 if force_reset or not self.server.nonce_time: 1350 self.server.nonce_time = time.time() 1351 return hashlib.md5('privatekey%s%d' % 1352 (self.path, self.server.nonce_time)).hexdigest() 1353 1354 def AuthDigestHandler(self): 1355 """This handler tests 'Digest' authentication. 1356 1357 It just sends a page with title 'user/pass' if you succeed. 1358 1359 A stale response is sent iff "stale" is present in the request path. 1360 """ 1361 1362 if not self._ShouldHandleRequest("/auth-digest"): 1363 return False 1364 1365 stale = 'stale' in self.path 1366 nonce = self.GetNonce(force_reset=stale) 1367 opaque = hashlib.md5('opaque').hexdigest() 1368 password = 'secret' 1369 realm = 'testrealm' 1370 1371 auth = self.headers.getheader('authorization') 1372 pairs = {} 1373 try: 1374 if not auth: 1375 raise Exception('no auth') 1376 if not auth.startswith('Digest'): 1377 raise Exception('not digest') 1378 # Pull out all the name="value" pairs as a dictionary. 1379 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth)) 1380 1381 # Make sure it's all valid. 1382 if pairs['nonce'] != nonce: 1383 raise Exception('wrong nonce') 1384 if pairs['opaque'] != opaque: 1385 raise Exception('wrong opaque') 1386 1387 # Check the 'response' value and make sure it matches our magic hash. 1388 # See http://www.ietf.org/rfc/rfc2617.txt 1389 hash_a1 = hashlib.md5( 1390 ':'.join([pairs['username'], realm, password])).hexdigest() 1391 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest() 1392 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs: 1393 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'], 1394 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest() 1395 else: 1396 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest() 1397 1398 if pairs['response'] != response: 1399 raise Exception('wrong password') 1400 except Exception, e: 1401 # Authentication failed. 1402 self.send_response(401) 1403 hdr = ('Digest ' 1404 'realm="%s", ' 1405 'domain="/", ' 1406 'qop="auth", ' 1407 'algorithm=MD5, ' 1408 'nonce="%s", ' 1409 'opaque="%s"') % (realm, nonce, opaque) 1410 if stale: 1411 hdr += ', stale="TRUE"' 1412 self.send_header('WWW-Authenticate', hdr) 1413 self.send_header('Content-Type', 'text/html') 1414 self.end_headers() 1415 self.wfile.write('<html><head>') 1416 self.wfile.write('<title>Denied: %s</title>' % e) 1417 self.wfile.write('</head><body>') 1418 self.wfile.write('auth=%s<p>' % auth) 1419 self.wfile.write('pairs=%s<p>' % pairs) 1420 self.wfile.write('You sent:<br>%s<p>' % self.headers) 1421 self.wfile.write('We are replying:<br>%s<p>' % hdr) 1422 self.wfile.write('</body></html>') 1423 return True 1424 1425 # Authentication successful. 1426 self.send_response(200) 1427 self.send_header('Content-Type', 'text/html') 1428 self.end_headers() 1429 self.wfile.write('<html><head>') 1430 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password)) 1431 self.wfile.write('</head><body>') 1432 self.wfile.write('auth=%s<p>' % auth) 1433 self.wfile.write('pairs=%s<p>' % pairs) 1434 self.wfile.write('</body></html>') 1435 1436 return True 1437 1438 def SlowServerHandler(self): 1439 """Wait for the user suggested time before responding. The syntax is 1440 /slow?0.5 to wait for half a second.""" 1441 1442 if not self._ShouldHandleRequest("/slow"): 1443 return False 1444 query_char = self.path.find('?') 1445 wait_sec = 1.0 1446 if query_char >= 0: 1447 try: 1448 wait_sec = int(self.path[query_char + 1:]) 1449 except ValueError: 1450 pass 1451 time.sleep(wait_sec) 1452 self.send_response(200) 1453 self.send_header('Content-Type', 'text/plain') 1454 self.end_headers() 1455 self.wfile.write("waited %d seconds" % wait_sec) 1456 return True 1457 1458 def ChunkedServerHandler(self): 1459 """Send chunked response. Allows to specify chunks parameters: 1460 - waitBeforeHeaders - ms to wait before sending headers 1461 - waitBetweenChunks - ms to wait between chunks 1462 - chunkSize - size of each chunk in bytes 1463 - chunksNumber - number of chunks 1464 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5 1465 waits one second, then sends headers and five chunks five bytes each.""" 1466 1467 if not self._ShouldHandleRequest("/chunked"): 1468 return False 1469 query_char = self.path.find('?') 1470 chunkedSettings = {'waitBeforeHeaders' : 0, 1471 'waitBetweenChunks' : 0, 1472 'chunkSize' : 5, 1473 'chunksNumber' : 5} 1474 if query_char >= 0: 1475 params = self.path[query_char + 1:].split('&') 1476 for param in params: 1477 keyValue = param.split('=') 1478 if len(keyValue) == 2: 1479 try: 1480 chunkedSettings[keyValue[0]] = int(keyValue[1]) 1481 except ValueError: 1482 pass 1483 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']) 1484 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding 1485 self.send_response(200) 1486 self.send_header('Content-Type', 'text/plain') 1487 self.send_header('Connection', 'close') 1488 self.send_header('Transfer-Encoding', 'chunked') 1489 self.end_headers() 1490 # Chunked encoding: sending all chunks, then final zero-length chunk and 1491 # then final CRLF. 1492 for i in range(0, chunkedSettings['chunksNumber']): 1493 if i > 0: 1494 time.sleep(0.001 * chunkedSettings['waitBetweenChunks']) 1495 self.sendChunkHelp('*' * chunkedSettings['chunkSize']) 1496 self.wfile.flush() # Keep in mind that we start flushing only after 1kb. 1497 self.sendChunkHelp('') 1498 return True 1499 1500 def ContentTypeHandler(self): 1501 """Returns a string of html with the given content type. E.g., 1502 /contenttype?text/css returns an html file with the Content-Type 1503 header set to text/css.""" 1504 1505 if not self._ShouldHandleRequest("/contenttype"): 1506 return False 1507 query_char = self.path.find('?') 1508 content_type = self.path[query_char + 1:].strip() 1509 if not content_type: 1510 content_type = 'text/html' 1511 self.send_response(200) 1512 self.send_header('Content-Type', content_type) 1513 self.end_headers() 1514 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n") 1515 return True 1516 1517 def NoContentHandler(self): 1518 """Returns a 204 No Content response.""" 1519 1520 if not self._ShouldHandleRequest("/nocontent"): 1521 return False 1522 self.send_response(204) 1523 self.end_headers() 1524 return True 1525 1526 def ServerRedirectHandler(self): 1527 """Sends a server redirect to the given URL. The syntax is 1528 '/server-redirect?http://foo.bar/asdf' to redirect to 1529 'http://foo.bar/asdf'""" 1530 1531 test_name = "/server-redirect" 1532 if not self._ShouldHandleRequest(test_name): 1533 return False 1534 1535 query_char = self.path.find('?') 1536 if query_char < 0 or len(self.path) <= query_char + 1: 1537 self.sendRedirectHelp(test_name) 1538 return True 1539 dest = self.path[query_char + 1:] 1540 1541 self.send_response(301) # moved permanently 1542 self.send_header('Location', dest) 1543 self.send_header('Content-Type', 'text/html') 1544 self.end_headers() 1545 self.wfile.write('<html><head>') 1546 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest) 1547 1548 return True 1549 1550 def ClientRedirectHandler(self): 1551 """Sends a client redirect to the given URL. The syntax is 1552 '/client-redirect?http://foo.bar/asdf' to redirect to 1553 'http://foo.bar/asdf'""" 1554 1555 test_name = "/client-redirect" 1556 if not self._ShouldHandleRequest(test_name): 1557 return False 1558 1559 query_char = self.path.find('?') 1560 if query_char < 0 or len(self.path) <= query_char + 1: 1561 self.sendRedirectHelp(test_name) 1562 return True 1563 dest = self.path[query_char + 1:] 1564 1565 self.send_response(200) 1566 self.send_header('Content-Type', 'text/html') 1567 self.end_headers() 1568 self.wfile.write('<html><head>') 1569 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest) 1570 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest) 1571 1572 return True 1573 1574 def MultipartHandler(self): 1575 """Send a multipart response (10 text/html pages).""" 1576 1577 test_name = '/multipart' 1578 if not self._ShouldHandleRequest(test_name): 1579 return False 1580 1581 num_frames = 10 1582 bound = '12345' 1583 self.send_response(200) 1584 self.send_header('Content-Type', 1585 'multipart/x-mixed-replace;boundary=' + bound) 1586 self.end_headers() 1587 1588 for i in xrange(num_frames): 1589 self.wfile.write('--' + bound + '\r\n') 1590 self.wfile.write('Content-Type: text/html\r\n\r\n') 1591 self.wfile.write('<title>page ' + str(i) + '</title>') 1592 self.wfile.write('page ' + str(i)) 1593 1594 self.wfile.write('--' + bound + '--') 1595 return True 1596 1597 def MultipartSlowHandler(self): 1598 """Send a multipart response (3 text/html pages) with a slight delay 1599 between each page. This is similar to how some pages show status using 1600 multipart.""" 1601 1602 test_name = '/multipart-slow' 1603 if not self._ShouldHandleRequest(test_name): 1604 return False 1605 1606 num_frames = 3 1607 bound = '12345' 1608 self.send_response(200) 1609 self.send_header('Content-Type', 1610 'multipart/x-mixed-replace;boundary=' + bound) 1611 self.end_headers() 1612 1613 for i in xrange(num_frames): 1614 self.wfile.write('--' + bound + '\r\n') 1615 self.wfile.write('Content-Type: text/html\r\n\r\n') 1616 time.sleep(0.25) 1617 if i == 2: 1618 self.wfile.write('<title>PASS</title>') 1619 else: 1620 self.wfile.write('<title>page ' + str(i) + '</title>') 1621 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->') 1622 1623 self.wfile.write('--' + bound + '--') 1624 return True 1625 1626 def GetSSLSessionCacheHandler(self): 1627 """Send a reply containing a log of the session cache operations.""" 1628 1629 if not self._ShouldHandleRequest('/ssl-session-cache'): 1630 return False 1631 1632 self.send_response(200) 1633 self.send_header('Content-Type', 'text/plain') 1634 self.end_headers() 1635 try: 1636 for (action, sessionID) in self.server.session_cache.log: 1637 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex'))) 1638 except AttributeError: 1639 self.wfile.write('Pass --https-record-resume in order to use' + 1640 ' this request') 1641 return True 1642 1643 def CloseSocketHandler(self): 1644 """Closes the socket without sending anything.""" 1645 1646 if not self._ShouldHandleRequest('/close-socket'): 1647 return False 1648 1649 self.wfile.close() 1650 return True 1651 1652 def DefaultResponseHandler(self): 1653 """This is the catch-all response handler for requests that aren't handled 1654 by one of the special handlers above. 1655 Note that we specify the content-length as without it the https connection 1656 is not closed properly (and the browser keeps expecting data).""" 1657 1658 contents = "Default response given for path: " + self.path 1659 self.send_response(200) 1660 self.send_header('Content-Type', 'text/html') 1661 self.send_header('Content-Length', len(contents)) 1662 self.end_headers() 1663 if (self.command != 'HEAD'): 1664 self.wfile.write(contents) 1665 return True 1666 1667 def RedirectConnectHandler(self): 1668 """Sends a redirect to the CONNECT request for www.redirect.com. This 1669 response is not specified by the RFC, so the browser should not follow 1670 the redirect.""" 1671 1672 if (self.path.find("www.redirect.com") < 0): 1673 return False 1674 1675 dest = "http://www.destination.com/foo.js" 1676 1677 self.send_response(302) # moved temporarily 1678 self.send_header('Location', dest) 1679 self.send_header('Connection', 'close') 1680 self.end_headers() 1681 return True 1682 1683 def ServerAuthConnectHandler(self): 1684 """Sends a 401 to the CONNECT request for www.server-auth.com. This 1685 response doesn't make sense because the proxy server cannot request 1686 server authentication.""" 1687 1688 if (self.path.find("www.server-auth.com") < 0): 1689 return False 1690 1691 challenge = 'Basic realm="WallyWorld"' 1692 1693 self.send_response(401) # unauthorized 1694 self.send_header('WWW-Authenticate', challenge) 1695 self.send_header('Connection', 'close') 1696 self.end_headers() 1697 return True 1698 1699 def DefaultConnectResponseHandler(self): 1700 """This is the catch-all response handler for CONNECT requests that aren't 1701 handled by one of the special handlers above. Real Web servers respond 1702 with 400 to CONNECT requests.""" 1703 1704 contents = "Your client has issued a malformed or illegal request." 1705 self.send_response(400) # bad request 1706 self.send_header('Content-Type', 'text/html') 1707 self.send_header('Content-Length', len(contents)) 1708 self.end_headers() 1709 self.wfile.write(contents) 1710 return True 1711 1712 def DeviceManagementHandler(self): 1713 """Delegates to the device management service used for cloud policy.""" 1714 1715 if not self._ShouldHandleRequest("/device_management"): 1716 return False 1717 1718 raw_request = self.ReadRequestBody() 1719 1720 if not self.server._device_management_handler: 1721 import device_management 1722 policy_path = os.path.join(self.server.data_dir, 'device_management') 1723 self.server._device_management_handler = ( 1724 device_management.TestServer(policy_path, 1725 self.server.policy_keys, 1726 self.server.policy_user)) 1727 1728 http_response, raw_reply = ( 1729 self.server._device_management_handler.HandleRequest(self.path, 1730 self.headers, 1731 raw_request)) 1732 self.send_response(http_response) 1733 if (http_response == 200): 1734 self.send_header('Content-Type', 'application/x-protobuffer') 1735 self.end_headers() 1736 self.wfile.write(raw_reply) 1737 return True 1738 1739 # called by the redirect handling function when there is no parameter 1740 def sendRedirectHelp(self, redirect_name): 1741 self.send_response(200) 1742 self.send_header('Content-Type', 'text/html') 1743 self.end_headers() 1744 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>') 1745 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name) 1746 self.wfile.write('</body></html>') 1747 1748 # called by chunked handling function 1749 def sendChunkHelp(self, chunk): 1750 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF 1751 self.wfile.write('%X\r\n' % len(chunk)) 1752 self.wfile.write(chunk) 1753 self.wfile.write('\r\n') 1754 1755 1756class SyncPageHandler(BasePageHandler): 1757 """Handler for the main HTTP sync server.""" 1758 1759 def __init__(self, request, client_address, sync_http_server): 1760 get_handlers = [self.ChromiumSyncTimeHandler, 1761 self.ChromiumSyncMigrationOpHandler, 1762 self.ChromiumSyncCredHandler, 1763 self.ChromiumSyncDisableNotificationsOpHandler, 1764 self.ChromiumSyncEnableNotificationsOpHandler, 1765 self.ChromiumSyncSendNotificationOpHandler, 1766 self.ChromiumSyncBirthdayErrorOpHandler, 1767 self.ChromiumSyncTransientErrorOpHandler, 1768 self.ChromiumSyncErrorOpHandler, 1769 self.ChromiumSyncSyncTabFaviconsOpHandler, 1770 self.ChromiumSyncCreateSyncedBookmarksOpHandler] 1771 1772 post_handlers = [self.ChromiumSyncCommandHandler, 1773 self.ChromiumSyncTimeHandler] 1774 BasePageHandler.__init__(self, request, client_address, 1775 sync_http_server, [], get_handlers, [], 1776 post_handlers, []) 1777 1778 1779 def ChromiumSyncTimeHandler(self): 1780 """Handle Chromium sync .../time requests. 1781 1782 The syncer sometimes checks server reachability by examining /time. 1783 """ 1784 1785 test_name = "/chromiumsync/time" 1786 if not self._ShouldHandleRequest(test_name): 1787 return False 1788 1789 # Chrome hates it if we send a response before reading the request. 1790 if self.headers.getheader('content-length'): 1791 length = int(self.headers.getheader('content-length')) 1792 _raw_request = self.rfile.read(length) 1793 1794 self.send_response(200) 1795 self.send_header('Content-Type', 'text/plain') 1796 self.end_headers() 1797 self.wfile.write('0123456789') 1798 return True 1799 1800 def ChromiumSyncCommandHandler(self): 1801 """Handle a chromiumsync command arriving via http. 1802 1803 This covers all sync protocol commands: authentication, getupdates, and 1804 commit. 1805 """ 1806 1807 test_name = "/chromiumsync/command" 1808 if not self._ShouldHandleRequest(test_name): 1809 return False 1810 1811 length = int(self.headers.getheader('content-length')) 1812 raw_request = self.rfile.read(length) 1813 http_response = 200 1814 raw_reply = None 1815 if not self.server.GetAuthenticated(): 1816 http_response = 401 1817 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % ( 1818 self.server.server_address[0]) 1819 else: 1820 http_response, raw_reply = self.server.HandleCommand( 1821 self.path, raw_request) 1822 1823 ### Now send the response to the client. ### 1824 self.send_response(http_response) 1825 if http_response == 401: 1826 self.send_header('www-Authenticate', challenge) 1827 self.end_headers() 1828 self.wfile.write(raw_reply) 1829 return True 1830 1831 def ChromiumSyncMigrationOpHandler(self): 1832 test_name = "/chromiumsync/migrate" 1833 if not self._ShouldHandleRequest(test_name): 1834 return False 1835 1836 http_response, raw_reply = self.server._sync_handler.HandleMigrate( 1837 self.path) 1838 self.send_response(http_response) 1839 self.send_header('Content-Type', 'text/html') 1840 self.send_header('Content-Length', len(raw_reply)) 1841 self.end_headers() 1842 self.wfile.write(raw_reply) 1843 return True 1844 1845 def ChromiumSyncCredHandler(self): 1846 test_name = "/chromiumsync/cred" 1847 if not self._ShouldHandleRequest(test_name): 1848 return False 1849 try: 1850 query = urlparse.urlparse(self.path)[4] 1851 cred_valid = urlparse.parse_qs(query)['valid'] 1852 if cred_valid[0] == 'True': 1853 self.server.SetAuthenticated(True) 1854 else: 1855 self.server.SetAuthenticated(False) 1856 except Exception: 1857 self.server.SetAuthenticated(False) 1858 1859 http_response = 200 1860 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated() 1861 self.send_response(http_response) 1862 self.send_header('Content-Type', 'text/html') 1863 self.send_header('Content-Length', len(raw_reply)) 1864 self.end_headers() 1865 self.wfile.write(raw_reply) 1866 return True 1867 1868 def ChromiumSyncDisableNotificationsOpHandler(self): 1869 test_name = "/chromiumsync/disablenotifications" 1870 if not self._ShouldHandleRequest(test_name): 1871 return False 1872 self.server.GetXmppServer().DisableNotifications() 1873 result = 200 1874 raw_reply = ('<html><title>Notifications disabled</title>' 1875 '<H1>Notifications disabled</H1></html>') 1876 self.send_response(result) 1877 self.send_header('Content-Type', 'text/html') 1878 self.send_header('Content-Length', len(raw_reply)) 1879 self.end_headers() 1880 self.wfile.write(raw_reply) 1881 return True 1882 1883 def ChromiumSyncEnableNotificationsOpHandler(self): 1884 test_name = "/chromiumsync/enablenotifications" 1885 if not self._ShouldHandleRequest(test_name): 1886 return False 1887 self.server.GetXmppServer().EnableNotifications() 1888 result = 200 1889 raw_reply = ('<html><title>Notifications enabled</title>' 1890 '<H1>Notifications enabled</H1></html>') 1891 self.send_response(result) 1892 self.send_header('Content-Type', 'text/html') 1893 self.send_header('Content-Length', len(raw_reply)) 1894 self.end_headers() 1895 self.wfile.write(raw_reply) 1896 return True 1897 1898 def ChromiumSyncSendNotificationOpHandler(self): 1899 test_name = "/chromiumsync/sendnotification" 1900 if not self._ShouldHandleRequest(test_name): 1901 return False 1902 query = urlparse.urlparse(self.path)[4] 1903 query_params = urlparse.parse_qs(query) 1904 channel = '' 1905 data = '' 1906 if 'channel' in query_params: 1907 channel = query_params['channel'][0] 1908 if 'data' in query_params: 1909 data = query_params['data'][0] 1910 self.server.GetXmppServer().SendNotification(channel, data) 1911 result = 200 1912 raw_reply = ('<html><title>Notification sent</title>' 1913 '<H1>Notification sent with channel "%s" ' 1914 'and data "%s"</H1></html>' 1915 % (channel, data)) 1916 self.send_response(result) 1917 self.send_header('Content-Type', 'text/html') 1918 self.send_header('Content-Length', len(raw_reply)) 1919 self.end_headers() 1920 self.wfile.write(raw_reply) 1921 return True 1922 1923 def ChromiumSyncBirthdayErrorOpHandler(self): 1924 test_name = "/chromiumsync/birthdayerror" 1925 if not self._ShouldHandleRequest(test_name): 1926 return False 1927 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError() 1928 self.send_response(result) 1929 self.send_header('Content-Type', 'text/html') 1930 self.send_header('Content-Length', len(raw_reply)) 1931 self.end_headers() 1932 self.wfile.write(raw_reply) 1933 return True 1934 1935 def ChromiumSyncTransientErrorOpHandler(self): 1936 test_name = "/chromiumsync/transienterror" 1937 if not self._ShouldHandleRequest(test_name): 1938 return False 1939 result, raw_reply = self.server._sync_handler.HandleSetTransientError() 1940 self.send_response(result) 1941 self.send_header('Content-Type', 'text/html') 1942 self.send_header('Content-Length', len(raw_reply)) 1943 self.end_headers() 1944 self.wfile.write(raw_reply) 1945 return True 1946 1947 def ChromiumSyncErrorOpHandler(self): 1948 test_name = "/chromiumsync/error" 1949 if not self._ShouldHandleRequest(test_name): 1950 return False 1951 result, raw_reply = self.server._sync_handler.HandleSetInducedError( 1952 self.path) 1953 self.send_response(result) 1954 self.send_header('Content-Type', 'text/html') 1955 self.send_header('Content-Length', len(raw_reply)) 1956 self.end_headers() 1957 self.wfile.write(raw_reply) 1958 return True 1959 1960 def ChromiumSyncSyncTabFaviconsOpHandler(self): 1961 test_name = "/chromiumsync/synctabfavicons" 1962 if not self._ShouldHandleRequest(test_name): 1963 return False 1964 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons() 1965 self.send_response(result) 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 ChromiumSyncCreateSyncedBookmarksOpHandler(self): 1973 test_name = "/chromiumsync/createsyncedbookmarks" 1974 if not self._ShouldHandleRequest(test_name): 1975 return False 1976 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks() 1977 self.send_response(result) 1978 self.send_header('Content-Type', 'text/html') 1979 self.send_header('Content-Length', len(raw_reply)) 1980 self.end_headers() 1981 self.wfile.write(raw_reply) 1982 return True 1983 1984 1985class OCSPHandler(BasePageHandler): 1986 def __init__(self, request, client_address, socket_server): 1987 handlers = [self.OCSPResponse] 1988 self.ocsp_response = socket_server.ocsp_response 1989 BasePageHandler.__init__(self, request, client_address, socket_server, 1990 [], handlers, [], handlers, []) 1991 1992 def OCSPResponse(self): 1993 self.send_response(200) 1994 self.send_header('Content-Type', 'application/ocsp-response') 1995 self.send_header('Content-Length', str(len(self.ocsp_response))) 1996 self.end_headers() 1997 1998 self.wfile.write(self.ocsp_response) 1999 2000 2001class TCPEchoHandler(SocketServer.BaseRequestHandler): 2002 """The RequestHandler class for TCP echo server. 2003 2004 It is instantiated once per connection to the server, and overrides the 2005 handle() method to implement communication to the client. 2006 """ 2007 2008 def handle(self): 2009 """Handles the request from the client and constructs a response.""" 2010 2011 data = self.request.recv(65536).strip() 2012 # Verify the "echo request" message received from the client. Send back 2013 # "echo response" message if "echo request" message is valid. 2014 try: 2015 return_data = echo_message.GetEchoResponseData(data) 2016 if not return_data: 2017 return 2018 except ValueError: 2019 return 2020 2021 self.request.send(return_data) 2022 2023 2024class UDPEchoHandler(SocketServer.BaseRequestHandler): 2025 """The RequestHandler class for UDP echo server. 2026 2027 It is instantiated once per connection to the server, and overrides the 2028 handle() method to implement communication to the client. 2029 """ 2030 2031 def handle(self): 2032 """Handles the request from the client and constructs a response.""" 2033 2034 data = self.request[0].strip() 2035 request_socket = self.request[1] 2036 # Verify the "echo request" message received from the client. Send back 2037 # "echo response" message if "echo request" message is valid. 2038 try: 2039 return_data = echo_message.GetEchoResponseData(data) 2040 if not return_data: 2041 return 2042 except ValueError: 2043 return 2044 request_socket.sendto(return_data, self.client_address) 2045 2046 2047class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 2048 """A request handler that behaves as a proxy server which requires 2049 basic authentication. Only CONNECT, GET and HEAD is supported for now. 2050 """ 2051 2052 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar 2053 2054 def parse_request(self): 2055 """Overrides parse_request to check credential.""" 2056 2057 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self): 2058 return False 2059 2060 auth = self.headers.getheader('Proxy-Authorization') 2061 if auth != self._AUTH_CREDENTIAL: 2062 self.send_response(407) 2063 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"') 2064 self.end_headers() 2065 return False 2066 2067 return True 2068 2069 def _start_read_write(self, sock): 2070 sock.setblocking(0) 2071 self.request.setblocking(0) 2072 rlist = [self.request, sock] 2073 while True: 2074 ready_sockets, _unused, errors = select.select(rlist, [], []) 2075 if errors: 2076 self.send_response(500) 2077 self.end_headers() 2078 return 2079 for s in ready_sockets: 2080 received = s.recv(1024) 2081 if len(received) == 0: 2082 return 2083 if s == self.request: 2084 other = sock 2085 else: 2086 other = self.request 2087 other.send(received) 2088 2089 def _do_common_method(self): 2090 url = urlparse.urlparse(self.path) 2091 port = url.port 2092 if not port: 2093 if url.scheme == 'http': 2094 port = 80 2095 elif url.scheme == 'https': 2096 port = 443 2097 if not url.hostname or not port: 2098 self.send_response(400) 2099 self.end_headers() 2100 return 2101 2102 if len(url.path) == 0: 2103 path = '/' 2104 else: 2105 path = url.path 2106 if len(url.query) > 0: 2107 path = '%s?%s' % (url.path, url.query) 2108 2109 sock = None 2110 try: 2111 sock = socket.create_connection((url.hostname, port)) 2112 sock.send('%s %s %s\r\n' % ( 2113 self.command, path, self.protocol_version)) 2114 for header in self.headers.headers: 2115 header = header.strip() 2116 if (header.lower().startswith('connection') or 2117 header.lower().startswith('proxy')): 2118 continue 2119 sock.send('%s\r\n' % header) 2120 sock.send('\r\n') 2121 self._start_read_write(sock) 2122 except Exception: 2123 self.send_response(500) 2124 self.end_headers() 2125 finally: 2126 if sock is not None: 2127 sock.close() 2128 2129 def do_CONNECT(self): 2130 try: 2131 pos = self.path.rfind(':') 2132 host = self.path[:pos] 2133 port = int(self.path[pos+1:]) 2134 except Exception: 2135 self.send_response(400) 2136 self.end_headers() 2137 2138 try: 2139 sock = socket.create_connection((host, port)) 2140 self.send_response(200, 'Connection established') 2141 self.end_headers() 2142 self._start_read_write(sock) 2143 except Exception: 2144 self.send_response(500) 2145 self.end_headers() 2146 finally: 2147 sock.close() 2148 2149 def do_GET(self): 2150 self._do_common_method() 2151 2152 def do_HEAD(self): 2153 self._do_common_method() 2154 2155 2156class ServerRunner(testserver_base.TestServerRunner): 2157 """TestServerRunner for the net test servers.""" 2158 2159 def __init__(self): 2160 super(ServerRunner, self).__init__() 2161 self.__ocsp_server = None 2162 2163 def __make_data_dir(self): 2164 if self.options.data_dir: 2165 if not os.path.isdir(self.options.data_dir): 2166 raise testserver_base.OptionError('specified data dir not found: ' + 2167 self.options.data_dir + ' exiting...') 2168 my_data_dir = self.options.data_dir 2169 else: 2170 # Create the default path to our data dir, relative to the exe dir. 2171 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..", 2172 "test", "data") 2173 2174 #TODO(ibrar): Must use Find* funtion defined in google\tools 2175 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data") 2176 2177 return my_data_dir 2178 2179 def create_server(self, server_data): 2180 port = self.options.port 2181 host = self.options.host 2182 2183 if self.options.server_type == SERVER_HTTP: 2184 if self.options.https: 2185 pem_cert_and_key = None 2186 if self.options.cert_and_key_file: 2187 if not os.path.isfile(self.options.cert_and_key_file): 2188 raise testserver_base.OptionError( 2189 'specified server cert file not found: ' + 2190 self.options.cert_and_key_file + ' exiting...') 2191 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read() 2192 else: 2193 # generate a new certificate and run an OCSP server for it. 2194 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler) 2195 print ('OCSP server started on %s:%d...' % 2196 (host, self.__ocsp_server.server_port)) 2197 2198 ocsp_der = None 2199 ocsp_state = None 2200 2201 if self.options.ocsp == 'ok': 2202 ocsp_state = minica.OCSP_STATE_GOOD 2203 elif self.options.ocsp == 'revoked': 2204 ocsp_state = minica.OCSP_STATE_REVOKED 2205 elif self.options.ocsp == 'invalid': 2206 ocsp_state = minica.OCSP_STATE_INVALID 2207 elif self.options.ocsp == 'unauthorized': 2208 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED 2209 elif self.options.ocsp == 'unknown': 2210 ocsp_state = minica.OCSP_STATE_UNKNOWN 2211 else: 2212 raise testserver_base.OptionError('unknown OCSP status: ' + 2213 self.options.ocsp_status) 2214 2215 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP( 2216 subject = "127.0.0.1", 2217 ocsp_url = ("http://%s:%d/ocsp" % 2218 (host, self.__ocsp_server.server_port)), 2219 ocsp_state = ocsp_state) 2220 2221 self.__ocsp_server.ocsp_response = ocsp_der 2222 2223 for ca_cert in self.options.ssl_client_ca: 2224 if not os.path.isfile(ca_cert): 2225 raise testserver_base.OptionError( 2226 'specified trusted client CA file not found: ' + ca_cert + 2227 ' exiting...') 2228 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key, 2229 self.options.ssl_client_auth, 2230 self.options.ssl_client_ca, 2231 self.options.ssl_bulk_cipher, 2232 self.options.record_resume, 2233 self.options.tls_intolerant) 2234 print 'HTTPS server started on %s:%d...' % (host, server.server_port) 2235 else: 2236 server = HTTPServer((host, port), TestPageHandler) 2237 print 'HTTP server started on %s:%d...' % (host, server.server_port) 2238 2239 server.data_dir = self.__make_data_dir() 2240 server.file_root_url = self.options.file_root_url 2241 server_data['port'] = server.server_port 2242 server._device_management_handler = None 2243 server.policy_keys = self.options.policy_keys 2244 server.policy_user = self.options.policy_user 2245 server.gdata_auth_token = self.options.auth_token 2246 elif self.options.server_type == SERVER_WEBSOCKET: 2247 # Launch pywebsocket via WebSocketServer. 2248 logger = logging.getLogger() 2249 logger.addHandler(logging.StreamHandler()) 2250 # TODO(toyoshim): Remove following os.chdir. Currently this operation 2251 # is required to work correctly. It should be fixed from pywebsocket side. 2252 os.chdir(self.__make_data_dir()) 2253 websocket_options = WebSocketOptions(host, port, '.') 2254 if self.options.cert_and_key_file: 2255 websocket_options.use_tls = True 2256 websocket_options.private_key = self.options.cert_and_key_file 2257 websocket_options.certificate = self.options.cert_and_key_file 2258 if self.options.ssl_client_auth: 2259 websocket_options.tls_client_auth = True 2260 if len(self.options.ssl_client_ca) != 1: 2261 raise testserver_base.OptionError( 2262 'one trusted client CA file should be specified') 2263 if not os.path.isfile(self.options.ssl_client_ca[0]): 2264 raise testserver_base.OptionError( 2265 'specified trusted client CA file not found: ' + 2266 self.options.ssl_client_ca[0] + ' exiting...') 2267 websocket_options.tls_client_ca = self.options.ssl_client_ca[0] 2268 server = WebSocketServer(websocket_options) 2269 print 'WebSocket server started on %s:%d...' % (host, server.server_port) 2270 server_data['port'] = server.server_port 2271 elif self.options.server_type == SERVER_SYNC: 2272 xmpp_port = self.options.xmpp_port 2273 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler) 2274 print 'Sync HTTP server started on port %d...' % server.server_port 2275 print 'Sync XMPP server started on port %d...' % server.xmpp_port 2276 server_data['port'] = server.server_port 2277 server_data['xmpp_port'] = server.xmpp_port 2278 elif self.options.server_type == SERVER_TCP_ECHO: 2279 # Used for generating the key (randomly) that encodes the "echo request" 2280 # message. 2281 random.seed() 2282 server = TCPEchoServer((host, port), TCPEchoHandler) 2283 print 'Echo TCP server started on port %d...' % server.server_port 2284 server_data['port'] = server.server_port 2285 elif self.options.server_type == SERVER_UDP_ECHO: 2286 # Used for generating the key (randomly) that encodes the "echo request" 2287 # message. 2288 random.seed() 2289 server = UDPEchoServer((host, port), UDPEchoHandler) 2290 print 'Echo UDP server started on port %d...' % server.server_port 2291 server_data['port'] = server.server_port 2292 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY: 2293 server = HTTPServer((host, port), BasicAuthProxyRequestHandler) 2294 print 'BasicAuthProxy server started on port %d...' % server.server_port 2295 server_data['port'] = server.server_port 2296 elif self.options.server_type == SERVER_FTP: 2297 my_data_dir = self.__make_data_dir() 2298 2299 # Instantiate a dummy authorizer for managing 'virtual' users 2300 authorizer = pyftpdlib.ftpserver.DummyAuthorizer() 2301 2302 # Define a new user having full r/w permissions and a read-only 2303 # anonymous user 2304 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw') 2305 2306 authorizer.add_anonymous(my_data_dir) 2307 2308 # Instantiate FTP handler class 2309 ftp_handler = pyftpdlib.ftpserver.FTPHandler 2310 ftp_handler.authorizer = authorizer 2311 2312 # Define a customized banner (string returned when client connects) 2313 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." % 2314 pyftpdlib.ftpserver.__ver__) 2315 2316 # Instantiate FTP server class and listen to address:port 2317 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler) 2318 server_data['port'] = server.socket.getsockname()[1] 2319 print 'FTP server started on port %d...' % server_data['port'] 2320 else: 2321 raise testserver_base.OptionError('unknown server type' + 2322 self.options.server_type) 2323 2324 return server 2325 2326 def run_server(self): 2327 if self.__ocsp_server: 2328 self.__ocsp_server.serve_forever_on_thread() 2329 2330 testserver_base.TestServerRunner.run_server(self) 2331 2332 if self.__ocsp_server: 2333 self.__ocsp_server.stop_serving() 2334 2335 def add_options(self): 2336 testserver_base.TestServerRunner.add_options(self) 2337 self.option_parser.add_option('-f', '--ftp', action='store_const', 2338 const=SERVER_FTP, default=SERVER_HTTP, 2339 dest='server_type', 2340 help='start up an FTP server.') 2341 self.option_parser.add_option('--sync', action='store_const', 2342 const=SERVER_SYNC, default=SERVER_HTTP, 2343 dest='server_type', 2344 help='start up a sync server.') 2345 self.option_parser.add_option('--tcp-echo', action='store_const', 2346 const=SERVER_TCP_ECHO, default=SERVER_HTTP, 2347 dest='server_type', 2348 help='start up a tcp echo server.') 2349 self.option_parser.add_option('--udp-echo', action='store_const', 2350 const=SERVER_UDP_ECHO, default=SERVER_HTTP, 2351 dest='server_type', 2352 help='start up a udp echo server.') 2353 self.option_parser.add_option('--basic-auth-proxy', action='store_const', 2354 const=SERVER_BASIC_AUTH_PROXY, 2355 default=SERVER_HTTP, dest='server_type', 2356 help='start up a proxy server which requires ' 2357 'basic authentication.') 2358 self.option_parser.add_option('--websocket', action='store_const', 2359 const=SERVER_WEBSOCKET, default=SERVER_HTTP, 2360 dest='server_type', 2361 help='start up a WebSocket server.') 2362 self.option_parser.add_option('--xmpp-port', default='0', type='int', 2363 help='Port used by the XMPP server. If ' 2364 'unspecified, the XMPP server will listen on ' 2365 'an ephemeral port.') 2366 self.option_parser.add_option('--data-dir', dest='data_dir', 2367 help='Directory from which to read the ' 2368 'files.') 2369 self.option_parser.add_option('--https', action='store_true', 2370 dest='https', help='Specify that https ' 2371 'should be used.') 2372 self.option_parser.add_option('--cert-and-key-file', 2373 dest='cert_and_key_file', help='specify the ' 2374 'path to the file containing the certificate ' 2375 'and private key for the server in PEM ' 2376 'format') 2377 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok', 2378 help='The type of OCSP response generated ' 2379 'for the automatically generated ' 2380 'certificate. One of [ok,revoked,invalid]') 2381 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant', 2382 default='0', type='int', 2383 help='If nonzero, certain TLS connections ' 2384 'will be aborted in order to test version ' 2385 'fallback. 1 means all TLS versions will be ' 2386 'aborted. 2 means TLS 1.1 or higher will be ' 2387 'aborted. 3 means TLS 1.2 or higher will be ' 2388 'aborted.') 2389 self.option_parser.add_option('--https-record-resume', 2390 dest='record_resume', const=True, 2391 default=False, action='store_const', 2392 help='Record resumption cache events rather ' 2393 'than resuming as normal. Allows the use of ' 2394 'the /ssl-session-cache request') 2395 self.option_parser.add_option('--ssl-client-auth', action='store_true', 2396 help='Require SSL client auth on every ' 2397 'connection.') 2398 self.option_parser.add_option('--ssl-client-ca', action='append', 2399 default=[], help='Specify that the client ' 2400 'certificate request should include the CA ' 2401 'named in the subject of the DER-encoded ' 2402 'certificate contained in the specified ' 2403 'file. This option may appear multiple ' 2404 'times, indicating multiple CA names should ' 2405 'be sent in the request.') 2406 self.option_parser.add_option('--ssl-bulk-cipher', action='append', 2407 help='Specify the bulk encryption ' 2408 'algorithm(s) that will be accepted by the ' 2409 'SSL server. Valid values are "aes256", ' 2410 '"aes128", "3des", "rc4". If omitted, all ' 2411 'algorithms will be used. This option may ' 2412 'appear multiple times, indicating ' 2413 'multiple algorithms should be enabled.'); 2414 self.option_parser.add_option('--file-root-url', default='/files/', 2415 help='Specify a root URL for files served.') 2416 self.option_parser.add_option('--policy-key', action='append', 2417 dest='policy_keys', 2418 help='Specify a path to a PEM-encoded ' 2419 'private key to use for policy signing. May ' 2420 'be specified multiple times in order to ' 2421 'load multipe keys into the server. If the ' 2422 'server has multiple keys, it will rotate ' 2423 'through them in at each request a ' 2424 'round-robin fashion. The server will ' 2425 'generate a random key if none is specified ' 2426 'on the command line.') 2427 self.option_parser.add_option('--policy-user', 2428 default='user@example.com', 2429 dest='policy_user', 2430 help='Specify the user name the server ' 2431 'should report back to the client as the ' 2432 'user owning the token used for making the ' 2433 'policy request.') 2434 self.option_parser.add_option('--auth-token', dest='auth_token', 2435 help='Specify the auth token which should be ' 2436 'used in the authorization header for GData.') 2437 2438 2439if __name__ == '__main__': 2440 sys.exit(ServerRunner().main()) 2441