コード例 #1
0
    def __init__(
        self,
        subject: Optional[str] = None,
        do_syntax_check_first: Optional[bool] = None,
        db_session: Optional[Session] = None,
        use_collection: Optional[bool] = None,
    ) -> None:
        self.dns_query_tool = DNSQueryTool()
        self.ipv4_reputation_query_tool = IPV4ReputationDataset()
        self.domain_syntax_checker = DomainSyntaxChecker()
        self.ip_syntax_checker = IPSyntaxChecker()
        self.url_syntax_checker = URLSyntaxChecker()

        self.params = ReputationCheckerParams()

        self.status = ReputationCheckerStatus()
        self.status.params = self.params
        self.status.dns_lookup_record = self.dns_query_tool.lookup_record

        super().__init__(
            subject,
            do_syntax_check_first=do_syntax_check_first,
            db_session=db_session,
            use_collection=use_collection,
        )
コード例 #2
0
ファイル: base.py プロジェクト: spirillen/PyFunceble
    def __init__(self, *args, **kwargs):
        if "timeout" in kwargs:
            self.timeout = float(kwargs["timeout"])
            del kwargs["timeout"]

        if "max_retries" in kwargs:
            kwargs["max_retries"] = requests.adapters.Retry(
                total=kwargs["max_retries"], respect_retry_after_header=False)

        if "dns_query_tool" in kwargs:
            self.dns_query_tool = kwargs["dns_query_tool"]
            del kwargs["dns_query_tool"]
        else:
            self.dns_query_tool = DNSQueryTool()

        super().__init__(*args, **kwargs)
コード例 #3
0
    def setUp(self) -> None:
        """
        Setups everything needed for the tests.
        """

        def fake_response(data: str) -> object:
            return dataclasses.make_dataclass(
                "FakeResponse", [("address", str, dataclasses.field(default=data))]
            )

        def fake_resolver(_: str, rqtype: str):
            if rqtype == "A":
                return [
                    fake_response("192.168.1.1"),
                    fake_response("10.47.91.9"),
                ]

            return [
                fake_response("fe80::6b01:9045:a42a:fb5f"),
                fake_response("fe80::6b01:9049:a42a:fb5f"),
            ]

        self.resolve_patch = unittest.mock.patch.object(
            dns.resolver.Resolver, "resolve"
        )
        self.udp_query_patch = unittest.mock.patch.object(dns.query, "udp")
        self.tcp_query_patch = unittest.mock.patch.object(dns.query, "tcp")
        self.https_query_patch = unittest.mock.patch.object(dns.query, "https")
        self.tls_query_patch = unittest.mock.patch.object(dns.query, "tls")

        self.mock_resolve = self.resolve_patch.start()
        self.mock_resolve.side_effect = fake_resolver

        self.mock_udp_query = self.udp_query_patch.start()
        self.mock_tcp_query = self.tcp_query_patch.start()
        self.mock_https_query = self.https_query_patch.start()
        self.mock_tls_query = self.tls_query_patch.start()

        self.query_tool = DNSQueryTool(nameservers=["example.org"])

        self.query_tool._get_result_from_response = lambda _: []
コード例 #4
0
    def test_set_trust_server_through_init(self) -> None:
        """
        Tests the overwritting of the `trust_server` attribute through the class
        constructor.
        """

        given = True
        expected = True

        query_tool = DNSQueryTool(trust_server=given)

        actual = query_tool.trust_server

        self.assertEqual(expected, actual)
コード例 #5
0
    def test_set_follow_nameserver_order_through_init(self) -> None:
        """
        Tests the overwritting of the `follow_nameserver_attribute` attribute
        through the class constructor.
        """

        given = False
        expected = False

        query_tool = DNSQueryTool(follow_nameserver_order=given)

        actual = query_tool.follow_nameserver_order

        self.assertEqual(expected, actual)
コード例 #6
0
    def test_set_delay_through_init(self) -> None:
        """
        Tests the method which let us set the delay.

        In this test we check that the transfert of the delay
        through the constructor is working.
        """

        given = 0.55

        query_tool = DNSQueryTool(delay=given)

        expected = 0.55
        actual = query_tool.delay

        self.assertEqual(expected, actual)
コード例 #7
0
    def test_set_preferred_protocol_through_init(self) -> None:
        """
        Tests the method which let us set the preferred protocol.

        In this test we check that the transfert of the preferred protocol
        through the constructor is working.
        """

        given = "TCP"

        query_tool = DNSQueryTool(preferred_protocol=given)

        expected = "TCP"
        actual = query_tool.preferred_protocol

        self.assertEqual(expected, actual)
コード例 #8
0
class ReputationCheckerBase(CheckerBase):
    """
    Provides the base of all our reputation checker classes.

    :param str subject:
        Optional, The subject to work with.
    :param bool do_syntax_check_first:
        Optional, Activates/Disables the check of the status before the actual
        status gathering.
    """

    dns_query_tool: Optional[DNSQueryTool] = None
    ipv4_reputation_query_tool: Optional[IPV4ReputationDataset] = None
    domain_syntax_checker: Optional[DomainSyntaxChecker] = None
    ip_syntax_checker: Optional[IPSyntaxChecker] = None
    url_syntax_checker: Optional[URLSyntaxChecker] = None

    status: Optional[ReputationCheckerStatus] = None
    params: Optional[ReputationCheckerParams] = None

    def __init__(
        self,
        subject: Optional[str] = None,
        do_syntax_check_first: Optional[bool] = None,
        db_session: Optional[Session] = None,
        use_collection: Optional[bool] = None,
    ) -> None:
        self.dns_query_tool = DNSQueryTool()
        self.ipv4_reputation_query_tool = IPV4ReputationDataset()
        self.domain_syntax_checker = DomainSyntaxChecker()
        self.ip_syntax_checker = IPSyntaxChecker()
        self.url_syntax_checker = URLSyntaxChecker()

        self.params = ReputationCheckerParams()

        self.status = ReputationCheckerStatus()
        self.status.params = self.params
        self.status.dns_lookup_record = self.dns_query_tool.lookup_record

        super().__init__(
            subject,
            do_syntax_check_first=do_syntax_check_first,
            db_session=db_session,
            use_collection=use_collection,
        )

    @staticmethod
    def is_valid() -> bool:
        raise NotImplementedError()

    def subject_propagator(self) -> "CheckerBase":
        """
        Propagate the currently set subject.

        .. warning::
            You are not invited to run this method directly.
        """

        self.dns_query_tool.set_subject(self.idna_subject)

        self.domain_syntax_checker.subject = self.idna_subject
        self.ip_syntax_checker.subject = self.idna_subject
        self.url_syntax_checker.subject = self.idna_subject

        self.status = ReputationCheckerStatus()
        self.status.params = self.params
        self.status.dns_lookup_record = self.dns_query_tool.lookup_record

        self.status.subject = self.subject
        self.status.idna_subject = self.idna_subject

        self.query_syntax_checker()

        return self

    def should_we_continue_test(self, status_post_syntax_checker: str) -> bool:
        """
        Checks if we are allowed to continue a standard testing.
        """

        return bool(
            not self.status.status
            or status_post_syntax_checker == PyFunceble.storage.STATUS.invalid)

    def query_syntax_checker(self) -> "ReputationCheckerBase":
        """
        Queries the syntax checker.
        """

        self.status.second_level_domain_syntax = (
            self.domain_syntax_checker.is_valid_second_level())
        self.status.subdomain_syntax = self.domain_syntax_checker.is_valid_subdomain(
        )
        self.status.domain_syntax = bool(
            self.status.subdomain_syntax
            or self.status.second_level_domain_syntax)

        self.status.ipv4_syntax = self.ip_syntax_checker.is_valid_v4()
        self.status.ipv6_syntax = self.ip_syntax_checker.is_valid_v6()
        self.status.ipv4_range_syntax = self.ip_syntax_checker.is_valid_v4_range(
        )
        self.status.ipv6_range_syntax = self.ip_syntax_checker.is_valid_v6_range(
        )
        self.status.ip_syntax = bool(self.status.ipv4_syntax
                                     or self.status.ipv6_syntax)
        self.status.url_syntax = self.url_syntax_checker.is_valid()

        return self

    def query_a_record(self) -> Optional[List[str]]:
        """
        Queries all the A record.
        """

        raise NotImplementedError()

    def try_to_query_status_from_dns_lookup(self) -> "ReputationCheckerBase":
        """
        Tries to query the status from the DNS lookup.
        """

        PyFunceble.facility.Logger.info(
            "Started to try to query the status of %r from: DNS Lookup",
            self.status.idna_subject,
        )

        if not self.status.ipv4_syntax:
            lookup_result = self.query_a_record()
            self.status.dns_lookup = lookup_result
        else:
            lookup_result = [self.status.subject]

        if lookup_result:
            for subject in lookup_result:
                if subject in self.ipv4_reputation_query_tool:
                    self.status.status = PyFunceble.storage.STATUS.malicious
                    self.status.status_source = "REPUTATION"

                    PyFunceble.facility.Logger.info(
                        "Could define the status of %r from: DNS Lookup",
                        self.status.idna_subject,
                    )

                    break

        PyFunceble.facility.Logger.info(
            "Finished to try to query the status of %r from: DNS Lookup",
            self.status.idna_subject,
        )

        return self

    def try_to_query_status_from_syntax_lookup(
            self) -> "ReputationCheckerBase":
        """
        Tries to query the status from the syntax.
        """

        PyFunceble.facility.Logger.info(
            "Started to try to query the status of %r from: Syntax Lookup",
            self.status.idna_subject,
        )

        if (not self.status.domain_syntax and not self.status.ip_syntax
                and not self.status.url_syntax):
            self.status.status = PyFunceble.storage.STATUS.invalid
            self.status.status_source = "SYNTAX"

            PyFunceble.facility.Logger.info(
                "Could define the status of %r from: Syntax Lookup",
                self.status.idna_subject,
            )

        PyFunceble.facility.Logger.info(
            "Finished to try to query the status of %r from: Syntax Lookup",
            self.status.idna_subject,
        )

        return self

    def try_to_query_status_from_collection(self) -> "ReputationCheckerBase":
        """
        Tries to get and set the status from the Collection API.
        """

        PyFunceble.facility.Logger.info(
            "Started to try to query the status of %r from: Collection Lookup",
            self.status.idna_subject,
        )

        data = self.collection_query_tool[self.idna_subject]

        if data and "status" in data:
            if (self.collection_query_tool.preferred_status_origin
                    == "frequent"
                    and data["status"]["reputation"]["frequent"]):
                self.status.status = data["status"]["reputation"]["frequent"]
                self.status.status_source = "COLLECTION"
            elif (self.collection_query_tool.preferred_status_origin
                  == "latest" and data["status"]["reputation"]["latest"]):
                self.status.status = data["status"]["reputation"]["latest"][
                    "status"]
                self.status.status_source = "COLLECTION"
            elif (self.collection_query_tool.preferred_status_origin
                  == "recommended"
                  and data["status"]["reputation"]["recommended"]):
                self.status.status = data["status"]["reputation"][
                    "recommended"]
                self.status.status_source = "COLLECTION"

            PyFunceble.facility.Logger.info(
                "Could define the status of %r from: Collection Lookup",
                self.status.idna_subject,
            )

        PyFunceble.facility.Logger.info(
            "Finished to try to query the status of %r from: Collection Lookup",
            self.status.idna_subject,
        )

    @CheckerBase.ensure_subject_is_given
    @CheckerBase.update_status_date_after_query
    def query_status(self) -> "ReputationCheckerBase":
        """
        Queries the status and for for more action.
        """

        status_post_syntax_checker = None

        if (not self.status.status and
                self.use_collection):  # pragma: no cover ## Underlying tested
            self.try_to_query_status_from_collection()

        if not self.status.status and self.do_syntax_check_first:
            self.try_to_query_status_from_syntax_lookup()

            if self.status.status:
                status_post_syntax_checker = self.status.status

        if self.should_we_continue_test(status_post_syntax_checker):
            self.try_to_query_status_from_dns_lookup()

        if not self.status.status:
            self.status.status = PyFunceble.storage.STATUS.sane
            self.status.status_source = "REPUTATION"

            PyFunceble.facility.Logger.info(
                "Could not define the status of %r. Setting to %r",
                self.status.idna_subject,
                self.status.status,
            )

        return self

    # pylint: disable=useless-super-delegation
    def get_status(self) -> Optional[ReputationCheckerStatus]:
        return super().get_status()
