Exemple #1
0
def _prepare_outgoing_headers(headers):
    if headers is None:
        headers = HTTPMessage()
    elif not isinstance(headers, HTTPMessage):
        new_headers = HTTPMessage()
        if hasattr(headers, "items"):
            iterator = headers.items()
        else:
            iterator = iter(headers)
        for k, v in iterator:
            new_headers[k] = v
        headers = new_headers
    _setdefault_header(headers, "User-Agent", DEFAULT_UA)
    return headers
Exemple #2
0
class ConnectionHandler(object):
    """
    Handler class for the connection to be proxied.
    """
    server_version = "SCION HTTP Proxy/" + VERSION

    def __init__(self, connection, address, conn_id):
        """
        Create a ConnectionHandler class to handle the incoming HTTP(S) request.
        :param connection: Socket belonging to the incoming connection.
        :type connection: socket
        :param address: Address of the connecting party.
        :type address: host, port
        """
        self.conn_id = conn_id
        self.connection = connection
        self.method = self.path = self.protocol = None
        self.headers = HTTPMessage()

        cur_thread = threading.current_thread()
        cur_thread.name = self.conn_id
        try:
            if not self.parse_request():
                # FIXME(kormat): need error reporting
                return
            self.handle_request()
        finally:
            cleanup(self.connection)

    def parse_request(self):
        """
        Extracts the request line of an incoming HTTP(S) request.
        :returns: HTTP(S) Method, Path, HTTP(S) Protocol Version
        :rtype: triple
        """
        data = []
        lf_count = 0
        while lf_count < 2:
            b = self.connection.recv(1)
            if not b:
                logging.info("Client closed the connection.")
                return False
            if b == b"\r":
                # Drop \r's, as recommended by rfc2616 19.3
                continue
            data.append(b)
            if b == b"\n":
                lf_count += 1
            else:
                lf_count = 0
        lines = b"".join(data).decode("ascii").split("\n")
        self.method, self.path, self.protocol = lines.pop(0).split(" ")
        for line in lines:
            if not line:
                break
            self.headers.add_header(*line.split(": ", 1))
        logging.info("Request: %s %s %s", self.method, self.path,
                     self.protocol)
        logging.debug("Request headers:\n%s", self.headers)
        return True

    def handle_request(self):
        if self.method == 'CONNECT':
            self.do_CONNECT()
        elif self.method in ('GET', 'HEAD', 'POST', 'PUT', 'DELETE'):
            self.handle_others()
        else:
            # FIXME(kormat): need error reporting
            logging.warning("Invalid HTTP(S) request")

    def do_CONNECT(self):
        """
        Handles the CONNECT method: Connects to the target address,
        and responds to the client (i.e. browser) with a 200
        Connection established response and starts proxying.
        """
        soc = self._connect_to(self.path)
        if not soc:
            # FIXME(kormat): needs error handling
            return
        reply = "\r\n".join([
            "HTTP/1.1 200 Connection established",
            "Proxy-agent: %s" % self.server_version
        ]) + "\r\n\r\n"
        try:
            self.connection.send(reply.encode("ascii"))
            self._read_write(soc)
        finally:
            cleanup(soc)

    def handle_others(self):
        """
        Handles the rest of the supported HTTP methods: Parses the path,
        connects to the target address, sends the complete request
        to the target and starts proxying.
        """
        (scm, netloc, path, params, query, _) = urlparse(self.path, 'http')
        if scm != 'http' or not netloc:
            logging.error("Bad URL %s" % self.path)
            return
        conn_hdr = self.headers["Connection"]
        if conn_hdr:
            del self.headers[conn_hdr]
            self.headers.replace_header("Connection", "close")
        soc = self._connect_to(netloc)
        if not soc:
            # FIXME(kormat): needs error handling
            return
        try:
            # Normal mode proxy strips out scm and netloc
            self._send_request(soc, '', '', path, params, query)
            self._read_write(soc)
        finally:
            cleanup(soc)
        logging.debug("Done")

    def _connect_to(self, netloc):
        """
        Establishes a connection to the target host.
        :param netloc: The hostname (and port) of the target to be connected to.
        :type netloc: string
        :returns: The socket that is used to connect.
        :rtype: socket
        """
        soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        if ':' in netloc:
            host, port = netloc.split(':')
        else:
            host, port = netloc, 80
        logging.debug("Connecting to %s:%s" % (host, port))
        try:
            soc.connect((host, int(port)))
        except OSError:
            log_exception("Error while connecting to %s:%s" % (host, port))
            return False
        logging.debug("Connected to %s:%s" % (host, port))
        return soc

    def _send_request(self, soc, scm, netloc, path, params, query):
        """
        Helper function that prepares and sends the request on the
        given socket.
        :param soc: The socket the request is going to be sent on.
        :type soc: socket
        :param path: The path of the HTTP request.
        :type path: String
        :param params: Parameters of the request (if any).
        :type params: String
        :param query: Query section of the HTTP request (if any).
        :type query: String
        """
        base = "%s %s HTTP/1.0" % (self.method,
                                   urlunparse(
                                       (scm, netloc, path, params, query, '')))
        req = []
        req.append(base)
        for hdr, val in self.headers.items():
            req.append("%s: %s" % (hdr, val))
        req_bytes = ("\r\n".join(req) + "\r\n\r\n").encode("ascii")
        logging.debug("Sending a request: %s", req_bytes)
        # FIXME(kormat): need error handling/reporting
        soc.send(req_bytes)

    def _read_write(self, target_sock):
        """
        The main function responsible for the proxying operation. It creates
        two threads to listen for incoming data on both client (i.e. browser)
        and server sockets and relays them accordingly between each other.
        :param target_sock: The socket belonging to the remote target.
        :type target_sock: socket
        """
        t1 = threading.Thread(target=thread_safety_net,
                              args=(ProxyData, self.connection, target_sock),
                              name="%s-c2s" % self.conn_id)
        t1.start()
        t2 = threading.Thread(target=thread_safety_net,
                              args=(ProxyData, target_sock, self.connection),
                              name="%s-s2c" % self.conn_id)
        t2.start()
        # Wait until both threads finish.
        t1.join()
        t2.join()