pyproxytools 0.4.1__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyproxytools-0.4.1/pyproxytools.egg-info → pyproxytools-0.4.2}/PKG-INFO +1 -1
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/__init__.py +1 -1
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/handlers/client.py +30 -43
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/handlers/http.py +99 -53
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/handlers/https.py +172 -137
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/pyproxy.py +40 -38
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/server.py +72 -46
- pyproxytools-0.4.2/pyproxy/utils/config.py +100 -0
- pyproxytools-0.4.2/pyproxy/utils/http_req.py +24 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/utils/logger.py +0 -1
- {pyproxytools-0.4.1 → pyproxytools-0.4.2/pyproxytools.egg-info}/PKG-INFO +1 -1
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxytools.egg-info/SOURCES.txt +0 -1
- pyproxytools-0.4.2/tests/utils/test_http_req.py +39 -0
- pyproxytools-0.4.1/pyproxy/utils/config.py +0 -124
- pyproxytools-0.4.1/pyproxy/utils/http_req.py +0 -53
- pyproxytools-0.4.1/pyproxy/utils/version.py +0 -0
- pyproxytools-0.4.1/tests/utils/test_http_req.py +0 -69
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/LICENSE +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/README.md +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/benchmark/benchmark.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/benchmark/utils/__init__.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/benchmark/utils/html.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/benchmark/utils/req.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproject.toml +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/__main__.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/handlers/__init__.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/modules/__init__.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/modules/cancel_inspect.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/modules/custom_header.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/modules/filter.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/modules/shortcuts.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/monitoring/__init__.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/monitoring/web.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/utils/__init__.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/utils/args.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/utils/crypto.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxytools.egg-info/dependency_links.txt +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxytools.egg-info/entry_points.txt +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxytools.egg-info/requires.txt +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxytools.egg-info/top_level.txt +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/requirements.txt +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/setup.cfg +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/setup.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/tests/modules/__init__.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/tests/modules/test_cancel_inspect.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/tests/modules/test_custom_header.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/tests/modules/test_filter.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/tests/modules/test_shortcuts.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/tests/utils/__init__.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/tests/utils/test_crypto.py +0 -0
- {pyproxytools-0.4.1 → pyproxytools-0.4.2}/tests/utils/test_logger.py +0 -0
|
@@ -39,9 +39,7 @@ class ProxyHandlers:
|
|
|
39
39
|
shortcuts,
|
|
40
40
|
custom_header,
|
|
41
41
|
active_connections,
|
|
42
|
-
|
|
43
|
-
proxy_host,
|
|
44
|
-
proxy_port,
|
|
42
|
+
proxy_config,
|
|
45
43
|
):
|
|
46
44
|
self.html_403 = html_403
|
|
47
45
|
self.logger_config = logger_config
|
|
@@ -58,11 +56,32 @@ class ProxyHandlers:
|
|
|
58
56
|
self.console_logger = console_logger
|
|
59
57
|
self.config_shortcuts = shortcuts
|
|
60
58
|
self.config_custom_header = custom_header
|
|
61
|
-
self.
|
|
62
|
-
self.proxy_host = proxy_host
|
|
63
|
-
self.proxy_port = proxy_port
|
|
59
|
+
self.proxy_config = proxy_config
|
|
64
60
|
self.active_connections = active_connections
|
|
65
61
|
|
|
62
|
+
def _create_handler(self, handler_class, **extra_kwargs):
|
|
63
|
+
"""
|
|
64
|
+
Factory to create handler instance with shared common parameters.
|
|
65
|
+
"""
|
|
66
|
+
params = dict(
|
|
67
|
+
html_403=self.html_403,
|
|
68
|
+
logger_config=self.logger_config,
|
|
69
|
+
filter_config=self.filter_config,
|
|
70
|
+
filter_queue=self.filter_queue,
|
|
71
|
+
filter_result_queue=self.filter_result_queue,
|
|
72
|
+
shortcuts_queue=self.shortcuts_queue,
|
|
73
|
+
shortcuts_result_queue=self.shortcuts_result_queue,
|
|
74
|
+
custom_header_queue=self.custom_header_queue,
|
|
75
|
+
custom_header_result_queue=self.custom_header_result_queue,
|
|
76
|
+
console_logger=self.console_logger,
|
|
77
|
+
shortcuts=self.config_shortcuts,
|
|
78
|
+
custom_header=self.config_custom_header,
|
|
79
|
+
proxy_config=self.proxy_config,
|
|
80
|
+
active_connections=self.active_connections,
|
|
81
|
+
)
|
|
82
|
+
params.update(extra_kwargs)
|
|
83
|
+
return handler_class(**params)
|
|
84
|
+
|
|
66
85
|
def handle_client(self, client_socket):
|
|
67
86
|
"""
|
|
68
87
|
Handles an incoming client connection by processing the request and forwarding
|
|
@@ -82,45 +101,13 @@ class ProxyHandlers:
|
|
|
82
101
|
first_line = request.decode(errors="ignore").split("\n")[0]
|
|
83
102
|
|
|
84
103
|
if first_line.startswith("CONNECT"):
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
logger_config=self.logger_config,
|
|
88
|
-
filter_config=self.filter_config,
|
|
104
|
+
https_handler = self._create_handler(
|
|
105
|
+
HttpsHandler,
|
|
89
106
|
ssl_config=self.ssl_config,
|
|
90
|
-
filter_queue=self.filter_queue,
|
|
91
|
-
filter_result_queue=self.filter_result_queue,
|
|
92
|
-
shortcuts_queue=self.shortcuts_queue,
|
|
93
|
-
shortcuts_result_queue=self.shortcuts_result_queue,
|
|
94
107
|
cancel_inspect_queue=self.cancel_inspect_queue,
|
|
95
108
|
cancel_inspect_result_queue=self.cancel_inspect_result_queue,
|
|
96
|
-
custom_header_queue=self.custom_header_queue,
|
|
97
|
-
custom_header_result_queue=self.custom_header_result_queue,
|
|
98
|
-
console_logger=self.console_logger,
|
|
99
|
-
shortcuts=self.config_shortcuts,
|
|
100
|
-
custom_header=self.config_custom_header,
|
|
101
|
-
proxy_enable=self.proxy_enable,
|
|
102
|
-
proxy_host=self.proxy_host,
|
|
103
|
-
proxy_port=self.proxy_port,
|
|
104
|
-
active_connections=self.active_connections,
|
|
105
109
|
)
|
|
106
|
-
|
|
110
|
+
https_handler.handle_https_connection(client_socket, first_line)
|
|
107
111
|
else:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
logger_config=self.logger_config,
|
|
111
|
-
filter_config=self.filter_config,
|
|
112
|
-
filter_queue=self.filter_queue,
|
|
113
|
-
filter_result_queue=self.filter_result_queue,
|
|
114
|
-
shortcuts_queue=self.shortcuts_queue,
|
|
115
|
-
shortcuts_result_queue=self.shortcuts_result_queue,
|
|
116
|
-
custom_header_queue=self.custom_header_queue,
|
|
117
|
-
custom_header_result_queue=self.custom_header_result_queue,
|
|
118
|
-
console_logger=self.console_logger,
|
|
119
|
-
shortcuts=self.config_shortcuts,
|
|
120
|
-
custom_header=self.config_custom_header,
|
|
121
|
-
proxy_enable=self.proxy_enable,
|
|
122
|
-
proxy_host=self.proxy_host,
|
|
123
|
-
proxy_port=self.proxy_port,
|
|
124
|
-
active_connections=self.active_connections,
|
|
125
|
-
)
|
|
126
|
-
client_http_handler.handle_http_request(client_socket, request)
|
|
112
|
+
http_handler = self._create_handler(HttpHandler)
|
|
113
|
+
http_handler.handle_http_request(client_socket, request)
|
|
@@ -8,8 +8,9 @@ HTTP client connections. It handles request forwarding, blocking, and custom hea
|
|
|
8
8
|
import socket
|
|
9
9
|
import os
|
|
10
10
|
import threading
|
|
11
|
+
from urllib.parse import urlparse
|
|
11
12
|
|
|
12
|
-
from pyproxy.utils.http_req import extract_headers
|
|
13
|
+
from pyproxy.utils.http_req import extract_headers
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class HttpHandler:
|
|
@@ -34,9 +35,7 @@ class HttpHandler:
|
|
|
34
35
|
shortcuts,
|
|
35
36
|
custom_header,
|
|
36
37
|
active_connections,
|
|
37
|
-
|
|
38
|
-
proxy_host,
|
|
39
|
-
proxy_port,
|
|
38
|
+
proxy_config,
|
|
40
39
|
):
|
|
41
40
|
self.html_403 = html_403
|
|
42
41
|
self.logger_config = logger_config
|
|
@@ -50,11 +49,81 @@ class HttpHandler:
|
|
|
50
49
|
self.console_logger = console_logger
|
|
51
50
|
self.config_shortcuts = shortcuts
|
|
52
51
|
self.config_custom_header = custom_header
|
|
53
|
-
self.
|
|
54
|
-
self.proxy_host = proxy_host
|
|
55
|
-
self.proxy_port = proxy_port
|
|
52
|
+
self.proxy_config = proxy_config
|
|
56
53
|
self.active_connections = active_connections
|
|
57
54
|
|
|
55
|
+
def _get_modified_headers(self, url, request_text):
|
|
56
|
+
"""
|
|
57
|
+
Extract headers from a request
|
|
58
|
+
"""
|
|
59
|
+
headers = extract_headers(request_text)
|
|
60
|
+
self.custom_header_queue.put(url)
|
|
61
|
+
try:
|
|
62
|
+
new_headers = self.custom_header_result_queue.get(timeout=5)
|
|
63
|
+
headers.update(new_headers)
|
|
64
|
+
except Exception:
|
|
65
|
+
self.console_logger.warning(
|
|
66
|
+
"Timeout while getting custom headers for %s", url
|
|
67
|
+
)
|
|
68
|
+
return headers
|
|
69
|
+
|
|
70
|
+
def _rebuild_http_request(self, request_line, headers, body=""):
|
|
71
|
+
"""
|
|
72
|
+
Reconstructs an HTTP request with the new headers.
|
|
73
|
+
"""
|
|
74
|
+
header_lines = [f"{key}: {value}" for key, value in headers.items()]
|
|
75
|
+
reconstructed_headers = "\r\n".join(header_lines)
|
|
76
|
+
return f"{request_line}\r\n{reconstructed_headers}\r\n\r\n{body}".encode()
|
|
77
|
+
|
|
78
|
+
def _apply_shortcut(self, url: str) -> str | None:
|
|
79
|
+
"""
|
|
80
|
+
Checks if a shortcut is defined for the given domain.
|
|
81
|
+
"""
|
|
82
|
+
if self.config_shortcuts and os.path.isfile(self.config_shortcuts):
|
|
83
|
+
parsed_url = urlparse(url)
|
|
84
|
+
domain = parsed_url.hostname
|
|
85
|
+
self.shortcuts_queue.put(domain)
|
|
86
|
+
try:
|
|
87
|
+
return self.shortcuts_result_queue.get(timeout=5)
|
|
88
|
+
except Exception:
|
|
89
|
+
self.console_logger.warning(
|
|
90
|
+
"Timeout while getting shortcut for %s", url
|
|
91
|
+
)
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
def _is_blocked(self, url: str) -> bool:
|
|
95
|
+
"""
|
|
96
|
+
Checks if a URL is blocked by the configuration filter.
|
|
97
|
+
"""
|
|
98
|
+
if not self.filter_config.no_filter:
|
|
99
|
+
self.filter_queue.put(url)
|
|
100
|
+
try:
|
|
101
|
+
result = self.filter_result_queue.get(timeout=5)
|
|
102
|
+
return result[1] == "Blocked"
|
|
103
|
+
except Exception:
|
|
104
|
+
self.console_logger.warning("Timeout while filtering %s", url)
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
def _send_403(self, client_socket, url, first_line):
|
|
108
|
+
"""
|
|
109
|
+
Sends an HTTP 403 Forbidden response to the client.
|
|
110
|
+
"""
|
|
111
|
+
if not self.logger_config.no_logging_block:
|
|
112
|
+
self.logger_config.block_logger.info(
|
|
113
|
+
"%s - %s - %s", client_socket.getpeername()[0], url, first_line
|
|
114
|
+
)
|
|
115
|
+
with open(self.html_403, "r", encoding="utf-8") as f:
|
|
116
|
+
custom_403_page = f.read()
|
|
117
|
+
response = (
|
|
118
|
+
f"HTTP/1.1 403 Forbidden\r\n"
|
|
119
|
+
f"Content-Length: {len(custom_403_page)}\r\n"
|
|
120
|
+
f"\r\n"
|
|
121
|
+
f"{custom_403_page}"
|
|
122
|
+
)
|
|
123
|
+
client_socket.sendall(response.encode())
|
|
124
|
+
client_socket.close()
|
|
125
|
+
self.active_connections.pop(threading.get_ident(), None)
|
|
126
|
+
|
|
58
127
|
def handle_http_request(self, client_socket, request):
|
|
59
128
|
"""
|
|
60
129
|
Processes an HTTP request, checks for URL filtering, applies shortcuts,
|
|
@@ -67,16 +136,8 @@ class HttpHandler:
|
|
|
67
136
|
first_line = request.decode(errors="ignore").split("\n")[0]
|
|
68
137
|
url = first_line.split(" ")[1]
|
|
69
138
|
|
|
70
|
-
if self.config_custom_header and os.path.isfile(self.config_custom_header):
|
|
71
|
-
headers = extract_headers(request.decode(errors="ignore"))
|
|
72
|
-
self.custom_header_queue.put(url)
|
|
73
|
-
new_headers = self.custom_header_result_queue.get(timeout=5)
|
|
74
|
-
headers.update(new_headers)
|
|
75
|
-
|
|
76
139
|
if self.config_shortcuts and os.path.isfile(self.config_shortcuts):
|
|
77
|
-
|
|
78
|
-
self.shortcuts_queue.put(domain)
|
|
79
|
-
shortcut_url = self.shortcuts_result_queue.get(timeout=5)
|
|
140
|
+
shortcut_url = self._apply_shortcut(url)
|
|
80
141
|
if shortcut_url:
|
|
81
142
|
response = (
|
|
82
143
|
f"HTTP/1.1 302 Found\r\n"
|
|
@@ -90,27 +151,12 @@ class HttpHandler:
|
|
|
90
151
|
self.active_connections.pop(threading.get_ident(), None)
|
|
91
152
|
return
|
|
92
153
|
|
|
93
|
-
if
|
|
94
|
-
self.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
"%s - %s - %s", client_socket.getpeername()[0], url, first_line
|
|
100
|
-
)
|
|
101
|
-
with open(self.html_403, "r", encoding="utf-8") as f:
|
|
102
|
-
custom_403_page = f.read()
|
|
103
|
-
response = (
|
|
104
|
-
f"HTTP/1.1 403 Forbidden\r\n"
|
|
105
|
-
f"Content-Length: {len(custom_403_page)}\r\n"
|
|
106
|
-
f"\r\n"
|
|
107
|
-
f"{custom_403_page}"
|
|
108
|
-
)
|
|
109
|
-
client_socket.sendall(response.encode())
|
|
110
|
-
client_socket.close()
|
|
111
|
-
self.active_connections.pop(threading.get_ident(), None)
|
|
112
|
-
return
|
|
113
|
-
server_host, _ = parse_url(url)
|
|
154
|
+
if self._is_blocked(url):
|
|
155
|
+
self._send_403(client_socket, url, first_line)
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
parsed_url = urlparse(url)
|
|
159
|
+
server_host = parsed_url.hostname
|
|
114
160
|
if not self.logger_config.no_logging_access:
|
|
115
161
|
self.logger_config.access_logger.info(
|
|
116
162
|
"%s - %s - %s",
|
|
@@ -120,20 +166,16 @@ class HttpHandler:
|
|
|
120
166
|
)
|
|
121
167
|
|
|
122
168
|
if self.config_custom_header and os.path.isfile(self.config_custom_header):
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
else:
|
|
132
|
-
body = ""
|
|
133
|
-
|
|
134
|
-
modified_request = (
|
|
135
|
-
f"{request_line}\r\n{reconstructed_headers}\r\n\r\n{body}".encode()
|
|
169
|
+
request_text = request.decode(errors="ignore")
|
|
170
|
+
request_lines = request_text.split("\r\n")
|
|
171
|
+
headers = self._get_modified_headers(url, request_text)
|
|
172
|
+
request_line = request_lines[0]
|
|
173
|
+
body = (
|
|
174
|
+
request_text.split("\r\n\r\n", 1)[1]
|
|
175
|
+
if "\r\n\r\n" in request_text
|
|
176
|
+
else ""
|
|
136
177
|
)
|
|
178
|
+
modified_request = self._rebuild_http_request(request_line, headers, body)
|
|
137
179
|
|
|
138
180
|
self.forward_request_to_server(client_socket, modified_request, url)
|
|
139
181
|
|
|
@@ -149,10 +191,14 @@ class HttpHandler:
|
|
|
149
191
|
request (bytes): The raw HTTP request sent by the client.
|
|
150
192
|
url (str): The target URL from the HTTP request.
|
|
151
193
|
"""
|
|
152
|
-
if self.
|
|
153
|
-
server_host, server_port = self.
|
|
194
|
+
if self.proxy_config.enable:
|
|
195
|
+
server_host, server_port = self.proxy_config.host, self.proxy_config.port
|
|
154
196
|
else:
|
|
155
|
-
|
|
197
|
+
parsed_url = urlparse(url)
|
|
198
|
+
server_host = parsed_url.hostname
|
|
199
|
+
server_port = parsed_url.port or (
|
|
200
|
+
443 if parsed_url.scheme == "https" else 80
|
|
201
|
+
)
|
|
156
202
|
thread_id = threading.get_ident()
|
|
157
203
|
|
|
158
204
|
if thread_id in self.active_connections:
|
|
@@ -42,9 +42,7 @@ class HttpsHandler:
|
|
|
42
42
|
shortcuts,
|
|
43
43
|
custom_header,
|
|
44
44
|
active_connections,
|
|
45
|
-
|
|
46
|
-
proxy_host,
|
|
47
|
-
proxy_port,
|
|
45
|
+
proxy_config,
|
|
48
46
|
):
|
|
49
47
|
self.html_403 = html_403
|
|
50
48
|
self.logger_config = logger_config
|
|
@@ -61,11 +59,149 @@ class HttpsHandler:
|
|
|
61
59
|
self.console_logger = console_logger
|
|
62
60
|
self.config_shortcuts = shortcuts
|
|
63
61
|
self.config_custom_header = custom_header
|
|
64
|
-
self.
|
|
65
|
-
self.proxy_host = proxy_host
|
|
66
|
-
self.proxy_port = proxy_port
|
|
62
|
+
self.proxy_config = proxy_config
|
|
67
63
|
self.active_connections = active_connections
|
|
68
64
|
|
|
65
|
+
def _is_blocked(self, url: str) -> bool:
|
|
66
|
+
"""
|
|
67
|
+
Checks if a URL is blocked by the configuration filter.
|
|
68
|
+
"""
|
|
69
|
+
if not self.filter_config.no_filter:
|
|
70
|
+
self.filter_queue.put(url)
|
|
71
|
+
try:
|
|
72
|
+
result = self.filter_result_queue.get(timeout=5)
|
|
73
|
+
return result[1] == "Blocked"
|
|
74
|
+
except Exception:
|
|
75
|
+
self.console_logger.warning("Timeout while filtering %s", url)
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
def _send_403(self, client_socket, url, first_line):
|
|
79
|
+
"""
|
|
80
|
+
Sends an HTTP 403 Forbidden response to the client.
|
|
81
|
+
"""
|
|
82
|
+
if not self.logger_config.no_logging_block:
|
|
83
|
+
self.logger_config.block_logger.info(
|
|
84
|
+
"%s - %s - %s", client_socket.getpeername()[0], url, first_line
|
|
85
|
+
)
|
|
86
|
+
with open(self.html_403, "r", encoding="utf-8") as f:
|
|
87
|
+
custom_403_page = f.read()
|
|
88
|
+
response = (
|
|
89
|
+
f"HTTP/1.1 403 Forbidden\r\n"
|
|
90
|
+
f"Content-Length: {len(custom_403_page)}\r\n"
|
|
91
|
+
f"\r\n"
|
|
92
|
+
f"{custom_403_page}"
|
|
93
|
+
)
|
|
94
|
+
client_socket.sendall(response.encode())
|
|
95
|
+
client_socket.close()
|
|
96
|
+
self.active_connections.pop(threading.get_ident(), None)
|
|
97
|
+
|
|
98
|
+
def _should_skip_inspection(self, server_host: str) -> bool:
|
|
99
|
+
"""
|
|
100
|
+
Determine if SSL inspection should be skipped for the given host.
|
|
101
|
+
"""
|
|
102
|
+
if (
|
|
103
|
+
self.ssl_config.ssl_inspect
|
|
104
|
+
and self.ssl_config.cancel_inspect
|
|
105
|
+
and os.path.isfile(self.ssl_config.cancel_inspect)
|
|
106
|
+
):
|
|
107
|
+
self.cancel_inspect_queue.put(server_host)
|
|
108
|
+
return self.cancel_inspect_result_queue.get(timeout=5)
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
def _establish_server_connection(self, server_host, server_port):
|
|
112
|
+
"""
|
|
113
|
+
Create and return a socket connected to the target server.
|
|
114
|
+
"""
|
|
115
|
+
if self.proxy_config.enable:
|
|
116
|
+
next_proxy_socket = socket.create_connection(
|
|
117
|
+
(self.proxy_config.host, self.proxy_config.port)
|
|
118
|
+
)
|
|
119
|
+
connect_command = (
|
|
120
|
+
f"CONNECT {server_host}:{server_port} HTTP/1.1\r\n"
|
|
121
|
+
f"Host: {server_host}:{server_port}\r\n\r\n"
|
|
122
|
+
)
|
|
123
|
+
next_proxy_socket.sendall(connect_command.encode())
|
|
124
|
+
|
|
125
|
+
response = b""
|
|
126
|
+
while b"\r\n\r\n" not in response:
|
|
127
|
+
chunk = next_proxy_socket.recv(4096)
|
|
128
|
+
if not chunk:
|
|
129
|
+
raise ConnectionError("Connection to next proxy failed")
|
|
130
|
+
response += chunk
|
|
131
|
+
|
|
132
|
+
if b"200 Connection Established" not in response:
|
|
133
|
+
raise ConnectionRefusedError("Next proxy refused CONNECT")
|
|
134
|
+
|
|
135
|
+
return next_proxy_socket
|
|
136
|
+
else:
|
|
137
|
+
return socket.create_connection((server_host, server_port))
|
|
138
|
+
|
|
139
|
+
def _wrap_client_socket_with_ssl(self, client_socket, cert_path, key_path):
|
|
140
|
+
"""
|
|
141
|
+
Wrap the client socket with an SSL context for interception.
|
|
142
|
+
"""
|
|
143
|
+
client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
144
|
+
client_context.load_cert_chain(certfile=cert_path, keyfile=key_path)
|
|
145
|
+
client_context.options |= (
|
|
146
|
+
ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
|
|
147
|
+
)
|
|
148
|
+
client_context.load_verify_locations(self.ssl_config.inspect_ca_cert)
|
|
149
|
+
|
|
150
|
+
ssl_client_socket = client_context.wrap_socket(
|
|
151
|
+
client_socket, server_side=True, do_handshake_on_connect=False
|
|
152
|
+
)
|
|
153
|
+
ssl_client_socket.do_handshake()
|
|
154
|
+
return ssl_client_socket
|
|
155
|
+
|
|
156
|
+
def _wrap_server_socket_with_ssl(self, server_socket, server_host):
|
|
157
|
+
"""
|
|
158
|
+
Wrap the server socket with an SSL context to enable encrypted communication.
|
|
159
|
+
"""
|
|
160
|
+
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
161
|
+
if self.proxy_config.enable:
|
|
162
|
+
server_context.check_hostname = False
|
|
163
|
+
server_context.verify_mode = ssl.CERT_NONE
|
|
164
|
+
else:
|
|
165
|
+
server_context.load_default_certs()
|
|
166
|
+
|
|
167
|
+
ssl_server_socket = server_context.wrap_socket(
|
|
168
|
+
server_socket,
|
|
169
|
+
server_hostname=server_host,
|
|
170
|
+
do_handshake_on_connect=True,
|
|
171
|
+
)
|
|
172
|
+
return ssl_server_socket
|
|
173
|
+
|
|
174
|
+
def _process_first_ssl_request(self, ssl_client_socket, server_host):
|
|
175
|
+
"""
|
|
176
|
+
Reads and processes the first SSL client request, extracts the method and full URL.
|
|
177
|
+
"""
|
|
178
|
+
try:
|
|
179
|
+
first_request = ssl_client_socket.recv(4096).decode(errors="ignore")
|
|
180
|
+
if not first_request:
|
|
181
|
+
raise ConnectionError("Empty request received")
|
|
182
|
+
|
|
183
|
+
request_line = first_request.split("\r\n")[0]
|
|
184
|
+
method, path, _ = request_line.split(" ")
|
|
185
|
+
|
|
186
|
+
full_url = f"https://{server_host}{path}"
|
|
187
|
+
|
|
188
|
+
if self._is_blocked(f"{server_host}{path}"):
|
|
189
|
+
return None, full_url, True
|
|
190
|
+
|
|
191
|
+
if not self.logger_config.no_logging_access:
|
|
192
|
+
self.logger_config.access_logger.info(
|
|
193
|
+
"%s - %s - %s %s",
|
|
194
|
+
ssl_client_socket.getpeername()[0],
|
|
195
|
+
f"https://{server_host}",
|
|
196
|
+
method,
|
|
197
|
+
full_url,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
return first_request, full_url, False
|
|
201
|
+
except Exception as e:
|
|
202
|
+
self.logger_config.error_logger.error(f"SSL request processing error : {e}")
|
|
203
|
+
return None, None, False
|
|
204
|
+
|
|
69
205
|
def handle_https_connection(self, client_socket, first_line):
|
|
70
206
|
"""
|
|
71
207
|
Handles HTTPS connections by establishing a connection with the target server
|
|
@@ -79,146 +215,45 @@ class HttpsHandler:
|
|
|
79
215
|
server_host, server_port = target.split(":")
|
|
80
216
|
server_port = int(server_port)
|
|
81
217
|
|
|
82
|
-
if
|
|
83
|
-
self.
|
|
84
|
-
|
|
85
|
-
if result[1] == "Blocked":
|
|
86
|
-
if not self.logger_config.no_logging_block:
|
|
87
|
-
self.logger_config.block_logger.info(
|
|
88
|
-
"%s - %s - %s",
|
|
89
|
-
client_socket.getpeername()[0],
|
|
90
|
-
target,
|
|
91
|
-
first_line,
|
|
92
|
-
)
|
|
93
|
-
with open(self.html_403, "r", encoding="utf-8") as f:
|
|
94
|
-
custom_403_page = f.read()
|
|
95
|
-
response = (
|
|
96
|
-
f"HTTP/1.1 403 Forbidden\r\n"
|
|
97
|
-
f"Content-Length: {len(custom_403_page)}\r\n"
|
|
98
|
-
f"\r\n"
|
|
99
|
-
f"{custom_403_page}"
|
|
100
|
-
)
|
|
101
|
-
client_socket.sendall(response.encode())
|
|
102
|
-
client_socket.close()
|
|
103
|
-
self.active_connections.pop(threading.get_ident(), None)
|
|
104
|
-
return
|
|
218
|
+
if self._is_blocked(target):
|
|
219
|
+
self._send_403(client_socket, target, first_line)
|
|
220
|
+
return
|
|
105
221
|
|
|
106
|
-
not_inspect =
|
|
107
|
-
if (
|
|
108
|
-
self.ssl_config.ssl_inspect
|
|
109
|
-
and self.ssl_config.cancel_inspect
|
|
110
|
-
and os.path.isfile(self.ssl_config.cancel_inspect)
|
|
111
|
-
):
|
|
112
|
-
self.cancel_inspect_queue.put(server_host)
|
|
113
|
-
not_inspect = self.cancel_inspect_result_queue.get(timeout=5)
|
|
222
|
+
not_inspect = self._should_skip_inspection(server_host)
|
|
114
223
|
|
|
115
224
|
if self.ssl_config.ssl_inspect and not not_inspect:
|
|
116
|
-
cert_path, key_path = generate_certificate(
|
|
117
|
-
server_host,
|
|
118
|
-
self.ssl_config.inspect_certs_folder,
|
|
119
|
-
self.ssl_config.inspect_ca_cert,
|
|
120
|
-
self.ssl_config.inspect_ca_key,
|
|
121
|
-
)
|
|
122
|
-
client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
123
|
-
client_context.load_cert_chain(certfile=cert_path, keyfile=key_path)
|
|
124
|
-
client_context.options |= (
|
|
125
|
-
ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
|
|
126
|
-
)
|
|
127
|
-
client_context.load_verify_locations(self.ssl_config.inspect_ca_cert)
|
|
128
|
-
|
|
129
225
|
try:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if self.proxy_enable:
|
|
137
|
-
next_proxy_socket = socket.create_connection(
|
|
138
|
-
(self.proxy_host, self.proxy_port)
|
|
139
|
-
)
|
|
140
|
-
connect_command = (
|
|
141
|
-
f"CONNECT {server_host}:{server_port} HTTP/1.1\r\n"
|
|
142
|
-
f"Host: {server_host}:{server_port}\r\n\r\n"
|
|
143
|
-
)
|
|
144
|
-
next_proxy_socket.sendall(connect_command.encode())
|
|
145
|
-
|
|
146
|
-
response = b""
|
|
147
|
-
while b"\r\n\r\n" not in response:
|
|
148
|
-
chunk = next_proxy_socket.recv(4096)
|
|
149
|
-
if not chunk:
|
|
150
|
-
raise ConnectionError("Connection to next proxy failed")
|
|
151
|
-
response += chunk
|
|
152
|
-
|
|
153
|
-
if b"200 Connection Established" not in response:
|
|
154
|
-
raise ConnectionRefusedError("Next proxy refused CONNECT")
|
|
155
|
-
|
|
156
|
-
server_socket = next_proxy_socket
|
|
157
|
-
else:
|
|
158
|
-
server_socket = socket.create_connection((server_host, server_port))
|
|
159
|
-
|
|
160
|
-
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
161
|
-
if self.proxy_enable:
|
|
162
|
-
server_context.check_hostname = False
|
|
163
|
-
server_context.verify_mode = ssl.CERT_NONE
|
|
164
|
-
else:
|
|
165
|
-
server_context.load_default_certs()
|
|
166
|
-
|
|
167
|
-
ssl_server_socket = server_context.wrap_socket(
|
|
168
|
-
server_socket,
|
|
169
|
-
server_hostname=server_host,
|
|
170
|
-
do_handshake_on_connect=True,
|
|
226
|
+
cert_path, key_path = generate_certificate(
|
|
227
|
+
server_host,
|
|
228
|
+
self.ssl_config.inspect_certs_folder,
|
|
229
|
+
self.ssl_config.inspect_ca_cert,
|
|
230
|
+
self.ssl_config.inspect_ca_key,
|
|
171
231
|
)
|
|
172
232
|
|
|
173
|
-
|
|
174
|
-
first_request = ssl_client_socket.recv(4096).decode(errors="ignore")
|
|
175
|
-
request_line = first_request.split("\r\n")[0]
|
|
176
|
-
method, path, _ = request_line.split(" ")
|
|
177
|
-
|
|
178
|
-
full_url = f"https://{server_host}{path}"
|
|
179
|
-
|
|
180
|
-
if not self.filter_config.no_filter:
|
|
181
|
-
self.filter_queue.put(f"{server_host}{path}")
|
|
182
|
-
result = self.filter_result_queue.get(timeout=5)
|
|
183
|
-
if result[1] == "Blocked":
|
|
184
|
-
if not self.logger_config.no_logging_block:
|
|
185
|
-
self.logger_config.block_logger.info(
|
|
186
|
-
"%s - %s - %s",
|
|
187
|
-
ssl_client_socket.getpeername()[0],
|
|
188
|
-
target,
|
|
189
|
-
first_line,
|
|
190
|
-
)
|
|
191
|
-
with open(self.html_403, "r", encoding="utf-8") as f:
|
|
192
|
-
custom_403_page = f.read()
|
|
193
|
-
response = (
|
|
194
|
-
f"HTTP/1.1 403 Forbidden\r\n"
|
|
195
|
-
f"Content-Length: {len(custom_403_page)}\r\n"
|
|
196
|
-
f"\r\n"
|
|
197
|
-
f"{custom_403_page}"
|
|
198
|
-
)
|
|
199
|
-
ssl_client_socket.sendall(response.encode())
|
|
200
|
-
ssl_client_socket.close()
|
|
201
|
-
self.active_connections.pop(threading.get_ident(), None)
|
|
202
|
-
return
|
|
203
|
-
|
|
204
|
-
if not self.logger_config.no_logging_access:
|
|
205
|
-
self.logger_config.access_logger.info(
|
|
206
|
-
"%s - %s - %s %s",
|
|
207
|
-
ssl_client_socket.getpeername()[0],
|
|
208
|
-
f"https://{server_host}",
|
|
209
|
-
method,
|
|
210
|
-
full_url,
|
|
211
|
-
)
|
|
233
|
+
client_socket.sendall(b"HTTP/1.1 200 Connection Established\r\n\r\n")
|
|
212
234
|
|
|
213
|
-
|
|
235
|
+
ssl_client_socket = self._wrap_client_socket_with_ssl(
|
|
236
|
+
client_socket, cert_path, key_path
|
|
237
|
+
)
|
|
214
238
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
239
|
+
server_socket = self._establish_server_connection(
|
|
240
|
+
server_host, server_port
|
|
241
|
+
)
|
|
242
|
+
ssl_server_socket = self._wrap_server_socket_with_ssl(
|
|
243
|
+
server_socket, server_host
|
|
244
|
+
)
|
|
219
245
|
|
|
220
|
-
|
|
221
|
-
|
|
246
|
+
first_request, full_url, is_blocked = self._process_first_ssl_request(
|
|
247
|
+
ssl_client_socket, server_host
|
|
248
|
+
)
|
|
249
|
+
if is_blocked:
|
|
250
|
+
self._send_403(ssl_client_socket, target, first_line)
|
|
251
|
+
return
|
|
252
|
+
if first_request is None:
|
|
253
|
+
ssl_client_socket.close()
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
ssl_server_socket.sendall(first_request.encode())
|
|
222
257
|
|
|
223
258
|
self.transfer_data_between_sockets(ssl_client_socket, ssl_server_socket)
|
|
224
259
|
|