コード例 #9
0
ファイル: base.py プロジェクト: spirillen/PyFunceble
class RequestAdapterBase(requests.adapters.HTTPAdapter):
    """
    Extends the built-in HTTP adapater and acts as a base for all our own
    adapter.
    """

    resolving_cache: dict = {}
    resolving_use_cache: bool = False
    timeout: float = 5.0

    def __init__(self, *args, **kwargs):
        if "timeout" in kwargs:
            self.timeout = float(kwargs["timeout"])
            del kwargs["timeout"]

        if "max_retries" in kwargs:
            kwargs["max_retries"] = requests.adapters.Retry(
                total=kwargs["max_retries"], respect_retry_after_header=False)

        if "dns_query_tool" in kwargs:
            self.dns_query_tool = kwargs["dns_query_tool"]
            del kwargs["dns_query_tool"]
        else:
            self.dns_query_tool = DNSQueryTool()

        super().__init__(*args, **kwargs)

    @staticmethod
    def fake_response() -> requests.models.Response:
        """
        Provides the fake response that is provided when we couldn't resolve the
        given domain.
        """

        raise PyFunceble.factory.Requester.exceptions.ConnectionError(
            "Could not resolve.")

    def resolve_with_cache(self, hostname: str) -> Optional[str]:
        """
        Try to resolve using an internal cache.
        """

        if hostname not in self.resolving_cache:
            self.resolving_cache[hostname] = self.resolve_without_cache(
                hostname)

        return self.resolving_cache[hostname]

    def resolve_without_cache(self, hostname: str) -> Optional[str]:
        """
        Resolves the IP of the given hostname.

        :param hostname:
            The hostname to get resolve.
        """
        def get_last_cname(subject: str) -> Optional[str]:
            """
            Given a subject, this function tries to query the CNAME until there
            is none.

            :param subject:
                The first subject.
            """

            last_cname_result = []
            last_cname_new_subject = subject

            while True:
                local_last_cname_result = (
                    self.dns_query_tool.set_query_record_type(
                        "CNAME").set_subject(last_cname_new_subject).query())

                if any(x in last_cname_result
                       for x in local_last_cname_result):
                    break

                last_cname_result.extend(local_last_cname_result)

                if local_last_cname_result:
                    last_cname_new_subject = local_last_cname_result[0]
                else:
                    break

            try:
                return last_cname_result[-1]
            except IndexError:
                return None

        result = set()

        if not IPSyntaxChecker(hostname).is_valid():
            last_cname = get_last_cname(hostname)

            if last_cname:
                result.update(
                    self.dns_query_tool.set_query_record_type("A").set_subject(
                        last_cname).query())
            else:
                result.update(
                    self.dns_query_tool.set_query_record_type("A").set_subject(
                        hostname).query())
        else:
            result.add(hostname)

        if result:
            return result.pop()
        return None

    def resolve(self, hostname: str) -> Optional[str]:
        """
        Resolves with the prefered method.
        """

        if hostname:
            if self.resolving_use_cache:
                return self.resolve_with_cache(hostname)
            return self.resolve_without_cache(hostname)
        return None
