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