From 3702661daa01ebedacfe838b67c989e4ac11f9c7 Mon Sep 17 00:00:00 2001 From: Pascal Bihler Date: Wed, 16 Jan 2019 12:42:57 +0100 Subject: [PATCH] Added --pac-file option to serve a Proxy Autoconfig file --- README.md | 5 +++++ proxy.py | 40 +++++++++++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 468243bb..5adbc1f8 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Features - Optimize for large file uploads and downloads - IPv4 and IPv6 support - Basic authentication support +- Can serve a PAC (Proxy Autoconfiguration) file Install ------- @@ -30,6 +31,7 @@ usage: proxy.py [-h] [--hostname HOSTNAME] [--port PORT] [--backlog BACKLOG] [--server-recvbuf-size SERVER_RECVBUF_SIZE] [--client-recvbuf-size CLIENT_RECVBUF_SIZE] [--log-level LOG_LEVEL] + [--pac-file AutoConfig] proxy.py v0.3 @@ -54,6 +56,9 @@ optional arguments: RAM. --log-level LOG_LEVEL DEBUG, INFO (default), WARNING, ERROR, CRITICAL + --pac-file A file (Proxy Auto Configuration) or string to serve when + the server receives a direct file request. + Example: proxy.py --pac-file "function FindProxyForURL(url, host) { return 'PROXY localhost:8899; DIRECT'; }" Having difficulty using proxy.py? Report at: https://github.com/abhinavsingh/proxy.py/issues/new diff --git a/proxy.py b/proxy.py index 05d73702..80720ea5 100755 --- a/proxy.py +++ b/proxy.py @@ -93,6 +93,12 @@ PROXY_AUTHENTICATION_REQUIRED_RESPONSE_PKT = CRLF.join([ CRLF ]) + b'Proxy Authentication Required' +PAC_FILE_RESPONSE_PREFIX = CRLF.join([ + b'HTTP/1.1 200 OK', + b'Content-Type: application/x-ns-proxy-autoconfig', + b'Connection: close', + CRLF +]) class ChunkParser(object): """HTTP chunked encoding response parser.""" @@ -423,7 +429,7 @@ class Proxy(threading.Thread): Accepts `Client` connection object and act as a proxy between client and server. """ - def __init__(self, client, auth_code=None, server_recvbuf_size=8192, client_recvbuf_size=8192): + def __init__(self, client, auth_code=None, server_recvbuf_size=8192, client_recvbuf_size=8192, pac_file = None): super(Proxy, self).__init__() self.start_time = self._now() @@ -438,6 +444,8 @@ class Proxy(threading.Thread): self.request = HttpParser(HttpParser.types.REQUEST_PARSER) self.response = HttpParser(HttpParser.types.RESPONSE_PARSER) + self.pac_file = pac_file + @staticmethod def _now(): return datetime.datetime.utcnow() @@ -477,6 +485,10 @@ class Proxy(threading.Thread): else: raise Exception('Invalid request\n%s' % self.request.raw) + if host == None and self.pac_file: + self._serve_pac_file() + return True + self.server = Server(host, port) try: logger.debug('connecting to server %s:%s' % (host, port)) @@ -549,7 +561,7 @@ class Proxy(threading.Thread): return True try: - self._process_request(data) + return self._process_request(data) except (ProxyAuthenticationFailed, ProxyConnectionFailed) as e: logger.exception(e) self.client.queue(Proxy._get_response_pkt_by_exception(e)) @@ -569,6 +581,19 @@ class Proxy(threading.Thread): return False + def _serve_pac_file(self): + logger.debug('serving pac file') + self.client.queue(PAC_FILE_RESPONSE_PREFIX) + try: + with open(self.pac_file) as f: + for line in f: + self.client.queue(line) + except IOError: + logger.debug('serving pac file directly') + self.client.queue(self.pac_file) + + self.client.flush() + def _process(self): while True: rlist, wlist, xlist = self._get_waitable_lists() @@ -653,17 +678,19 @@ class HTTP(TCP): """ def __init__(self, hostname='127.0.0.1', port=8899, backlog=100, - auth_code=None, server_recvbuf_size=8192, client_recvbuf_size=8192): + auth_code=None, server_recvbuf_size=8192, client_recvbuf_size=8192, pac_file=None): super(HTTP, self).__init__(hostname, port, backlog) self.auth_code = auth_code self.client_recvbuf_size = client_recvbuf_size self.server_recvbuf_size = server_recvbuf_size + self.pac_file = pac_file def handle(self, client): proxy = Proxy(client, auth_code=self.auth_code, server_recvbuf_size=self.server_recvbuf_size, - client_recvbuf_size=self.client_recvbuf_size) + client_recvbuf_size=self.client_recvbuf_size, + pac_file=self.pac_file) proxy.daemon = True proxy.start() @@ -704,6 +731,8 @@ def main(): 'Maximum number of files (TCP connections) ' 'that proxy.py can open concurrently.') parser.add_argument('--log-level', default='INFO', help='DEBUG, INFO (default), WARNING, ERROR, CRITICAL') + parser.add_argument('--pac-file', default='', help='A file (Proxy Auto Configuration) or string to serve when ' + 'the server receives a direct file request.') args = parser.parse_args() logging.basicConfig(level=getattr(logging, args.log_level), @@ -721,7 +750,8 @@ def main(): backlog=int(args.backlog), auth_code=auth_code, server_recvbuf_size=int(args.server_recvbuf_size), - client_recvbuf_size=int(args.client_recvbuf_size)) + client_recvbuf_size=int(args.client_recvbuf_size), + pac_file=args.pac_file) proxy.run() except KeyboardInterrupt: pass