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.
Files changed (51) hide show
  1. {pyproxytools-0.4.1/pyproxytools.egg-info → pyproxytools-0.4.2}/PKG-INFO +1 -1
  2. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/__init__.py +1 -1
  3. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/handlers/client.py +30 -43
  4. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/handlers/http.py +99 -53
  5. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/handlers/https.py +172 -137
  6. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/pyproxy.py +40 -38
  7. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/server.py +72 -46
  8. pyproxytools-0.4.2/pyproxy/utils/config.py +100 -0
  9. pyproxytools-0.4.2/pyproxy/utils/http_req.py +24 -0
  10. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/utils/logger.py +0 -1
  11. {pyproxytools-0.4.1 → pyproxytools-0.4.2/pyproxytools.egg-info}/PKG-INFO +1 -1
  12. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxytools.egg-info/SOURCES.txt +0 -1
  13. pyproxytools-0.4.2/tests/utils/test_http_req.py +39 -0
  14. pyproxytools-0.4.1/pyproxy/utils/config.py +0 -124
  15. pyproxytools-0.4.1/pyproxy/utils/http_req.py +0 -53
  16. pyproxytools-0.4.1/pyproxy/utils/version.py +0 -0
  17. pyproxytools-0.4.1/tests/utils/test_http_req.py +0 -69
  18. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/LICENSE +0 -0
  19. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/README.md +0 -0
  20. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/benchmark/benchmark.py +0 -0
  21. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/benchmark/utils/__init__.py +0 -0
  22. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/benchmark/utils/html.py +0 -0
  23. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/benchmark/utils/req.py +0 -0
  24. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproject.toml +0 -0
  25. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/__main__.py +0 -0
  26. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/handlers/__init__.py +0 -0
  27. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/modules/__init__.py +0 -0
  28. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/modules/cancel_inspect.py +0 -0
  29. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/modules/custom_header.py +0 -0
  30. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/modules/filter.py +0 -0
  31. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/modules/shortcuts.py +0 -0
  32. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/monitoring/__init__.py +0 -0
  33. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/monitoring/web.py +0 -0
  34. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/utils/__init__.py +0 -0
  35. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/utils/args.py +0 -0
  36. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxy/utils/crypto.py +0 -0
  37. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxytools.egg-info/dependency_links.txt +0 -0
  38. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxytools.egg-info/entry_points.txt +0 -0
  39. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxytools.egg-info/requires.txt +0 -0
  40. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/pyproxytools.egg-info/top_level.txt +0 -0
  41. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/requirements.txt +0 -0
  42. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/setup.cfg +0 -0
  43. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/setup.py +0 -0
  44. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/tests/modules/__init__.py +0 -0
  45. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/tests/modules/test_cancel_inspect.py +0 -0
  46. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/tests/modules/test_custom_header.py +0 -0
  47. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/tests/modules/test_filter.py +0 -0
  48. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/tests/modules/test_shortcuts.py +0 -0
  49. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/tests/utils/__init__.py +0 -0
  50. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/tests/utils/test_crypto.py +0 -0
  51. {pyproxytools-0.4.1 → pyproxytools-0.4.2}/tests/utils/test_logger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyproxytools
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: Lightweight and fast python web proxy
5
5
  Author: 6C656C65
6
6
  License-Expression: MIT
@@ -5,7 +5,7 @@ that holds the current version number of the application.
5
5
 
6
6
  import os
7
7
 
8
- __version__ = "0.4.1"
8
+ __version__ = "0.4.2"
9
9
 
10
10
  if os.path.isdir("pyproxy/monitoring"):
11
11
  __slim__ = False
@@ -39,9 +39,7 @@ class ProxyHandlers:
39
39
  shortcuts,
40
40
  custom_header,
41
41
  active_connections,
42
- proxy_enable,
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.proxy_enable = proxy_enable
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
- client_https_handler = HttpsHandler(
86
- html_403=self.html_403,
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
- client_https_handler.handle_https_connection(client_socket, first_line)
110
+ https_handler.handle_https_connection(client_socket, first_line)
107
111
  else:
108
- client_http_handler = HttpHandler(
109
- html_403=self.html_403,
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, parse_url
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
- proxy_enable,
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.proxy_enable = proxy_enable
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
- domain, _ = parse_url(url)
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 not self.filter_config.no_filter:
94
- self.filter_queue.put(url)
95
- result = self.filter_result_queue.get(timeout=5)
96
- if result[1] == "Blocked":
97
- if not self.logger_config.no_logging_block:
98
- self.logger_config.block_logger.info(
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
- request_lines = request.decode(errors="ignore").split("\r\n")
124
- request_line = request_lines[0] # GET / HTTP/1.1
125
-
126
- header_lines = [f"{key}: {value}" for key, value in headers.items()]
127
- reconstructed_headers = "\r\n".join(header_lines)
128
-
129
- if "\r\n\r\n" in request.decode(errors="ignore"):
130
- body = request.decode(errors="ignore").split("\r\n\r\n", 1)[1]
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.proxy_enable:
153
- server_host, server_port = self.proxy_host, self.proxy_port
194
+ if self.proxy_config.enable:
195
+ server_host, server_port = self.proxy_config.host, self.proxy_config.port
154
196
  else:
155
- server_host, server_port = parse_url(url)
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
- proxy_enable,
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.proxy_enable = proxy_enable
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 not self.filter_config.no_filter:
83
- self.filter_queue.put(target)
84
- result = self.filter_result_queue.get(timeout=5)
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 = False
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
- client_socket.sendall(b"HTTP/1.1 200 Connection Established\r\n\r\n")
131
- ssl_client_socket = client_context.wrap_socket(
132
- client_socket, server_side=True, do_handshake_on_connect=False
133
- )
134
- ssl_client_socket.do_handshake()
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
- try:
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
- ssl_server_socket.sendall(first_request.encode())
235
+ ssl_client_socket = self._wrap_client_socket_with_ssl(
236
+ client_socket, cert_path, key_path
237
+ )
214
238
 
215
- except ValueError:
216
- self.console_logger.error(
217
- "Error parsing request: malformed request line."
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
- except (socket.error, ssl.SSLError) as e:
221
- self.console_logger.error("Network or SSL error : %s", str(e))
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