1import salt.utils.platform
2import salt.utils.url
3from tests.support.mock import MagicMock, patch
4from tests.support.unit import TestCase
5
6
7class UrlTestCase(TestCase):
8    """
9    TestCase for salt.utils.url module
10    """
11
12    # parse tests
13
14    def test_parse_path(self):
15        """
16        Test parsing an ordinary path
17        """
18        path = "interesting?/path&.conf:and other things"
19
20        self.assertEqual(salt.utils.url.parse(path), (path, None))
21
22    def test_parse_salt_url(self):
23        """
24        Test parsing a 'salt://' URL
25        """
26        path = "?funny/path with {interesting|chars}"
27        url = "salt://" + path
28        if salt.utils.platform.is_windows():
29            path = "_funny/path with {interesting_chars}"
30
31        self.assertEqual(salt.utils.url.parse(url), (path, None))
32
33    def test_parse_salt_saltenv(self):
34        """
35        Test parsing a 'salt://' URL with a '?saltenv=' query
36        """
37        saltenv = "ambience"
38        path = "?funny/path&with {interesting|chars}"
39        url = "salt://" + path + "?saltenv=" + saltenv
40        if salt.utils.platform.is_windows():
41            path = "_funny/path&with {interesting_chars}"
42
43        self.assertEqual(salt.utils.url.parse(url), (path, saltenv))
44
45    # create tests
46
47    def test_create_url(self):
48        """
49        Test creating a 'salt://' URL
50        """
51        path = "? interesting/&path.filetype"
52        url = "salt://" + path
53        if salt.utils.platform.is_windows():
54            url = "salt://_ interesting/&path.filetype"
55
56        self.assertEqual(salt.utils.url.create(path), url)
57
58    def test_create_url_saltenv(self):
59        """
60        Test creating a 'salt://' URL with a saltenv
61        """
62        saltenv = "raumklang"
63        path = "? interesting/&path.filetype"
64        if salt.utils.platform.is_windows():
65            path = "_ interesting/&path.filetype"
66
67        url = "salt://" + path + "?saltenv=" + saltenv
68
69        self.assertEqual(salt.utils.url.create(path, saltenv), url)
70
71    # is_escaped tests
72
73    def test_is_escaped_windows(self):
74        """
75        Test not testing a 'salt://' URL on windows
76        """
77        url = "salt://dir/file.ini"
78
79        with patch("salt.utils.platform.is_windows", MagicMock(return_value=True)):
80            self.assertFalse(salt.utils.url.is_escaped(url))
81
82    def test_is_escaped_escaped_path(self):
83        """
84        Test testing an escaped path
85        """
86        path = "|dir/file.conf?saltenv=basic"
87
88        self.assertTrue(salt.utils.url.is_escaped(path))
89
90    def test_is_escaped_unescaped_path(self):
91        """
92        Test testing an unescaped path
93        """
94        path = "dir/file.conf"
95
96        self.assertFalse(salt.utils.url.is_escaped(path))
97
98    def test_is_escaped_escaped_url(self):
99        """
100        Test testing an escaped 'salt://' URL
101        """
102        url = "salt://|dir/file.conf?saltenv=basic"
103
104        self.assertTrue(salt.utils.url.is_escaped(url))
105
106    def test_is_escaped_unescaped_url(self):
107        """
108        Test testing an unescaped 'salt://' URL
109        """
110        url = "salt://dir/file.conf"
111
112        self.assertFalse(salt.utils.url.is_escaped(url))
113
114    def test_is_escaped_generic_url(self):
115        """
116        Test testing an unescaped 'salt://' URL
117        """
118        url = "https://gentoo.org/"
119
120        self.assertFalse(salt.utils.url.is_escaped(url))
121
122    # escape tests
123
124    def test_escape_windows(self):
125        """
126        Test not escaping a 'salt://' URL on windows
127        """
128        url = "salt://dir/file.ini"
129
130        with patch("salt.utils.platform.is_windows", MagicMock(return_value=True)):
131            self.assertEqual(salt.utils.url.escape(url), url)
132
133    def test_escape_escaped_path(self):
134        """
135        Test escaping an escaped path
136        """
137        resource = "|dir/file.conf?saltenv=basic"
138
139        self.assertEqual(salt.utils.url.escape(resource), resource)
140
141    def test_escape_unescaped_path(self):
142        """
143        Test escaping an unescaped path
144        """
145        path = "dir/file.conf"
146        escaped_path = "|" + path
147        if salt.utils.platform.is_windows():
148            escaped_path = path
149
150        self.assertEqual(salt.utils.url.escape(path), escaped_path)
151
152    def test_escape_escaped_url(self):
153        """
154        Test testing an escaped 'salt://' URL
155        """
156        url = "salt://|dir/file.conf?saltenv=basic"
157
158        self.assertEqual(salt.utils.url.escape(url), url)
159
160    def test_escape_unescaped_url(self):
161        """
162        Test testing an unescaped 'salt://' URL
163        """
164        path = "dir/file.conf"
165        url = "salt://" + path
166        escaped_url = "salt://|" + path
167        if salt.utils.platform.is_windows():
168            escaped_url = url
169
170        self.assertEqual(salt.utils.url.escape(url), escaped_url)
171
172    def test_escape_generic_url(self):
173        """
174        Test testing an unescaped 'salt://' URL
175        """
176        url = "https://gentoo.org/"
177
178        self.assertEqual(salt.utils.url.escape(url), url)
179
180    # unescape tests
181
182    def test_unescape_windows(self):
183        """
184        Test not escaping a 'salt://' URL on windows
185        """
186        url = "salt://dir/file.ini"
187
188        with patch("salt.utils.platform.is_windows", MagicMock(return_value=True)):
189            self.assertEqual(salt.utils.url.unescape(url), url)
190
191    def test_unescape_escaped_path(self):
192        """
193        Test escaping an escaped path
194        """
195        resource = "dir/file.conf?saltenv=basic"
196        escaped_path = "|" + resource
197
198        self.assertEqual(salt.utils.url.unescape(escaped_path), resource)
199
200    def test_unescape_unescaped_path(self):
201        """
202        Test escaping an unescaped path
203        """
204        path = "dir/file.conf"
205
206        self.assertEqual(salt.utils.url.unescape(path), path)
207
208    def test_unescape_escaped_url(self):
209        """
210        Test testing an escaped 'salt://' URL
211        """
212        resource = "dir/file.conf?saltenv=basic"
213        url = "salt://" + resource
214        escaped_url = "salt://|" + resource
215
216        self.assertEqual(salt.utils.url.unescape(escaped_url), url)
217
218    def test_unescape_unescaped_url(self):
219        """
220        Test testing an unescaped 'salt://' URL
221        """
222        url = "salt://dir/file.conf"
223
224        self.assertEqual(salt.utils.url.unescape(url), url)
225
226    def test_unescape_generic_url(self):
227        """
228        Test testing an unescaped 'salt://' URL
229        """
230        url = "https://gentoo.org/"
231
232        self.assertEqual(salt.utils.url.unescape(url), url)
233
234    # add_env tests
235
236    def test_add_env_not_salt(self):
237        """
238        Test not adding a saltenv to a non 'salt://' URL
239        """
240        saltenv = "higgs"
241        url = "https://pdg.lbl.gov/"
242
243        self.assertEqual(salt.utils.url.add_env(url, saltenv), url)
244
245    def test_add_env(self):
246        """
247        Test adding a saltenv to a 'salt://' URL
248        """
249        saltenv = "erstwhile"
250        url = "salt://salted/file.conf"
251        url_env = url + "?saltenv=" + saltenv
252
253        self.assertEqual(salt.utils.url.add_env(url, saltenv), url_env)
254
255    # split_env tests
256
257    def test_split_env_non_salt(self):
258        """
259        Test not splitting a saltenv from a non 'salt://' URL
260        """
261        saltenv = "gravitodynamics"
262        url = "https://arxiv.org/find/all/?" + saltenv
263
264        self.assertEqual(salt.utils.url.split_env(url), (url, None))
265
266    def test_split_env(self):
267        """
268        Test splitting a 'salt://' URL
269        """
270        saltenv = "elsewhere"
271        url = "salt://salted/file.conf"
272        url_env = url + "?saltenv=" + saltenv
273
274        self.assertEqual(salt.utils.url.split_env(url_env), (url, saltenv))
275
276    # validate tests
277
278    def test_validate_valid(self):
279        """
280        Test URL valid validation
281        """
282        url = "salt://config/file.name?saltenv=vapid"
283        protos = ["salt", "pepper", "cinnamon", "melange"]
284
285        self.assertTrue(salt.utils.url.validate(url, protos))
286
287    def test_validate_invalid(self):
288        """
289        Test URL invalid validation
290        """
291        url = "cumin://config/file.name?saltenv=vapid"
292        protos = ["salt", "pepper", "cinnamon", "melange"]
293
294        self.assertFalse(salt.utils.url.validate(url, protos))
295
296    # strip tests
297
298    def test_strip_url_with_scheme(self):
299        """
300        Test stripping of URL scheme
301        """
302        scheme = "git+salt+rsync+AYB://"
303        resource = "all/the/things.stuff;parameter?query=I guess"
304        url = scheme + resource
305
306        self.assertEqual(salt.utils.url.strip_proto(url), resource)
307
308    def test_strip_url_without_scheme(self):
309        """
310        Test stripping of a URL without a scheme
311        """
312        resource = "all/the/things.stuff;parameter?query=I guess"
313
314        self.assertEqual(salt.utils.url.strip_proto(resource), resource)
315
316    def test_http_basic_auth(self):
317        """
318        Tests that adding basic auth to a URL works as expected
319        """
320        # ((user, password), expected) tuples
321        test_inputs = (
322            ((None, None), "http://example.com"),
323            (("user", None), "http://user@example.com"),
324            (("user", "pass"), "http://user:pass@example.com"),
325        )
326        for (user, password), expected in test_inputs:
327            kwargs = {
328                "url": "http://example.com",
329                "user": user,
330                "password": password,
331            }
332            # Test http
333            result = salt.utils.url.add_http_basic_auth(**kwargs)
334            self.assertEqual(result, expected)
335            # Test https
336            kwargs["url"] = kwargs["url"].replace("http://", "https://", 1)
337            expected = expected.replace("http://", "https://", 1)
338            result = salt.utils.url.add_http_basic_auth(**kwargs)
339            self.assertEqual(result, expected)
340
341    def test_http_basic_auth_https_only(self):
342        """
343        Tests that passing a non-https URL with https_only=True will raise a
344        ValueError.
345        """
346        kwargs = {
347            "url": "http://example.com",
348            "user": "foo",
349            "password": "bar",
350            "https_only": True,
351        }
352        self.assertRaises(ValueError, salt.utils.url.add_http_basic_auth, **kwargs)
353
354    def test_redact_http_basic_auth(self):
355        sensitive_outputs = (
356            "https://deadbeaf@example.com",
357            "https://user:pw@example.com",
358        )
359        sanitized = "https://<redacted>@example.com"
360        for sensitive_output in sensitive_outputs:
361            result = salt.utils.url.redact_http_basic_auth(sensitive_output)
362            self.assertEqual(result, sanitized)
363
364    def test_redact_non_auth_output(self):
365        non_auth_output = "This is just normal output"
366        self.assertEqual(
367            non_auth_output, salt.utils.url.redact_http_basic_auth(non_auth_output)
368        )
369