Skip to content

Commit

Permalink
Much better IDN support, fixes #73
Browse files Browse the repository at this point in the history
  • Loading branch information
getpinga committed Jan 23, 2024
1 parent 4d57ad7 commit 137f817
Show file tree
Hide file tree
Showing 16 changed files with 235 additions and 60 deletions.
14 changes: 7 additions & 7 deletions automation/write-zone.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,19 @@
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);
Expand Down Expand Up @@ -91,7 +91,7 @@
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
Expand All @@ -109,7 +109,7 @@
$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 . '.');
Expand Down
12 changes: 11 additions & 1 deletion cp/app/Controllers/ApplicationsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,24 @@ public function listApplications(Request $request, Response $response)
return $response->withHeader('Location', '/domains')->withStatus(302);
}
}

public function createApplication(Request $request, Response $response)
{
if ($request->getMethod() === 'POST') {
// Retrieve POST data
$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) {
Expand Down
31 changes: 31 additions & 0 deletions cp/app/Controllers/DomainsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ public function checkDomain(Request $request, Response $response)
$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);

Expand Down Expand Up @@ -105,6 +116,16 @@ public function createDomain(Request $request, Response $response)
$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) {
Expand Down Expand Up @@ -2229,6 +2250,16 @@ public function requestTransfer(Request $request, Response $response)
$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;
Expand Down
27 changes: 19 additions & 8 deletions cp/app/Controllers/HostsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ public function createHost(Request $request, Response $response)
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) {
Expand Down Expand Up @@ -235,14 +246,14 @@ public function viewHost(Request $request, Response $response, $args)

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);
}
Expand Down Expand Up @@ -306,14 +317,14 @@ public function updateHost(Request $request, Response $response, $args)

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);
}
Expand Down
22 changes: 17 additions & 5 deletions cp/app/Controllers/SystemController.php
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ public function createTld(Request $request, Response $response)
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;
Expand All @@ -365,10 +365,22 @@ public function createTld(Request $request, Response $response)

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,
Expand Down Expand Up @@ -536,11 +548,11 @@ public function manageTld(Request $request, Response $response, $args)
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),
Expand Down Expand Up @@ -827,7 +839,7 @@ public function manageTld(Request $request, Response $response, $args)
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);
}
Expand Down
4 changes: 4 additions & 0 deletions cp/bootstrap/helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 1 addition & 1 deletion cp/resources/views/partials/footer.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
8 changes: 4 additions & 4 deletions cp/resources/views/partials/js-applications.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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>`;
}
Expand Down
Loading

0 comments on commit 137f817

Please sign in to comment.