コード例 #10
0
class TestDNSQueryTool(unittest.TestCase):
    """
    Tests our DNS query tool.
    """

    def setUp(self) -> None:
        """
        Setups everything needed for the tests.
        """

        def fake_response(data: str) -> object:
            return dataclasses.make_dataclass(
                "FakeResponse", [("address", str, dataclasses.field(default=data))]
            )

        def fake_resolver(_: str, rqtype: str):
            if rqtype == "A":
                return [
                    fake_response("192.168.1.1"),
                    fake_response("10.47.91.9"),
                ]

            return [
                fake_response("fe80::6b01:9045:a42a:fb5f"),
                fake_response("fe80::6b01:9049:a42a:fb5f"),
            ]

        self.resolve_patch = unittest.mock.patch.object(
            dns.resolver.Resolver, "resolve"
        )
        self.udp_query_patch = unittest.mock.patch.object(dns.query, "udp")
        self.tcp_query_patch = unittest.mock.patch.object(dns.query, "tcp")
        self.https_query_patch = unittest.mock.patch.object(dns.query, "https")
        self.tls_query_patch = unittest.mock.patch.object(dns.query, "tls")

        self.mock_resolve = self.resolve_patch.start()
        self.mock_resolve.side_effect = fake_resolver

        self.mock_udp_query = self.udp_query_patch.start()
        self.mock_tcp_query = self.tcp_query_patch.start()
        self.mock_https_query = self.https_query_patch.start()
        self.mock_tls_query = self.tls_query_patch.start()

        self.query_tool = DNSQueryTool(nameservers=["example.org"])

        self.query_tool._get_result_from_response = lambda _: []

    def tearDown(self) -> None:
        """
        Destroys everything previously initiated for the tests.
        """

        self.resolve_patch.stop()
        self.udp_query_patch.stop()
        self.tcp_query_patch.stop()
        self.https_query_patch.stop()
        self.tls_query_patch.stop()

        del self.resolve_patch
        del self.udp_query_patch
        del self.tcp_query_patch
        del self.https_query_patch
        del self.tls_query_patch

        del self.mock_resolve
        del self.mock_udp_query
        del self.mock_tcp_query
        del self.mock_https_query
        del self.mock_tls_query

    @staticmethod
    def timout_response(*args, **kwargs) -> None:
        """
        Provides a response which raise a timeout error exception.
        """

        raise dns.exception.Timeout()

    @staticmethod
    def malformed_response(*args, **kwargs) -> None:
        """
        Provides a response which raises a malformed value error.
        """

        raise ValueError("Input malformed")

    @staticmethod
    def unexpected_source_response(*args, **kwargs) -> None:
        """
        Provides a response which raises an unexpected source exception.
        """

        raise dns.query.UnexpectedSource("got a response from XXX instead of XXX.")

    @staticmethod
    def bad_response_response(*args, **kwargs) -> None:
        """
        Provides a response which raises a bad response error.
        """

        raise dns.query.BadResponse(
            "A DNS query response does not respond to the question asked."
        )

    @staticmethod
    def socket_error(*args, **kwargs) -> None:
        """
        Provides a response which raises a socket error.
        """

        raise socket.gaierror("Socket Error.")

    def test_set_subject(self) -> None:
        """
        Tests the method which let us set the subject to work with.
        """

        given = "example.net"

        expected_dns_name = None
        actual = self.query_tool.dns_name

        self.assertEqual(expected_dns_name, actual)

        expected = "example.net"
        self.query_tool.set_subject(given)

        actual = self.query_tool.subject

        self.assertEqual(expected, actual)

        self.assertIsInstance(self.query_tool.dns_name, dns.name.Name)

    def test_set_subject_absolute(self) -> None:
        """
        Tests the method which let us set the subject to work with.
        """

        given = "example.net."

        expected_dns_name = None
        actual = self.query_tool.dns_name

        self.assertEqual(expected_dns_name, actual)

        expected = "example.net."
        self.query_tool.set_subject(given)

        actual = self.query_tool.subject

        self.assertEqual(expected, actual)

        self.assertIsInstance(self.query_tool.dns_name, dns.name.Name)

    def test_set_subject_not_str(self) -> None:
        """
        Tests the method which let us set the subject to work with for the case
        that the given subject is not a string.
        """

        given = ["Hello", "World!"]

        self.assertRaises(TypeError, lambda: self.query_tool.set_subject(given))

    def test_set_subject_empty_str(self) -> None:
        """
        Tests the method which let us set the subject to work with for the case
        that the given subject is an empty string.
        """

        given = ""

        self.assertRaises(ValueError, lambda: self.query_tool.set_subject(given))

    def test_set_follow_nameserver_order(self) -> None:
        """
        Tests the method which let us allow/disallow the shuffle of the list
        of nameserver.
        """

        given = True
        expected = True

        self.query_tool.set_follow_nameserver_order(given)

        actual = self.query_tool.follow_nameserver_order

        self.assertEqual(expected, actual)

    def test_set_follow_nameserver_order_not_bool(self) -> None:
        """
        Tests the method which let us allow/disallow the shuffle of the list
        of nameserver for the case that the given value is not a boolean
        """

        given = ["Hello", "World"]

        self.assertRaises(
            TypeError, lambda: self.query_tool.set_follow_nameserver_order(given)
        )

    def test_set_follow_nameserver_order_through_init(self) -> None:
        """
        Tests the overwritting of the `follow_nameserver_attribute` attribute
        through the class constructor.
        """

        given = False
        expected = False

        query_tool = DNSQueryTool(follow_nameserver_order=given)

        actual = query_tool.follow_nameserver_order

        self.assertEqual(expected, actual)

    def test_guess_and_set_follow_nameserver_order(self) -> None:
        """
        Tests the method which let us guess and set the order of the server.
        """

        config_loader = ConfigLoader()
        config_loader.set_custom_config({"dns": {"follow_server_order": False}}).start()

        self.query_tool.guess_and_set_follow_nameserver_order()

        expected = False
        actual = self.query_tool.follow_nameserver_order

        self.assertEqual(expected, actual)

        del config_loader

    def test_guess_and_set_follow_nameserver_order_none(self) -> None:
        """
        Tests the method which let us guess and set the order of the server.

        In this test, we check the case that the given value is set to None.
        """

        config_loader = ConfigLoader()
        config_loader.set_custom_config({"dns": {"follow_server_order": None}}).start()

        self.query_tool.guess_and_set_follow_nameserver_order()

        expected = self.query_tool.STD_FOLLOW_NAMESERVER_ORDER
        actual = self.query_tool.follow_nameserver_order

        self.assertEqual(expected, actual)

        del config_loader

    def test_set_query_record_type_from_name(self) -> None:
        """
        Tests the method which let us set the type of the record with for the
        case that the string representation of the record type is given.
        """

        given = "A"

        self.query_tool.set_query_record_type(given)

        expected = dns.rdatatype.RdataType.A
        actual = self.query_tool.query_record_type

        self.assertEqual(expected, actual)

        expected = "A"
        actual = self.query_tool.get_human_query_record_type()

        self.assertEqual(expected, actual)

    def test_set_query_record_type_from_value(self) -> None:
        """
        Tests the method which let us set the type of the record with for the
        case that the actual value (int) of the record type is given.
        """

        given = dns.rdatatype.RdataType.AAAA

        self.query_tool.set_query_record_type(given)

        expected = dns.rdatatype.RdataType.AAAA
        actual = self.query_tool.query_record_type

        self.assertEqual(expected, actual)

        expected = "AAAA"
        actual = self.query_tool.get_human_query_record_type()

        self.assertEqual(expected, actual)

    def test_set_query_record_type_unknown_value(self) -> None:
        """
        Tests the method which let us set the type of the record with for the
        case that the actual value is unknown
        """

        given = 1010101010110101

        self.assertRaises(
            ValueError, lambda: self.query_tool.set_query_record_type(given)
        )

    def test_get_dns_name_from_subject_and_query_type(self) -> None:
        """
        Tests themethod which let us us get the dns name that dnspython has to
        use base on the given subject and query type.
        """

        self.query_tool.subject = "example.org"
        self.query_tool.query_record_type = "A"

        expected = dns.name.from_text("example.org")
        actual = self.query_tool.get_dns_name_from_subject_and_query_type()

        self.assertEqual(expected, actual)

    def test_get_dns_name_from_subject_and_query_type_label_too_long(self) -> None:
        """
        Tests themethod which let us us get the dns name that dnspython has to
        use base on the given subject and query type.

        In this case we check that too long entries are handled correctly.
        """

        self.query_tool.subject = f"{secrets.token_urlsafe(300)}.org"
        self.query_tool.query_record_type = "A"

        expected = None
        actual = self.query_tool.get_dns_name_from_subject_and_query_type()

        self.assertEqual(expected, actual)

    def test_get_dns_name_from_subject_and_query_type_label_not_ptr_compliant(
        self,
    ) -> None:
        """
        Tests themethod which let us us get the dns name that dnspython has to
        use base on the given subject and query type.

        In this case we check the case that a non PTR compliant IP is given.
        """

        self.query_tool.subject = "93.184.216.34"
        self.query_tool.query_record_type = "PTR"

        expected = dns.name.from_text("34.216.184.93.in-addr.arpa")
        actual = self.query_tool.get_dns_name_from_subject_and_query_type()

        self.assertEqual(expected, actual)

    def test_get_dns_name_from_subject_and_query_type_label_ptr_compliant(
        self,
    ) -> None:
        """
        Tests themethod which let us us get the dns name that dnspython has to
        use base on the given subject and query type.

        In this case we check the case that a non PTR compliant IP is given.
        """

        self.query_tool.subject = "34.216.184.93.in-addr.arpa"
        self.query_tool.query_record_type = "PTR"

        expected = dns.name.from_text("34.216.184.93.in-addr.arpa")
        actual = self.query_tool.get_dns_name_from_subject_and_query_type()

        self.assertEqual(expected, actual)

    def test_set_query_record_type_not_str_nor_int(self) -> None:
        """
        Tests the method which let us set the type of the record with for the
        case that the given value is not a string nor integer.
        """

        given = ["Hello", "World!"]

        self.assertRaises(
            TypeError, lambda: self.query_tool.set_query_record_type(given)
        )

    def test_set_timeout(self) -> None:
        """
        Tests the method which let us set the timeout to apply.
        """

        given = 5.0
        expected = 5.0

        self.query_tool.set_timeout(given)
        actual = self.query_tool.query_timeout

        self.assertEqual(expected, actual)

    def test_set_timeout_not_int_nor_float(self) -> None:
        """
        Tests the method which let us set the timeout to apply for the case that
        the given timeout is not a float nor a integer.
        """

        given = ["Hello", "World!"]

        self.assertRaises(TypeError, lambda: self.query_tool.set_timeout(given))

    def test_guess_and_set_timeout(self) -> None:
        """
        Tests the method which let us guess and set the timeout from the
        configuration file.
        """

        config_loader = ConfigLoader()
        config_loader.set_custom_config({"lookup": {"timeout": 10.0}}).start()

        self.query_tool.guess_and_set_timeout()

        expected = 10.0
        actual = self.query_tool.query_timeout

        self.assertEqual(expected, actual)

        del config_loader

    def test_guess_and_set_timeout_none(self) -> None:
        """
        Tests the method which let us guess and set the timeout from the
        configuration file.

        In this test, we check the case the None is given.
        """

        config_loader = ConfigLoader()
        config_loader.set_custom_config({"lookup": {"timeout": None}}).start()

        self.query_tool.guess_and_set_timeout()

        expected = self.query_tool.STD_TIMEOUT
        actual = self.query_tool.query_timeout

        self.assertEqual(expected, actual)

        del config_loader

    def test_guess_and_set_timeout_config_not_loaded(self) -> None:
        """
        Tests the method which let us guess and set the timeout from the
        configuration file.

        In this test, we check the case the config was not loader yet.
        """

        self.query_tool.guess_and_set_timeout()

        expected = self.query_tool.STD_TIMEOUT
        actual = self.query_tool.query_timeout

        self.assertEqual(expected, actual)

    def test_set_trust_server(self) -> None:
        """
        Tests the method which let us trust all the given server.
        """

        given = True
        expected = True

        self.query_tool.set_trust_server(given)
        actual = self.query_tool.trust_server

        self.assertEqual(expected, actual)

    def test_set_trust_server_not_bool(self) -> None:
        """
        Tests the method which let us trust all the given server.

        In this test we check the case that a non-boolean value is given.
        """

        given = ["Hello", "World"]

        self.assertRaises(TypeError, lambda: self.query_tool.set_trust_server(given))

    def test_set_trust_server_through_init(self) -> None:
        """
        Tests the overwritting of the `trust_server` attribute through the class
        constructor.
        """

        given = True
        expected = True

        query_tool = DNSQueryTool(trust_server=given)

        actual = query_tool.trust_server

        self.assertEqual(expected, actual)

    def test_guess_and_set_trust_server(self) -> None:
        """
        Tests the method which let us guess and set the trust flag from the
        configuration file.
        """

        config_loader = ConfigLoader()
        config_loader.set_custom_config({"dns": {"trust_server": True}}).start()

        self.query_tool.guess_and_set_trust_server()

        expected = True
        actual = self.query_tool.trust_server

        self.assertEqual(expected, actual)

        del config_loader

    def test_guess_and_set_trust_server_none(self) -> None:
        """
        Tests the method which let us guess and set the trust flag from the
        configuration file.

        In this case, we test the case that None or implicitly a non boolean
        value is given.
        """

        config_loader = ConfigLoader()
        config_loader.set_custom_config({"dns": {"trust_server": None}}).start()

        self.query_tool.guess_and_set_trust_server()

        expected = self.query_tool.STD_TRUST_SERVER
        actual = self.query_tool.trust_server

        self.assertEqual(expected, actual)

        del config_loader

    def test_set_preferred_protocol(self) -> None:
        """
        Tests the method which let us set the preferred protocol.
        """

        given = "TCP"
        expected = "TCP"

        self.query_tool.set_preferred_protocol(given)
        actual = self.query_tool.preferred_protocol

        self.assertEqual(expected, actual)

    def test_set_preferred_protocol_through_init(self) -> None:
        """
        Tests the method which let us set the preferred protocol.

        In this test we check that the transfert of the preferred protocol
        through the constructor is working.
        """

        given = "TCP"

        query_tool = DNSQueryTool(preferred_protocol=given)

        expected = "TCP"
        actual = query_tool.preferred_protocol

        self.assertEqual(expected, actual)

    def test_set_preferred_protocol_not_str(self) -> None:
        """
        Tests the method which let us set the preferred protocol for the case
        that the given protocol is not a string.
        """

        given = ["Hello", "World"]

        self.assertRaises(
            TypeError, lambda: self.query_tool.set_preferred_protocol(given)
        )

    def test_set_preferred_protocol_not_supported(self) -> None:
        """
        Tests the method which let us set the preferred protocol for the case
        that the given protocol is not supported.
        """

        given = "SNMP"

        self.assertRaises(
            ValueError,
            lambda: self.query_tool.set_preferred_protocol(given),
        )

    def test_guess_and_set_preferred_protocol(self) -> None:
        """
        Tests the method which let us guess and set the preferred protocol.
        """

        config_loader = ConfigLoader()
        config_loader.set_custom_config({"dns": {"protocol": "HTTPS"}}).start()

        self.query_tool.guess_and_set_preferred_protocol()

        expected = "HTTPS"
        actual = self.query_tool.preferred_protocol

        self.assertEqual(expected, actual)

        del config_loader

    def test_guess_and_set_preferred_protocol_none(self) -> None:
        """
        Tests the method which let us guess and set the preferred protocol.

        In this test, we check the case that the given protocol is set to None.
        """

        config_loader = ConfigLoader()
        config_loader.set_custom_config({"dns": {"protocol": None}}).start()

        self.query_tool.guess_and_set_preferred_protocol()

        expected = self.query_tool.STD_PROTOCOL
        actual = self.query_tool.preferred_protocol

        self.assertEqual(expected, actual)

    def test_set_delay(self) -> None:
        """
        Tests the method which let us set the delay.
        """

        given = 0.48
        expected = 0.48

        self.query_tool.set_delay(given)
        actual = self.query_tool.delay

        self.assertEqual(expected, actual)

    def test_set_delay_through_init(self) -> None:
        """
        Tests the method which let us set the delay.

        In this test we check that the transfert of the delay
        through the constructor is working.
        """

        given = 0.55

        query_tool = DNSQueryTool(delay=given)

        expected = 0.55
        actual = query_tool.delay

        self.assertEqual(expected, actual)

    def test_set_delay_not_float(self) -> None:
        """
        Tests the method which let us set the delay for the case
        that the given delay is not a float or int.
        """

        given = ["Hello", "World"]

        self.assertRaises(TypeError, lambda: self.query_tool.set_delay(given))

    def test_guess_and_set_delay(self) -> None:
        """
        Tests the method which let us guess and set the delay between each
        queries.

        In this test, we check the case that the given delay is set to a value
        greater than 0.
        """

        config_loader = ConfigLoader()
        config_loader.set_custom_config({"dns": {"delay": 0.45}}).start()

        self.query_tool.guess_and_set_delay()

        expected = 0.45
        actual = self.query_tool.delay

        self.assertEqual(expected, actual)

    def test_guess_and_set_delay_less_than_zero(self) -> None:
        """
        Tests the method which let us guess and set the delay between each
        queries.

        In this test, we check the case that the given delay is set to a value
        less than 0.
        """

        config_loader = ConfigLoader()
        config_loader.set_custom_config({"dns": {"delay": -3.0}}).start()

        # pylint: disable=unnecessary-lambda
        self.assertRaises(ValueError, lambda: self.query_tool.guess_and_set_delay())

    def test_guess_and_set_delay_none(self) -> None:
        """
        Tests the method which let us guess and set the delay between each
        queries.

        In this test, we check the case that the given delay is set to None.
        """

        config_loader = ConfigLoader()
        config_loader.set_custom_config({"dns": {"delay": None}}).start()

        self.query_tool.guess_and_set_delay()

        expected = self.query_tool.STD_DELAY
        actual = self.query_tool.delay

        self.assertEqual(expected, actual)

    def test_get_lookup_record(self) -> None:
        """
        Tests the method which let us get the lookup record.
        """

        self.query_tool.preferred_protocol = "UDP"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        _ = self.query_tool.query()

        actual = self.query_tool.get_lookup_record()

        self.assertIsInstance(actual, DNSQueryToolRecord)

        expected = "example.org"
        self.assertEqual(expected, actual.subject)

    def test_udp_query(self) -> None:
        """
        Tests the method which let us query through the UDP protocol.
        """

        self.query_tool.preferred_protocol = "UDP"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        _ = self.query_tool.query()

        self.mock_udp_query.assert_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_https_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_udp_query_no_info(self) -> None:
        """
        Tests the method which let us query through the UDP protocol.

        In this test, we check the case that we don't give any information.
        """

        # pylint: disable=unnecessary-lambda
        self.assertRaises(TypeError, lambda: self.query_tool.query())

        self.mock_udp_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_https_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_udp_query_timeout(self) -> None:
        """
        Tests the method which let us query through the UDP protocol.

        In this case, we check the case that a timeout exception is raised.
        """

        self.query_tool.preferred_protocol = "UDP"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_udp_query.side_effect = self.timout_response

        _ = self.query_tool.query()

        self.mock_udp_query.assert_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_https_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_udp_query_malformed_input(self) -> None:
        """
        Tests the method which let us query through the UDP protocol.

        In this case, we check the case that a ValueError is raised.
        """

        self.query_tool.preferred_protocol = "UDP"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_udp_query.side_effect = self.malformed_response

        _ = self.query_tool.query()

        self.mock_udp_query.assert_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_https_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_udp_query_unexpected_source(self) -> None:
        """
        Tests the method which let us query through the UDP protocol.

        In this case, we check the case that an UnexpectedSource exception
        is raised.
        """

        self.query_tool.preferred_protocol = "UDP"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_udp_query.side_effect = self.unexpected_source_response

        _ = self.query_tool.query()

        self.mock_udp_query.assert_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_https_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_udp_query_bad_response(self) -> None:
        """
        Tests the method which let us query through the UDP protocol.

        In this case, we check the case that an BadResponse exception
        is raised.
        """

        self.query_tool.preferred_protocol = "UDP"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_udp_query.side_effect = self.bad_response_response

        _ = self.query_tool.query()

        self.mock_udp_query.assert_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_https_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_udp_query_socket_error(self) -> None:
        """
        Tests the method which let us query through the UDP protocol.

        In this case, we check the case that a socket error is raised.
        """

        self.query_tool.preferred_protocol = "UDP"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_udp_query.side_effect = self.socket_error

        _ = self.query_tool.query()

        self.mock_udp_query.assert_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_https_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_udp_query_with_result(self) -> None:
        """
        Tests the method which let us query through the UDP protocol.

        In this case we check that the response is is properly parsed to the
        lookup record.
        """

        self.query_tool.preferred_protocol = "UDP"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.query_tool._get_result_from_response = lambda _: ["93.184.216.34"]

        actual = self.query_tool.query()

        self.mock_udp_query.assert_called()
        self.mock_https_query.assert_not_called()
        self.mock_tls_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()

        # pragma: no cover ## Let's trust upstream
        expected = ["93.184.216.34"]

        self.assertEqual(expected, actual)

        self.assertEqual(expected, self.query_tool.lookup_record.response)

    def test_udp_query_bad_escape(self) -> None:
        """
        Tests the method which let us query through the UDP protocol.

        In this case we check the case that a subject has some bad escape
        character.
        """

        self.query_tool.preferred_protocol = "UDP"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org\\"

        _ = self.query_tool.query()

        # Not called because inputted is invalid.
        self.mock_udp_query.assert_not_called()
        self.mock_https_query.assert_not_called()
        self.mock_tls_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()

    def test_tcp_query(self) -> None:
        """
        Tests the method which let us query through the TCP protocol.
        """

        self.query_tool.preferred_protocol = "TCP"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        _ = self.query_tool.query()

        self.mock_tcp_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_https_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_tcp_query_timeout(self) -> None:
        """
        Tests the method which let us query through the TCP protocol.

        In this case, we assume that a timeout was given.
        """

        self.query_tool.preferred_protocol = "TCP"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_tcp_query.side_effect = self.timout_response

        _ = self.query_tool.query()

        self.mock_tcp_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_https_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_tcp_query_malformed_input(self) -> None:
        """
        Tests the method which let us query through the TCP protocol.

        In this case, we check the case that a ValueError is raised.
        """

        self.query_tool.preferred_protocol = "TCP"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_tcp_query.side_effect = self.malformed_response

        _ = self.query_tool.query()

        self.mock_tcp_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_https_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_tcp_query_unexpected_source(self) -> None:
        """
        Tests the method which let us query through the TCP protocol.

        In this case, we check the case that an UnexpectedSource exception
        is raised.
        """

        self.query_tool.preferred_protocol = "TCP"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_tcp_query.side_effect = self.unexpected_source_response

        _ = self.query_tool.query()

        self.mock_tcp_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_https_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_tcp_query_bad_response(self) -> None:
        """
        Tests the method which let us query through the TCP protocol.

        In this case, we check the case that an BadResponse exception
        is raised.
        """

        self.query_tool.preferred_protocol = "TCP"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_tcp_query.side_effect = self.bad_response_response

        _ = self.query_tool.query()

        self.mock_tcp_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_https_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_tcp_query_socket_error(self) -> None:
        """
        Tests the method which let us query through the TCP protocol.

        In this case, we check the case that a socket error is raised.
        """

        self.query_tool.preferred_protocol = "TCP"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_tcp_query.side_effect = self.socket_error

        _ = self.query_tool.query()

        self.mock_tcp_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_https_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_tcp_query_with_result(self) -> None:
        """
        Tests the method which let us query through the TCP protocol.

        In this case we check that the response is is properly parsed to the
        lookup record.
        """

        self.query_tool.preferred_protocol = "TCP"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.query_tool._get_result_from_response = lambda _: ["93.184.216.34"]

        actual = self.query_tool.query()

        self.mock_tcp_query.assert_called()
        self.mock_tls_query.assert_not_called()
        self.mock_udp_query.assert_not_called()
        self.mock_https_query.assert_not_called()

        # pragma: no cover ## Let's trust upstream
        expected = ["93.184.216.34"]

        self.assertEqual(expected, actual)

        self.assertEqual(expected, self.query_tool.lookup_record.response)

    def test_tcp_query_bad_escape(self) -> None:
        """
        Tests the method which let us query through the TCP protocol.

        In this case we check the case that a subject has some bad escape
        character.
        """

        self.query_tool.preferred_protocol = "TCP"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org\\"

        _ = self.query_tool.query()

        # Not called because inputted is invalid.
        self.mock_tcp_query.assert_not_called()
        self.mock_udp_query.assert_not_called()
        self.mock_https_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_https_query(self) -> None:
        """
        Tests the method which let us query through the HTTPS protocol.
        """

        self.query_tool.preferred_protocol = "HTTPS"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        _ = self.query_tool.query()

        self.mock_https_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_https_query_timeout(self) -> None:
        """
        Tests the method which let us query through the HTTPS protocol.

        In this case we check the case that a timeout exception is raised.
        """

        self.query_tool.preferred_protocol = "HTTPS"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_https_query.side_effect = self.timout_response

        _ = self.query_tool.query()

        self.mock_https_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_https_query_malformed_input(self) -> None:
        """
        Tests the method which let us query through the HTTPS protocol.

        In this case, we check the case that a ValueError is raised.
        """

        self.query_tool.preferred_protocol = "HTTPS"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_https_query.side_effect = self.malformed_response

        _ = self.query_tool.query()

        self.mock_https_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_https_query_socket_error(self) -> None:
        """
        Tests the method which let us query through the HTTPS protocol.

        In this case, we check the case that a socket error is raised.
        """

        self.query_tool.preferred_protocol = "HTTPS"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_https_query.side_effect = self.socket_error

        _ = self.query_tool.query()

        self.mock_https_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_https_query_unexpected_source(self) -> None:
        """
        Tests the method which let us query through the HTTPS protocol.

        In this case, we check the case that an UnexpectedSource exception
        is raised.
        """

        self.query_tool.preferred_protocol = "HTTPS"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_https_query.side_effect = self.unexpected_source_response

        _ = self.query_tool.query()

        self.mock_https_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_https_query_bad_response(self) -> None:
        """
        Tests the method which let us query through the HTTPS protocol.

        In this case, we check the case that an BadResponse exception
        is raised.
        """

        self.query_tool.preferred_protocol = "HTTPS"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_https_query.side_effect = self.bad_response_response

        _ = self.query_tool.query()

        self.mock_https_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_https_query_with_result(self) -> None:
        """
        Tests the method which let us query through the HTTPS protocol.

        In this case we check that the response is is properly parsed to the
        lookup record.
        """

        self.query_tool.preferred_protocol = "HTTPS"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.query_tool._get_result_from_response = lambda _: ["93.184.216.34"]

        actual = self.query_tool.query()

        self.mock_https_query.assert_called()
        self.mock_tls_query.assert_not_called()
        self.mock_udp_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()

        # pragma: no cover ## Let's trust upstream
        expected = ["93.184.216.34"]

        self.assertEqual(expected, actual)

        self.assertEqual(expected, self.query_tool.lookup_record.response)

    def test_https_query_bad_escape(self) -> None:
        """
        Tests the method which let us query through the HTTPS protocol.

        In this case we check the case that a subject has some bad escape
        character.
        """

        self.query_tool.preferred_protocol = "HTTPS"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org\\"

        self.query_tool._get_result_from_response = lambda _: ["93.184.216.34"]

        _ = self.query_tool.query()

        # Not called because inputted is invalid.
        self.mock_https_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_udp_query.assert_not_called()
        self.mock_tls_query.assert_not_called()

    def test_tls_query(self) -> None:
        """
        Tests the method which let us query through the TLS protocol.
        """

        self.query_tool.preferred_protocol = "TLS"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        _ = self.query_tool.query()

        self.mock_tls_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_https_query.assert_not_called()

    def test_tls_query_timeout(self) -> None:
        """
        Tests the method which let us query through the TLS protocol.

        In this case we check the case that a timeout exception is raised.Is
        """

        self.query_tool.preferred_protocol = "TLS"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_tls_query.side_effect = self.timout_response

        _ = self.query_tool.query()

        self.mock_tls_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_https_query.assert_not_called()

    def test_tls_query_malformed_input(self) -> None:
        """
        Tests the method which let us query through the TLS protocol.

        In this case, we check the case that a ValueError is raised.
        """

        self.query_tool.preferred_protocol = "TLS"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_tls_query.side_effect = self.malformed_response

        _ = self.query_tool.query()

        self.mock_tls_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_https_query.assert_not_called()

    def test_tls_query_socket_error(self) -> None:
        """
        Tests the method which let us query through the TLS protocol.

        In this case, we check the case that a socket error is raised.
        """

        self.query_tool.preferred_protocol = "TLS"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_tls_query.side_effect = self.socket_error

        _ = self.query_tool.query()

        self.mock_tls_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_https_query.assert_not_called()

    def test_tls_query_unexpected_source(self) -> None:
        """
        Tests the method which let us query through the TLS protocol.

        In this case, we check the case that an UnexpectedSource exception
        is raised.
        """

        self.query_tool.preferred_protocol = "TLS"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_tls_query.side_effect = self.unexpected_source_response

        _ = self.query_tool.query()

        self.mock_tls_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_https_query.assert_not_called()

    def test_tls_query_bad_response(self) -> None:
        """
        Tests the method which let us query through the TLS protocol.

        In this case, we check the case that an BadResponse exception
        is raised.
        """

        self.query_tool.preferred_protocol = "TLS"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.mock_tls_query.side_effect = self.bad_response_response

        _ = self.query_tool.query()

        self.mock_tls_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_https_query.assert_not_called()

    def test_tls_query_with_result(self) -> None:
        """
        Tests the method which let us query through the TLS protocol.

        In this case we check that the response is is properly parsed to the
        lookup record.
        """

        self.query_tool.preferred_protocol = "TLS"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org"

        self.query_tool._get_result_from_response = lambda _: ["93.184.216.34"]

        actual = self.query_tool.query()

        self.mock_tls_query.assert_called()
        self.mock_udp_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_https_query.assert_not_called()

        # pragma: no cover ## Let's trust upstream
        expected = ["93.184.216.34"]

        self.assertEqual(expected, actual)

        self.assertEqual(expected, self.query_tool.lookup_record.response)

    def test_tls_query_bad_escape(self) -> None:
        """
        Tests the method which let us query through the TLS protocol.

        In this case we check the case that a subject has some bad escape
        character.
        """

        self.query_tool.preferred_protocol = "TLS"
        self.query_tool.query_record_type = "A"
        self.query_tool.subject = "example.org\\"

        _ = self.query_tool.query()

        # Not called because inputted is invalid.
        self.mock_tls_query.assert_not_called()
        self.mock_tcp_query.assert_not_called()
        self.mock_udp_query.assert_not_called()
        self.mock_https_query.assert_not_called()
