pyproxytools 0.4.1__tar.gz → 0.4.3__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 (54) hide show
  1. {pyproxytools-0.4.1/pyproxytools.egg-info → pyproxytools-0.4.3}/PKG-INFO +7 -1
  2. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/README.md +6 -0
  3. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxy/__init__.py +1 -1
  4. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxy/handlers/client.py +30 -43
  5. pyproxytools-0.4.3/pyproxy/handlers/http.py +267 -0
  6. pyproxytools-0.4.3/pyproxy/handlers/https.py +402 -0
  7. pyproxytools-0.4.3/pyproxy/pyproxy.py +159 -0
  8. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxy/server.py +80 -48
  9. pyproxytools-0.4.3/pyproxy/utils/config.py +102 -0
  10. pyproxytools-0.4.3/pyproxy/utils/http_req.py +24 -0
  11. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxy/utils/logger.py +24 -3
  12. {pyproxytools-0.4.1 → pyproxytools-0.4.3/pyproxytools.egg-info}/PKG-INFO +7 -1
  13. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxytools.egg-info/SOURCES.txt +0 -1
  14. pyproxytools-0.4.3/tests/utils/test_http_req.py +39 -0
  15. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/tests/utils/test_logger.py +15 -6
  16. pyproxytools-0.4.1/pyproxy/handlers/http.py +0 -197
  17. pyproxytools-0.4.1/pyproxy/handlers/https.py +0 -308
  18. pyproxytools-0.4.1/pyproxy/pyproxy.py +0 -120
  19. pyproxytools-0.4.1/pyproxy/utils/config.py +0 -124
  20. pyproxytools-0.4.1/pyproxy/utils/http_req.py +0 -53
  21. pyproxytools-0.4.1/pyproxy/utils/version.py +0 -0
  22. pyproxytools-0.4.1/tests/utils/test_http_req.py +0 -69
  23. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/LICENSE +0 -0
  24. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/benchmark/benchmark.py +0 -0
  25. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/benchmark/utils/__init__.py +0 -0
  26. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/benchmark/utils/html.py +0 -0
  27. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/benchmark/utils/req.py +0 -0
  28. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproject.toml +0 -0
  29. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxy/__main__.py +0 -0
  30. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxy/handlers/__init__.py +0 -0
  31. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxy/modules/__init__.py +0 -0
  32. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxy/modules/cancel_inspect.py +0 -0
  33. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxy/modules/custom_header.py +0 -0
  34. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxy/modules/filter.py +0 -0
  35. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxy/modules/shortcuts.py +0 -0
  36. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxy/monitoring/__init__.py +0 -0
  37. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxy/monitoring/web.py +0 -0
  38. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxy/utils/__init__.py +0 -0
  39. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxy/utils/args.py +0 -0
  40. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxy/utils/crypto.py +0 -0
  41. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxytools.egg-info/dependency_links.txt +0 -0
  42. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxytools.egg-info/entry_points.txt +0 -0
  43. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxytools.egg-info/requires.txt +0 -0
  44. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/pyproxytools.egg-info/top_level.txt +0 -0
  45. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/requirements.txt +0 -0
  46. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/setup.cfg +0 -0
  47. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/setup.py +0 -0
  48. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/tests/modules/__init__.py +0 -0
  49. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/tests/modules/test_cancel_inspect.py +0 -0
  50. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/tests/modules/test_custom_header.py +0 -0
  51. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/tests/modules/test_filter.py +0 -0
  52. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/tests/modules/test_shortcuts.py +0 -0
  53. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/tests/utils/__init__.py +0 -0
  54. {pyproxytools-0.4.1 → pyproxytools-0.4.3}/tests/utils/test_crypto.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.3
4
4
  Summary: Lightweight and fast python web proxy
5
5
  Author: 6C656C65
6
6
  License-Expression: MIT
@@ -93,6 +93,12 @@ docker run -d ghcr.io/6c656c65/pyproxy:latest
93
93
  ```
94
94
  You can use slim images by adding `-slim` to the end of the tags
95
95
 
96
+ ### Install with Compose
97
+ ```bash
98
+ wget https://raw.githubusercontent.com/6C656C65/pyproxy/main/docker-compose.yml
99
+ docker-compose up -d
100
+ ```
101
+
96
102
  ## 🚀 **Usage**
97
103
 
98
104
  ### Start the proxy
@@ -60,6 +60,12 @@ docker run -d ghcr.io/6c656c65/pyproxy:latest
60
60
  ```
