#!/usr/bin/env python3

# Tiny HTTP Proxy. Based on the work of SUZUKI Hisao.
#
# Ported to py3 and modified to remove the bits we don't need
# and modernized.

import os
import http.server
import select
import socket
import socketserver
import sys
import urllib.parse
import argparse
import time


class ProxyHandler(http.server.BaseHTTPRequestHandler):
    server_version = "testsproxy/1.0"
    block_all = False
    stall_for_seconds = 0

    def log_request(self, m=""):
        super().log_request(m)
        sys.stdout.flush()
        sys.stderr.flush()

    def handle(self):
        (ip, port) = self.client_address
        super().handle()

    def _connect_to(self, netloc, soc):
        i = netloc.find(":")
        if i >= 0:
            host_port = netloc[:i], int(netloc[i + 1 :])
        else:
            host_port = netloc, 80
        try:
            soc.connect(host_port)
        except socket.error as arg:
            try:
                msg = arg[1]
            except:
                msg = arg
            self.send_error(404, msg)
            return False
        return True

    def do_CONNECT(self):
        if self.block_all:
            self.log_error("blocking all traffic")
            self.send_error(400, "All traffic blocked")
            return

        if self.stall_for_seconds > 0:
            self.log_message("stalling for %d seconds", self.stall_for_seconds)
            time.sleep(self.stall_for_seconds)

        soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            if self._connect_to(self.path, soc):
                self.log_request(200)
                s = self.protocol_version + " 200 Connection established\r\n"
                self.wfile.write(s.encode())
                s = "Proxy-agent: {}\r\n".format(self.version_string())
                self.wfile.write(s.encode())
                self.wfile.write("\r\n".encode())
                self._read_write(soc, 300)
        finally:
            soc.close()
            self.connection.close()

    def do_GET(self):
        (scm, netloc, path, params, query, fragment) = urllib.parse.urlparse(
            self.path, "http"
        )
        if scm != "http" or fragment or not netloc:
            s = "bad url {}".format(self.path)
            self.send_error(400, s.encode())
            return
        soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            if self._connect_to(netloc, soc):
                self.log_request()
                s = "{} {} {}\r\n".format(
                    self.command,
                    urllib.parse.urlunparse(("", "", path, params, query, "")),
                    self.request_version,
                )
                soc.send(s.encode())
                self.headers["Connection"] = "close"
                del self.headers["Proxy-Connection"]
                for key, val in self.headers.items():
                    s = "{}: {}\r\n".format(key, val)
                    soc.send(s.encode())
                soc.send("\r\n".encode())
                self._read_write(soc)
        finally:
            soc.close()
            self.connection.close()

    def _read_write(self, soc, max_idling=20):
        iw = [self.connection, soc]
        ow = []
        count = 0
        while True:
            count += 1
            (ins, _, exs) = select.select(iw, ow, iw, 3)
            if exs:
                break
            if ins:
                for i in ins:
                    if i is soc:
                        out = self.connection
                    else:
                        out = soc
                    data = i.recv(8192)
                    if data:
                        out.send(data)
                        count = 0
            if count == max_idling:
                break

    do_HEAD = do_GET
    do_POST = do_GET
    do_PUT = do_GET
    do_DELETE = do_GET


def maybe_sd_notify(s: str) -> None:
    addr = os.getenv("NOTIFY_SOCKET")
    if not addr:
        return
    soc = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
    soc.connect(addr)
    soc.sendall(s.encode())


class ThreadingHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
    def __init__(self, *args):
        super().__init__(*args)
        maybe_sd_notify("READY=1")


def parse_arguments():
    parser = argparse.ArgumentParser(description="tiny proxy")
    parser.add_argument("--block-all", help="block all requests", action="store_true")
    parser.add_argument(
        "--stall-for-seconds", help="block all requests", type=int, default=0
    )

    return parser.parse_args()


if __name__ == "__main__":
    opts = parse_arguments()
    port = 3128

    if opts.block_all:
        print("blocking all traffic")
        ProxyHandler.block_all = True

    if opts.stall_for_seconds > 0:
        print("stalling for", opts.stall_for_seconds, "seconds")
        ProxyHandler.stall_for_seconds = opts.stall_for_seconds

    print("starting tinyproxy on port {}".format(port))
    http.server.test(ProxyHandler, ThreadingHTTPServer, port=port)
