Fix query parameter corruption in base_url with trailing slash enforcement

When a base_url contained query parameters, _enforce_trailing_slash() was
appending a slash to the entire raw_path (including the query string), which
corrupted the query parameter values.

For example:
- Before: base_url="https://api.com?data=1" became "https://api.com?data=1/"
  (query param corrupted to "data=1/" instead of "data=1")
- After: base_url="https://api.com?data=1" becomes "https://api.com/?data=1"
  (query param correctly preserved as "data=1")

The fix splits the raw_path into path and query components, adds the trailing
slash only to the path portion, then recombines them.

Fixes #3614
This commit is contained in:
Varun Chawla 2026-02-07 18:04:59 -08:00
parent b5addb64f0
commit b2adc529e7
No known key found for this signature in database
2 changed files with 46 additions and 3 deletions

View File

@ -232,9 +232,19 @@ class BaseClient:
return self._trust_env
def _enforce_trailing_slash(self, url: URL) -> URL:
if url.raw_path.endswith(b"/"):
return url
return url.copy_with(raw_path=url.raw_path + b"/")
# Split raw_path into path and query to avoid corrupting query parameters
raw_path = url.raw_path
if b"?" in raw_path:
# URL has query parameters - only check/add slash to path portion
path, query = raw_path.split(b"?", 1)
if path.endswith(b"/"):
return url
return url.copy_with(raw_path=path + b"/?" + query)
else:
# No query parameters - check/add slash to entire raw_path
if raw_path.endswith(b"/"):
return url
return url.copy_with(raw_path=raw_path + b"/")
def _get_proxy_map(
self, proxy: ProxyTypes | None, allow_env_proxies: bool

View File

@ -460,3 +460,36 @@ def test_client_decode_text_using_explicit_encoding():
assert response.reason_phrase == "OK"
assert response.encoding == "ISO-8859-1"
assert response.text == text
def test_base_url_with_query_params():
"""
Test that base_url with query parameters doesn't corrupt the query values.
Regression test for issue #3614.
"""
client = httpx.Client(base_url="https://example.com/api?data=1")
# Query parameter should not be corrupted with trailing slash
assert client.base_url.query == b"data=1"
assert str(client.base_url) == "https://example.com/api/?data=1"
def test_base_url_with_trailing_slash_and_query():
"""
Test that base_url with existing trailing slash and query params works correctly.
"""
client = httpx.Client(base_url="https://example.com/api/?key=value")
assert client.base_url.query == b"key=value"
assert str(client.base_url) == "https://example.com/api/?key=value"
def test_base_url_with_multiple_query_params():
"""
Test that base_url with multiple query parameters works correctly.
"""
client = httpx.Client(base_url="https://example.com/api?a=1&b=2&c=3")
assert client.base_url.query == b"a=1&b=2&c=3"
assert str(client.base_url) == "https://example.com/api/?a=1&b=2&c=3"