61
61
  You can use slim images by adding `-slim` to the end of the tags
62
62
 
63
+ ### Install with Compose
64
+ ```bash
65
+ wget https://raw.githubusercontent.com/6C656C65/pyproxy/main/docker-compose.yml
66
+ docker-compose up -d
67
+ ```
68
+
63
69
  ## 🚀 **Usage**
64
70
 
65
71
  ### Start the proxy
@@ -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.3"
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)
@@ -0,0 +1,267 @@
1
+ """
2
+ pyproxy.handlers.http.py
3
+
4
+ This module defines the HttpHandler class used by the proxy server to process
5
+ HTTP client connections. It handles request forwarding, blocking, and custom headers.
6
+ """
7
+
8
+ import socket
9
+ import os
10
+ import threading
11
+ from urllib.parse import urlparse
12
+
13
+ from pyproxy.utils.http_req import extract_headers
14
+
15
+
16
+ class HttpHandler:
17
+ """
18
+ HttpHandler manages client HTTP connections for a proxy server,
19
+ handling request forwarding, filtering, blocking, and custom header modification
20
+ based on configuration settings.
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ html_403,
26
+ logger_config,
27
+ filter_config,
28
+ filter_queue,
29
+ filter_result_queue,
30
+ shortcuts_queue,
31
+ shortcuts_result_queue,
32
+ custom_header_queue,
33
+ custom_header_result_queue,
34
+ console_logger,
35
+ shortcuts,
36
+ custom_header,
37
+ active_connections,
38
+ proxy_config,
39
+ ):
40
+ self.html_403 = html_403
41
+ self.logger_config = logger_config
42
+ self.filter_config = filter_config
43
+ self.filter_queue = filter_queue
44
+ self.filter_result_queue = filter_result_queue
45
+ self.shortcuts_queue = shortcuts_queue
46
+ self.shortcuts_result_queue = shortcuts_result_queue
47
+ self.custom_header_queue = custom_header_queue
48
+ self.custom_header_result_queue = custom_header_result_queue
49
+ self.console_logger = console_logger
50
+ self.config_shortcuts = shortcuts
51
+ self.config_custom_header = custom_header
52
+ self.proxy_config = proxy_config
53
+ self.active_connections = active_connections
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
+ method, domain_port, protocol = first_line.split(" ")
113
+ domain, port = domain_port.split(":")
114
+ self.logger_config.block_logger.info(
115
+ "",
116
+ extra={
117
+ "ip_src": client_socket.getpeername()[0],
118
+ "url": url,
119
+ "method": method,
120
+ "domain": domain,
121
+ "port": port,
122
+ "protocol": protocol,
123
+ },
124
+ )
125
+ with open(self.html_403, "r", encoding="utf-8") as f:
126
+ custom_403_page = f.read()
127
+ response = (
128
+ f"HTTP/1.1 403 Forbidden\r\n"
129
+ f"Content-Length: {len(custom_403_page)}\r\n"
130
+ f"\r\n"
131
+ f"{custom_403_page}"
132
+ )
133
+ client_socket.sendall(response.encode())
134
+ client_socket.close()
135
+ self.active_connections.pop(threading.get_ident(), None)
136
+
137
+ def handle_http_request(self, client_socket, request):
138
+ """
139
+ Processes an HTTP request, checks for URL filtering, applies shortcuts,
140
+ and forwards the request to the target server if not blocked.
141
+
142
+ Args:
143
+ client_socket (socket): The socket object for the client connection.
144
+ request (bytes): The raw HTTP request sent by the client.
145
+ """
146
+ first_line = request.decode(errors="ignore").split("\n")[0]
147
+ url = first_line.split(" ")[1]
148
+
149
+ if self.config_shortcuts and os.path.isfile(self.config_shortcuts):
150
+ shortcut_url = self._apply_shortcut(url)
151
+ if shortcut_url:
152
+ response = (
153
+ f"HTTP/1.1 302 Found\r\n"
154
+ f"Location: {shortcut_url}\r\n"
155
+ f"Content-Length: 0\r\n"
156
+ "\r\n"
157
+ )
158
+
159
+ client_socket.sendall(response.encode())
160
+ client_socket.close()
161
+ self.active_connections.pop(threading.get_ident(), None)
162
+ return
163
+
164
+ if self._is_blocked(url):
165
+ self._send_403(client_socket, url, first_line)
166
+ return
167
+
168
+ if self.config_custom_header and os.path.isfile(self.config_custom_header):
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 ""
177
+ )
178
+ modified_request = self._rebuild_http_request(request_line, headers, body)
179
+
180
+ self.forward_request_to_server(
181
+ client_socket, modified_request, url, first_line
182
+ )
183
+
184
+ else:
185
+ self.forward_request_to_server(client_socket, request, url, first_line)
186
+
187
+ def forward_request_to_server(self, client_socket, request, url, first_line):
188
+ """
189
+ Forwards the HTTP request to the target server and sends the response back to the client.
190
+
191
+ Args:
192
+ client_socket (socket): The socket object for the client connection.
193
+ request (bytes): The raw HTTP request sent by the client.
194
+ url (str): The target URL from the HTTP request.
195
+ first_line (str): The first line of the HTTP request (e.g., "GET / HTTP/1.1").
196
+ """
197
+ if self.proxy_config.enable:
198
+ server_host, server_port = self.proxy_config.host, self.proxy_config.port
199
+ else:
200
+ parsed_url = urlparse(url)
201
+ server_host = parsed_url.hostname
202
+ server_port = parsed_url.port or (
203
+ 443 if parsed_url.scheme == "https" else 80
204
+ )
205
+ thread_id = threading.get_ident()
206
+
207
+ if thread_id in self.active_connections:
208
+ self.active_connections[thread_id] = {
209
+ "target_ip": server_host,
210
+ "target_port": server_port,
211
+ "bytes_sent": 0,
212
+ "bytes_received": 0,
213
+ }
214
+
215
+ try:
216
+ server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
217
+ server_socket.connect((server_host, server_port))
218
+ server_socket.sendall(request)
219
+ server_socket.settimeout(5)
220
+ self.active_connections[thread_id]["bytes_sent"] += len(request)
221
+
222
+ while True:
223
+ try:
224
+ response = server_socket.recv(4096)
225
+ if response:
226
+ client_socket.send(response)
227
+ self.active_connections[thread_id]["bytes_received"] += len(
228
+ response
229
+ )
230
+ else:
231
+ break
232
+ except socket.timeout:
233
+ break
234
+ except (socket.timeout, socket.gaierror, ConnectionRefusedError, OSError) as e:
235
+ self.console_logger.error(
236
+ "Error connecting to the server %s : %s", server_host, e
237
+ )
238
+ response = (
239
+ f"HTTP/1.1 502 Bad Gateway\r\n"
240
+ f"Content-Length: {len('Bad Gateway')} \r\n"
241
+ "\r\n"
242
+ f"Bad Gateway"
243
+ )
244
+ client_socket.sendall(response.encode())
245
+ client_socket.close()
246
+ self.active_connections.pop(thread_id, None)
247
+ finally:
248
+ if not self.logger_config.no_logging_access:
249
+ method, url, protocol = first_line.split(" ")
250
+
251
+ conn_data = self.active_connections.get(thread_id, {})
252
+ self.logger_config.access_logger.info(
253
+ "",
254
+ extra={
255
+ "ip_src": client_socket.getpeername()[0],
256
+ "url": f"http://{server_host}",
257
+ "method": method,
258
+ "domain": parsed_url.hostname,
259
+ "port": parsed_url.port,
260
+ "protocol": protocol,
261
+ "bytes_sent": conn_data.get("bytes_sent", 0),
262
+ "bytes_received": conn_data.get("bytes_received", 0),
263
+ },
264
+ )
265
+ client_socket.close()
266
+ server_socket.close()
267
+ self.active_connections.pop(thread_id, None)