Home | History | Annotate | Download | only in test
      1 # Copyright 2014 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """An https server that forwards requests to another server. This allows a
      6 server that supports http only to be accessed over https.
      7 """
      8 
      9 import BaseHTTPServer
     10 import os
     11 import SocketServer
     12 import sys
     13 import urllib2
     14 import urlparse
     15 import testserver_base
     16 import tlslite.api
     17 
     18 
     19 class RedirectSuppressor(urllib2.HTTPErrorProcessor):
     20   """Prevents urllib2 from following http redirects.
     21 
     22   If this class is placed in an urllib2.OpenerDirector's handler chain before
     23   the default urllib2.HTTPRedirectHandler, it will terminate the processing of
     24   responses containing redirect codes (301, 302, 303, 307) before they reach the
     25   default redirect handler.
     26   """
     27 
     28   def http_response(self, req, response):
     29     return response
     30 
     31   def https_response(self, req, response):
     32     return response
     33 
     34 
     35 class RequestForwarder(BaseHTTPServer.BaseHTTPRequestHandler):
     36   """Handles requests received by forwarding them to the another server."""
     37 
     38   def do_GET(self):
     39     """Forwards GET requests."""
     40     self._forward(None)
     41 
     42   def do_POST(self):
     43     """Forwards POST requests."""
     44     self._forward(self.rfile.read(int(self.headers['Content-Length'])))
     45 
     46   def _forward(self, body):
     47     """Forwards a GET or POST request to another server.
     48 
     49     Args:
     50       body: The request body. This should be |None| for GET requests.
     51     """
     52     request_url = urlparse.urlparse(self.path)
     53     url = urlparse.urlunparse((self.server.forward_scheme,
     54                                self.server.forward_netloc,
     55                                self.server.forward_path + request_url[2],
     56                                request_url[3],
     57                                request_url[4],
     58                                request_url[5]))
     59 
     60     headers = dict((key, value) for key, value in dict(self.headers).iteritems()
     61                    if key.lower() != 'host')
     62     opener = urllib2.build_opener(RedirectSuppressor)
     63     forward = opener.open(urllib2.Request(url, body, headers))
     64 
     65     self.send_response(forward.getcode())
     66     for key, value in dict(forward.info()).iteritems():
     67       self.send_header(key, value)
     68     self.end_headers()
     69     self.wfile.write(forward.read())
     70 
     71 
     72 class MultiThreadedHTTPSServer(SocketServer.ThreadingMixIn,
     73                                tlslite.api.TLSSocketServerMixIn,
     74                                testserver_base.ClientRestrictingServerMixIn,
     75                                testserver_base.BrokenPipeHandlerMixIn,
     76                                testserver_base.StoppableHTTPServer):
     77   """A multi-threaded version of testserver.HTTPSServer."""
     78 
     79   def __init__(self, server_address, request_hander_class, pem_cert_and_key):
     80     """Initializes the server.
     81 
     82     Args:
     83       server_address: Server host and port.
     84       request_hander_class: The class that will handle requests to the server.
     85       pem_cert_and_key: Path to file containing the https cert and private key.
     86     """
     87     self.cert_chain = tlslite.api.X509CertChain()
     88     self.cert_chain.parsePemList(pem_cert_and_key)
     89     # Force using only python implementation - otherwise behavior is different
     90     # depending on whether m2crypto Python module is present (error is thrown
     91     # when it is). m2crypto uses a C (based on OpenSSL) implementation under
     92     # the hood.
     93     self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
     94                                                private=True,
     95                                                implementations=['python'])
     96 
     97     testserver_base.StoppableHTTPServer.__init__(self,
     98                                                  server_address,
     99                                                  request_hander_class)
    100 
    101   def handshake(self, tlsConnection):
    102     """Performs the SSL handshake for an https connection.
    103 
    104     Args:
    105       tlsConnection: The https connection.
    106     Returns:
    107       Whether the SSL handshake succeeded.
    108     """
    109     try:
    110       self.tlsConnection = tlsConnection
    111       tlsConnection.handshakeServer(certChain=self.cert_chain,
    112                                     privateKey=self.private_key)
    113       tlsConnection.ignoreAbruptClose = True
    114       return True
    115     except:
    116       return False
    117 
    118 
    119 class ServerRunner(testserver_base.TestServerRunner):
    120   """Runner that starts an https server which forwards requests to another
    121   server.
    122   """
    123 
    124   def create_server(self, server_data):
    125     """Performs the SSL handshake for an https connection.
    126 
    127     Args:
    128       server_data: Dictionary that holds information about the server.
    129     Returns:
    130       The started server.
    131     """
    132     port = self.options.port
    133     host = self.options.host
    134 
    135     if not os.path.isfile(self.options.cert_and_key_file):
    136       raise testserver_base.OptionError(
    137           'Specified server cert file not found: ' +
    138           self.options.cert_and_key_file)
    139     pem_cert_and_key = open(self.options.cert_and_key_file).read()
    140 
    141     server = MultiThreadedHTTPSServer((host, port),
    142                                       RequestForwarder,
    143                                       pem_cert_and_key)
    144     print 'HTTPS server started on %s:%d...' % (host, server.server_port)
    145 
    146     forward_target = urlparse.urlparse(self.options.forward_target)
    147     server.forward_scheme = forward_target[0]
    148     server.forward_netloc = forward_target[1]
    149     server.forward_path = forward_target[2].rstrip('/')
    150     server.forward_host = forward_target.hostname
    151     if forward_target.port:
    152       server.forward_host += ':' + str(forward_target.port)
    153     server_data['port'] = server.server_port
    154     return server
    155 
    156   def add_options(self):
    157     """Specifies the command-line options understood by the server."""
    158     testserver_base.TestServerRunner.add_options(self)
    159     self.option_parser.add_option('--https', action='store_true',
    160                                   help='Ignored (provided for compatibility '
    161                                   'only).')
    162     self.option_parser.add_option('--cert-and-key-file', help='The path to the '
    163                                   'file containing the certificate and private '
    164                                   'key for the server in PEM format.')
    165     self.option_parser.add_option('--forward-target', help='The URL prefix to '
    166                                   'which requests will be forwarded.')
    167 
    168 
    169 if __name__ == '__main__':
    170   sys.exit(ServerRunner().main())
    171