Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 38 additions & 8 deletions packages/core/src/tlds/sources.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,47 @@ export const decodeEntities = (s) =>
/**
* The root zone database page: one table row per label with its type and
* manager. The link, not the text, carries the ASCII label: an IDN row shows
* `.褉褎` and links to `xn--p1ai.html`. "Not assigned" is IANA's word for a
* retired label, kept as the manager so a reader sees it.
* `.褉褎` and links to `xn--p1ai` (`xn--p1ai.html` before IANA's 2026 redesign).
* "Not assigned" is IANA's word for a retired label, kept as the manager so a
* reader sees it.
*
* Two layouts are read. The old one is `label | type | manager`. The redesign
* (live by 2026-10-21) is `label | status | type | manager | script | ...`, with
* the manager cell marked `col-manager`, types in title case ("Country Code"),
* and a retired label shown as status "Retired" with an empty manager. Types are
* normalised to the old lower-case, dashed vocabulary (`country-code`).
*/
export function parseRootDb(html) {
const out = [];
const re =
/href="\/domains\/root\/db\/([^"/]+)\.html"[^>]*>[\s\S]*?<\/td>\s*<td>([\s\S]*?)<\/td>\s*<td>([\s\S]*?)<\/td>/g;
for (const m of String(html ?? '').matchAll(re)) {
const tld = decodeURIComponent(m[1]).toLowerCase();
if (!/^[a-z0-9-]+$/.test(tld)) continue;
out.push({ tld, type: decodeEntities(m[2]) || null, manager: decodeEntities(m[3]) || null });
const seen = new Set();
const text = (cell) => decodeEntities(cell.replace(/<[^>]*>/g, ' '));
for (const row of String(html ?? '')
.split(/<tr\b/i)
.slice(1)) {
const link = row.match(/href="\/domains\/root\/db\/([^"/]+?)(?:\.html)?"/);
if (!link) continue;
const tld = decodeURIComponent(link[1]).toLowerCase();
if (!/^[a-z0-9-]+$/.test(tld) || seen.has(tld)) continue;
const cells = [...row.matchAll(/<td\b([^>]*)>([\s\S]*?)<\/td>/gi)];
const at = cells.findIndex((c) => /col-manager/.test(c[1]));
let type;
let manager;
if (at > 0) {
type = text(cells[at - 1][2]);
manager = text(cells[at][2]);
const status = at > 1 ? text(cells[1][2]) : '';
if (!manager && /^retired$/i.test(status)) manager = 'Not assigned';
} else {
if (cells.length < 3) continue;
type = text(cells[1][2]);
manager = text(cells[2][2]);
}
seen.add(tld);
out.push({
tld,
type: type ? type.toLowerCase().replace(/\s+/g, '-') : null,
manager: manager || null,
});
}
return out;
}
Expand Down
32 changes: 32 additions & 0 deletions test/tlds.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,38 @@ describe('IANA parsers', () => {
expect(rows.find((r) => r.tld === 'ai').manager).toBe('Government of Anguilla & Co');
});

test('the root zone database, redesigned: status column, col-manager, title-case types', () => {
const page = `
<table><tbody>
<tr>
<td><a href="/domains/root/db/aaa"> .aaa <span class="rz-icon-sm tld-type-inline"><img src="/g.svg" alt=""></span></a></td>
<td><span class="index-label"><span class="rz-icon-sm"><img src="/c.svg" alt=""></span> Active </span></td>
<td><span class="index-label"><span class="rz-icon-sm"><img src="/g.svg" alt=""></span> Generic </span></td>
<td class="col-manager"> American Automobile Association, Inc. </td>
<td> Latn </td><td> Latin </td><td> en||aaa </td>
</tr>
<tr>
<td><a href="/domains/root/db/ai"> .ai </a></td>
<td><span class="index-label"> Active </span></td>
<td><span class="index-label"> Country Code </span></td>
<td class="col-manager"> Government of Anguilla &amp; Co </td>
<td> Latn </td>
</tr>
<tr>
<td><a href="/domains/root/db/xn--0zwm56d"> .娴嬭瘯 </a></td>
<td><span class="index-label"> Retired </span></td>
<td><span class="index-label"> Test </span></td>
<td class="col-manager"> </td>
<td> Hani </td>
</tr>
</tbody></table>`;
expect(src.parseRootDb(page)).toEqual([
{ tld: 'aaa', type: 'generic', manager: 'American Automobile Association, Inc.' },
{ tld: 'ai', type: 'country-code', manager: 'Government of Anguilla & Co' },
{ tld: 'xn--0zwm56d', type: 'test', manager: 'Not assigned' },
]);
});

test('RDAP bootstrap: https wins, trailing slash added', () => {
const rows = src.parseRdapBootstrap(RDAP_BOOT);
expect(rows).toContainEqual({ tld: 'com', rdap: 'https://rdap.verisign.com/com/v1/' });
Expand Down
Loading