1
|
#!/usr/bin/env python3
|
2
|
#
|
3
|
# Copyright (C) 2021 jahoti <jahoti@tilde.team>
|
4
|
# Licensing information is collated in the `copyright` file
|
5
|
|
6
|
"""
|
7
|
A modular "virtual network" proxy,
|
8
|
wrapping the classes in proxy_core.py
|
9
|
"""
|
10
|
|
11
|
import proxy_core
|
12
|
from urllib.parse import parse_qs
|
13
|
from misc_constants import *
|
14
|
from world_wide_library import catalog as internet
|
15
|
|
16
|
class RequestHijacker(proxy_core.ProxyRequestHandler):
|
17
|
def handle_request(self, req_body):
|
18
|
path_components = self.path.split('?', maxsplit=1)
|
19
|
path = path_components[0]
|
20
|
try:
|
21
|
# Response format: (status_code, headers (dict. of strings),
|
22
|
# body as bytes or filename containing body as string)
|
23
|
if path in internet:
|
24
|
info = internet[path]
|
25
|
if type(info) == tuple:
|
26
|
status_code, headers, body_file = info
|
27
|
if type(body_file) == str:
|
28
|
if 'Content-Type' not in headers and '.' in body_file:
|
29
|
ext = body_file.rsplit('.', maxsplit=1)[-1]
|
30
|
if ext in mime_types:
|
31
|
headers['Content-Type'] = mime_types[ext]
|
32
|
|
33
|
with open(body_file, mode='rb') as f:
|
34
|
body_file = f.read()
|
35
|
|
36
|
else:
|
37
|
# A function to evaluate to get the response
|
38
|
get_params, post_params = {}, {}
|
39
|
if len(path_components) == 2:
|
40
|
get_params = parse_qs(path_components[1])
|
41
|
|
42
|
# Parse POST parameters; currently only supports
|
43
|
# application/x-www-form-urlencoded
|
44
|
if req_body:
|
45
|
post_params = parse_qs(req_body.encode())
|
46
|
|
47
|
status_code, headers, body_file = info(self.command, get_params, post_params)
|
48
|
if type(body_file) == str:
|
49
|
body_file = body_file.encode()
|
50
|
|
51
|
if type(status_code) != int or status_code <= 0:
|
52
|
raise Exception('Invalid status code %r' % status_code)
|
53
|
|
54
|
for header, header_value in headers.items():
|
55
|
if type(header) != str:
|
56
|
raise Exception('Invalid header key %r' % header)
|
57
|
|
58
|
elif type(header_value) != str:
|
59
|
raise Exception('Invalid header value %r' % header_value)
|
60
|
else:
|
61
|
status_code, headers = 404, {'Content-Type': 'text/plain'}
|
62
|
body_file = b'Handler for this URL not found.'
|
63
|
|
64
|
except Exception as e:
|
65
|
status_code, headers, body_file = 500, {'Content-Type': 'text/plain'}, b'Internal Error:\n' + repr(e).encode()
|
66
|
|
67
|
headers['Content-Length'] = str(len(body_file))
|
68
|
self.send_response(status_code)
|
69
|
for header, header_value in headers.items():
|
70
|
self.send_header(header, header_value)
|
71
|
|
72
|
self.end_headers()
|
73
|
self.wfile.write(body_file)
|
74
|
|
75
|
|
76
|
|
77
|
def do_an_internet(certdir, port):
|
78
|
"""Start up the proxy/server"""
|
79
|
proxy_core.certdir = certdir
|
80
|
httpd = proxy_core.ThreadingHTTPServer(('', port), RequestHijacker)
|
81
|
httpd.serve_forever()
|
82
|
|
83
|
if __name__ == '__main__':
|
84
|
import sys
|
85
|
def fail(msg, error_code):
|
86
|
print('Error:', msg)
|
87
|
print('Usage:', sys.argv[0], '[certificates directory] (port)')
|
88
|
sys.exit(error_code)
|
89
|
|
90
|
if len(sys.argv) < 2:
|
91
|
fail('missing required argument "certificates directory".', 1)
|
92
|
|
93
|
certdir = sys.argv[1]
|
94
|
if not proxy_core.os.path.isdir(certdir):
|
95
|
fail('selected certificate directory does not exist.', 2)
|
96
|
|
97
|
port = sys.argv[2] if len(sys.argv) > 2 else '1337'
|
98
|
if not port.isnumeric():
|
99
|
fail('port must be an integer.', 3)
|
100
|
|
101
|
do_an_internet(certdir, int(port))
|