diff --git a/cp/bootstrap/helper.php b/cp/bootstrap/helper.php index c98b584..ec94190 100644 --- a/cp/bootstrap/helper.php +++ b/cp/bootstrap/helper.php @@ -211,89 +211,89 @@ function validate_identifier($identifier) { } } -function isDomainValid(string $domain): bool { - // Ensure the domain only contains valid characters (letters, numbers, hyphens, and dots) - if (!preg_match('/^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/', $domain)) { - return false; - } - - // Split the domain into its labels (subdomains, SLD, etc.) - $labels = explode('.', $domain); - foreach ($labels as $label) { - if (strlen($label) > 63 || empty($label)) { // Labels cannot be empty or exceed 63 chars - return false; - } - } - return true; -} - -function validate_label($label, $db) { - if (!$label) { +function validate_label($domain, $db) { + if (!$domain) { return 'You must enter a domain name'; } - // Ensure input contains only valid domain characters - if (!preg_match('/^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/', $label)) { - return 'Invalid domain name format: only letters, numbers, dots, and hyphens allowed'; + // Ensure domain has at least one dot (.) separating labels + if (strpos($domain, '.') === false) { + return 'Invalid domain name format: must contain at least one dot (.)'; } - if (!isDomainValid($label)) { - return 'Domain label is too long (exceeds 63 characters)'; - } + // Split domain into labels (subdomains, SLD, TLD) + $labels = explode('.', $domain); + foreach ($labels as $label) { + $len = strlen($label); + + // Labels cannot be empty, shorter than 2, or longer than 63 characters + if ($len < 2 || $len > 63) { + return 'Each domain label must be between 2 and 63 characters'; + } - // Ensure domain starts and ends with an alphanumeric character - $parts = explode('.', $label); - foreach ($parts as $part) { - if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$/', $part)) { - return 'Each domain label must start and end with an alphanumeric character'; + if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$/', $label)) { + return 'Each domain label must start and end with a letter or number and contain only letters, numbers, or hyphens'; + } + + // Check if it's a Punycode label (IDN) + if (strpos($label, 'xn--') === 0) { + // Ensure valid Punycode structure + if (!preg_match('/^xn--[a-zA-Z0-9-]+$/', $label)) { + return 'Invalid Punycode format'; + } + + // Convert Punycode to UTF-8 + $decoded = idn_to_utf8($label, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + if ($decoded === false || $decoded === '') { + return 'Invalid Punycode conversion'; + } + + // Ensure decoded label follows normal domain rules + if (!preg_match('/^[\p{L}0-9][\p{L}0-9-]*[\p{L}0-9]$/u', $decoded)) { + return 'IDN must start and end with a letter or number'; + } + } else { + // Prevent consecutive or invalid hyphen usage + if (preg_match('/--|\.\./', $label)) { + return 'Domain labels cannot contain consecutive dashes (--) or dots (..)'; + } } } // Extract domain and TLD - $parts = extractDomainAndTLD($label); + $parts = extractDomainAndTLD($domain); if (!$parts || empty($parts['domain']) || empty($parts['tld'])) { return 'Invalid domain structure, unable to parse domain name'; } $tld = "." . $parts['tld']; - if (strlen($parts['domain']) > 63) { - return 'Total length of your domain must be less than 63 characters'; - } - - if (strlen($parts['domain']) < 2) { - return 'Total length of your domain must be greater than 2 characters'; - } - - if (strpos($label, '.') === false) { - return 'Invalid domain name format, must contain at least one dot (.)'; - } - - // Ensure no invalid use of hyphens (double dashes, leading/trailing dashes) - if (strpos($parts['domain'], 'xn--') === false && preg_match("/(^-|^\.|-\.|\.-|--|\.\.|-$|\.$)/", $parts['domain'])) { - return 'Domain name cannot contain consecutive dashes (--) unless it is a punycode domain'; + // Validate domain length + $domainLength = strlen($parts['domain']); + if ($domainLength < 2 || $domainLength > 63) { + return 'Domain length must be between 2 and 63 characters'; } // Check if the TLD exists in the domain_tld table - $tldExists = $db->select('SELECT COUNT(*) FROM domain_tld WHERE tld = ?', [$tld]); + $tldExists = $db->selectValue('SELECT COUNT(*) FROM domain_tld WHERE tld = ?', [$tld]); - if ($tldExists[0]["COUNT(*)"] == 0) { + if (!$tldExists) { return 'Zone is not supported'; } - // If domain is an IDN (Punycode), convert it to Unicode + // IDN-specific validation (only if the domain contains Punycode) if (strpos($parts['domain'], 'xn--') === 0) { $label = idn_to_utf8($parts['domain'], IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); - // Fetch the IDN regex for the given TLD (only if it's an IDN) - $idnRegex = $db->selectRow('SELECT idn_table FROM domain_tld WHERE tld = ?', [$tld]); + // Fetch the IDN regex for the given TLD + $idnRegex = $db->selectValue('SELECT idn_table FROM domain_tld WHERE tld = ?', [$tld]); if (!$idnRegex) { return 'Failed to fetch domain IDN table'; } - // Check for invalid characters using fetched regex - if (!preg_match($idnRegex['idn_table'], $label)) { + // Check against IDN regex + if (!preg_match($idnRegex, $label)) { return 'Invalid domain name format, please review registry policy about accepted labels'; } } diff --git a/epp/src/helpers.php b/epp/src/helpers.php index 6374d81..de37358 100644 --- a/epp/src/helpers.php +++ b/epp/src/helpers.php @@ -203,7 +203,7 @@ function validate_label($domain, $pdo) { } if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$/', $label)) { - return 'Each domain label must start and end with an alphanumeric character'; + return 'Each domain label must start and end with a letter or number and contain only letters, numbers, or hyphens'; } // Check if it's a Punycode label (IDN)