174 lines
5.4 KiB
Python
174 lines
5.4 KiB
Python
"""
|
|
proxy.py
|
|
~~~~~~~~
|
|
Lightweight, Programmable, TLS interceptor Proxy for HTTP(S), HTTP2, WebSockets protocols in a single Python file.
|
|
|
|
:copyright: (c) 2013-present by Abhinav Singh and contributors.
|
|
:license: BSD, see LICENSE for more details.
|
|
"""
|
|
import json
|
|
import os
|
|
import tempfile
|
|
import time
|
|
from typing import Optional, BinaryIO
|
|
from urllib import parse as urlparse
|
|
|
|
import proxy
|
|
|
|
|
|
@proxy.route(b'/hello-world')
|
|
def hello_world(_request: proxy.HttpParser) -> bytes:
|
|
"""A HttpWebServerRoutePlugin plugin for inbuilt web server."""
|
|
return proxy.build_http_response(200, body=b'Hello World')
|
|
|
|
|
|
class ProposedRestApiPlugin(proxy.HttpProxyBasePlugin):
|
|
"""Mock responses for your upstream REST API.
|
|
|
|
Used to test and develop client side applications
|
|
without need of an actual upstream REST API server.
|
|
|
|
Returns proposed REST API mock responses to the client."""
|
|
|
|
API_SERVER = b'api.example.com'
|
|
|
|
REST_API_SPEC = {
|
|
b'/v1/users/': {
|
|
'count': 2,
|
|
'next': None,
|
|
'previous': None,
|
|
'results': [
|
|
{
|
|
'email': 'you@example.com',
|
|
'groups': [],
|
|
'url': proxy.text_(API_SERVER) + '/v1/users/1/',
|
|
'username': 'admin',
|
|
},
|
|
{
|
|
'email': 'someone@example.com',
|
|
'groups': [],
|
|
'url': proxy.text_(API_SERVER) + '/v1/users/2/',
|
|
'username': 'someone',
|
|
},
|
|
]
|
|
},
|
|
}
|
|
|
|
def before_upstream_connection(self) -> bool:
|
|
"""Called after client request is received and
|
|
before connecting to upstream server."""
|
|
if self.request.host == self.API_SERVER and self.request.url:
|
|
if self.request.url.path in self.REST_API_SPEC:
|
|
self.client.send(proxy.build_http_response(
|
|
200, reason=b'OK',
|
|
headers={b'Content-Type': b'application/json'},
|
|
body=proxy.bytes_(json.dumps(
|
|
self.REST_API_SPEC[self.request.url.path]))
|
|
))
|
|
else:
|
|
self.client.send(proxy.build_http_response(
|
|
404, reason=b'NOT FOUND', body=b'Not Found'
|
|
))
|
|
return True
|
|
return False
|
|
|
|
def on_upstream_connection(self) -> None:
|
|
pass
|
|
|
|
def handle_upstream_response(self, raw: bytes) -> bytes:
|
|
return raw
|
|
|
|
def on_upstream_connection_close(self) -> None:
|
|
pass
|
|
|
|
|
|
class RedirectToCustomServerPlugin(proxy.HttpProxyBasePlugin):
|
|
"""Modifies client request to redirect all incoming requests to a fixed server address."""
|
|
|
|
UPSTREAM_SERVER = b'http://localhost:8899'
|
|
|
|
def before_upstream_connection(self) -> bool:
|
|
# Redirect all non-https requests to inbuilt WebServer.
|
|
if self.request.method != b'CONNECT':
|
|
self.request.url = urlparse.urlsplit(self.UPSTREAM_SERVER)
|
|
self.request.set_host_port()
|
|
return False
|
|
|
|
def on_upstream_connection(self) -> None:
|
|
pass
|
|
|
|
def handle_upstream_response(self, raw: bytes) -> bytes:
|
|
return raw
|
|
|
|
def on_upstream_connection_close(self) -> None:
|
|
pass
|
|
|
|
|
|
class FilterByUpstreamHostPlugin(proxy.HttpProxyBasePlugin):
|
|
"""Drop traffic by inspecting upstream host."""
|
|
|
|
FILTERED_DOMAINS = [b'google.com', b'www.google.com']
|
|
|
|
def before_upstream_connection(self) -> bool:
|
|
if self.request.host in self.FILTERED_DOMAINS:
|
|
raise proxy.HttpRequestRejected(
|
|
status_code=418, reason=b'I\'m a tea pot')
|
|
return False
|
|
|
|
def on_upstream_connection(self) -> None:
|
|
pass
|
|
|
|
def handle_upstream_response(self, raw: bytes) -> bytes:
|
|
return raw
|
|
|
|
def on_upstream_connection_close(self) -> None:
|
|
pass
|
|
|
|
|
|
class CacheResponsesPlugin(proxy.HttpProxyBasePlugin):
|
|
"""Caches Upstream Server Responses."""
|
|
|
|
CACHE_DIR = tempfile.gettempdir()
|
|
|
|
def __init__(self, config: proxy.ProtocolConfig, client: proxy.TcpClientConnection,
|
|
request: proxy.HttpParser) -> None:
|
|
super().__init__(config, client, request)
|
|
self.cache_file_path: Optional[str] = None
|
|
self.cache_file: Optional[BinaryIO] = None
|
|
|
|
def before_upstream_connection(self) -> bool:
|
|
return False
|
|
|
|
def on_upstream_connection(self) -> None:
|
|
self.cache_file_path = os.path.join(
|
|
self.CACHE_DIR,
|
|
'%s-%s.txt' % (proxy.text_(self.request.host), str(time.time())))
|
|
self.cache_file = open(self.cache_file_path, "wb")
|
|
|
|
def handle_upstream_response(self, chunk: bytes) -> bytes:
|
|
if self.cache_file:
|
|
self.cache_file.write(chunk)
|
|
return chunk
|
|
|
|
def on_upstream_connection_close(self) -> None:
|
|
if self.cache_file:
|
|
self.cache_file.close()
|
|
proxy.logger.info('Cached response at %s', self.cache_file_path)
|
|
|
|
|
|
class ManInTheMiddlePlugin(proxy.HttpProxyBasePlugin):
|
|
"""Modifies upstream server responses."""
|
|
|
|
def before_upstream_connection(self) -> bool:
|
|
return False
|
|
|
|
def on_upstream_connection(self) -> None:
|
|
pass
|
|
|
|
def handle_upstream_response(self, raw: bytes) -> bytes:
|
|
return proxy.build_http_response(
|
|
200, reason=b'OK', body=b'Hello from man in the middle')
|
|
|
|
def on_upstream_connection_close(self) -> None:
|
|
pass
|