From b77cf3433d6290cb9f31b03d9bc8c61f19c199b4 Mon Sep 17 00:00:00 2001 From: Paul Wouters Date: Jul 11 2021 20:47:21 +0000 Subject: - Initial package --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..21ca8cc --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/fqdn-1.5.1.tar.gz diff --git a/python-fqdn.spec b/python-fqdn.spec new file mode 100644 index 0000000..6b22b33 --- /dev/null +++ b/python-fqdn.spec @@ -0,0 +1,48 @@ +%global srcname %(echo %{name} | sed 's/^python-//') +Name: python-fqdn +Version: 1.5.1 +Release: 2%{?dist} +Summary: Validates fully-qualified domain names against RFC 1123 +BuildArch: noarch +License: MPLv2.0 +URL: https://github.com/ypcrts/fqdn +Source0: %pypi_source +# Missing in pypi tar ball release, present at github :( +Source1: test_fqdn.py + +%global _description %{expand: +Validates fully-qualified domain names against RFC 1123, so that they +are acceptable to modern browsers.} +%description %_description + +%package -n python3-%{srcname} +Summary: %{summary} +BuildRequires: python3-devel +BuildRequires: python3dist(setuptools) +BuildRequires: python3-pytest +BuildRequires: python3-pytest-cov +%description -n python3-%{srcname} %_description + +%prep +%autosetup -n %{srcname}-%{version} + +%build +%py3_build + +%install +%py3_install + +%check +mkdir tests/ +touch tests/__init__.py +cp %{SOURCE1} tests/ +%pytest + +%files -n python3-%{srcname} +%doc README.rst +%{python3_sitelib}/%{srcname}-*.egg-info/ +%{python3_sitelib}/%{srcname} + +%changelog +* Fri Jun 25 2021 Paul Wouters - 1.5.1-2 +- Initial package diff --git a/sources b/sources new file mode 100644 index 0000000..fc77991 --- /dev/null +++ b/sources @@ -0,0 +1 @@ +SHA512 (fqdn-1.5.1.tar.gz) = 6ddb0f7646c3e3da4a8b3f61c1a5fd93c65a46a844a13ee9f02d4bf4892ed6b0a669e65ed2ed7a3511212fb28fc3efd99059e76b2df849f67d3cfae6e0517d63 diff --git a/test_fqdn.py b/test_fqdn.py new file mode 100644 index 0000000..af9e66f --- /dev/null +++ b/test_fqdn.py @@ -0,0 +1,289 @@ +# coding=utf-8 +import sys + +import pytest +from fqdn import FQDN + + +@pytest.fixture(params=(True, False)) +def a_u(request): + return request.param + + +class TestFQDNValidation: + def test_constructor(self, a_u): + with pytest.raises(ValueError): + FQDN(None, allow_underscores=a_u) + + # Python 3-specific tests + if sys.version_info >= (3, 0): + + def test_constructor_raises_on_bytes(self, a_u): + with pytest.raises(ValueError): + FQDN(b"", allow_underscores=a_u) + + with pytest.raises(ValueError): + FQDN(b"helloworld", allow_underscores=a_u) + + def test_str(self, a_u): + d = "greatdomain.com" + f = FQDN(d, allow_underscores=a_u) + assert f.absolute == str(f) + + def test_rfc_1035_s_2_3_4__label_max_length(self, a_u): + assert FQDN( + "www.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com", + allow_underscores=a_u, + ).is_valid + assert FQDN( + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk", + allow_underscores=a_u, + ).is_valid + + def test_rfc_1035_s_2_3_4__label_too_long(self, a_u): + self.__assert_invalid_from_seq("A" * 64, "com", allow_underscores=a_u) + self.__assert_invalid_from_seq("b" * 63, "A" * 64, "com", allow_underscores=a_u) + self.__assert_invalid_from_seq("com", "b" * 63, "A" * 64, allow_underscores=a_u) + + def test_rfc_1035_s_2_3_4__name_too_long_254_octets(self, a_u): + parts = [(chr(ord("A") + i % 26)) for i in range(int(254 / 2) - 1)] + parts.append("co") + fqdn = ".".join(parts) + assert len(fqdn) == 254 + self.__assert_invalid_from_seq(fqdn, allow_underscores=a_u) + + def test_rfc_1035_s_2_3_4__name_ok_253_octets(self, a_u): + parts = [(chr(ord("A") + i % 26)) for i in range(int(254 / 2))] + fqdn = ".".join(parts) + assert len(fqdn) == 253 + self.__assert_valid_from_seq(fqdn, allow_underscores=a_u) + + def test_rfc_1035_s_3_1__trailing_byte(self, a_u): + parts = [(chr(ord("A") + i % 26)) for i in range(int(254 / 2))] + fqdn = ".".join(parts) + "." + assert len(fqdn) == 254 + self.__assert_valid_from_seq(fqdn, allow_underscores=a_u) + + def test_rfc_3696_s_2__label_invalid_starts_or_ends_with_hyphen(self): + self.__assert_invalid_fwd_and_bkwd_from_seq("-a", "com", allow_underscores=a_u) + self.__assert_invalid_fwd_and_bkwd_from_seq("a-", "com", allow_underscores=a_u) + self.__assert_invalid_fwd_and_bkwd_from_seq("-a-", "com", allow_underscores=a_u) + + def test_rfc_3696_s_2__preferred_form_invalid_chars(self, a_u): + # these should use punycode instead + self.__assert_invalid_fwd_and_bkwd_from_seq("є", "com", allow_underscores=a_u) + self.__assert_invalid_fwd_and_bkwd_from_seq( + "le-tour-est-joué", "com", allow_underscores=a_u + ) + self.__assert_invalid_fwd_and_bkwd_from_seq( + "invalid", "cóm", allow_underscores=a_u + ) + self.__assert_invalid_fwd_and_bkwd_from_seq( + "ich-hätte-gern-ein-Umlaut", "de", allow_underscores=a_u + ) + self.__assert_invalid_fwd_and_bkwd_from_seq( + "\x01", "com", allow_underscores=a_u + ) + self.__assert_invalid_fwd_and_bkwd_from_seq( + "x", "\x01\x02\x01", allow_underscores=a_u + ) + + def test_underscores_extra_mode(self): + self.__assert_valid_fwd_and_bkwd_from_seq("_", "dog", allow_underscores=True) + self.__assert_valid_fwd_and_bkwd_from_seq("i_", "dog", allow_underscores=True) + self.__assert_valid_fwd_and_bkwd_from_seq("o_o", "dog", allow_underscores=True) + + self.__assert_invalid_fwd_and_bkwd_from_seq("_", "dog", allow_underscores=False) + self.__assert_invalid_fwd_and_bkwd_from_seq( + "i_", "dog", allow_underscores=False + ) + self.__assert_invalid_fwd_and_bkwd_from_seq( + "o_o", "dog", allow_underscores=False + ) + + def test_rfc_3696_s_2__valid(self): + assert FQDN("net", min_labels=1, allow_underscores=a_u).is_valid + assert FQDN("who.is", allow_underscores=a_u).is_valid + assert FQDN("bbc.co.uk", allow_underscores=a_u).is_valid + self.__assert_valid_fwd_and_bkwd_from_seq( + "sh4d05-7357", "c00-mm", allow_underscores=a_u + ) + + def test_rfc_1035_s_2_3_1__label_can_have_inital_digit(self, a_u): + self.__assert_valid_fwd_and_bkwd_from_seq("www", "1", allow_underscores=a_u) + self.__assert_valid_fwd_and_bkwd_from_seq("1w", "1", allow_underscores=a_u) + self.__assert_valid_fwd_and_bkwd_from_seq("1w", "a", allow_underscores=a_u) + self.__assert_valid_fwd_and_bkwd_from_seq("1w1", "d", allow_underscores=a_u) + self.__assert_valid_fwd_and_bkwd_from_seq("111", "a", allow_underscores=a_u) + self.__assert_valid_fwd_and_bkwd_from_seq("www", "1a", allow_underscores=a_u) + + def test_rfc_1123__label_can_have_medial_and_terminal_digits(self, a_u): + self.__assert_valid_fwd_and_bkwd_from_seq("www1", "a", allow_underscores=a_u) + self.__assert_valid_fwd_and_bkwd_from_seq("ww1a", "c", allow_underscores=a_u) + + self.__assert_valid_fwd_and_bkwd_from_seq("w2w", "c", allow_underscores=a_u) + self.__assert_valid_fwd_and_bkwd_from_seq("a111", "a", allow_underscores=a_u) + self.__assert_valid_fwd_and_bkwd_from_seq("a1c1", "a", allow_underscores=a_u) + + def __assert_valid_fwd_and_bkwd_from_seq(self, *seq, **kwargs): + rseq = reversed(seq) + self.__assert_valid_from_seq(*rseq, **kwargs) + + def __assert_invalid_fwd_and_bkwd_from_seq(self, *seq, **kwargs): + rseq = reversed(seq) + self.__assert_invalid_from_seq(*rseq, **kwargs) + + def __assert_invalid_from_seq(self, *seq, **kwargs): + assert not (self.__is_valid_fqdn_from_labels_seq(seq, **kwargs)) + + def __assert_valid_from_seq(self, *seq, **kwargs): + assert self.__is_valid_fqdn_from_labels_seq(seq, **kwargs) + + def __is_valid_fqdn_from_labels_seq(self, fqdn_labels_seq, **kwargs): + fqdn = ".".join(fqdn_labels_seq) + return FQDN(fqdn, **kwargs).is_valid + + +class TestMinLabels: + def test_labels_count(self, a_u): + assert FQDN("label").labels_count == 1 + assert FQDN("label.").labels_count == 1 + assert FQDN("label.babel").labels_count == 2 + assert FQDN("label.babel.").labels_count == 2 + assert FQDN(".label.babel.").labels_count == 3 + + def test_min_labels_defaults_to_require_2(self): + dn = FQDN("label") + assert dn._min_labels == 2 + assert dn.labels_count == 1 + assert not dn.is_valid + + def test_min_labels_valid_set_to_1(self): + with pytest.raises(ValueError): + FQDN("", min_labels=1).is_valid + assert FQDN("label", min_labels=1).is_valid + assert not FQDN(".label", min_labels=1).is_valid + assert FQDN("label.babel", min_labels=1).is_valid + assert FQDN("label.babel.", min_labels=1).is_valid + assert not FQDN(".label.babel", min_labels=1).is_valid + + def test_min_labels_valid_set_to_3(self): + assert not FQDN("label", min_labels=3).is_valid + assert not FQDN("label.babel", min_labels=3).is_valid + assert not FQDN(".babel", min_labels=3).is_valid + assert not FQDN("babel.", min_labels=3).is_valid + assert not FQDN(".babel.", min_labels=3).is_valid + assert not FQDN("label.babel.", min_labels=3).is_valid + assert not FQDN(".label.babel.", min_labels=3).is_valid + assert FQDN("fable.label.babel.", min_labels=3).is_valid + assert FQDN("fable.label.babel", min_labels=3).is_valid + + +class TestAbsoluteFQDN: + def test_absolute_fqdn(self, a_u): + assert FQDN("trainwreck.com.", allow_underscores=a_u).is_valid_absolute is True + + def test_absolute_fqdn__fail(self, a_u): + assert FQDN("trainwreck.com", allow_underscores=a_u).is_valid_absolute is False + + def test_to_absolute_fqdn_from_relative(self, a_u): + assert ( + FQDN("trainwreck.com", allow_underscores=a_u).absolute == "trainwreck.com." + ) + + def test_to_absolute_fqdn_from_absolute(self, a_u): + assert ( + FQDN("absolutetrainwreck.com.", allow_underscores=a_u).absolute + == "absolutetrainwreck.com." + ) + + def test_to_absolute_fqdn__raises_ValueError(self, a_u): + with pytest.raises(ValueError): + FQDN("trainwreckcom", allow_underscores=a_u).absolute + + def test_relative_fqdn_true(self, a_u): + assert FQDN("relative.com", allow_underscores=a_u).is_valid_relative is True + + def test_relative_fqdn_false(self, a_u): + assert FQDN("relative.com.", allow_underscores=a_u).is_valid_relative is False + + +class TestRelativeFQDN: + def test_relative_fqdn_from_relative(self, a_u): + assert ( + FQDN("trainwreck.com", allow_underscores=a_u).relative == "trainwreck.com" + ) + + def test_relative_fqdn_from_absolute(self, a_u): + assert ( + FQDN("trainwreck.com.", allow_underscores=a_u).relative == "trainwreck.com" + ) + + def test_relative_fqdn_from_invalid(self, a_u): + with pytest.raises(ValueError): + FQDN("trainwreck..", allow_underscores=a_u).relative + + +class TestEquality: + def test_absolutes_are_equal(self, a_u): + assert FQDN("trainwreck.com.", allow_underscores=a_u) == FQDN( + "trainwreck.com.", allow_underscores=a_u + ) + + def test_relatives_are_equal(self, a_u): + assert FQDN("trainwreck.com", allow_underscores=a_u) == FQDN( + "trainwreck.com", allow_underscores=a_u + ) + + def test_mismatch_are_equal(self, a_u): + assert FQDN("trainwreck.com.", allow_underscores=a_u) == FQDN( + "trainwreck.com", allow_underscores=a_u + ) + + def test_equality_is_case_insensitive(self, a_u): + assert FQDN( + "all-letters-were-created-equal.com.", allow_underscores=a_u + ) == FQDN("ALL-LETTERS-WERE-CREATED-EQUAL.COM.", allow_underscores=a_u) + + def test_strict_and_loose_can_be_equal(self): + assert FQDN("trainwreck.com.", allow_underscores=False) == FQDN( + "trainwreck.com", allow_underscores=True + ) + + +class TestHash: + def test_is_hashable(self, a_u): + assert hash(FQDN("trainwreck.com.")) + + def test_absolutes_are_equal(self, a_u): + assert hash(FQDN("trainwreck.com.", allow_underscores=a_u)) == hash( + FQDN("trainwreck.com.", allow_underscores=a_u) + ) + + def test_relatives_are_equal(self, a_u): + assert hash(FQDN("trainwreck.com", allow_underscores=a_u)) == hash( + FQDN("trainwreck.com", allow_underscores=a_u) + ) + + def test_mismatch_are_equal(self, a_u): + assert hash(FQDN("trainwreck.com.", allow_underscores=a_u)) == hash( + FQDN("trainwreck.com", allow_underscores=a_u) + ) + + def test_equality_is_case_insensitive(self, a_u): + assert hash( + FQDN("all-letters-were-created-equal.com.", allow_underscores=a_u) + ) == hash(FQDN("ALL-LETTERS-WERE-CREATED-EQUAL.COM.", allow_underscores=a_u)) + + def test_not_equal_to_string(self, a_u): + assert hash(FQDN("trainwreck.com.", allow_underscores=a_u)) != hash( + "trainwreck.com." + ) + + def test_different_fqdns_are_not_equal(self, a_u): + assert hash(FQDN("trainwreck.com.")) != hash(FQDN("test.com.")) + + def test_strict_and_loose_hashs_are_equal(self): + assert hash(FQDN("trainwreck.com.", allow_underscores=False)) == hash( + FQDN("trainwreck.com", allow_underscores=True) + )