Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix connecting to npipe://, tcp://, and unix:// urls #8632

Merged
merged 35 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
53d2b3e
Fix connecting to unix:// urls
bdraco Aug 7, 2024
7c01a11
changelog
bdraco Aug 7, 2024
fe4833e
fix "npipe" as well
bdraco Aug 7, 2024
b6cd175
Update CHANGES/8632.bugfix.rst
bdraco Aug 7, 2024
c8ae1f2
Make allowed protocols per connector, allow all in base connector
bdraco Aug 7, 2024
9de9578
Merge remote-tracking branch 'upstream/fix_unix_tcp' into fix_unix_tcp
bdraco Aug 7, 2024
857b6c7
Make allowed protocols per connector, allow all in base connector
bdraco Aug 7, 2024
64292c0
Update aiohttp/connector.py
bdraco Aug 7, 2024
1d79ef2
Make allowed protocols per connector, allow all in base connector
bdraco Aug 7, 2024
d713232
ensure base connector allows all protocols by default
bdraco Aug 7, 2024
83fd6f0
ensure base connector allows all protocols by default
bdraco Aug 7, 2024
9d2fe1a
fixes
bdraco Aug 7, 2024
883c2f6
fixes
bdraco Aug 7, 2024
1b65087
fixes
bdraco Aug 7, 2024
fba214f
type
bdraco Aug 7, 2024
af4cc59
fix tcp:// as well
bdraco Aug 7, 2024
ce827f1
Update CHANGES/8632.bugfix.rst
bdraco Aug 7, 2024
709f824
fix tcp:// as well
bdraco Aug 7, 2024
a0aeb7c
Merge remote-tracking branch 'upstream/fix_unix_tcp' into fix_unix_tcp
bdraco Aug 7, 2024
5967e5a
rename to BASE_PROTOCOL_SCHEMA_SET
bdraco Aug 7, 2024
412e12f
Merge remote-tracking branch 'upstream/master' into fix_unix_tcp
bdraco Aug 7, 2024
71bf4ae
drop now
bdraco Aug 7, 2024
9c95012
drop now
bdraco Aug 7, 2024
ad51c59
Update aiohttp/client.py
Dreamsorcerer Aug 7, 2024
2256884
drop ones that are no longer in the base connector
bdraco Aug 7, 2024
cde13a6
Update aiohttp/connector.py
bdraco Aug 7, 2024
374821b
kiss
bdraco Aug 7, 2024
232d377
preen
bdraco Aug 7, 2024
f7996bf
Update tests/test_client_session.py
bdraco Aug 7, 2024
258eae9
explict unix test
bdraco Aug 7, 2024
4652d6b
Merge remote-tracking branch 'upstream/fix_unix_tcp' into fix_unix_tcp
bdraco Aug 7, 2024
f5d3bb4
Update tests/test_client_session.py
bdraco Aug 7, 2024
83db4a1
fix type
bdraco Aug 7, 2024
5b22c58
Merge remote-tracking branch 'upstream/fix_unix_tcp' into fix_unix_tcp
bdraco Aug 7, 2024
3bbf448
fix type
bdraco Aug 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/8632.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed connecting to ``npipe://`` and ``unix://`` urls -- by :user:`bdraco`.
bdraco marked this conversation as resolved.
Show resolved Hide resolved
18 changes: 11 additions & 7 deletions aiohttp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,13 @@
ClientWebSocketResponse,
ClientWSTimeout,
)
from .connector import BaseConnector, NamedPipeConnector, TCPConnector, UnixConnector
from .connector import (
HTTP_AND_EMPTY_SCHEMA_SET,
BaseConnector,
NamedPipeConnector,
TCPConnector,
UnixConnector,
)
from .cookiejar import CookieJar
from .helpers import (
_SENTINEL,
Expand Down Expand Up @@ -210,9 +216,7 @@ class ClientTimeout:

# https://www.rfc-editor.org/rfc/rfc9110#section-9.2.2
IDEMPOTENT_METHODS = frozenset({"GET", "HEAD", "OPTIONS", "TRACE", "PUT", "DELETE"})
HTTP_SCHEMA_SET = frozenset({"http", "https", ""})
WS_SCHEMA_SET = frozenset({"ws", "wss"})
ALLOWED_PROTOCOL_SCHEMA_SET = HTTP_SCHEMA_SET | WS_SCHEMA_SET

Dreamsorcerer marked this conversation as resolved.
Show resolved Hide resolved

_RetType = TypeVar("_RetType")
_CharsetResolver = Callable[[ClientResponse, bytes], str]
Expand Down Expand Up @@ -455,7 +459,8 @@ async def _request(
except ValueError as e:
raise InvalidUrlClientError(str_or_url) from e

if url.scheme not in ALLOWED_PROTOCOL_SCHEMA_SET:
assert self._connector is not None
if url.scheme not in self._connector.allowed_protocol_schema_set:
raise NonHttpUrlClientError(url)

skip_headers = set(self._skip_auto_headers)
Expand Down Expand Up @@ -586,7 +591,6 @@ async def _request(
real_timeout.connect,
ceil_threshold=real_timeout.ceil_threshold,
):
assert self._connector is not None
conn = await self._connector.connect(
req, traces=traces, timeout=real_timeout
)
Expand Down Expand Up @@ -682,7 +686,7 @@ async def _request(
) from e

scheme = parsed_redirect_url.scheme
if scheme not in HTTP_SCHEMA_SET:
if scheme not in HTTP_AND_EMPTY_SCHEMA_SET:
resp.close()
raise NonHttpUrlRedirectClientError(r_url)
elif not scheme:
Expand Down
42 changes: 42 additions & 0 deletions aiohttp/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import warnings
from collections import defaultdict, deque
from contextlib import suppress
from functools import cached_property
from http import HTTPStatus
from http.cookies import SimpleCookie
from itertools import cycle, islice
Expand All @@ -21,6 +22,7 @@
Callable,
DefaultDict,
Dict,
FrozenSet,
Iterator,
List,
Literal,
Expand Down Expand Up @@ -63,6 +65,23 @@
SSLContext = object # type: ignore[misc,assignment]


EMPTY_SCHEMA_SET = frozenset({""})
Dreamsorcerer marked this conversation as resolved.
Show resolved Hide resolved
HTTP_SCHEMA_SET = frozenset({"http", "https"})
WS_SCHEMA_SET = frozenset({"ws", "wss"})
TCP_PROTOCOL_SCHEMA_SET = frozenset({"tcp"})
UNIX_PROTOCOL_SCHEMA_SET = frozenset({"unix"})
NAMED_PIPE_PROTOCOL_SCHEMA_SET = frozenset({"npipe"})

HTTP_AND_EMPTY_SCHEMA_SET = HTTP_SCHEMA_SET | EMPTY_SCHEMA_SET
HIGH_LEVEL_SCHEMA_SET = HTTP_AND_EMPTY_SCHEMA_SET | WS_SCHEMA_SET
ALLOWED_PROTOCOL_SCHEMA_SET = (
HIGH_LEVEL_SCHEMA_SET
| TCP_PROTOCOL_SCHEMA_SET
| UNIX_PROTOCOL_SCHEMA_SET
| NAMED_PIPE_PROTOCOL_SCHEMA_SET
)


__all__ = ("BaseConnector", "TCPConnector", "UnixConnector", "NamedPipeConnector")


Expand Down Expand Up @@ -243,6 +262,14 @@ def __init__(
self._cleanup_closed_transports: List[Optional[asyncio.Transport]] = []
self._cleanup_closed()

@cached_property
def allowed_protocol_schema_set(self) -> FrozenSet[str]:
"""Return allowed protocol schema set.

By default we allow all protocols for backwards compatibility.
bdraco marked this conversation as resolved.
Show resolved Hide resolved
"""
return ALLOWED_PROTOCOL_SCHEMA_SET
Dreamsorcerer marked this conversation as resolved.
Show resolved Hide resolved

def __del__(self, _warnings: Any = warnings) -> None:
if self._closed:
return
Expand Down Expand Up @@ -790,6 +817,11 @@ def _close_immediately(self) -> List["asyncio.Future[None]"]:
ev.cancel()
return super()._close_immediately()

@cached_property
def allowed_protocol_schema_set(self) -> FrozenSet[str]:
"""Return allowed protocol schema set."""
return HIGH_LEVEL_SCHEMA_SET | TCP_PROTOCOL_SCHEMA_SET

@property
def family(self) -> int:
"""Socket family like AF_INET."""
Expand Down Expand Up @@ -1357,6 +1389,11 @@ def __init__(
)
self._path = path

@cached_property
def allowed_protocol_schema_set(self) -> FrozenSet[str]:
"""Return allowed protocol schema set."""
return HIGH_LEVEL_SCHEMA_SET | UNIX_PROTOCOL_SCHEMA_SET

@property
def path(self) -> str:
"""Path to unix socket."""
Expand Down Expand Up @@ -1417,6 +1454,11 @@ def __init__(
)
self._path = path

@cached_property
def allowed_protocol_schema_set(self) -> FrozenSet[str]:
"""Return allowed protocol schema set."""
return HIGH_LEVEL_SCHEMA_SET | NAMED_PIPE_PROTOCOL_SCHEMA_SET

@property
def path(self) -> str:
"""Path to the named pipe."""
Expand Down
10 changes: 6 additions & 4 deletions tests/test_client_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ async def create_connection(req, traces, timeout):
c.__del__()
Dismissed Show dismissed Hide dismissed


@pytest.mark.parametrize("protocol", ["http", "https", "ws", "wss"])
@pytest.mark.parametrize("protocol", ["http", "https", "npipe", "ws", "wss", "unix"])
async def test_ws_connect_allowed_protocols(
create_session: Any,
create_mocked_conn: Any,
Expand All @@ -482,15 +482,17 @@ async def test_ws_connect_allowed_protocols(
hdrs.CONNECTION: "upgrade",
hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,
}
resp.url = URL(f"{protocol}://example.com")
resp.url = URL(f"{protocol}://example")
resp.cookies = SimpleCookie()
resp.start = mock.AsyncMock()

req = mock.create_autospec(aiohttp.ClientRequest, spec_set=True)
req_factory = mock.Mock(return_value=req)
req.send = mock.AsyncMock(return_value=resp)
# BaseConnector allows all protocols by default
bdraco marked this conversation as resolved.
Show resolved Hide resolved
connector = BaseConnector()

session = await create_session(request_class=req_factory)
session = await create_session(connector=connector, request_class=req_factory)

connections = []
original_connect = session._connector.connect
Expand All @@ -510,7 +512,7 @@ async def create_connection(req, traces, timeout):
"aiohttp.client.os"
) as m_os:
m_os.urandom.return_value = key_data
await session.ws_connect(f"{protocol}://example.com")
await session.ws_connect(f"{protocol}://example")

# normally called during garbage collection. triggers an exception
# if the connection wasn't already closed
Expand Down
34 changes: 34 additions & 0 deletions tests/test_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -1490,6 +1490,11 @@ async def test_tcp_connector_ctor(loop: Any) -> None:
assert conn.family == 0


async def test_tcp_connector_allowed_protocols(loop: Any) -> None:
conn = aiohttp.TCPConnector()
assert conn.allowed_protocol_schema_set == {"", "tcp", "http", "https", "ws", "wss"}


async def test_invalid_ssl_param() -> None:
with pytest.raises(TypeError):
aiohttp.TCPConnector(ssl=object())
Expand Down Expand Up @@ -1649,6 +1654,19 @@ async def test_ctor_with_default_loop(loop: Any) -> None:
assert loop is conn._loop


async def test_base_connector_allows_all_protocols(loop: Any) -> None:
conn = aiohttp.BaseConnector()
assert conn.allowed_protocol_schema_set == {
"",
"http",
"https",
"ws",
"wss",
"unix",
"npipe",
}


async def test_connect_with_limit(loop: Any, key: Any) -> None:
proto = create_mocked_conn(loop)
proto.is_connected.return_value = True
Expand Down Expand Up @@ -2420,6 +2438,14 @@ async def handler(request):

connector = aiohttp.UnixConnector(unix_sockname)
assert unix_sockname == connector.path
assert connector.allowed_protocol_schema_set == {
"",
"http",
"https",
"ws",
"wss",
"unix",
}

session = client.ClientSession(connector=connector)
r = await session.get(url)
Expand All @@ -2445,6 +2471,14 @@ async def handler(request):

connector = aiohttp.NamedPipeConnector(pipe_name)
assert pipe_name == connector.path
assert connector.allowed_protocol_schema_set == {
"",
"http",
"https",
"ws",
"wss",
"npipe",
}

session = client.ClientSession(connector=connector)
r = await session.get(url)
Expand Down