import ipaddress from urllib.parse import urlsplit import dns.exception import dns.resolver class AddressResolutionError(Exception): pass def is_public_url(url: str) -> bool: """ Check whether a URL resolves exclusively to public IP addresses. :param url: URL whose hostname should be resolved. :return: Whether all resolved addresses are globally routable. :raises ValueError: If the URL does not contain a hostname. :raises AddressResolutionError: If the hostname cannot be resolved. """ hostname = urlsplit(url).hostname if hostname is None: raise ValueError("No hostname was specified") try: answers = dns.resolver.resolve_name(hostname) except dns.exception.DNSException as exc: raise AddressResolutionError( f"Could not resolve hostname {hostname!r}" ) from exc else: for address in answers.addresses(): if not ipaddress.ip_address(address).is_global: return False return True def is_http_url(url: str) -> bool: """ Check whether a URL is an absolute HTTP or HTTPS URL. :param url: URL to validate. :return: Whether the URL uses HTTP or HTTPS and contains a hostname. """ try: parsed = urlsplit(url) except ValueError: return False return parsed.scheme.lower() in {"http", "https"} and parsed.hostname is not None