From b69be9e34a57fc4f799e1e93e6c4f6c6ba95fc5a Mon Sep 17 00:00:00 2001 From: Tom Most Date: Mon, 28 Dec 2020 14:38:59 -0800 Subject: [PATCH 1/2] Test URL round-tripping of + in query --- src/hyperlink/test/test_url.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 159d6a58..37c91726 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -133,6 +133,8 @@ "https://example.com/?a=%23", # hash in query param value "https://example.com/?a=%26", # ampersand in query param value "https://example.com/?a=%3D", # equals in query param value + "https://example.com/?foo+bar=baz", # plus in query param name + "https://example.com/?foo=bar+baz", # plus in query param value # double-encoded percent sign in all percent-encodable positions: "http://(%2525):(%2525)@example.com/(%2525)/?(%2525)=(%2525)#(%2525)", # colon in first part of schemeless relative url From a9eba07c7223fe34ad828712963689b66ceeaf6a Mon Sep 17 00:00:00 2001 From: Tom Most Date: Mon, 28 Dec 2020 21:43:47 -0800 Subject: [PATCH 2/2] Don't force encode + Before: >>> URL(scheme='https', host='foo', query={'f+o o': 'b+a r'}).to_text() 'https://foo/?f%2Bo o=b%2Ba r' After: >>> URL(scheme='https', host='foo', query={'f+o o': 'b+a r'}).to_text() 'https://foo/?f+o o=b+a r' If spaces pass unencoded, surely + can. --- src/hyperlink/_url.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 6e42f013..be69baf6 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -183,7 +183,7 @@ def __nonzero__(self): _SCHEMELESS_PATH_DELIMS = _ALL_DELIMS - _SCHEMELESS_PATH_SAFE _FRAGMENT_SAFE = _UNRESERVED_CHARS | _PATH_SAFE | set(u"/?") _FRAGMENT_DELIMS = _ALL_DELIMS - _FRAGMENT_SAFE -_QUERY_VALUE_SAFE = _UNRESERVED_CHARS | _FRAGMENT_SAFE - set(u"&+") +_QUERY_VALUE_SAFE = _UNRESERVED_CHARS | _FRAGMENT_SAFE - set(u"&") _QUERY_VALUE_DELIMS = _ALL_DELIMS - _QUERY_VALUE_SAFE _QUERY_KEY_SAFE = _UNRESERVED_CHARS | _QUERY_VALUE_SAFE - set(u"=") _QUERY_KEY_DELIMS = _ALL_DELIMS - _QUERY_KEY_SAFE @@ -931,9 +931,9 @@ class URL(object): https://example.com/hello/world The constructor runs basic type checks. All strings are expected - to be decoded (:class:`unicode` in Python 2). All arguments are - optional, defaulting to appropriately empty values. A full list of - constructor arguments is below. + to be text (:class:`str` in Python 3, :class:`unicode` in Python 2). All + arguments are optional, defaulting to appropriately empty values. A full + list of constructor arguments is below. Args: scheme: The text name of the scheme. @@ -943,9 +943,9 @@ class URL(object): it is known. See the ``SCHEME_PORT_MAP`` and :func:`register_default_port` for more info. path: A tuple of strings representing the slash-separated parts of the - path. + path, each percent-encoded. query: The query parameters, as a dictionary or as an sequence of - key-value pairs. + percent-encoded key-value pairs. fragment: The fragment part of the URL. rooted: A rooted URL is one which indicates an absolute path. This is True on any URL that includes a host, or any relative URL