Much better IDN support, fixes #73

This commit is contained in:
Pinga 2024-01-23 12:58:01 +02:00
parent 4d57ad71a1
commit 137f8170e2
16 changed files with 235 additions and 60 deletions

View file

@ -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 . '.');

View file

@ -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) {

View file

@ -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;

View file

@ -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);
}

View file

@ -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}(?<!-)(.(?!-)(?!.*--)[A-Z0-9-]{1,63}(?<!-))*$/i';
break;
@ -365,10 +365,22 @@ class SystemController extends Controller
try {
$db->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);
}

View file

@ -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';

View file

@ -14,7 +14,7 @@
<a href="https://namingo.org" target="_blank" class="link-secondary" rel="noopener">Namingo</a>
</li>
<li class="list-inline-item">
v1.0.0-beta4
v1.0.0-beta5
</li>
</ul>
</div>

View file

@ -21,10 +21,10 @@
var isInvalid = rowData.application_status.some(statusObj => statusObj.status && statusObj.status.includes('invalid'));
if (!isRejected && !isInvalid) {
actionButtons += `<a class="btn btn-green btn-icon" href="application/approve/${cell.getRow().getData().name}" title="Approve Application"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l5 5l10 -10" /></svg></a> `;
actionButtons += `<a class="btn btn-warning btn-icon" href="application/reject/${cell.getRow().getData().name}" title="Reject Application"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M5.7 5.7l12.6 12.6" /></svg></a> `;
actionButtons += `<a class="btn btn-info btn-icon" href="application/update/${cell.getRow().getData().name}" title="Update Application"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1"></path><path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z"></path><path d="M16 5l3 3"></path></svg></a> `;
actionButtons += `<a class="btn btn-danger btn-icon delete-btn" id="delete-btn" href="javascript:void(0);" data-delete-url="application/delete/${cell.getRow().getData().name}" title="Delete Application"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M4 7h16"></path><path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12"></path><path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"></path><path d="M10 12l4 4m0 -4l-4 4"></path></svg></a>`;
actionButtons += `<a class="btn btn-outline-green btn-icon" href="application/approve/${cell.getRow().getData().name}" title="Approve Application"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l5 5l10 -10" /></svg></a> `;
actionButtons += `<a class="btn btn-outline-warning btn-icon" href="application/reject/${cell.getRow().getData().name}" title="Reject Application"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M5.7 5.7l12.6 12.6" /></svg></a> `;
actionButtons += `<a class="btn btn-outline-info btn-icon" href="application/update/${cell.getRow().getData().name}" title="Update Application"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1"></path><path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z"></path><path d="M16 5l3 3"></path></svg></a> `;
actionButtons += `<a class="btn btn-outline-danger btn-icon delete-btn" id="delete-btn" href="javascript:void(0);" data-delete-url="application/delete/${cell.getRow().getData().name}" title="Delete Application"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M4 7h16"></path><path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12"></path><path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"></path><path d="M10 12l4 4m0 -4l-4 4"></path></svg></a>`;
} else {
actionButtons += `<strong class="text-success"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M9 12l2 2l4 -4" /></svg> Completed</strong>`;
}

View file

@ -16,8 +16,8 @@
function actionsFormatter(cell, formatterParams, onRendered) {
return `
<a class="btn btn-primary btn-icon update-btn" href="contact/update/${cell.getRow().getData().identifier}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1"></path><path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z"></path><path d="M16 5l3 3"></path></svg></a>
<a class="btn btn-danger btn-icon delete-btn" id="delete-btn" href="javascript:void(0);" data-delete-url="contact/delete/${cell.getRow().getData().identifier}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M4 7h16"></path><path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12"></path><path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"></path><path d="M10 12l4 4m0 -4l-4 4"></path></svg></a>
<a class="btn btn-outline-primary btn-icon update-btn" href="contact/update/${cell.getRow().getData().identifier}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1"></path><path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z"></path><path d="M16 5l3 3"></path></svg></a>
<a class="btn btn-outline-danger btn-icon delete-btn" id="delete-btn" href="javascript:void(0);" data-delete-url="contact/delete/${cell.getRow().getData().identifier}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M4 7h16"></path><path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12"></path><path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"></path><path d="M10 12l4 4m0 -4l-4 4"></path></svg></a>
`;
}

