diff --git a/automation/write-zone.php b/automation/write-zone.php index 3154160..4c85270 100644 --- a/automation/write-zone.php +++ b/automation/write-zone.php @@ -42,19 +42,19 @@ Coroutine::create(function () use ($pool, $log, $c) { while (list($id, $tld) = $sth->fetch(PDO::FETCH_NUM)) { $tldRE = preg_quote($tld, '/'); $cleanedTld = ltrim(strtolower($tld), '.'); - $zone = new Zone('.'); + $zone = new Zone($cleanedTld.'.'); $zone->setDefaultTtl(3600); $soa = new ResourceRecord; - $soa->setName($cleanedTld . '.'); + $soa->setName('@'); $soa->setClass(Classes::INTERNET); $soa->setRdata(Factory::Soa( $c['ns']['ns1'] . '.', $c['dns_soa'] . '.', $timestamp, - 3600, - 14400, - 604800, + 900, + 1800, + 3600000, 3600 )); $zone->addResourceRecord($soa); @@ -91,7 +91,7 @@ Coroutine::create(function () use ($pool, $log, $c) { while (list($did, $dname) = $sthDomains->fetch(PDO::FETCH_NUM)) { if (isset($statuses[$did])) continue; - $dname_clean = trim($dname, "$tldRE."); + $dname_clean = $dname; $dname_clean = ($dname_clean == "$tld.") ? '@' : $dname_clean; // NS records for the domain @@ -109,7 +109,7 @@ Coroutine::create(function () use ($pool, $log, $c) { $sthHostRecords = $pdo->prepare("SELECT host.name, host_addr.ip, host_addr.addr FROM host INNER JOIN host_addr ON host.id = host_addr.host_id WHERE host.domain_id = :did ORDER BY host.name"); $sthHostRecords->execute([':did' => $did]); while (list($hname, $type, $addr) = $sthHostRecords->fetch(PDO::FETCH_NUM)) { - $hname_clean = trim($hname, "$tldRE."); + $hname_clean = $hname; $hname_clean = ($hname_clean == "$tld.") ? '@' : $hname_clean; $record = new ResourceRecord; $record->setName($hname_clean . '.'); diff --git a/cp/app/Controllers/ApplicationsController.php b/cp/app/Controllers/ApplicationsController.php index e8e11d5..40eb3a0 100644 --- a/cp/app/Controllers/ApplicationsController.php +++ b/cp/app/Controllers/ApplicationsController.php @@ -23,7 +23,7 @@ class ApplicationsController extends Controller return $response->withHeader('Location', '/domains')->withStatus(302); } } - + public function createApplication(Request $request, Response $response) { if ($request->getMethod() === 'POST') { @@ -31,6 +31,16 @@ class ApplicationsController extends Controller $data = $request->getParsedBody(); $db = $this->container->get('db'); $domainName = $data['domainName'] ?? null; + // Convert to Punycode if the domain is not in ASCII + if (!mb_detect_encoding($domainName, 'ASCII', true)) { + $convertedDomain = idn_to_ascii($domainName, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + if ($convertedDomain === false) { + $this->container->get('flash')->addMessage('error', 'Application name conversion to Punycode failed'); + return $response->withHeader('Location', '/application/create')->withStatus(302); + } else { + $domainName = $convertedDomain; + } + } $registrar_id = $data['registrar'] ?? null; $registrars = $db->select("SELECT id, clid, name FROM registrar"); if ($_SESSION["auth_roles"] != 0) { diff --git a/cp/app/Controllers/DomainsController.php b/cp/app/Controllers/DomainsController.php index cb50d87..bbb2cb8 100644 --- a/cp/app/Controllers/DomainsController.php +++ b/cp/app/Controllers/DomainsController.php @@ -27,6 +27,17 @@ class DomainsController extends Controller $claims = $data['claims'] ?? null; if ($domainName) { + // Convert to Punycode if the domain is not in ASCII + if (!mb_detect_encoding($domainName, 'ASCII', true)) { + $convertedDomain = idn_to_ascii($domainName, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + if ($convertedDomain === false) { + $this->container->get('flash')->addMessage('error', 'Domain conversion to Punycode failed'); + return $response->withHeader('Location', '/domain/check')->withStatus(302); + } else { + $domainName = $convertedDomain; + } + } + $domainName = preg_replace('/[^\p{L}0-9-.]/u', '', $domainName); $parts = extractDomainAndTLD($domainName); @@ -105,6 +116,16 @@ class DomainsController extends Controller $data = $request->getParsedBody(); $db = $this->container->get('db'); $domainName = $data['domainName'] ?? null; + // Convert to Punycode if the domain is not in ASCII + if (!mb_detect_encoding($domainName, 'ASCII', true)) { + $convertedDomain = idn_to_ascii($domainName, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + if ($convertedDomain === false) { + $this->container->get('flash')->addMessage('error', 'Domain conversion to Punycode failed'); + return $response->withHeader('Location', '/domain/create')->withStatus(302); + } else { + $domainName = $convertedDomain; + } + } $registrar_id = $data['registrar'] ?? null; $registrars = $db->select("SELECT id, clid, name FROM registrar"); if ($_SESSION["auth_roles"] != 0) { @@ -2229,6 +2250,16 @@ class DomainsController extends Controller $data = $request->getParsedBody(); $db = $this->container->get('db'); $domainName = $data['domainName'] ?? null; + // Convert to Punycode if the domain is not in ASCII + if (!mb_detect_encoding($domainName, 'ASCII', true)) { + $convertedDomain = idn_to_ascii($domainName, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + if ($convertedDomain === false) { + $this->container->get('flash')->addMessage('error', 'Domain conversion to Punycode failed'); + return $response->withHeader('Location', '/transfers')->withStatus(302); + } else { + $domainName = $convertedDomain; + } + } $registrar_id = $data['registrar'] ?? null; $authInfo = $data['authInfo'] ?? null; $transferYears = $data['transferYears'] ?? null; diff --git a/cp/app/Controllers/HostsController.php b/cp/app/Controllers/HostsController.php index 8c54cc7..b13362e 100644 --- a/cp/app/Controllers/HostsController.php +++ b/cp/app/Controllers/HostsController.php @@ -34,6 +34,17 @@ class HostsController extends Controller if ($hostName) { $hostModel = new Host($this->container->get('db')); + // Convert to Punycode if the host is not in ASCII + if (!mb_detect_encoding($hostName, 'ASCII', true)) { + $convertedDomain = idn_to_ascii($hostName, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + if ($convertedDomain === false) { + $this->container->get('flash')->addMessage('error', 'Host conversion to Punycode failed'); + return $response->withHeader('Location', '/host/create')->withStatus(302); + } else { + $hostName = $convertedDomain; + } + } + if (preg_match('/^([A-Z0-9]([A-Z0-9-]{0,61}[A-Z0-9]){0,1}\.){1,125}[A-Z0-9]([A-Z0-9-]{0,61}[A-Z0-9])$/i', $hostName) && strlen($hostName) < 254) { $host_id_already_exist = $hostModel->getHostByNom($hostName); if ($host_id_already_exist) { @@ -235,14 +246,14 @@ class HostsController extends Controller function isValidHostname($hostname) { $hostname = trim($hostname); - + // Check for IDN and convert to ASCII if necessary if (mb_detect_encoding($hostname, 'ASCII', true) === false) { - $hostname = idn_to_ascii($hostname, 0, INTL_IDNA_VARIANT_UTS46); + $hostname = idn_to_ascii($hostname, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); } - // Regular expression for validating a hostname (simplified version) - $pattern = '/^([a-zA-Z0-9-]{1,63}\.){1,}[a-zA-Z]{2,63}$/'; + // Regular expression for validating a hostname + $pattern = '/^((xn--[a-zA-Z0-9-]{1,63}|[a-zA-Z0-9-]{1,63})\.){2,}(xn--[a-zA-Z0-9-]{2,63}|[a-zA-Z]{2,63})$/'; return preg_match($pattern, $hostname); } @@ -306,14 +317,14 @@ class HostsController extends Controller function isValidHostname($hostname) { $hostname = trim($hostname); - + // Check for IDN and convert to ASCII if necessary if (mb_detect_encoding($hostname, 'ASCII', true) === false) { - $hostname = idn_to_ascii($hostname, 0, INTL_IDNA_VARIANT_UTS46); + $hostname = idn_to_ascii($hostname, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); } - // Regular expression for validating a hostname (simplified version) - $pattern = '/^([a-zA-Z0-9-]{1,63}\.){1,}[a-zA-Z]{2,63}$/'; + // Regular expression for validating a hostname + $pattern = '/^((xn--[a-zA-Z0-9-]{1,63}|[a-zA-Z0-9-]{1,63})\.){2,}(xn--[a-zA-Z0-9-]{2,63}|[a-zA-Z]{2,63})$/'; return preg_match($pattern, $hostname); } diff --git a/cp/app/Controllers/SystemController.php b/cp/app/Controllers/SystemController.php index 3b4cd4e..ba84e54 100644 --- a/cp/app/Controllers/SystemController.php +++ b/cp/app/Controllers/SystemController.php @@ -345,7 +345,7 @@ class SystemController extends Controller return $response->withHeader('Location', '/registry/tld/create')->withStatus(302); } - switch ($data['extension']) { + switch ($data['script']) { case 'ascii': $idntable = '/^(?!-)(?!.*--)[A-Z0-9-]{1,63}(?beginTransaction(); - + $currentDateTime = new \DateTime(); $crdate = $currentDateTime->format('Y-m-d H:i:s.v'); // Current timestamp + // Convert to Punycode if the domain is not in ASCII + if (!mb_detect_encoding($data['extension'], 'ASCII', true)) { + $data['extension'] = str_replace('.', '', $data['extension']); + $convertedDomain = idn_to_ascii($data['extension'], IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + if ($convertedDomain === false) { + $this->container->get('flash')->addMessage('error', 'TLD conversion to Punycode failed'); + return $response->withHeader('Location', '/registry/tld/create')->withStatus(302); + } else { + $data['extension'] = '.' . $convertedDomain; + } + } + $db->insert('domain_tld', [ 'tld' => $data['extension'], 'idn_table' => $idntable, @@ -536,11 +548,11 @@ class SystemController extends Controller if ($args) { $args = trim($args); - if (!preg_match('/^\.[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)?[^\.]$/', $args)) { + if (!preg_match('/^\.(xn--[a-zA-Z0-9-]+|[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)?)$/', $args)) { $this->container->get('flash')->addMessage('error', 'Invalid TLD format'); return $response->withHeader('Location', '/registry/tlds')->withStatus(302); } - + $validators = [ 'extension' => v::stringType()->notEmpty()->length(3, 64), 'createm0' => v::numericVal()->between(0.00, 9999999.99, true), @@ -827,7 +839,7 @@ class SystemController extends Controller if ($args) { $args = trim($args); - if (!preg_match('/^\.[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)?[^\.]$/', $args)) { + if (!preg_match('/^\.(xn--[a-zA-Z0-9-]+|[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)?)$/', $args)) { $this->container->get('flash')->addMessage('error', 'Invalid TLD format'); return $response->withHeader('Location', '/registry/tlds')->withStatus(302); } diff --git a/cp/bootstrap/helper.php b/cp/bootstrap/helper.php index 6078694..46e53f5 100644 --- a/cp/bootstrap/helper.php +++ b/cp/bootstrap/helper.php @@ -232,6 +232,10 @@ function validate_label($label, $db) { return 'Failed to fetch domain IDN table'; } + if (strpos($parts['domain'], 'xn--') === 0) { + $label = idn_to_utf8($parts['domain'], IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + } + // Check for invalid characters using fetched regex if (!preg_match($idnRegex['idn_table'], $label)) { return 'Invalid domain name format, please review registry policy about accepted labels'; diff --git a/cp/resources/views/partials/footer.twig b/cp/resources/views/partials/footer.twig index 13cd25b..c36737e 100644 --- a/cp/resources/views/partials/footer.twig +++ b/cp/resources/views/partials/footer.twig @@ -14,7 +14,7 @@ Namingo
  • - v1.0.0-beta4 + v1.0.0-beta5
  • diff --git a/cp/resources/views/partials/js-applications.twig b/cp/resources/views/partials/js-applications.twig index a083daa..0bae1e3 100644 --- a/cp/resources/views/partials/js-applications.twig +++ b/cp/resources/views/partials/js-applications.twig @@ -21,10 +21,10 @@ var isInvalid = rowData.application_status.some(statusObj => statusObj.status && statusObj.status.includes('invalid')); if (!isRejected && !isInvalid) { - actionButtons += ` `; - actionButtons += ` `; - actionButtons += ` `; - actionButtons += ``; + actionButtons += ` `; + actionButtons += ` `; + actionButtons += ` `; + actionButtons += ``; } else { actionButtons += ` Completed`; } diff --git a/cp/resources/views/partials/js-contacts.twig b/cp/resources/views/partials/js-contacts.twig index 97c6b58..a77fada 100644 --- a/cp/resources/views/partials/js-contacts.twig +++ b/cp/resources/views/partials/js-contacts.twig @@ -16,8 +16,8 @@ function actionsFormatter(cell, formatterParams, onRendered) { return ` - - + + `; } diff --git a/cp/resources/views/partials/js-domains.twig b/cp/resources/views/partials/js-domains.twig index 7b14c3f..c6653b9 100644 --- a/cp/resources/views/partials/js-domains.twig +++ b/cp/resources/views/partials/js-domains.twig @@ -21,15 +21,15 @@ var hasPendingRestore = rowData.rgpstatus ? rowData.rgpstatus.includes('pendingRestore') : false; // Common action button for all statuses - actionButtons += ` `; + actionButtons += ` `; if (hasPendingRestore) { actionButtons += ``; } else if (hasPendingDelete) { actionButtons += ``; } else { - actionButtons += ` `; - actionButtons += ``; + actionButtons += ` `; + actionButtons += ``; } return actionButtons; diff --git a/cp/resources/views/partials/js-hosts.twig b/cp/resources/views/partials/js-hosts.twig index 6eea534..9f52f1d 100644 --- a/cp/resources/views/partials/js-hosts.twig +++ b/cp/resources/views/partials/js-hosts.twig @@ -16,8 +16,8 @@ function actionsFormatter(cell, formatterParams, onRendered) { return ` - - + + `; } diff --git a/cp/resources/views/partials/js-transfers.twig b/cp/resources/views/partials/js-transfers.twig index 4f35058..21a1f36 100644 --- a/cp/resources/views/partials/js-transfers.twig +++ b/cp/resources/views/partials/js-transfers.twig @@ -21,14 +21,14 @@ var clidValue = document.getElementById('clid').value; if (hasPendingStatus && clidValue === '0') { - actionButtons += ` `; - actionButtons += ` `; - actionButtons += ``; + actionButtons += ` `; + actionButtons += ` `; + actionButtons += ``; } else if (clidValue === rowData.reid) { - actionButtons += ` `; + actionButtons += ` `; } else if (clidValue === rowData.acid) { - actionButtons += ` `; - actionButtons += ``; + actionButtons += ` `; + actionButtons += ``; } else { actionButtons += ` {{ __('Completed') }}`; } diff --git a/das/start_das.php b/das/start_das.php index 9a6ded3..3f63527 100644 --- a/das/start_das.php +++ b/das/start_das.php @@ -67,11 +67,21 @@ $server->on('receive', function ($server, $fd, $reactorId, $data) use ($c, $pool $server->send($fd, "domain name is too long"); $server->close($fd); } - $domain = strtoupper($domain); - if (preg_match("/(^-|^\.|-\.|\.-|--|\.\.|-$|\.$)/", $domain)) { + // Convert to Punycode if the domain is not in ASCII + if (!mb_detect_encoding($domain, 'ASCII', true)) { + $convertedDomain = idn_to_ascii($domain, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + if ($convertedDomain === false) { + $server->send($fd, "Domain conversion to Punycode failed"); + $server->close($fd); + } else { + $domain = $convertedDomain; + } + } + if (!preg_match('/^(?:(xn--[a-zA-Z0-9-]{1,63}|[a-zA-Z0-9-]{1,63})\.){1,3}(xn--[a-zA-Z0-9-]{2,63}|[a-zA-Z]{2,63})$/', $domain)) { $server->send($fd, "domain name invalid format"); $server->close($fd); } + $domain = strtoupper($domain); // Extract TLD from the domain and prepend a dot $parts = explode('.', $domain); @@ -113,8 +123,13 @@ $server->on('receive', function ($server, $fd, $reactorId, $data) use ($c, $pool } // Check for invalid characters using fetched regex - if (!preg_match($idnRegex, $domain)) { - $server->send($fd, "Domain name invalid format"); + if (strpos(strtolower($parts[0]), 'xn--') === 0) { + $label = idn_to_utf8(strtolower($parts[0]), IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + } else { + $label = strtolower($parts[0]); + } + if (!preg_match($idnRegex, $label)) { + $server->send($fd, "Domain name invalid IDN characters"); $server->close($fd); return; } diff --git a/epp/src/helpers.php b/epp/src/helpers.php index 562a803..3c4a879 100644 --- a/epp/src/helpers.php +++ b/epp/src/helpers.php @@ -195,7 +195,7 @@ function validate_label($label, $pdo) { if (strlen($label) < 2) { return 'Total lenght of your domain must be greater then 2 characters'; } - if (preg_match("/(^-|^\.|-\.|\.-|--|\.\.|-$|\.$)/", $label)) { + if (strpos($label, 'xn--') === false && preg_match("/(^-|^\.|-\.|\.-|--|\.\.|-$|\.$)/", $label)) { return 'Invalid domain name format, cannot begin or end with a hyphen (-)'; } @@ -223,6 +223,10 @@ function validate_label($label, $pdo) { return 'Failed to fetch domain IDN table'; } + if (strpos($parts['domain'], 'xn--') === 0) { + $label = idn_to_utf8($parts['domain'], IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + } + // Check for invalid characters using fetched regex if (!preg_match($idnRegex, $label)) { $server->send($fd, "Domain name invalid format"); @@ -247,7 +251,7 @@ function extractDomainAndTLD($urlString) { // Parse the URL to get the host $parts = parse_url($urlString); $host = $parts['host'] ?? $urlString; - + // Sort test TLDs by length (longest first) to match the longest possible TLD usort($testTlds, function ($a, $b) { return strlen($b) - strlen($a); diff --git a/rdap/start_rdap.php b/rdap/start_rdap.php index 119885d..f71fcee 100644 --- a/rdap/start_rdap.php +++ b/rdap/start_rdap.php @@ -173,7 +173,7 @@ $http->start(); function handleDomainQuery($request, $response, $pdo, $domainName, $c, $log) { // Extract and validate the domain name from the request $domain = trim($domainName); - + // Empty domain check if (!$domain) { $response->header('Content-Type', 'application/json'); @@ -189,9 +189,22 @@ function handleDomainQuery($request, $response, $pdo, $domainName, $c, $log) { $response->end(json_encode(['error' => 'Domain name is too long'])); return; } - + + // Convert to Punycode if the domain is not in ASCII + if (!mb_detect_encoding($domain, 'ASCII', true)) { + $convertedDomain = idn_to_ascii($domain, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + if ($convertedDomain === false) { + $response->header('Content-Type', 'application/json'); + $response->status(400); // Bad Request + $response->end(json_encode(['error' => 'Domain conversion to Punycode failed'])); + return; + } else { + $domain = $convertedDomain; + } + } + // Check for prohibited patterns in domain names - if (preg_match("/(^-|^\.|-\.|\.-|--|\.\.|-$|\.$)/", $domain)) { + if (!preg_match('/^(?:(xn--[a-zA-Z0-9-]{1,63}|[a-zA-Z0-9-]{1,63})\.){1,3}(xn--[a-zA-Z0-9-]{2,63}|[a-zA-Z]{2,63})$/', $domain)) { $response->header('Content-Type', 'application/json'); $response->status(400); // Bad Request $response->end(json_encode(['error' => 'Domain name invalid format'])); @@ -241,10 +254,15 @@ function handleDomainQuery($request, $response, $pdo, $domainName, $c, $log) { } // Check for invalid characters using fetched regex - if (!preg_match($idnRegex, $domain)) { + if (strpos($parts[0], 'xn--') === 0) { + $label = idn_to_utf8($parts[0], IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + } else { + $label = $parts[0]; + } + if (!preg_match($idnRegex, $label)) { $response->header('Content-Type', 'application/json'); $response->status(400); // Bad Request - $response->end(json_encode(['error' => 'Domain name invalid format'])); + $response->end(json_encode(['error' => 'Domain name invalid IDN characters'])); return; } @@ -1018,9 +1036,22 @@ function handleNameserverQuery($request, $response, $pdo, $nameserverHandle, $c, $response->end(json_encode(['error' => 'Nameserver is too long'])); return; } + + // Convert to Punycode if the host is not in ASCII + if (!mb_detect_encoding($ns, 'ASCII', true)) { + $convertedDomain = idn_to_ascii($ns, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + if ($convertedDomain === false) { + $response->header('Content-Type', 'application/json'); + $response->status(400); // Bad Request + $response->end(json_encode(['error' => 'Host conversion to Punycode failed'])); + return; + } else { + $ns = $convertedDomain; + } + } // Check for prohibited patterns in nameserver - if (!preg_match("/^(?!-)[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/", $ns)) { + if (!preg_match('/^((xn--[a-zA-Z0-9-]{1,63}|[a-zA-Z0-9-]{1,63})\.){2,}(xn--[a-zA-Z0-9-]{2,63}|[a-zA-Z]{2,63})$/', $ns)) { $response->header('Content-Type', 'application/json'); $response->status(400); // Bad Request $response->end(json_encode(['error' => 'Nameserver invalid format'])); @@ -1420,8 +1451,21 @@ function handleDomainSearchQuery($request, $response, $pdo, $searchPattern, $c, return; } + // Convert to Punycode if the domain is not in ASCII + if (!mb_detect_encoding($domain, 'ASCII', true)) { + $convertedDomain = idn_to_ascii($domain, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + if ($convertedDomain === false) { + $response->header('Content-Type', 'application/json'); + $response->status(400); // Bad Request + $response->end(json_encode(['error' => 'Domain conversion to Punycode failed'])); + return; + } else { + $domain = $convertedDomain; + } + } + // Check for prohibited patterns in domain names - if (preg_match("/(^-|^\.|-\.|\.-|--|\.\.|-$|\.$)/", $domain)) { + if (!preg_match('/^(?:(xn--[a-zA-Z0-9-]{1,63}|[a-zA-Z0-9-]{1,63})\.){1,3}(xn--[a-zA-Z0-9-]{2,63}|[a-zA-Z]{2,63})$/', $domain)) { $response->header('Content-Type', 'application/json'); $response->status(400); // Bad Request $response->end(json_encode(['error' => 'Domain name invalid format'])); @@ -1471,10 +1515,15 @@ function handleDomainSearchQuery($request, $response, $pdo, $searchPattern, $c, } // Check for invalid characters using fetched regex - if (!preg_match($idnRegex, $domain)) { + if (strpos($parts[0], 'xn--') === 0) { + $label = idn_to_utf8($parts[0], IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + } else { + $label = $parts[0]; + } + if (!preg_match($idnRegex, $label)) { $response->header('Content-Type', 'application/json'); $response->status(400); // Bad Request - $response->end(json_encode(['error' => 'Domain name invalid format'])); + $response->end(json_encode(['error' => 'Domain name invalid IDN characters'])); return; } @@ -1928,8 +1977,21 @@ function handleNameserverSearchQuery($request, $response, $pdo, $searchPattern, return; } + // Convert to Punycode if the host is not in ASCII + if (!mb_detect_encoding($ns, 'ASCII', true)) { + $convertedDomain = idn_to_ascii($ns, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + if ($convertedDomain === false) { + $response->header('Content-Type', 'application/json'); + $response->status(400); // Bad Request + $response->end(json_encode(['error' => 'Host conversion to Punycode failed'])); + return; + } else { + $ns = $convertedDomain; + } + } + // Check for prohibited patterns in nameserver - if (!preg_match("/^(?!-)[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/", $ns)) { + if (!preg_match('/^((xn--[a-zA-Z0-9-]{1,63}|[a-zA-Z0-9-]{1,63})\.){2,}(xn--[a-zA-Z0-9-]{2,63}|[a-zA-Z]{2,63})$/', $ns)) { $response->header('Content-Type', 'application/json'); $response->status(400); // Bad Request $response->end(json_encode(['error' => 'Nameserver invalid format'])); diff --git a/whois/port43/start_whois.php b/whois/port43/start_whois.php index adcd8a2..785b176 100644 --- a/whois/port43/start_whois.php +++ b/whois/port43/start_whois.php @@ -74,11 +74,21 @@ $server->on('receive', function ($server, $fd, $reactorId, $data) use ($c, $pool $server->send($fd, "domain name is too long"); $server->close($fd); } - $domain = strtoupper($domain); - if (preg_match("/(^-|^\.|-\.|\.-|--|\.\.|-$|\.$)/", $domain)) { + // Convert to Punycode if the domain is not in ASCII + if (!mb_detect_encoding($domain, 'ASCII', true)) { + $convertedDomain = idn_to_ascii($domain, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + if ($convertedDomain === false) { + $server->send($fd, "Domain conversion to Punycode failed"); + $server->close($fd); + } else { + $domain = $convertedDomain; + } + } + if (!preg_match('/^(?:(xn--[a-zA-Z0-9-]{1,63}|[a-zA-Z0-9-]{1,63})\.){1,3}(xn--[a-zA-Z0-9-]{2,63}|[a-zA-Z]{2,63})$/', $domain)) { $server->send($fd, "domain name invalid format"); $server->close($fd); } + $domain = strtoupper($domain); // Extract TLD from the domain and prepend a dot $parts = explode('.', $domain); @@ -120,8 +130,13 @@ $server->on('receive', function ($server, $fd, $reactorId, $data) use ($c, $pool } // Check for invalid characters using fetched regex - if (!preg_match($idnRegex, $domain)) { - $server->send($fd, "Domain name invalid format"); + if (strpos(strtolower($parts[0]), 'xn--') === 0) { + $label = idn_to_utf8(strtolower($parts[0]), IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + } else { + $label = strtolower($parts[0]); + } + if (!preg_match($idnRegex, $label)) { + $server->send($fd, "Domain name invalid IDN characters"); $server->close($fd); return; } @@ -434,8 +449,19 @@ $server->on('receive', function ($server, $fd, $reactorId, $data) use ($c, $pool $server->send($fd, "nameserver is too long"); $server->close($fd); } + + // Convert to Punycode if the host is not in ASCII + if (!mb_detect_encoding($nameserver, 'ASCII', true)) { + $convertedDomain = idn_to_ascii($nameserver, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + if ($convertedDomain === false) { + $server->send($fd, "Host conversion to Punycode failed."); + $server->close($fd); + } else { + $nameserver = $convertedDomain; + } + } - if (!preg_match('/^([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,}$/', $nameserver)) { + if (!preg_match('/^((xn--[a-zA-Z0-9-]{1,63}|[a-zA-Z0-9-]{1,63})\.){2,}(xn--[a-zA-Z0-9-]{2,63}|[a-zA-Z]{2,63})$/', $nameserver)) { $server->send($fd, "Nameserver contains invalid characters or is not in the correct format."); $server->close($fd); }