# Copyright (c) 2011 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """HTTP proxy request handler with SSL support. RequestHandler: Utility class for parsing HTTP requests. ProxyHandler: HTTP proxy handler. """ import BaseHTTPServer import cgi import OpenSSL import os import socket import SocketServer import sys import traceback import urlparse class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): """Class for reading HTTP requests and writing HTTP responses""" protocol_version = "HTTP/1.1" request_version = protocol_version class HTTPRequestException(Exception): pass def __init__(self, rfile, wfile, server): self.rfile = rfile self.wfile = wfile self.server = server def ReadRequest(self): "Reads and parses single HTTP request from self.rfile" self.raw_requestline = self.rfile.readline() if not self.raw_requestline: self.close_connection = 1 raise HTTPRequestException('failed to read request line') if not self.parse_request(): raise HTTPRequestException('failed to parse request') self.headers = dict(self.headers) self.body = None if 'content-length' in self.headers: self.body = self.rfile.read(int(self.headers['content-length'])) def log_message(self, format, *args): pass class ProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler): "Request handler class for proxy server" server_version = "PlaybackProxy/0.0.1" protocol_version = "HTTP/1.1" def do_CONNECT(self): "Handles CONNECT HTTP request" server = self.path.split(':')[0] certificate_file = os.path.join(self.certificate_directory, server) if not os.path.isfile(certificate_file): sys.stderr.write('request to connect %s is ignored\n' % server) self.send_response(501) self.send_header('Proxy-agent', self.version_string()) self.end_headers() return # Send confirmation to browser. self.send_response(200, 'Connection established') self.send_header('Proxy-agent', self.version_string()) self.end_headers() # Create SSL context. context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) context.use_privatekey_file(certificate_file) context.use_certificate_file(certificate_file) # Create and initialize SSL connection atop of tcp socket. ssl_connection = OpenSSL.SSL.Connection(context, self.connection) ssl_connection.set_accept_state() ssl_connection.do_handshake() ssl_rfile = socket._fileobject(ssl_connection, "rb", self.rbufsize) ssl_wfile = socket._fileobject(ssl_connection, "wb", self.wbufsize) # Handle http requests coming from ssl_connection. handler = RequestHandler(ssl_rfile, ssl_wfile, self.path) try: handler.close_connection = 1 while True: handler.ReadRequest() self.driver.ProcessRequest(handler) if handler.close_connection: break except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError): pass finally: self.close_connection = 1 def do_GET(self): self.driver.ProcessRequest(self) def do_POST(self): if 'content-length' in self.headers: self.body = self.rfile.read(int(self.headers['content-length'])) self.driver.ProcessRequest(self) def log_message(self, format, *args): sys.stdout.write((format % args) + '\n') class ThreadingHTTPServer (SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): pass def CreateServer(driver, port, certificate_directory=None): if not certificate_directory: certificate_directory = os.path.join(os.getcwd(), 'certificates') ProxyHandler.driver = driver ProxyHandler.certificate_directory = certificate_directory return ThreadingHTTPServer(('', port), ProxyHandler)