diff --git a/apps/pwa/src/lib/moshpit-name.mjs b/apps/pwa/src/lib/moshpit-name.mjs index 8b3251d..d044e50 100644 --- a/apps/pwa/src/lib/moshpit-name.mjs +++ b/apps/pwa/src/lib/moshpit-name.mjs @@ -44,8 +44,10 @@ export function normalizeTld(input) { // registering the wrong thing. const label = normalizeLabel(raw); if (!label) return null; - // All-numeric would be ambiguous against an IPv4 literal in a hostname. - if (/^\d+$/.test(label)) return null; + // All-numeric endings are fine: `.420`, `.187`, `.911` are names people want, + // and an ending on its own is never mistaken for an address. The ambiguity + // with an IPv4 literal belongs to the whole hostname — `1.420` reads as one, + // `blue.420` cannot — so parseMoshpitName rejects that case and this does not. return label; } @@ -72,6 +74,13 @@ export function parseMoshpitName(input) { const normalizedLabel = normalizeLabel(label); const normalizedTld = normalizeTld(tld); if (!normalizedLabel || !normalizedTld) return null; + + // `1.420` is indistinguishable from an abbreviated IPv4 literal — several + // parsers read a two-part dotted number as an address — so a name whose every + // label is numeric is refused. `blue.420` and `420.blue` are unambiguous and + // allowed; it takes both halves being numbers to create the collision. + if (/^\d+$/.test(normalizedLabel) && /^\d+$/.test(normalizedTld)) return null; + return { label: normalizedLabel, tld: normalizedTld }; } diff --git a/apps/pwa/test/moshpit-name.test.mjs b/apps/pwa/test/moshpit-name.test.mjs index 38641b0..6cf9648 100644 --- a/apps/pwa/test/moshpit-name.test.mjs +++ b/apps/pwa/test/moshpit-name.test.mjs @@ -23,16 +23,23 @@ test("normalizeTld rejects what could never be a TLD", () => { assert.equal(normalizeTld("-eggs"), null); assert.equal(normalizeTld("eggs-"), null); assert.equal(normalizeTld("egg s"), null); - assert.equal(normalizeTld("123"), null, "ambiguous against an IPv4 literal"); assert.equal(normalizeTld("a".repeat(64)), null); assert.equal(normalizeTld(null), null); assert.equal(normalizeTld(undefined), null); }); -test("hostname labels may be numeric even though TLDs may not", () => { +test("either half of a name may be numeric — but not both", () => { assert.equal(normalizeLabel("123"), "123"); - assert.equal(normalizeTld("123"), null); + // .420 and .187 are endings people want, and an ending on its own is never + // mistaken for an address. + assert.equal(normalizeTld("123"), "123"); assert.deepEqual(parseMoshpitName("123.eggs"), { label: "123", tld: "eggs" }); + assert.deepEqual(parseMoshpitName("blue.420"), { label: "blue", tld: "420" }); + + // Both halves numeric is where the IPv4 ambiguity actually lives: several + // parsers read a two-part dotted number as an abbreviated address. + assert.equal(parseMoshpitName("1.420"), null); + assert.equal(parseMoshpitName("192.168"), null); }); test("reserved names cannot be claimed", () => { @@ -92,3 +99,29 @@ test("overriding DNS is opt-in, never the default", () => { assert.notEqual(resolutionPreference({ registered: true, mode }), "moshpit"); } }); + +test("all-numeric endings", async (t) => { + const { normalizeTld, parseMoshpitName } = await import("../src/lib/moshpit-name.mjs"); + + await t.test("an ending may be all digits", () => { + // .420, .187, .911 are names people want; an ending on its own is never + // mistaken for an address. + for (const [input, expected] of [[".420", "420"], ["187", "187"], [".911", "911"], ["0", "0"]]) { + assert.equal(normalizeTld(input), expected, input); + } + }); + + await t.test("a name is fine when only one half is numeric", () => { + assert.deepEqual(parseMoshpitName("blue.420"), { label: "blue", tld: "420" }); + assert.deepEqual(parseMoshpitName("bud.420"), { label: "bud", tld: "420" }); + assert.deepEqual(parseMoshpitName("420.blue"), { label: "420", tld: "blue" }); + }); + + await t.test("both halves numeric is refused — that reads as an address", () => { + // Several parsers read a two-part dotted number as an abbreviated IPv4, + // so `1.420` is genuinely ambiguous where `blue.420` is not. + assert.equal(parseMoshpitName("1.420"), null); + assert.equal(parseMoshpitName("192.168"), null); + assert.equal(parseMoshpitName("0.0"), null); + }); +}); diff --git a/apps/pwa/test/moshpit-registry.test.mjs b/apps/pwa/test/moshpit-registry.test.mjs index 80f182f..735b07c 100644 --- a/apps/pwa/test/moshpit-registry.test.mjs +++ b/apps/pwa/test/moshpit-registry.test.mjs @@ -226,8 +226,10 @@ test("moshpit registry", { skip: installed ? false : "pwa dependencies not insta assert.equal(await m.getName("eggs", "123"), null); }); - await t.test("the TLD itself still may not be numeric", async () => { - assert.equal((await m.registerTld({ tld: "123", userId: ALICE })).ok, false); + await t.test("an all-numeric ending can be claimed", async () => { + // .420, .187, .911. The IPv4 ambiguity is a property of a whole hostname, + // not of an ending, so it is enforced by parseMoshpitName instead. + assert.equal((await m.registerTld({ tld: "420", userId: ALICE })).ok, true); }); await t.test("minting is logged", async () => {