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