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