From 9e54a469a4beec0798019fa45f90fae81ce858ba Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Wed, 8 Jul 2026 12:39:45 +0000 Subject: [PATCH 1/2] node:url: run domainToUnicode regardless of string backing store jsDomainToUnicode returned its argument verbatim whenever the input WTF::String was 16-bit backed. is8Bit() is a representation property, not a value property: an all-ASCII punycode label can be stored in a 16-bit buffer (domainToASCII always returns one, as do slices of a 16-bit rope), and UTS #46 ToUnicode is defined over all inputs, not only Latin-1 ones. The result was that domainToUnicode(domainToASCII(d)) never decoded, and two === strings could produce different outputs. Drop the fast path and always call uidna_nameToUnicode, matching the structure jsDomainToASCII already uses. --- src/jsc/bindings/NodeURL.cpp | 7 ++--- .../node/url/url-domain-ascii-unicode.test.js | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/jsc/bindings/NodeURL.cpp b/src/jsc/bindings/NodeURL.cpp index 09af0674a4e..7218b764c42 100644 --- a/src/jsc/bindings/NodeURL.cpp +++ b/src/jsc/bindings/NodeURL.cpp @@ -117,11 +117,8 @@ JSC_DEFINE_HOST_FUNCTION(jsDomainToUnicode, (JSC::JSGlobalObject * globalObject, ) return JSC::JSValue::encode(jsEmptyString(vm)); - if (!domain.is8Bit()) - // this function is only for undoing punycode so its okay if utf-16 text makes it out unchanged. - return JSC::JSValue::encode(arg0); - - domain.convertTo16Bit(); + if (domain.is8Bit()) + domain.convertTo16Bit(); constexpr static int allowedNameToUnicodeErrors = UIDNA_ERROR_EMPTY_LABEL | UIDNA_ERROR_LABEL_TOO_LONG | UIDNA_ERROR_DOMAIN_NAME_TOO_LONG | UIDNA_ERROR_LEADING_HYPHEN | UIDNA_ERROR_TRAILING_HYPHEN | UIDNA_ERROR_HYPHEN_3_4; constexpr static int hostnameBufferLength = 2048; diff --git a/test/js/node/url/url-domain-ascii-unicode.test.js b/test/js/node/url/url-domain-ascii-unicode.test.js index d46b30f8c2e..5fe3f0dfa27 100644 --- a/test/js/node/url/url-domain-ascii-unicode.test.js +++ b/test/js/node/url/url-domain-ascii-unicode.test.js @@ -98,4 +98,34 @@ describe("url.domainToUnicode", () => { expect(url.domainToASCII(input)).toEqual(expected); }); } + + // The result must depend only on the string's value, not on whether JSC + // happened to back it with an 8-bit or 16-bit buffer. + test("does not depend on the string's internal representation", () => { + const eight = "xn--bcher-kva.de"; + const sixteen = "xn--bcher-kva.de\u4e2d".slice(0, -1); + expect(sixteen).toBe(eight); + expect({ + eight: url.domainToUnicode(eight), + sixteen: url.domainToUnicode(sixteen), + }).toEqual({ + eight: "bücher.de", + sixteen: "bücher.de", + }); + }); + + test("round-trips through domainToASCII", () => { + for (const [domain] of pairs) { + expect(url.domainToUnicode(url.domainToASCII(domain))).toBe(domain); + } + }); + + test("decodes A-labels that appear alongside non-Latin-1 labels", () => { + expect(url.domainToUnicode("中.xn--fiqs8s")).toBe("中.中国"); + expect(url.domainToUnicode("пример.xn--p1ai")).toBe("пример.рф"); + }); + + test("applies UTS #46 mapping to non-Latin-1 input", () => { + expect(url.domainToUnicode("\ufb00.COM")).toBe("ff.com"); + }); }); From 7ca8fa5ecbd513f3de9d808865e1202484e88826 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Wed, 8 Jul 2026 12:59:38 +0000 Subject: [PATCH 2/2] ci: retrigger