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