View file

@ -21,15 +21,15 @@
var hasPendingRestore = rowData.rgpstatus ? rowData.rgpstatus.includes('pendingRestore') : false;
// Common action button for all statuses
actionButtons += `<a class="btn btn-info btn-icon" href="domain/update/${cell.getRow().getData().name}" title="Update Domain"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1"></path><path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z"></path><path d="M16 5l3 3"></path></svg></a> `;
actionButtons += `<a class="btn btn-outline-info btn-icon" href="domain/update/${cell.getRow().getData().name}" title="Update Domain"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1"></path><path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z"></path><path d="M16 5l3 3"></path></svg></a> `;
if (hasPendingRestore) {
actionButtons += `<a class="btn btn-outline-dark btn-icon report-btn" id="report-btn" href="javascript:void(0);" data-report-url="domain/report/${cell.getRow().getData().name}" title="Submit Report"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3.06 13a9 9 0 1 0 .49 -4.087" /><path d="M3 4.001v5h5" /><path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /></svg></a>`;
} else if (hasPendingDelete) {
actionButtons += `<a class="btn btn-outline-warning btn-icon restore-btn" id="restore-btn" href="javascript:void(0);" data-restore-url="domain/restore/${cell.getRow().getData().name}" title="Restore Domain"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 4.55a8 8 0 0 0 -6 14.9m0 -4.45v5h-5" /><path d="M18.37 7.16l0 .01" /><path d="M13 19.94l0 .01" /><path d="M16.84 18.37l0 .01" /><path d="M19.37 15.1l0 .01" /><path d="M19.94 11l0 .01" /></svg></a>`;
} else {
actionButtons += `<a class="btn btn-success btn-icon" href="domain/renew/${cell.getRow().getData().name}" title="Renew Domain"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path><path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path></svg></a> `;
actionButtons += `<a class="btn btn-danger btn-icon delete-btn" id="delete-btn" href="javascript:void(0);" data-delete-url="domain/delete/${cell.getRow().getData().name}" title="Delete Domain"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M4 7h16"></path><path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12"></path><path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"></path><path d="M10 12l4 4m0 -4l-4 4"></path></svg></a>`;
actionButtons += `<a class="btn btn-outline-success btn-icon" href="domain/renew/${cell.getRow().getData().name}" title="Renew Domain"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path><path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path></svg></a> `;
actionButtons += `<a class="btn btn-outline-danger btn-icon delete-btn" id="delete-btn" href="javascript:void(0);" data-delete-url="domain/delete/${cell.getRow().getData().name}" title="Delete Domain"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M4 7h16"></path><path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12"></path><path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"></path><path d="M10 12l4 4m0 -4l-4 4"></path></svg></a>`;
}
return actionButtons;

View file

@ -16,8 +16,8 @@
function actionsFormatter(cell, formatterParams, onRendered) {
return `
<a class="btn btn-primary btn-icon update-btn" href="host/update/${cell.getRow().getData().name}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1"></path><path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z"></path><path d="M16 5l3 3"></path></svg></a>
<a class="btn btn-danger btn-icon delete-btn" id="delete-btn" href="javascript:void(0);" data-delete-url="host/delete/${cell.getRow().getData().name}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M4 7h16"></path><path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12"></path><path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"></path><path d="M10 12l4 4m0 -4l-4 4"></path></svg></a>
<a class="btn btn-outline-primary btn-icon update-btn" href="host/update/${cell.getRow().getData().name}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1"></path><path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z"></path><path d="M16 5l3 3"></path></svg></a>
<a class="btn btn-outline-danger btn-icon delete-btn" id="delete-btn" href="javascript:void(0);" data-delete-url="host/delete/${cell.getRow().getData().name}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M4 7h16"></path><path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12"></path><path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"></path><path d="M10 12l4 4m0 -4l-4 4"></path></svg></a>
`;
}

View file

