1# -*- coding: utf-8 -*-
2"""Tests for the validators module."""
3import rfc3986
4from rfc3986 import exceptions
5from rfc3986 import validators
6
7import pytest
8
9
10def test_defaults():
11    """Verify the default Validator settings."""
12    validator = validators.Validator()
13
14    assert validator.required_components == {
15        c: False for c in validator.COMPONENT_NAMES
16    }
17    assert validator.allow_password is True
18    assert validator.allowed_schemes == set()
19    assert validator.allowed_hosts == set()
20    assert validator.allowed_ports == set()
21
22
23def test_allowing_schemes():
24    """Verify the ability to select schemes to be allowed."""
25    validator = validators.Validator().allow_schemes("http", "https")
26
27    assert "http" in validator.allowed_schemes
28    assert "https" in validator.allowed_schemes
29
30
31def test_allowing_hosts():
32    """Verify the ability to select hosts to be allowed."""
33    validator = validators.Validator().allow_hosts(
34        "pypi.python.org",
35        "pypi.org",
36    )
37
38    assert "pypi.python.org" in validator.allowed_hosts
39    assert "pypi.org" in validator.allowed_hosts
40
41
42def test_allowing_ports():
43    """Verify the ability select ports to be allowed."""
44    validator = validators.Validator().allow_ports("80", "100")
45
46    assert "80" in validator.allowed_ports
47    assert "100" in validator.allowed_ports
48
49
50def test_requiring_invalid_component():
51    """Verify that we validate required component names."""
52    with pytest.raises(ValueError):
53        validators.Validator().require_presence_of("frob")
54
55
56def test_checking_validity_of_component():
57    """Verify that we validate components we're validating."""
58    with pytest.raises(ValueError):
59        validators.Validator().check_validity_of("frob")
60
61
62def test_use_of_password():
63    """Verify the behaviour of {forbid,allow}_use_of_password."""
64    validator = validators.Validator()
65    assert validator.allow_password is True
66
67    validator.forbid_use_of_password()
68    assert validator.allow_password is False
69
70    validator.allow_use_of_password()
71    assert validator.allow_password is True
72
73
74@pytest.mark.parametrize(
75    "uri",
76    [
77        rfc3986.uri_reference("https://user:password@github.com"),
78        rfc3986.uri_reference("https://user:password@github.com/path"),
79        rfc3986.uri_reference("https://user:password@github.com/path?query"),
80        rfc3986.uri_reference(
81            "https://user:password@github.com/path?query#frag"
82        ),
83        rfc3986.uri_reference("//user:password@github.com"),
84    ],
85)
86def test_forbidden_passwords(uri):
87    """Verify that passwords are disallowed."""
88    validator = validators.Validator().forbid_use_of_password()
89    with pytest.raises(exceptions.PasswordForbidden):
90        validator.validate(uri)
91
92
93@pytest.mark.parametrize(
94    "uri",
95    [
96        rfc3986.uri_reference("https://user@github.com"),
97        rfc3986.uri_reference("https://user@github.com/path"),
98        rfc3986.uri_reference("https://user@github.com/path?query"),
99        rfc3986.uri_reference("https://user@github.com/path?query#frag"),
100        rfc3986.uri_reference("//user@github.com"),
101        rfc3986.uri_reference("//github.com"),
102        rfc3986.uri_reference("https://github.com"),
103    ],
104)
105def test_passwordless_uris_pass_validation(uri):
106    """Verify password-less URLs validate properly."""
107    validator = validators.Validator().forbid_use_of_password()
108    validator.validate(uri)
109
110
111@pytest.mark.parametrize(
112    "uri",
113    [
114        rfc3986.uri_reference("https://"),
115        rfc3986.uri_reference("/path/to/resource"),
116    ],
117)
118def test_missing_host_component(uri):
119    """Verify that missing host components cause errors."""
120    validators.Validator().validate(uri)
121
122    validator = validators.Validator().require_presence_of("host")
123    with pytest.raises(exceptions.MissingComponentError):
124        validator.validate(uri)
125
126
127@pytest.mark.parametrize(
128    "uri",
129    [
130        rfc3986.uri_reference("https://"),
131        rfc3986.uri_reference("//google.com"),
132        rfc3986.uri_reference("//google.com?query=value"),
133        rfc3986.uri_reference("//google.com#fragment"),
134        rfc3986.uri_reference("https://google.com"),
135        rfc3986.uri_reference("https://google.com#fragment"),
136        rfc3986.uri_reference("https://google.com?query=value"),
137    ],
138)
139def test_missing_path_component(uri):
140    """Verify that missing path components cause errors."""
141    validator = validators.Validator().require_presence_of("path")
142    with pytest.raises(exceptions.MissingComponentError):
143        validator.validate(uri)
144
145
146@pytest.mark.parametrize(
147    "uri",
148    [
149        rfc3986.uri_reference("//google.com"),
150        rfc3986.uri_reference("//google.com?query=value"),
151        rfc3986.uri_reference("//google.com#fragment"),
152    ],
153)
154def test_multiple_missing_components(uri):
155    """Verify that multiple missing components are caught."""
156    validator = validators.Validator().require_presence_of("scheme", "path")
157    with pytest.raises(exceptions.MissingComponentError) as captured_exc:
158        validator.validate(uri)
159    exception = captured_exc.value
160    assert 2 == len(exception.args[-1])
161
162
163@pytest.mark.parametrize(
164    "uri",
165    [
166        rfc3986.uri_reference("smtp://"),
167        rfc3986.uri_reference("telnet://"),
168    ],
169)
170def test_ensure_uri_has_a_scheme(uri):
171    """Verify validation with allowed schemes."""
172    validator = validators.Validator().allow_schemes("https", "http")
173    with pytest.raises(exceptions.UnpermittedComponentError):
174        validator.validate(uri)
175
176
177@pytest.mark.parametrize(
178    "uri, failed_component",
179    [
180        (rfc3986.uri_reference("git://github.com"), "scheme"),
181        (rfc3986.uri_reference("http://github.com"), "scheme"),
182        (rfc3986.uri_reference("ssh://gitlab.com"), "host"),
183        (rfc3986.uri_reference("https://gitlab.com"), "host"),
184    ],
185)
186def test_allowed_hosts_and_schemes(uri, failed_component):
187    """Verify each of these fails."""
188    validator = (
189        validators.Validator()
190        .allow_schemes(
191            "https",
192            "ssh",
193        )
194        .allow_hosts(
195            "github.com",
196            "git.openstack.org",
197        )
198    )
199    with pytest.raises(exceptions.UnpermittedComponentError) as caught_exc:
200        validator.validate(uri)
201
202    exc = caught_exc.value
203    assert exc.component_name == failed_component
204
205
206@pytest.mark.parametrize(
207    "uri",
208    [
209        rfc3986.uri_reference("https://github.com/sigmavirus24"),
210        rfc3986.uri_reference("ssh://github.com/sigmavirus24"),
211        rfc3986.uri_reference("ssh://ssh@github.com:22/sigmavirus24"),
212        rfc3986.uri_reference("https://github.com:443/sigmavirus24"),
213        rfc3986.uri_reference("https://gitlab.com/sigmavirus24"),
214        rfc3986.uri_reference("ssh://gitlab.com/sigmavirus24"),
215        rfc3986.uri_reference("ssh://ssh@gitlab.com:22/sigmavirus24"),
216        rfc3986.uri_reference("https://gitlab.com:443/sigmavirus24"),
217        rfc3986.uri_reference("https://bitbucket.org/sigmavirus24"),
218        rfc3986.uri_reference("ssh://bitbucket.org/sigmavirus24"),
219        rfc3986.uri_reference("ssh://ssh@bitbucket.org:22/sigmavirus24"),
220        rfc3986.uri_reference("https://bitbucket.org:443/sigmavirus24"),
221        rfc3986.uri_reference("https://git.openstack.org/sigmavirus24"),
222        rfc3986.uri_reference("ssh://git.openstack.org/sigmavirus24"),
223        rfc3986.uri_reference("ssh://ssh@git.openstack.org:22/sigmavirus24"),
224        rfc3986.uri_reference("https://git.openstack.org:443/sigmavirus24"),
225        rfc3986.uri_reference(
226            "ssh://ssh@git.openstack.org:22/sigmavirus24?foo=bar#fragment"
227        ),
228        rfc3986.uri_reference(
229            "ssh://git.openstack.org:22/sigmavirus24?foo=bar#fragment"
230        ),
231        rfc3986.uri_reference("ssh://git.openstack.org:22/?foo=bar#fragment"),
232        rfc3986.uri_reference(
233            "ssh://git.openstack.org:22/sigmavirus24#fragment"
234        ),
235        rfc3986.uri_reference("ssh://git.openstack.org:22/#fragment"),
236        rfc3986.uri_reference("ssh://git.openstack.org:22/"),
237        rfc3986.uri_reference(
238            "ssh://ssh@git.openstack.org:22/?foo=bar#fragment"
239        ),
240        rfc3986.uri_reference(
241            "ssh://ssh@git.openstack.org:22/sigmavirus24#fragment"
242        ),
243        rfc3986.uri_reference("ssh://ssh@git.openstack.org:22/#fragment"),
244        rfc3986.uri_reference("ssh://ssh@git.openstack.org:22/"),
245    ],
246)
247def test_successful_complex_validation(uri):
248    """Verify we do not raise ValidationErrors for good URIs."""
249    validators.Validator().allow_schemes("https", "ssh",).allow_hosts(
250        "github.com",
251        "bitbucket.org",
252        "gitlab.com",
253        "git.openstack.org",
254    ).allow_ports("22", "443",).require_presence_of(
255        "scheme",
256        "host",
257        "path",
258    ).check_validity_of(
259        "scheme",
260        "userinfo",
261        "host",
262        "port",
263        "path",
264        "query",
265        "fragment",
266    ).validate(
267        uri
268    )
269
270
271def test_invalid_uri_generates_error(invalid_uri):
272    """Verify we catch invalid URIs."""
273    uri = rfc3986.uri_reference(invalid_uri)
274    with pytest.raises(exceptions.InvalidComponentsError):
275        validators.Validator().check_validity_of("host").validate(uri)
276
277
278def test_invalid_uri_with_invalid_path(invalid_uri):
279    """Verify we catch multiple invalid components."""
280    uri = rfc3986.uri_reference(invalid_uri)
281    uri = uri.copy_with(path="#foobar")
282    with pytest.raises(exceptions.InvalidComponentsError):
283        validators.Validator().check_validity_of(
284            "host",
285            "path",
286        ).validate(uri)
287
288
289def test_validating_rfc_4007_ipv6_zone_ids():
290    """Verify that RFC 4007 IPv6 Zone IDs are invalid
291    host/authority but after normalization are valid
292    """
293    uri = rfc3986.uri_reference("http://[::1%eth0]")
294    with pytest.raises(exceptions.InvalidComponentsError):
295        validators.Validator().check_validity_of("host").validate(uri)
296
297    uri = uri.normalize()
298    assert uri.host == "[::1%25eth0]"
299
300    validators.Validator().check_validity_of("host").validate(uri)
301