コード例 #11
0
ファイル: base.py プロジェクト: spirillen/PyFunceble
class AvailabilityCheckerBase(CheckerBase):
    """
    Provides the base of all our availability checker classes.

    :param str subject:
        Optional, The subject to work with.
    :param bool use_extra_rules:
        Optional, Activates/Disables the usage of our own set of extra rules.
    :param bool use_whois_lookup:
        Optional, Activates/Disables the usage of the WHOIS lookup to gather
        the status of the given :code:`subject`.
    :param bool use_dns_lookup:
        Optional, Activates/Disables the usage of the DNS lookup to gather the
        status of the given :code:`subject`.
    :param bool use_netinfo_lookup:
        Optional, Activates/Disables the usage of the network information
        lookup module to gather the status of the given :code:`subject`.
    :param bool use_http_code_lookup:
        Optional, Activates/Disables the usage of the HTTP status code lookup
        to gather the status of the given :code:`subject`.
    :param bool use_reputation_lookup:
        Optional, Activates/Disables the usage of the reputation dataset
        lookup to gather the status of the given :code:`subject`.
    :param bool do_syntax_check_first:
        Optional, Activates/Disables the check of the status before the actual
        status gathering.
    :param bool use_whois_db:
        Optional, Activates/Disable the usage of a local database to store the
        WHOIS datasets.
    """

    # pylint: disable=too-many-public-methods, too-many-instance-attributes

    STD_USE_EXTRA_RULES: bool = True
    STD_USE_WHOIS_LOOKUP: bool = True
    STD_USE_DNS_LOOKUP: bool = True
    STD_USE_NETINFO_LOOKUP: bool = True
    STD_USE_HTTP_CODE_LOOKUP: bool = True
    STD_USE_REPUTATION_LOOKUP: bool = False
    STD_USE_WHOIS_DB: bool = True

    dns_query_tool: Optional[DNSQueryTool] = None
    whois_query_tool: Optional[WhoisQueryTool] = None
    addressinfo_query_tool: Optional[AddressInfo] = None
    hostbyaddr_query_tool: Optional[HostByAddrInfo] = None
    http_status_code_query_tool: Optional[HTTPStatusCode] = None
    domain_syntax_checker: Optional[DomainSyntaxChecker] = None
    ip_syntax_checker: Optional[IPSyntaxChecker] = None
    url_syntax_checker: Optional[URLSyntaxChecker] = None
    extra_rules_handler: Optional[ExtraRulesHandler] = None

    _use_extra_rules: bool = False
    _use_whois_lookup: bool = False
    _use_dns_lookup: bool = False
    _use_netinfo_lookup: bool = False
    _use_http_code_lookup: bool = False
    _use_reputation_lookup: bool = False
    _use_whois_db: bool = False

    status: Optional[AvailabilityCheckerStatus] = None
    params: Optional[AvailabilityCheckerParams] = None

    def __init__(
        self,
        subject: Optional[str] = None,
        *,
        use_extra_rules: Optional[bool] = None,
        use_whois_lookup: Optional[bool] = None,
        use_dns_lookup: Optional[bool] = None,
        use_netinfo_lookup: Optional[bool] = None,
        use_http_code_lookup: Optional[bool] = None,
        use_reputation_lookup: Optional[bool] = None,
        do_syntax_check_first: Optional[bool] = None,
        db_session: Optional[Session] = None,
        use_whois_db: Optional[bool] = None,
        use_collection: Optional[bool] = None,
    ) -> None:
        self.dns_query_tool = DNSQueryTool()
        self.whois_query_tool = WhoisQueryTool()
        self.addressinfo_query_tool = AddressInfo()
        self.hostbyaddr_query_tool = HostByAddrInfo()
        self.http_status_code_query_tool = HTTPStatusCode()
        self.domain_syntax_checker = DomainSyntaxChecker()
        self.ip_syntax_checker = IPSyntaxChecker()
        self.url_syntax_checker = URLSyntaxChecker()
        self.extra_rules_handler = ExtraRulesHandler()
        self.db_session = db_session

        self.params = AvailabilityCheckerParams()

        self.status = AvailabilityCheckerStatus()
        self.status.params = self.params
        self.status.dns_lookup_record = self.dns_query_tool.lookup_record
        self.status.whois_lookup_record = self.whois_query_tool.lookup_record

        if use_extra_rules is not None:
            self.use_extra_rules = use_extra_rules
        else:
            self.guess_and_set_use_extra_rules()

        if use_whois_lookup is not None:
            self.use_whois_lookup = use_whois_lookup
        else:
            self.guess_and_set_use_whois_lookup()

        if use_dns_lookup is not None:
            self.use_dns_lookup = use_dns_lookup
        else:
            self.guess_and_set_dns_lookup()

        if use_netinfo_lookup is not None:
            self.use_netinfo_lookup = use_netinfo_lookup
        else:
            self.guess_and_set_use_netinfo_lookup()

        if use_http_code_lookup is not None:
            self.use_http_code_lookup = use_http_code_lookup
        else:
            self.guess_and_set_use_http_code_lookup()

        if use_reputation_lookup is not None:
            self.use_reputation_lookup = use_reputation_lookup
        else:
            self.guess_and_set_use_reputation_lookup()

        if use_whois_db is not None:
            self.use_whois_db = use_whois_db
        else:
            self.guess_and_set_use_whois_db()

        super().__init__(
            subject,
            do_syntax_check_first=do_syntax_check_first,
            db_session=db_session,
            use_collection=use_collection,
        )

    @property
    def use_extra_rules(self) -> bool:
        """
        Provides the current value of the :code:`_use_extra_rules` attribute.
        """

        return self._use_extra_rules

    @use_extra_rules.setter
    def use_extra_rules(self, value: bool) -> None:
        """
        Sets the value which authorizes the usage of the special rule.

        :param value:
            The value to set.

        :raise TypeError:
            When the given :code:`value` is not a :py:class:`bool`.
        """

        if not isinstance(value, bool):
            raise TypeError(f"<value> should be {bool}, {type(value)} given.")

        self._use_extra_rules = self.params.use_extra_rules = value

    def set_use_extra_rules(self, value: bool) -> "AvailabilityCheckerBase":
        """
        Sets the value which authorizes the usage of the special rule.

        :param value:
            The value to set.
        """

        self.use_extra_rules = value

        return self

    @property
    def use_whois_lookup(self) -> bool:
        """
        Provides the current value of the :code:`_use_whois_lookup` attribute.
        """

        return self._use_whois_lookup

    @use_whois_lookup.setter
    def use_whois_lookup(self, value: bool) -> None:
        """
        Sets the value which authorizes the usage of the WHOIS lookup.

        :param value:
            The value to set.

        :raise TypeError:
            When the given :code:`value` is not a :py:class:`bool`.
        """

        if not isinstance(value, bool):
            raise TypeError(f"<value> should be {bool}, {type(value)} given.")

        self._use_whois_lookup = self.params.use_whois_lookup = value

    def set_use_whois_lookup(self, value: bool) -> "AvailabilityCheckerBase":
        """
        Sets the value which authorizes the usage of the WHOIS lookup.

        :param value:
            The value to set.
        """

        self.use_whois_lookup = value

        return self

    @property
    def use_dns_lookup(self) -> bool:
        """
        Provides the current value of the :code:`_use_dns_lookup` attribute.
        """

        return self._use_dns_lookup

    @use_dns_lookup.setter
    def use_dns_lookup(self, value: bool) -> None:
        """
        Sets the value which authorizes the usage of the DNS Lookup.

        :param value:
            The value to set.

        :raise TypeError:
            When the given :code:`value` is not a :py:class:`bool`.
        """

        if not isinstance(value, bool):
            raise TypeError(f"<value> should be {bool}, {type(value)} given.")

        self._use_dns_lookup = self.params.use_dns_lookup = value

    def set_use_dns_lookup(self, value: bool) -> "AvailabilityCheckerBase":
        """
        Sets the value which authorizes the usage of the DNS Lookup.

        :param value:
            The value to set.
        """

        self.use_dns_lookup = value

        return self

    @property
    def use_netinfo_lookup(self) -> bool:
        """
        Provides the current value of the :code:`_use_netinfo_lookup` attribute.
        """

        return self._use_netinfo_lookup

    @use_netinfo_lookup.setter
    def use_netinfo_lookup(self, value: bool) -> None:
        """
        Sets the value which authorizes the usage of the network information
        lookup.

        :param value:
            The value to set.

        :raise TypeError:
            When the given :code:`value` is not a :py:class:`bool`.
        """

        if not isinstance(value, bool):
            raise TypeError(f"<value> should be {bool}, {type(value)} given.")

        self._use_netinfo_lookup = self.params.use_netinfo_lookup = value

    def set_use_netinfo_lookup(self, value: bool) -> "AvailabilityCheckerBase":
        """
        Sets the value which authorizes the usage of the network information
        lookup.

        :param value:
            The value to set.
        """

        self.use_netinfo_lookup = value

        return self

    @property
    def use_http_code_lookup(self) -> None:
        """
        Provides the current value of the :code:`_use_http_code_lookup` attribute.
        """

        return self._use_http_code_lookup

    @use_http_code_lookup.setter
    def use_http_code_lookup(self, value: bool) -> None:
        """
        Sets the value which authorizes the usage of the HTTP status code
        lookup.

        :param value:
            The value to set.

        :raise TypeError:
            When the given :code:`value` is not a :py:class:`bool`.
        """

        if not isinstance(value, bool):
            raise TypeError(f"<value> should be {bool}, {type(value)} given.")

        self._use_http_code_lookup = self.params.use_http_code_lookup = value

    def set_use_http_code_lookup(self,
                                 value: bool) -> "AvailabilityCheckerBase":
        """
        Sets the value which authorizes the usage of the HTTP status code
        lookup.

        :param value:
            The value to set.
        """

        self.use_http_code_lookup = value

        return self

    @property
    def use_reputation_lookup(self) -> bool:
        """
        Provides the current value of the :code:`_use_reputation_lookup` attribute.
        """

        return self._use_reputation_lookup

    @use_reputation_lookup.setter
    def use_reputation_lookup(self, value: bool) -> None:
        """
        Sets the value which authorizes the usage of the reputation
        lookup.

        :param value:
            The value to set.

        :raise TypeError:
            When the given :code:`value` is not a :py:class:`bool`.
        """

        if not isinstance(value, bool):
            raise TypeError(f"<value> should be {bool}, {type(value)} given.")

        self._use_reputation_lookup = self.params.use_reputation_lookup = value

    def set_use_reputation_lookup(self,
                                  value: bool) -> "AvailabilityCheckerBase":
        """
        Sets the value which authorizes the usage of the reputation
        lookup.

        :param value:
            The value to set.
        """

        self.use_reputation_lookup = value

        return self

    @property
    def use_whois_db(self) -> bool:
        """
        Provides the current value of the :code:`_use_whois_db` attribute.
        """

        return self._use_whois_db

    @use_whois_db.setter
    def use_whois_db(self, value: bool) -> None:
        """
        Sets the value which authorizes the usage of the WHOIS DB.

        :param value:
            The value to set.

        :param TypeError:
            When the given :code:`use_whois_db` is not a :py:class:`bool`.
        """

        if not isinstance(value, bool):
            raise TypeError(f"<value> should be {bool}, {type(value)} given.")

        self._use_whois_db = self.params.use_whois_db = value

    def set_use_whois_db(self, value: bool) -> "AvailabilityCheckerBase":
        """
        Sets the value which authorizes the usage of the WHOIS DB.

        :param value:
            The value to set.
        """

        self.use_whois_db = value

        return self

    def subject_propagator(self) -> "CheckerBase":
        """
        Propagate the currently set subject.

        .. warning::
            You are not invited to run this method directly.
        """

        self.dns_query_tool.set_subject(self.idna_subject)
        self.whois_query_tool.set_subject(self.idna_subject)
        self.addressinfo_query_tool.set_subject(self.idna_subject)
        self.hostbyaddr_query_tool.set_subject(self.idna_subject)
        self.http_status_code_query_tool.set_subject(self.idna_subject)

        self.domain_syntax_checker.subject = self.idna_subject
        self.ip_syntax_checker.subject = self.idna_subject
        self.url_syntax_checker.subject = self.idna_subject

        self.status = AvailabilityCheckerStatus()
        self.status.params = self.params
        self.status.dns_lookup_record = self.dns_query_tool.lookup_record
        self.status.whois_lookup_record = self.whois_query_tool.lookup_record

        self.status.subject = self.subject
        self.status.idna_subject = self.idna_subject
        self.status.status = None

        self.query_syntax_checker()

        return self

    def should_we_continue_test(self, status_post_syntax_checker: str) -> bool:
        """
        Checks if we are allowed to continue a standard testing.
        """

        return bool(
            not self.status.status
            or status_post_syntax_checker == PyFunceble.storage.STATUS.invalid)

    def guess_and_set_use_extra_rules(self) -> "AvailabilityCheckerBase":
        """
        Try to guess and set the value of the :code:`use_extra_rules` attribute
        from the configuration file.
        """

        if PyFunceble.facility.ConfigLoader.is_already_loaded():
            self.use_extra_rules = PyFunceble.storage.CONFIGURATION.lookup.special
        else:
            self.use_extra_rules = self.STD_USE_EXTRA_RULES

        return self

    def guess_and_set_use_whois_lookup(self) -> "AvailabilityCheckerBase":
        """
        Try to guess and set the value of the :code:`use_whois` attribute
        from the configuration file.
        """

        if PyFunceble.facility.ConfigLoader.is_already_loaded():
            self.use_whois_lookup = PyFunceble.storage.CONFIGURATION.lookup.whois
        else:
            self.use_whois_lookup = self.STD_USE_WHOIS_LOOKUP

        return self

    def guess_and_set_dns_lookup(self) -> "AvailabilityCheckerBase":
        """
        Try to guess and set the value of the :code:`use_dns_lookup` attribute
        from the configuration file.
        """

        if PyFunceble.facility.ConfigLoader.is_already_loaded():
            self.use_dns_lookup = PyFunceble.storage.CONFIGURATION.lookup.dns
        else:
            self.use_dns_lookup = self.STD_USE_DNS_LOOKUP

        return self

    def guess_and_set_use_netinfo_lookup(self) -> "AvailabilityCheckerBase":
        """
        Try to guess and set the value of the :code:`use_netinfo_lookup` attribute
        from the configuration file.
        """

        if PyFunceble.facility.ConfigLoader.is_already_loaded():
            self.use_netinfo_lookup = PyFunceble.storage.CONFIGURATION.lookup.netinfo
        else:
            self.use_netinfo_lookup = self.STD_USE_NETINFO_LOOKUP

        return self

    def guess_and_set_use_http_code_lookup(self) -> "AvailabilityCheckerBase":
        """
        Try to guess and set the value of the :code:`use_http_code_lookup` attribute
        from the configuration file.
        """

        if PyFunceble.facility.ConfigLoader.is_already_loaded():
            self.use_http_code_lookup = (
                PyFunceble.storage.CONFIGURATION.lookup.http_status_code)
        else:
            self.use_http_code_lookup = self.STD_USE_HTTP_CODE_LOOKUP

        return self

    def guess_and_set_use_reputation_lookup(self) -> "AvailabilityCheckerBase":
        """
        Try to guess and set the value of the :code:`use_reputation_lookup` attribute
        from the configuration file.
        """

        if PyFunceble.facility.ConfigLoader.is_already_loaded():
            self.use_reputation_lookup = (
                PyFunceble.storage.CONFIGURATION.lookup.reputation)
        else:
            self.use_reputation_lookup = self.STD_USE_REPUTATION_LOOKUP

        return self

    def guess_and_set_use_whois_db(self) -> "AvailabilityCheckerBase":
        """
        Try to guess and set the value of the :code:`use_whois_db` attribute.
        """

        if PyFunceble.facility.ConfigLoader.is_already_loaded():
            self.use_whois_db = PyFunceble.storage.CONFIGURATION.cli_testing.whois_db
        else:
            self.use_whois_db = self.STD_USE_WHOIS_DB

    def guess_all_settings(
        self,
    ) -> "AvailabilityCheckerBase":  # pragma: no cover ## Method are more important
        """
        Try to guess all settings.
        """

        to_ignore = ["guess_all_settings"]

        for method in dir(self):
            if method in to_ignore or not method.startswith("guess_"):
                continue

            getattr(self, method)()

        return self

    def query_syntax_checker(self) -> "AvailabilityCheckerBase":
        """
        Queries the syntax checker.
        """

        PyFunceble.facility.Logger.info("Started to check the syntax of %r",
                                        self.status.idna_subject)

        self.status.second_level_domain_syntax = (
            self.domain_syntax_checker.is_valid_second_level())
        self.status.subdomain_syntax = self.domain_syntax_checker.is_valid_subdomain(
        )
        self.status.domain_syntax = bool(self.status.subdomain_syntax) or bool(
            self.status.second_level_domain_syntax)

        self.status.ipv4_syntax = self.ip_syntax_checker.is_valid_v4()
        self.status.ipv6_syntax = self.ip_syntax_checker.is_valid_v6()
        self.status.ipv4_range_syntax = self.ip_syntax_checker.is_valid_v4_range(
        )
        self.status.ipv6_range_syntax = self.ip_syntax_checker.is_valid_v6_range(
        )
        self.status.ip_syntax = bool(self.status.ipv4_syntax
                                     or self.status.ipv6_syntax)
        self.status.url_syntax = self.url_syntax_checker.is_valid()

        PyFunceble.facility.Logger.info("Finished to check the syntax of %r",
                                        self.status.idna_subject)

        return self

    @CheckerBase.ensure_subject_is_given
    def query_dns_record(self) -> Optional[Dict[str, Optional[List[str]]]]:
        """
        Tries to query the DNS record(s) of the given subject.
        """

        PyFunceble.facility.Logger.info(
            "Started to try to query the DNS record of %r.",
            self.status.idna_subject,
        )

        result = {}

        if self.status.subdomain_syntax:
            lookup_order = ["NS", "A", "AAAA", "CNAME", "DNAME"]
        elif self.status.domain_syntax:
            lookup_order = ["NS", "CNAME", "A", "AAAA", "DNAME"]
        elif self.status.ip_syntax:
            lookup_order = ["PTR"]
        else:
            lookup_order = []

        if lookup_order:
            for record_type in lookup_order:
                local_result = self.dns_query_tool.set_query_record_type(
                    record_type).query()

                if local_result:
                    result[record_type] = local_result

                    break

        PyFunceble.facility.Logger.debug("DNS Record:\n%r", result)

        PyFunceble.facility.Logger.info(
            "Finished to try to query the DNS record of %r",
            self.status.idna_subject,
        )

        return result

    def try_to_query_status_from_whois(self, ) -> "AvailabilityCheckerBase":
        """
        Tries to get and the status from the WHOIS record.

        .. warning::
            If the configuration is loaded, this method try to query from the
            best database first.

            If it's not found it will try to query it from the best WHOIS server
            then add it into the database (if the expiration date
            extraction is successful).

        .. note::
            The addition into the WHOIS database is only done if this method is
            running in a process with a name that does not starts with
            :code:`PyFunceble` (case sensitive).
        """

        PyFunceble.facility.Logger.info(
            "Started to try to query the status of %r from: WHOIS Lookup",
            self.status.idna_subject,
        )

        if (
                PyFunceble.facility.ConfigLoader.is_already_loaded()
                and self.use_whois_db
        ):  # pragma: no cover ## Not interesting enough to spend time on it.
            whois_object = PyFunceble.checker.utils.whois.get_whois_dataset_object(
                db_session=self.db_session)
            known_record = whois_object[self.subject]

            if known_record and not isinstance(known_record, dict):
                # Comes from DB engine.
                known_record = known_record.to_dict()

            if not known_record:
                # We assume that expired dataset are never saved into the
                # dataset.
                self.status.expiration_date = (
                    self.whois_query_tool.get_expiration_date())
                self.status.whois_record = self.whois_query_tool.lookup_record.record

                if (self.status.expiration_date and
                        not multiprocessing.current_process().name.startswith(
                            PyFunceble.storage.PROJECT_NAME.lower())):
                    whois_object.update({
                        "subject":
                        self.subject,
                        "idna_subject":
                        self.idna_subject,
                        "expiration_date":
                        self.status.expiration_date,
                        "epoch":
                        str(
                            datetime.strptime(self.status.expiration_date,
                                              "%d-%b-%Y").timestamp()),
                    })
            else:
                self.status.expiration_date = known_record["expiration_date"]
                self.status.whois_record = None
        else:
            self.status.expiration_date = self.whois_query_tool.get_expiration_date(
            )
            self.status.whois_record = self.whois_query_tool.lookup_record.record

        if self.status.expiration_date:
            self.status.status = PyFunceble.storage.STATUS.up
            self.status.status_source = "WHOIS"

            PyFunceble.facility.Logger.info(
                "Could define the status of %r from: WHOIS Lookup",
                self.status.idna_subject,
            )

        PyFunceble.facility.Logger.info(
            "Finished to try to query the status of %r from: WHOIS Lookup",
            self.status.idna_subject,
        )

        return self

    def try_to_query_status_from_dns(self) -> "AvailabilityCheckerBase":
        """
        Tries to query the status from the DNS lookup.
        """

        PyFunceble.facility.Logger.info(
            "Started to try to query the status of %r from: DNS Lookup",
            self.status.idna_subject,
        )

        lookup_result = self.query_dns_record()

        if lookup_result:
            self.status.dns_lookup = lookup_result
            self.status.status = PyFunceble.storage.STATUS.up
            self.status.status_source = "DNSLOOKUP"

            PyFunceble.facility.Logger.info(
                "Could define the status of %r from: DNS Lookup",
                self.status.idna_subject,
            )

        PyFunceble.facility.Logger.info(
            "Finished to try to query the status of %r from: DNS Lookup",
            self.status.idna_subject,
        )

        return self

    def try_to_query_status_from_netinfo(self) -> "AvailabilityCheckerBase":
        """
        Tries to query the status from the network information.
        """

        PyFunceble.facility.Logger.info(
            "Started to try to query the status of %r from: NETINFO Lookup",
            self.status.idna_subject,
        )

        if self.status.domain_syntax:
            lookup_result = self.addressinfo_query_tool.get_info()
        elif self.status.ip_syntax:
            lookup_result = self.hostbyaddr_query_tool.get_info()
        elif self.status.idna_subject.isdigit():
            lookup_result = None
        else:
            lookup_result = self.addressinfo_query_tool.get_info()

        if lookup_result:
            self.status.netinfo = lookup_result
            self.status.status = PyFunceble.storage.STATUS.up
            self.status.status_source = "NETINFO"

            PyFunceble.facility.Logger.info(
                "Could define the status of %r from: NETINFO Lookup",
                self.status.idna_subject,
            )

        PyFunceble.facility.Logger.info(
            "Finished to try to query the status of %r from: NETINFO Lookup",
            self.status.idna_subject,
        )

        return self

    def try_to_query_status_from_http_status_code(
            self) -> "AvailabilityCheckerBase":
        """
        Tries to query the status from the HTTP status code.
        """

        PyFunceble.facility.Logger.info(
            "Started to try to query the status of %r from: HTTP Status code Lookup",
            self.status.idna_subject,
        )

        if not self.status.url_syntax and not RegexHelper("[^a-z0-9._]").match(
                self.idna_subject, return_match=False):
            # The regex is there because while testing for domain, sometime we
            # may see something like mailto:[email protected]

            self.http_status_code_query_tool.set_subject(
                f"http://{self.idna_subject}:80")

        lookup_result = self.http_status_code_query_tool.get_status_code()

        if (lookup_result and lookup_result !=
                self.http_status_code_query_tool.STD_UNKNOWN_STATUS_CODE):
            self.status.http_status_code = lookup_result

            if (PyFunceble.facility.ConfigLoader.is_already_loaded()
                ):  # pragma: no cover ## Special behavior.
                dataset = PyFunceble.storage.HTTP_CODES
            else:
                dataset = PyFunceble.storage.STD_HTTP_CODES

            if (not self.status.status
                    or self.status.status == PyFunceble.storage.STATUS.down
                ) and (self.status.http_status_code in dataset.list.up
                       or self.status.http_status_code
                       in dataset.list.potentially_up):
                self.status.status = PyFunceble.storage.STATUS.up
                self.status.status_source = "HTTP CODE"

                PyFunceble.facility.Logger.info(
                    "Could define the status of %r from: HTTP Status code Lookup",
                    self.status.idna_subject,
                )
        else:
            self.status.http_status_code = None

        PyFunceble.facility.Logger.info(
            "Finished to try to query the status of %r from: HTTP Status code Lookup",
            self.status.idna_subject,
        )

        return self

    def try_to_query_status_from_syntax_lookup(
            self) -> "AvailabilityCheckerBase":
        """
        Tries to query the status from the syntax.
        """

        PyFunceble.facility.Logger.info(
            "Started to try to query the status of %r from: Syntax Lookup",
            self.status.idna_subject,
        )

        if (not self.status.domain_syntax and not self.status.ip_syntax
                and not self.status.url_syntax):
            self.status.status = PyFunceble.storage.STATUS.invalid
            self.status.status_source = "SYNTAX"

            PyFunceble.facility.Logger.info(
                "Could define the status of %r from: Syntax Lookup",
                self.status.idna_subject,
            )

        PyFunceble.facility.Logger.info(
            "Finished to try to query the status of %r from: Syntax Lookup",
            self.status.idna_subject,
        )

        return self

    def try_to_query_status_from_reputation(self) -> "AvailabilityCheckerBase":
        """
        Tries to query the status from the reputation lookup.
        """

        raise NotImplementedError()

    def try_to_query_status_from_collection(self) -> "AvailabilityCheckerBase":
        """
        Tries to get and set the status from the Collection API.
        """

        PyFunceble.facility.Logger.info(
            "Started to try to query the status of %r from: Collection Lookup",
            self.status.idna_subject,
        )

        data = self.collection_query_tool.pull(self.idna_subject)

        if data and "status" in data:
            if (self.collection_query_tool.preferred_status_origin
                    == "frequent"
                    and data["status"]["availability"]["frequent"]):
                self.status.status = data["status"]["availability"]["frequent"]
                self.status.status_source = "COLLECTION"
            elif (self.collection_query_tool.preferred_status_origin
                  == "latest" and data["status"]["availability"]["latest"]):
                self.status.status = data["status"]["availability"]["latest"][
                    "status"]
                self.status.status_source = "COLLECTION"
            elif (self.collection_query_tool.preferred_status_origin
                  == "recommended"
                  and data["status"]["availability"]["recommended"]):
                self.status.status = data["status"]["availability"][
                    "recommended"]
                self.status.status_source = "COLLECTION"

            PyFunceble.facility.Logger.info(
                "Could define the status of %r from: Collection Lookup",
                self.status.idna_subject,
            )

        PyFunceble.facility.Logger.info(
            "Finished to try to query the status of %r from: Collection Lookup",
            self.status.idna_subject,
        )

    @CheckerBase.ensure_subject_is_given
    @CheckerBase.update_status_date_after_query
    def query_status(self) -> "AvailabilityCheckerBase":
        """
        Queries the status and for for more action.
        """

        raise NotImplementedError()

    # pylint: disable=useless-super-delegation
    def get_status(self) -> Optional[AvailabilityCheckerStatus]:
        return super().get_status()
