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