Source code for duck.utils.net

"""
Network helper utilily tools.
"""
import re
import socket
import ipaddress

from typing import Tuple


# Pre-compile regex and constants for speed
HOSTNAME_LABEL_RE = re.compile(r"^(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
MAX_HOSTNAME_LENGTH = 253


[docs] def is_ipv6(ip_address: str) -> bool: """ Check if the provided IP address is a valid IPv6 address. """ try: socket.inet_pton(socket.AF_INET6, ip_address) return True except socket.error: return False
[docs] def is_ipv4(ip_address: str) -> bool: """ Check if the provided IP address is a valid IPv4 address. """ try: socket.inet_pton(socket.AF_INET, ip_address) return True except socket.error: return False
[docs] def is_domain(name) -> bool: """ Check if the provided name is a valid domain name. """ return all( [len(part) <= 63 and part.isalnum() for part in name.split(".")])
[docs] def is_valid_host(host) -> Tuple[bool, str]: """ Super-fast validation of hostname or IP address, optionally with a port. Returns a tuple (is_valid, message). """ if not host: return False, "Hostname is empty" # Fast split for port (IPv4/hostname:port case) if ':' in host and host.count(':') == 1: h, port = host.split(':', 1) if not port.isdigit() or not (0 < int(port) < 65536): return False, f"Invalid port number '{port}'. Port must be an integer between 1 and 65535." host = h # continue validating host only elif ':' in host: # IPv6 with port, e.g., [::1]:8000 if host.startswith('[') and ']' in host: i = host.find(']') addr = host[1:i] port = host[i+2:] if host[i+1:i+2] == ':' else '' try: ip = ipaddress.ip_address(addr) except Exception: return False, "Malformed IPv6 address." if not port.isdigit() or not (0 < int(port) < 65536): return False, f"Invalid port '{port}' on IPv6 address." return True, "Valid IPv6 address with port." # else: fall through to next checks # Try IP address (IPv4 or IPv6) try: ip = ipaddress.ip_address(host.strip("[]")) return True, f"Valid IP address (IPv{ip.version})." except Exception: pass # Hostname validation if len(host) > MAX_HOSTNAME_LENGTH: return False, f"Hostname exceeds the maximum length of {MAX_HOSTNAME_LENGTH} characters." labels = host.split(".") if any(not label for label in labels): return False, "Hostname contains empty labels (e.g., consecutive dots)." for label in labels: if not HOSTNAME_LABEL_RE.match(label): return False, f"Invalid label '{label}' in hostname." return True, "Valid hostname."