@ -21,14 +21,14 @@
var clidValue = document.getElementById('clid').value;
if (hasPendingStatus && clidValue === '0') {
actionButtons += `<a class="btn btn-success btn-icon approve-btn" id="approve-btn" href="javascript:void(0);" data-approve-url="transfer/approve/${cell.getRow().getData().name}" title="{{ __('Approve Transfer') }}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 11l3 3l8 -8" /><path d="M20 12v6a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h9" /></svg></a> `;
actionButtons += `<a class="btn btn-dark btn-icon cancel-btn" id="cancel-btn" href="javascript:void(0);" data-cancel-url="transfer/cancel/${cell.getRow().getData().name}" title="{{ __('Cancel Transfer') }}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M10 10l4 4m0 -4l-4 4" /></svg></a> `;
actionButtons += `<a class="btn btn-danger btn-icon reject-btn" id="reject-btn" href="javascript:void(0);" data-reject-url="transfer/reject/${cell.getRow().getData().name}" title="{{ __('Reject Transfer') }}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M5.7 5.7l12.6 12.6" /></svg></a>`;
actionButtons += `<a class="btn btn-outline-success btn-icon approve-btn" id="approve-btn" href="javascript:void(0);" data-approve-url="transfer/approve/${cell.getRow().getData().name}" title="{{ __('Approve Transfer') }}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 11l3 3l8 -8" /><path d="M20 12v6a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h9" /></svg></a> `;
actionButtons += `<a class="btn btn-outline-dark btn-icon cancel-btn" id="cancel-btn" href="javascript:void(0);" data-cancel-url="transfer/cancel/${cell.getRow().getData().name}" title="{{ __('Cancel Transfer') }}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M10 10l4 4m0 -4l-4 4" /></svg></a> `;
actionButtons += `<a class="btn btn-outline-danger btn-icon reject-btn" id="reject-btn" href="javascript:void(0);" data-reject-url="transfer/reject/${cell.getRow().getData().name}" title="{{ __('Reject Transfer') }}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M5.7 5.7l12.6 12.6" /></svg></a>`;
} else if (clidValue === rowData.reid) {
actionButtons += `<a class="btn btn-dark btn-icon cancel-btn" id="cancel-btn" href="javascript:void(0);" data-cancel-url="transfer/cancel/${cell.getRow().getData().name}" title="{{ __('Cancel Transfer') }}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M10 10l4 4m0 -4l-4 4" /></svg></a> `;
actionButtons += `<a class="btn btn-outline-dark btn-icon cancel-btn" id="cancel-btn" href="javascript:void(0);" data-cancel-url="transfer/cancel/${cell.getRow().getData().name}" title="{{ __('Cancel Transfer') }}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M10 10l4 4m0 -4l-4 4" /></svg></a> `;
} else if (clidValue === rowData.acid) {
actionButtons += `<a class="btn btn-success btn-icon approve-btn" id="approve-btn" href="javascript:void(0);" data-approve-url="transfer/approve/${cell.getRow().getData().name}" title="{{ __('Approve Transfer') }}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 11l3 3l8 -8" /><path d="M20 12v6a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h9" /></svg></a> `;
actionButtons += `<a class="btn btn-danger btn-icon reject-btn" id="reject-btn" href="javascript:void(0);" data-reject-url="transfer/reject/${cell.getRow().getData().name}" title="{{ __('Reject Transfer') }}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M5.7 5.7l12.6 12.6" /></svg></a>`;
actionButtons += `<a class="btn btn-outline-success btn-icon approve-btn" id="approve-btn" href="javascript:void(0);" data-approve-url="transfer/approve/${cell.getRow().getData().name}" title="{{ __('Approve Transfer') }}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 11l3 3l8 -8" /><path d="M20 12v6a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h9" /></svg></a> `;
actionButtons += `<a class="btn btn-outline-danger btn-icon reject-btn" id="reject-btn" href="javascript:void(0);" data-reject-url="transfer/reject/${cell.getRow().getData().name}" title="{{ __('Reject Transfer') }}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M5.7 5.7l12.6 12.6" /></svg></a>`;
} else {
actionButtons += `<strong class="text-success"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M9 12l2 2l4 -4" /></svg> {{ __('Completed') }}</strong>`;
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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']));

View file

@ -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);
}