コード例 #12
0
ファイル: base.py プロジェクト: spirillen/PyFunceble
    def __init__(
        self,
        subject: Optional[str] = None,
        *,
        use_extra_rules: Optional[bool] = None,
        use_whois_lookup: Optional[bool] = None,
        use_dns_lookup: Optional[bool] = None,
        use_netinfo_lookup: Optional[bool] = None,
        use_http_code_lookup: Optional[bool] = None,
        use_reputation_lookup: Optional[bool] = None,
        do_syntax_check_first: Optional[bool] = None,
        db_session: Optional[Session] = None,
        use_whois_db: Optional[bool] = None,
        use_collection: Optional[bool] = None,
    ) -> None:
        self.dns_query_tool = DNSQueryTool()
        self.whois_query_tool = WhoisQueryTool()
        self.addressinfo_query_tool = AddressInfo()
        self.hostbyaddr_query_tool = HostByAddrInfo()
        self.http_status_code_query_tool = HTTPStatusCode()
        self.domain_syntax_checker = DomainSyntaxChecker()
        self.ip_syntax_checker = IPSyntaxChecker()
        self.url_syntax_checker = URLSyntaxChecker()
        self.extra_rules_handler = ExtraRulesHandler()
        self.db_session = db_session

        self.params = AvailabilityCheckerParams()

        self.status = AvailabilityCheckerStatus()
        self.status.params = self.params
        self.status.dns_lookup_record = self.dns_query_tool.lookup_record
        self.status.whois_lookup_record = self.whois_query_tool.lookup_record

        if use_extra_rules is not None:
            self.use_extra_rules = use_extra_rules
        else:
            self.guess_and_set_use_extra_rules()

        if use_whois_lookup is not None:
            self.use_whois_lookup = use_whois_lookup
        else:
            self.guess_and_set_use_whois_lookup()

        if use_dns_lookup is not None:
            self.use_dns_lookup = use_dns_lookup
        else:
            self.guess_and_set_dns_lookup()

        if use_netinfo_lookup is not None:
            self.use_netinfo_lookup = use_netinfo_lookup
        else:
            self.guess_and_set_use_netinfo_lookup()

        if use_http_code_lookup is not None:
            self.use_http_code_lookup = use_http_code_lookup
        else:
            self.guess_and_set_use_http_code_lookup()

        if use_reputation_lookup is not None:
            self.use_reputation_lookup = use_reputation_lookup
        else:
            self.guess_and_set_use_reputation_lookup()

        if use_whois_db is not None:
            self.use_whois_db = use_whois_db
        else:
            self.guess_and_set_use_whois_db()

        super().__init__(
            subject,
            do_syntax_check_first=do_syntax_check_first,
            db_session=db_session,
            use_collection=use_collection,
        )