1
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
2
|
|
3
|
"""
|
4
|
A modular "virtual network" proxy,
|
5
|
wrapping the classes in proxy_core.py
|
6
|
"""
|
7
|
|
8
|
# This file is part of Haketilo.
|
9
|
#
|
10
|
# Copyright (C) 2021 jahoti <jahoti@tilde.team>
|
11
|
# Copyright (C) 2021 Wojtek Kosior <koszko@koszko.org>
|
12
|
#
|
13
|
# This program is free software: you can redistribute it and/or modify
|
14
|
# it under the terms of the GNU Affero General Public License as
|
15
|
# published by the Free Software Foundation, either version 3 of the
|
16
|
# License, or (at your option) any later version.
|
17
|
#
|
18
|
# This program is distributed in the hope that it will be useful,
|
19
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
20
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
21
|
# GNU Affero General Public License for more details.
|
22
|
#
|
23
|
# You should have received a copy of the GNU Affero General Public License
|
24
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
25
|
#
|
26
|
#
|
27
|
# I, Wojtek Kosior, thereby promise not to sue for violation of this
|
28
|
# file's license. Although I request that you do not make use of this code
|
29
|
# in a proprietary program, I am not going to enforce this in court.
|
30
|
|
31
|
from pathlib import Path
|
32
|
from urllib.parse import parse_qs
|
33
|
from threading import Thread
|
34
|
import traceback
|
35
|
|
36
|
from selenium.webdriver.common.utils import free_port
|
37
|
|
38
|
from .proxy_core import ProxyRequestHandler, ThreadingHTTPServer
|
39
|
from .misc_constants import *
|
40
|
from .world_wide_library import catalog as internet
|
41
|
|
42
|
class RequestHijacker(ProxyRequestHandler):
|
43
|
def handle_request(self, req_body):
|
44
|
path_components = self.path.split('?', maxsplit=1)
|
45
|
path = path_components[0]
|
46
|
try:
|
47
|
# Response format: (status_code, headers (dict. of strings),
|
48
|
# body as bytes or filename containing body as string)
|
49
|
if path in internet:
|
50
|
info = internet[path]
|
51
|
if type(info) is tuple:
|
52
|
status_code, headers, body_file = info
|
53
|
resp_body = b''
|
54
|
if body_file is not None:
|
55
|
if 'Content-Type' not in headers:
|
56
|
ext = body_file.suffix[1:]
|
57
|
if ext and ext in mime_types:
|
58
|
headers['Content-Type'] = mime_types[ext]
|
59
|
|
60
|
with open(body_file, mode='rb') as f:
|
61
|
resp_body = f.read()
|
62
|
else:
|
63
|
# A function to evaluate to get the response
|
64
|
get_params, post_params = {}, {}
|
65
|
if len(path_components) == 2:
|
66
|
get_params = parse_qs(path_components[1])
|
67
|
|
68
|
# Parse POST parameters; currently only supports
|
69
|
# application/x-www-form-urlencoded
|
70
|
if req_body:
|
71
|
post_params = parse_qs(req_body.encode())
|
72
|
|
73
|
status_code, headers, resp_body = info(self.command, get_params, post_params)
|
74
|
if type(resp_body) == str:
|
75
|
resp_body = resp_body.encode()
|
76
|
|
77
|
if type(status_code) != int or status_code <= 0:
|
78
|
raise Exception('Invalid status code %r' % status_code)
|
79
|
|
80
|
for header, header_value in headers.items():
|
81
|
if type(header) != str:
|
82
|
raise Exception('Invalid header key %r' % header)
|
83
|
|
84
|
elif type(header_value) != str:
|
85
|
raise Exception('Invalid header value %r' % header_value)
|
86
|
else:
|
87
|
status_code, headers = 404, {'Content-Type': 'text/plain'}
|
88
|
resp_body = b'Handler for this URL not found.'
|
89
|
|
90
|
except Exception:
|
91
|
status_code = 500
|
92
|
headers = {'Content-Type': 'text/plain'}
|
93
|
resp_body = b'Internal Error:\n' + traceback.format_exc().encode()
|
94
|
|
95
|
headers['Content-Length'] = str(len(resp_body))
|
96
|
self.send_response(status_code)
|
97
|
for header, header_value in headers.items():
|
98
|
self.send_header(header, header_value)
|
99
|
|
100
|
self.end_headers()
|
101
|
if resp_body:
|
102
|
self.wfile.write(resp_body)
|
103
|
|
104
|
def do_an_internet(certdir=default_cert_dir, port=None):
|
105
|
"""Start up the proxy/server"""
|
106
|
if port is None:
|
107
|
port = free_port()
|
108
|
|
109
|
class RequestHijackerWithCertdir(RequestHijacker):
|
110
|
def __init__(self, *args, **kwargs):
|
111
|
super().__init__(*args, certdir=certdir, **kwargs)
|
112
|
|
113
|
httpd = ThreadingHTTPServer(('', port), RequestHijackerWithCertdir)
|
114
|
Thread(target=httpd.serve_forever).start()
|
115
|
|
116
|
return httpd
|