diff --git a/httpx/_urlparse.py b/httpx/_urlparse.py index a9a0aecd9b..311e65c90a 100644 --- a/httpx/_urlparse.py +++ b/httpx/_urlparse.py @@ -258,16 +258,16 @@ def urlparse(url: str = "", **kwargs: typing.Optional[str]) -> ParseResult: # specific component. # For 'path' we need to drop ? and # from the GEN_DELIMS set. - parsed_path: str = quote(path, safe=SUB_DELIMS + ":/[]@") + parsed_path: str = quote(path, safe=SUB_DELIMS + ":/[]@%") # For 'query' we need to drop '#' from the GEN_DELIMS set. # We also exclude '/' because it is more robust to replace it with a percent # encoding despite it not being a requirement of the spec. parsed_query: typing.Optional[str] = ( - None if query is None else quote(query, safe=SUB_DELIMS + ":?[]@") + None if query is None else quote(query, safe=SUB_DELIMS + ":?[]@%") ) # For 'fragment' we can include all of the GEN_DELIMS set. parsed_fragment: typing.Optional[str] = ( - None if fragment is None else quote(fragment, safe=SUB_DELIMS + ":/?#[]@") + None if fragment is None else quote(fragment, safe=SUB_DELIMS + ":/?#[]@%") ) # The parsed ASCII bytestrings are our canonical form. diff --git a/tests/test_urlparse.py b/tests/test_urlparse.py index b03291b44f..f187210dcc 100644 --- a/tests/test_urlparse.py +++ b/tests/test_urlparse.py @@ -150,6 +150,26 @@ def test_param_with_existing_escape_requires_encoding(): assert str(url) == "http://webservice?u=http%3A%2F%2Fexample.com%3Fq%3Dfoo%252Fa" +def test_param_with_existing_escape(): + url = httpx.URL("https://webservice/?u=/%3D%26&v=1%202") + assert str(url) == "https://webservice/?u=%2F%3D%26&v=1%202" + assert url.params["u"] == "/=&" + assert url.params["v"] == "1 2" + + +def test_param_nested_urls_in_query(): + src = "special;string with:reserved?cha%20raca/ters&d" + data = str(httpx.URL("http://webservice", params={"u": src})) + data = str(httpx.URL("http://webservice", params={"u": data})) + data = str(httpx.URL("http://webservice", params={"u": data})) + + url = httpx.URL(data) + url = httpx.URL(url.params["u"]) + url = httpx.URL(url.params["u"]) + + assert url.params["u"] == src + + # Tests for invalid URLs @@ -260,9 +280,9 @@ def test_copy_with(): def test_path_percent_encoding(): # Test percent encoding for SUB_DELIMS ALPHA NUM and allowable GEN_DELIMS - url = httpx.URL("https://example.com/!$&'()*+,;= abc ABC 123 :/[]@") - assert url.raw_path == b"/!$&'()*+,;=%20abc%20ABC%20123%20:/[]@" - assert url.path == "/!$&'()*+,;= abc ABC 123 :/[]@" + url = httpx.URL("https://example.com/!$&'()*+,;= abc ABC 123 :/[]@%20") + assert url.raw_path == b"/!$&'()*+,;=%20abc%20ABC%20123%20:/[]@%20" + assert url.path == "/!$&'()*+,;= abc ABC 123 :/[]@ " assert url.query == b"" assert url.fragment == "" @@ -278,8 +298,8 @@ def test_query_percent_encoding(): def test_fragment_percent_encoding(): # Test percent encoding for SUB_DELIMS ALPHA NUM and allowable GEN_DELIMS - url = httpx.URL("https://example.com/#!$&'()*+,;= abc ABC 123 :/[]@" + "?#") + url = httpx.URL("https://example.com/#!$&'()*+,;= abc ABC 123 :/[]@%20" + "?#") assert url.raw_path == b"/" assert url.path == "/" assert url.query == b"" - assert url.fragment == "!$&'()*+,;= abc ABC 123 :/[]@?#" + assert url.fragment == "!$&'()*+,;= abc ABC 123 :/[]@ ?#"