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