-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathproxy_helper.py
More file actions
148 lines (124 loc) · 4.6 KB
/
Copy pathproxy_helper.py
File metadata and controls
148 lines (124 loc) · 4.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import socket
import threading
import base64
import os
import shutil
class LocalProxyTunnel:
"""Lightweight local HTTP CONNECT tunnel for authenticated proxy support.
Chrome connects to localhost (no auth needed), and this tunnel
injects proxy credentials before forwarding to the upstream proxy.
"""
def __init__(self, upstream_host, upstream_port, username, password):
self.upstream_host = upstream_host
self.upstream_port = int(upstream_port)
self.username = username
self.password = password
self.server = None
self.local_port = None
self._thread = None
def _get_auth_header(self):
creds = f"{self.username}:{self.password}".encode()
encoded = base64.b64encode(creds).decode()
return f"Proxy-Authorization: Basic {encoded}\r\n"
def _handle_client(self, client_sock):
try:
# Read the CONNECT request from browser
data = b""
while b"\r\n\r\n" not in data:
chunk = client_sock.recv(4096)
if not chunk:
break
data += chunk
if not data:
return
# Connect to upstream proxy
upstream_sock = socket.create_connection(
(self.upstream_host, self.upstream_port), timeout=15
)
# Inject auth header into the original request
first_line_end = data.find(b"\r\n")
first_line = data[:first_line_end]
rest = data[first_line_end + 2:]
auth_header = self._get_auth_header().encode()
modified_request = first_line + b"\r\n" + auth_header + rest
upstream_sock.sendall(modified_request)
# Read upstream response
response = b""
while b"\r\n\r\n" not in response:
chunk = upstream_sock.recv(4096)
if not chunk:
break
response += chunk
# Forward response back to browser
client_sock.sendall(response)
# Bi-directional data forwarding
def forward(src, dst):
try:
while True:
data = src.recv(8192)
if not data:
break
dst.sendall(data)
except:
pass
finally:
try: src.close()
except: pass
try: dst.close()
except: pass
t1 = threading.Thread(target=forward, args=(client_sock, upstream_sock), daemon=True)
t2 = threading.Thread(target=forward, args=(upstream_sock, client_sock), daemon=True)
t1.start()
t2.start()
except Exception:
try: client_sock.close()
except: pass
def _run_server(self):
while True:
try:
client_sock, _ = self.server.accept()
t = threading.Thread(target=self._handle_client, args=(client_sock,), daemon=True)
t.start()
except OSError:
break
def start(self):
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server.bind(('127.0.0.1', 0))
self.local_port = self.server.getsockname()[1]
self.server.listen(50)
self._thread = threading.Thread(target=self._run_server, daemon=True)
self._thread.start()
return self.local_port
def stop(self):
if self.server:
try:
self.server.close()
except:
pass
def create_proxy_tunnel(proxy_url):
"""Start a local tunnel for an authenticated proxy (user:pass@host:port).
Returns (tunnel, local_port) or (None, None) on failure.
"""
if '@' not in proxy_url:
return None, None
auth, host_port = proxy_url.split('@', 1)
if ':' not in auth or ':' not in host_port:
return None, None
usr, pwd = auth.split(':', 1)
host, port = host_port.split(':', 1)
tunnel = LocalProxyTunnel(host, int(port), usr, pwd)
local_port = tunnel.start()
return tunnel, local_port
EXTENSION_DIR = "Proxy_Extensions"
def init_extension_dir():
"""Reset the proxy extension directory to a clean state."""
if os.path.exists(EXTENSION_DIR):
try:
shutil.rmtree(EXTENSION_DIR)
except Exception:
pass
try:
os.makedirs(EXTENSION_DIR, exist_ok=True)
except:
pass