pyproxytools 0.4.0__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.0/pyproxytools.egg-info → pyproxytools-0.4.2}/PKG-INFO +5 -5
  2. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/README.md +3 -4
  3. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/__init__.py +1 -1
  4. pyproxytools-0.4.2/pyproxy/__main__.py +4 -0
  5. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/handlers/client.py +30 -43
  6. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/handlers/http.py +99 -53
  7. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/handlers/https.py +172 -137
  8. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/pyproxy.py +47 -32
  9. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/server.py +73 -47
  10. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/utils/args.py +1 -1
  11. pyproxytools-0.4.2/pyproxy/utils/config.py +100 -0
  12. pyproxytools-0.4.2/pyproxy/utils/http_req.py +24 -0
  13. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/utils/logger.py +19 -4
  14. {pyproxytools-0.4.0 → pyproxytools-0.4.2/pyproxytools.egg-info}/PKG-INFO +5 -5
  15. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxytools.egg-info/SOURCES.txt +1 -1
  16. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxytools.egg-info/requires.txt +1 -0
  17. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/requirements.txt +2 -1
  18. pyproxytools-0.4.2/tests/utils/test_http_req.py +39 -0
  19. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/tests/utils/test_logger.py +10 -1
  20. pyproxytools-0.4.0/pyproxy/utils/config.py +0 -110
  21. pyproxytools-0.4.0/pyproxy/utils/http_req.py +0 -53
  22. pyproxytools-0.4.0/pyproxy/utils/version.py +0 -0
  23. pyproxytools-0.4.0/tests/utils/test_http_req.py +0 -69
  24. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/LICENSE +0 -0
  25. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/benchmark/benchmark.py +0 -0
  26. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/benchmark/utils/__init__.py +0 -0
  27. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/benchmark/utils/html.py +0 -0
  28. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/benchmark/utils/req.py +0 -0
  29. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproject.toml +0 -0
  30. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/handlers/__init__.py +0 -0
  31. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/modules/__init__.py +0 -0
  32. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/modules/cancel_inspect.py +0 -0
  33. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/modules/custom_header.py +0 -0
  34. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/modules/filter.py +0 -0
  35. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/modules/shortcuts.py +0 -0
  36. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/monitoring/__init__.py +0 -0
  37. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/monitoring/web.py +0 -0
  38. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/utils/__init__.py +0 -0
  39. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxy/utils/crypto.py +0 -0
  40. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxytools.egg-info/dependency_links.txt +0 -0
  41. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxytools.egg-info/entry_points.txt +0 -0
  42. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/pyproxytools.egg-info/top_level.txt +0 -0
  43. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/setup.cfg +0 -0
  44. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/setup.py +0 -0
  45. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/tests/modules/__init__.py +0 -0
  46. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/tests/modules/test_cancel_inspect.py +0 -0
  47. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/tests/modules/test_custom_header.py +0 -0
  48. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/tests/modules/test_filter.py +0 -0
  49. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/tests/modules/test_shortcuts.py +0 -0
  50. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/tests/utils/__init__.py +0 -0
  51. {pyproxytools-0.4.0 → pyproxytools-0.4.2}/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.0
3
+ Version: 0.4.2
4
4
  Summary: Lightweight and fast python web proxy
5
5
  Author: 6C656C65
6
6
  License-Expression: MIT
@@ -28,6 +28,7 @@ Requires-Dist: requests>=2.31.0
28
28
  Requires-Dist: Flask>=3.1.0
29
29
  Requires-Dist: Flask-HTTPAuth>=4.8.0
30
30
  Requires-Dist: psutil>=5.9.8
31
+ Requires-Dist: colorlog>=6.9.0
31
32
  Dynamic: license-file
32
33
 
33
34
  <div align="center">
@@ -47,9 +48,8 @@ Dynamic: license-file
47
48
  <img src="https://img.shields.io/github/commit-activity/w/6C656C65/pyproxy?style=for-the-badge">
48
49
  <img src="https://img.shields.io/github/contributors/6C656C65/pyproxy?style=for-the-badge">
49
50
  <br>
50
- <img src="https://img.shields.io/github/actions/workflow/status/6C656C65/pyproxy/code-scan.yml?label=Scan&style=for-the-badge">
51
- <img src="https://img.shields.io/github/actions/workflow/status/6C656C65/pyproxy/unittest.yml?label=Tests&style=for-the-badge">
52
- <img src="https://img.shields.io/github/actions/workflow/status/6C656C65/pyproxy/docker-images.yml?label=Delivery&style=for-the-badge">
51
+ <img src="https://img.shields.io/pypi/v/pyproxytools?style=for-the-badge">
52
+ <img src="https://img.shields.io/pypi/pyversions/pyproxytools?style=for-the-badge">
53
53
  </p>
54
54
 
55
55
  ---
@@ -97,7 +97,7 @@ You can use slim images by adding `-slim` to the end of the tags
97
97
 
98
98
  ### Start the proxy
99
99
  ```bash
100
- python3 -m pyproxy.pyproxy
100
+ python3 -m pyproxy
101
101
  ```
102
102
  The proxy will be available at: `0.0.0.0:8080`.
103
103
  The access log will be available at `./logs/access.log`.
@@ -15,9 +15,8 @@
15
15
  <img src="https://img.shields.io/github/commit-activity/w/6C656C65/pyproxy?style=for-the-badge">
16
16
  <img src="https://img.shields.io/github/contributors/6C656C65/pyproxy?style=for-the-badge">
17
17
  <br>
18
- <img src="https://img.shields.io/github/actions/workflow/status/6C656C65/pyproxy/code-scan.yml?label=Scan&style=for-the-badge">
19
- <img src="https://img.shields.io/github/actions/workflow/status/6C656C65/pyproxy/unittest.yml?label=Tests&style=for-the-badge">
20
- <img src="https://img.shields.io/github/actions/workflow/status/6C656C65/pyproxy/docker-images.yml?label=Delivery&style=for-the-badge">
18
+ <img src="https://img.shields.io/pypi/v/pyproxytools?style=for-the-badge">
19
+ <img src="https://img.shields.io/pypi/pyversions/pyproxytools?style=for-the-badge">
21
20
  </p>
22
21
 
23
22
  ---
@@ -65,7 +64,7 @@ You can use slim images by adding `-slim` to the end of the tags
65
64
 
66
65
  ### Start the proxy
67
66
  ```bash
68
- python3 -m pyproxy.pyproxy
67
+ python3 -m pyproxy
69
68
  ```
70
69
  The proxy will be available at: `0.0.0.0:8080`.
71
70
  The access log will be available at `./logs/access.log`.
@@ -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.0"
8
+ __version__ = "0.4.2"
9
9
 
10
10
  if os.path.isdir("pyproxy/monitoring"):
11
11
  __slim__ = False
@@ -0,0 +1,4 @@
1
+ from .pyproxy import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -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: