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