feat: make SOCKS5 proxy configurable per adapter
Add `proxy` config option to server (IRC), teams, telegram, and mumble sections. IRC defaults to false (preserving current direct-connect behavior); all others default to true. The `derp.http` module now accepts `proxy=True/False` on urlopen, create_connection, open_connection, and build_opener -- when false, uses stdlib directly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"""Tests for the SOCKS5 proxy HTTP/TCP module."""
|
||||
"""Tests for the HTTP/TCP module with optional SOCKS5 proxy."""
|
||||
|
||||
import socket
|
||||
import ssl
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
@@ -267,3 +268,106 @@ class TestCreateConnection:
|
||||
mock_cls.return_value = sock
|
||||
result = create_connection(("example.com", 443))
|
||||
assert result is sock
|
||||
|
||||
|
||||
# -- proxy=False paths -------------------------------------------------------
|
||||
|
||||
class TestUrlopenDirect:
|
||||
"""Tests for urlopen(proxy=False) -- stdlib direct path."""
|
||||
|
||||
@patch("derp.http.urllib.request.urlopen")
|
||||
def test_uses_stdlib_urlopen(self, mock_urlopen):
|
||||
resp = MagicMock()
|
||||
mock_urlopen.return_value = resp
|
||||
result = urlopen("https://example.com/", proxy=False)
|
||||
mock_urlopen.assert_called_once()
|
||||
assert result is resp
|
||||
|
||||
@patch("derp.http.urllib.request.urlopen")
|
||||
def test_passes_timeout(self, mock_urlopen):
|
||||
resp = MagicMock()
|
||||
mock_urlopen.return_value = resp
|
||||
urlopen("https://example.com/", timeout=15, proxy=False)
|
||||
_, kwargs = mock_urlopen.call_args
|
||||
assert kwargs["timeout"] == 15
|
||||
|
||||
@patch("derp.http.urllib.request.urlopen")
|
||||
def test_passes_context(self, mock_urlopen):
|
||||
resp = MagicMock()
|
||||
mock_urlopen.return_value = resp
|
||||
ctx = ssl.create_default_context()
|
||||
urlopen("https://example.com/", context=ctx, proxy=False)
|
||||
_, kwargs = mock_urlopen.call_args
|
||||
assert kwargs["context"] is ctx
|
||||
|
||||
@patch.object(derp.http, "_get_pool")
|
||||
@patch("derp.http.urllib.request.urlopen")
|
||||
def test_skips_socks_pool(self, mock_urlopen, mock_pool_fn):
|
||||
resp = MagicMock()
|
||||
mock_urlopen.return_value = resp
|
||||
urlopen("https://example.com/", proxy=False)
|
||||
mock_pool_fn.assert_not_called()
|
||||
|
||||
|
||||
class TestBuildOpenerDirect:
|
||||
"""Tests for build_opener(proxy=False) -- no SOCKS5 handler."""
|
||||
|
||||
def test_no_proxy_handler(self):
|
||||
opener = build_opener(proxy=False)
|
||||
proxy_handlers = [h for h in opener.handlers
|
||||
if isinstance(h, _ProxyHandler)]
|
||||
assert len(proxy_handlers) == 0
|
||||
|
||||
def test_with_extra_handler(self):
|
||||
class Custom(urllib.request.HTTPRedirectHandler):
|
||||
pass
|
||||
|
||||
opener = build_opener(Custom, proxy=False)
|
||||
custom = [h for h in opener.handlers if isinstance(h, Custom)]
|
||||
assert len(custom) == 1
|
||||
proxy_handlers = [h for h in opener.handlers
|
||||
if isinstance(h, _ProxyHandler)]
|
||||
assert len(proxy_handlers) == 0
|
||||
|
||||
|
||||
class TestCreateConnectionDirect:
|
||||
"""Tests for create_connection(proxy=False) -- stdlib socket."""
|
||||
|
||||
@patch("derp.http.socket.socket")
|
||||
def test_uses_stdlib_socket(self, mock_sock_cls):
|
||||
sock = MagicMock()
|
||||
mock_sock_cls.return_value = sock
|
||||
result = create_connection(("example.com", 443), proxy=False)
|
||||
mock_sock_cls.assert_called_once_with(
|
||||
socket.AF_INET, socket.SOCK_STREAM,
|
||||
)
|
||||
assert result is sock
|
||||
|
||||
@patch("derp.http.socket.socket")
|
||||
def test_connects_to_target(self, mock_sock_cls):
|
||||
sock = MagicMock()
|
||||
mock_sock_cls.return_value = sock
|
||||
create_connection(("example.com", 443), proxy=False)
|
||||
sock.connect.assert_called_once_with(("example.com", 443))
|
||||
|
||||
@patch("derp.http.socket.socket")
|
||||
def test_sets_timeout(self, mock_sock_cls):
|
||||
sock = MagicMock()
|
||||
mock_sock_cls.return_value = sock
|
||||
create_connection(("example.com", 443), timeout=10, proxy=False)
|
||||
sock.settimeout.assert_called_once_with(10)
|
||||
|
||||
@patch("derp.http.socket.socket")
|
||||
def test_no_socks_proxy_set(self, mock_sock_cls):
|
||||
sock = MagicMock()
|
||||
mock_sock_cls.return_value = sock
|
||||
create_connection(("example.com", 443), proxy=False)
|
||||
sock.set_proxy.assert_not_called()
|
||||
|
||||
@patch("derp.http.socks.socksocket")
|
||||
@patch("derp.http.socket.socket")
|
||||
def test_no_socksocket_created(self, mock_sock_cls, mock_socks_cls):
|
||||
sock = MagicMock()
|
||||
mock_sock_cls.return_value = sock
|
||||
create_connection(("example.com", 443), proxy=False)
|
||||
mock_socks_cls.assert_not_called()
|
||||
|
||||
Reference in New Issue
Block a user