From 028d1db0b4374e1b19d5a65cdb63ef23456424d0 Mon Sep 17 00:00:00 2001 From: Kostya Esmukov Date: Sun, 2 Jul 2017 21:10:08 +0300 Subject: [PATCH 1/3] Make tests fail: add "%20!=" part to userinfo which is allowed as per RFC3986 --- tests/base.py | 4 ++-- tests/conftest.py | 6 +++--- tests/test_parseresult.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/base.py b/tests/base.py index 9c67b70..703c3f6 100644 --- a/tests/base.py +++ b/tests/base.py @@ -56,7 +56,7 @@ class BaseTestParsesURIs: assert uri.path is None assert uri.query is None assert uri.fragment is None - assert uri.userinfo == 'user:pass' + assert uri.userinfo == 'user%20!=:pass' def test_handles_basic_uri_with_path(self, basic_uri_with_path): """Test that self.test_class can handle a URI with a path.""" @@ -93,7 +93,7 @@ class BaseTestParsesURIs: assert uri.path == '/path/to/resource' assert uri.query == 'key=value' assert uri.fragment == 'fragment' - assert uri.userinfo == 'user:pass' + assert uri.userinfo == 'user%20!=:pass' assert str(uri.port) == '443' def test_handles_relative_uri(self, relative_uri): diff --git a/tests/conftest.py b/tests/conftest.py index 7358b9e..f0fb264 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -77,7 +77,7 @@ def basic_uri_with_port(request): @pytest.fixture(params=valid_hosts) def uri_with_port_and_userinfo(request): - return 'ssh://user:pass@%s:22' % request.param + return 'ssh://%s@%s:22' % ('user%20!=:pass', request.param) @pytest.fixture(params=valid_hosts) @@ -92,8 +92,8 @@ def uri_with_path_and_query(request): @pytest.fixture(params=valid_hosts) def uri_with_everything(request): - return 'https://user:pass@%s:443/path/to/resource?key=value#fragment' % ( - request.param) + return 'https://%s@%s:443/path/to/resource?key=value#fragment' % ( + 'user%20!=:pass', request.param) @pytest.fixture(params=valid_hosts) diff --git a/tests/test_parseresult.py b/tests/test_parseresult.py index 6304a1c..c2c0ad4 100644 --- a/tests/test_parseresult.py +++ b/tests/test_parseresult.py @@ -113,7 +113,7 @@ class TestParseResultBytes: assert uri.path == b'/path/to/resource' assert uri.query == b'key=value' assert uri.fragment == b'fragment' - assert uri.userinfo == b'user:pass' + assert uri.userinfo == b'user%20!=:pass' assert uri.port == 443 assert isinstance(uri.authority, bytes) is True From ae5fc8b0f5f9d4e22d6b6385de98845e9929c57b Mon Sep 17 00:00:00 2001 From: Kostya Esmukov Date: Sun, 2 Jul 2017 21:13:11 +0300 Subject: [PATCH 2/3] Add some chars (!, =, etc) as allowed to the userinfo part of authority As per https://tools.ietf.org/html/rfc3986#section-3.2.1 userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) Where, according to https://tools.ietf.org/html/rfc3986#section-2.2 , sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" The sub-delims and pct-encoded sets were added in this commit --- src/rfc3986/abnf_regexp.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/rfc3986/abnf_regexp.py b/src/rfc3986/abnf_regexp.py index 90bd1e4..f226413 100644 --- a/src/rfc3986/abnf_regexp.py +++ b/src/rfc3986/abnf_regexp.py @@ -132,7 +132,9 @@ HOST_RE = HOST_PATTERN = '({0}|{1}|{2})'.format( IPv4_RE, IP_LITERAL_RE, ) -USERINFO_RE = '^[A-Za-z0-9_.~\-%:]+' +USERINFO_RE = '^([' + UNRESERVED_RE + SUB_DELIMITERS_RE + ':]|%s)+' % ( + PCT_ENCODED +) PORT_RE = '[0-9]{1,5}' # #################### From c6b149207c556e4984d34c97abefd1d5bd23be78 Mon Sep 17 00:00:00 2001 From: Kostya Esmukov Date: Fri, 7 Jul 2017 17:55:26 +0300 Subject: [PATCH 3/3] Add a separate testcase for special chars in userinfo and revert changes to other tests --- tests/base.py | 19 ++++++++++++++++++- tests/conftest.py | 9 +++++++-- tests/test_parseresult.py | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/tests/base.py b/tests/base.py index 703c3f6..dbc64a0 100644 --- a/tests/base.py +++ b/tests/base.py @@ -56,6 +56,23 @@ class BaseTestParsesURIs: assert uri.path is None assert uri.query is None assert uri.fragment is None + assert uri.userinfo == 'user:pass' + + def test_handles_tricky_userinfo( + self, uri_with_port_and_tricky_userinfo): + """ + Test that self.test_class can handle a URI with unusual + (non a-z) chars in userinfo. + """ + uri = self.test_class.from_string(uri_with_port_and_tricky_userinfo) + assert uri.scheme == 'ssh' + # 6 == len('ftp://') + assert uri.authority == uri_with_port_and_tricky_userinfo[6:] + assert uri.host != uri.authority + assert str(uri.port) == '22' + assert uri.path is None + assert uri.query is None + assert uri.fragment is None assert uri.userinfo == 'user%20!=:pass' def test_handles_basic_uri_with_path(self, basic_uri_with_path): @@ -93,7 +110,7 @@ class BaseTestParsesURIs: assert uri.path == '/path/to/resource' assert uri.query == 'key=value' assert uri.fragment == 'fragment' - assert uri.userinfo == 'user%20!=:pass' + assert uri.userinfo == 'user:pass' assert str(uri.port) == '443' def test_handles_relative_uri(self, relative_uri): diff --git a/tests/conftest.py b/tests/conftest.py index f0fb264..e056a79 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -77,6 +77,11 @@ def basic_uri_with_port(request): @pytest.fixture(params=valid_hosts) def uri_with_port_and_userinfo(request): + return 'ssh://user:pass@%s:22' % request.param + + +@pytest.fixture(params=valid_hosts) +def uri_with_port_and_tricky_userinfo(request): return 'ssh://%s@%s:22' % ('user%20!=:pass', request.param) @@ -92,8 +97,8 @@ def uri_with_path_and_query(request): @pytest.fixture(params=valid_hosts) def uri_with_everything(request): - return 'https://%s@%s:443/path/to/resource?key=value#fragment' % ( - 'user%20!=:pass', request.param) + return 'https://user:pass@%s:443/path/to/resource?key=value#fragment' % ( + request.param) @pytest.fixture(params=valid_hosts) diff --git a/tests/test_parseresult.py b/tests/test_parseresult.py index c2c0ad4..6304a1c 100644 --- a/tests/test_parseresult.py +++ b/tests/test_parseresult.py @@ -113,7 +113,7 @@ class TestParseResultBytes: assert uri.path == b'/path/to/resource' assert uri.query == b'key=value' assert uri.fragment == b'fragment' - assert uri.userinfo == b'user%20!=:pass' + assert uri.userinfo == b'user:pass' assert uri.port == 443 assert isinstance(uri.authority, bytes) is True