Repository navigation
Conversation
|
Updated 12:49 PM PT - Sep 24th, 2026
✅ @robobun, your commit af2c7a4bb40116447f21769c2327141354e120ab passed in 🧪 To try this PR locally: bunx bun-pr 43872That installs a local version of the PR into your bun-43872 --bun |
|
Status: draft. The code is ready. It waits for one decision from a maintainer (@Jarred-Sumner): does bun follow whatwg/url#914 and stop rejecting all-ASCII hosts whose
|
40fb040 to
4ccacde
Compare
|
Related: oven-sh/WebKit#724 (bun side: #43883, draft) deletes the IDNA pass that |
e4325c2 to
3db6208
Compare
4ccacde to
de4acb4
Compare
The vendored copy was WPT 9504a83e01. The new copy is byte-identical to WPT c48d58747e and to Node main's test/fixtures/wpt. It adds about 450 lines of rows. Bun passes all of them except the 8 rows that whatwg/url#914 changed from failure to parse (an all-ASCII host whose xn-- label fails Unicode ToASCII). url-wpt-constructor.test.ts lists those 8 inputs and asserts that bun still rejects them, so the fix shows up there. toascii.json stays at the old snapshot because its 7 changed rows need the behavior change.
An all-ASCII host now passes through every door, lowercased, even when an xn-- label fails Unicode ToASCII: new URL, URL.canParse, URL.parse, the href, host and hostname setters, url.parse, url.domainToASCII and url.domainToUnicode. WebKit's host parser already does this. The rejection came from a layer that bun added on top of it to match Node before ada 4.0.0, and that layer is deleted: ASCIIHostPunycodeCheck.h, NodeURLHelpers.h, Bun::hasValidPunycodeHost, hasValidParsedHost and containsXNDashDash in DOMURL.cpp, hasAcceptableHost in URLDecomposition.cpp, and the ASCII fast path of icuToASCII. One helper, idnaToASCII, mirrors ada::idna::to_ascii for url.parse and the TLS matchers. A non-ASCII domain with a bad label, a forbidden domain code point and a host that ends in a number still fail. Tests: toascii.json moves to WPT c48d58747e, IdnaTestV2.json, IdnaTestV2-removed.json and setters_tests.json are vendored from the same commit with grouped runners, and the 8 rows that #43851 pinned in url-wpt-constructor.test.ts are unpinned.
de4acb4 to
af2c7a4
Compare
Problem
new URL("https://xn--8i7caa.famitei.net/")throwsERR_INVALID_URL. The site is live. The URL Standard (Add Unicode ToASCII fallback for ASCII domains whatwg/url#914), WPT and Node v26.7.0+ parse it.URL.canParse, thehref/host/hostnamesetters,url.parse,url.domainToASCIIandurl.domainToUnicodereject the same hosts.Bun::hasValidPunycodeHost, a UTS Bun v0.0.41 #46 check that bun runs after WebKit's host parser. WebKit's parser already passes an ASCII host through.Fix
ASCIIHostPunycodeCheck.h) and its callers inDOMURL.cppandURLDecomposition.cpp.url.parsereturns an ASCII hostname lowercased, asada::idna::to_asciidoes.IdnaTestV2.jsonintest/js/web/url/url-wpt-constructor.test.ts(main fails 760 of 2670 rows). Self-reviewed: 6 concerns raised, 6 addressed.Background
xn--label is the Punycode form of a Unicode label. UTS Bun v0.0.41 #46 ToASCII decodes and validates it.node:urlonly. Current Node accepts them there too.Downsides
new URLorURL.canParseas a strict host check now accepts hosts such asxn--a.com(replacement in Notes).u.hostname = "é.%78n--a"setsxn--9ca.xn--a. Node refuses it.URL.canParse("https://xn--ls8h.com/")goes from 2469 to 529 instructions. Release.textshrinks by 5,632 B.Notes
Evidence for the direction.
xn--8i7caa.famitei.net,xn--board-ngr.palungjit.org. Implementer bugs: Gecko 2049177, WebKit 316554, Deno 35411, Node 64045.deps/ada/ada.hsays 4.0.0 at v26.7.0 and v24.20.0, and 3.4.4 at v26.6.0 and v24.19.0.toascii.jsonhas 7 rows that changed fromnullto the input,urltestdata.jsonhas 8 rows that changed from failure to parse,IdnaTestV2.jsonexpects failure for 2 of its 994 ASCII inputs (both contain%)..claude/docs/landing-prs.md: for URL "the WHATWG/W3C spec and WPT are the bar", fornode:*modules "real Node's observed behavior is the spec".URLParser::domainToASCIIlowercases acontainsOnlyASCII()domain and calls ICU only for a non-ASCII one.History.
new URL,URL.canParse, thehostnamesetter,url.parseandnew Requestrejectxn--8i7caa.famitei.neton bun 1.3.7, 1.3.13 and 1.3.14 (through WebKit's parser of that time).url.domainToASCIIpassed it through in those releases. On 1.4.x the rejection comes from the layer this PR deletes,url.domainToASCIIreturns"", andnew Requestaccepts the host. Node v26.3.0 rejects at every door, Node v26.10.0 accepts at every door.Merge order and open PRs that conflict.
fetch,RequestandWebSocket, and url: give blob: URLs a null origin when the inner host has invalid punycode #39404 extends it toblob:origins. Both were written against Node before ada 4.0.0. They must close when this PR merges. url: give blob: URLs a null origin when the inner host has invalid punycode #39404 adds ahasAcceptableHostcall inURLDecomposition::origin(), a hunk this PR does not touch, so both would merge cleanly and break the build.Self-review. Six concerns, all addressed: the stack is rebased on main after #43040, which already made
tls.checkServerIdentitymatch an ASCII host as typed, so the TLS test and claim of an earlier version of this PR are gone. The history above is corrected (an earlier version said the rejection was new in 1.4).new Request,Response.redirect,blob:origin,fetchandWebSocketrows pin the entry points that #41772 and #39404 name. WPTIdnaTestV2.json,IdnaTestV2-removed.jsonandsetters_tests.jsonare vendored with runners. The ASCII pass-through ofurl.parselives in one helper.Door comparison. Inputs: every
inputandoutputof WPTIdnaTestV2.jsonandtoascii.jsonplus 27 hand-written hosts, 2794 in total. Doors:new URL,URL.canParse,hostname=,host=,href=,url.parse,url.domainToASCII,url.domainToUnicode. Against Node v26.10.0, a release build of main 6d504dd differs on 785 inputs (777 at each WHATWG door, 784 aturl.parse, 781 atdomainToUnicode). This PR differs on 1 input,é.%78n--a, athostname=andhost=.The setter case.
WTF::URL::setHostruns ICU on a non-ASCII value before it splices the value into the URL and parses again (appendEncodedHostname). Percent-decoding happens in the parser, after that pass, so ICU never seesxn--anext toé. The planned change in oven-sh/WebKit hands the raw value to the parser, which runs IDNA once, after percent-decoding.A quadratic path goes away. With 32768 bad labels behind one right-to-left label (
"xn--mgbh0fb." + "xn--xn--zca-hia.".repeat(32768) + "com"),URL.canParse,url.domainToASCIIandurl.domainToUnicodeeach take 555 ms on main (10, 37, 141, 555 ms for 4096 to 32768 labels). The time is inhasValidPunycodeHost: ICU's whole-host ToASCII inserts U+FFFD after each bad label and moves the rest of the host. With this PR the same calls take 0 to 12 ms.Possible follow-up. After this PR the
internalBinding("icu")shim (jsIcuToASCII,jsIcuToUnicode,icuToUnicode) has one consumer, the vendoredtest-icu-punycode.js, which Node deleted together with its ICU-based punycode (nodejs/node#55156). Removing the shim and that test is a separate change.Strict check in user code.
url.domainToUnicode(host).split(".").some(label => label.startsWith("xn--"))is true when a label did not decode. It needs #41464.TLS. TLS matching does not change.
tls.checkServerIdentityand the native matcher callidnaToASCIIonly for a name with a non-ASCII character, and that path is the same UTS #46 conversion as on main. An ASCII name is matched as typed (#43040).Instructions per call. Release builds of main 6d504dd and of this branch at 40fb040 on that base, inclusive user instructions of one call, single-stepped under gdb with
BUN_JSC_useJIT=0after a warm-up pass. Later commits moved the ASCII check ofurl.parseinto theidnaToASCIIhelper and rebased the stack.URL.canParsehttps://example.com/https://my-site.example.com/a--bhttps://xn--ls8h.com/https://xn--mgbh0fb.xn--kgbechtv/https://münchen.de/hostname=other.examplexn--ls8h.comxn--mgbh0fb.xn--kgbechtvmünchen.deurl.domainToASCIIexample.comxn--ls8h.comxn--mgbh0fb.xn--kgbechtvmünchen.deurl.parseIDNA stepexample.comxn--ls8h.comxn--mgbh0fb.xn--kgbechtvmünchen.deRelease
.texton that base: main 80,682,903 B, #41464 80,683,559 B, this PR 80,677,927 B. That is 5,632 B less than #41464 and 4,976 B less than main.Tests.
test/js/web/url/url.test.ts: the three blocks that pinned the rejection now pin the values of Node v26.10.0, with new blocks for the hosts that must still fail, for the three setters, and fornew Request,Response.redirect,blob:origins,fetchandWebSocket(through a local proxy, so no DNS).test/js/web/url/url-wpt-constructor.test.ts: the 8 pinned rows of test: re-sync urltestdata.json from WPT and pin the 8 rows that whatwg/url#914 changed #43851 go away, and a new block runs WPTIdnaTestV2.json(2670 rows) andIdnaTestV2-removed.json(20 rows). The 993 ASCII rows run everywhere. The non-ASCII rows need the Unicode data of ICU 78, so they skip on an older system ICU (macOS).test/js/web/url/url-wpt-setters.test.ts(new, one runner per WPT data file, besideurl-wpt-constructor.test.ts): WPTsetters_tests.json, 278 rows. Two rows that have nothing to do with IDNA are pinned as known deviations (host = "example.com:80"on a URL with port 8080, and a whitespace-onlyport). Main fails 4 rows.test/js/node/url/url-domain-ascii-unicode.test.js: newdomainToASCIIanddomainToUnicoderows, and theknownRejectDeviationslist of url: domainToUnicode converts per label like ada (bad label unchanged, linear time) #41464 goes away: all 32 rows of its table now give the value of Node.test/js/node/url/url.test.ts:url.parserows.toascii.jsonmoves to WPT c48d58747e (7 rows changed fromnullto the input).test-whatwg-url-custom-domainto.jsis a vendored Node test that Node deleted in 7443a28c28 (2024). Its round tripdomainToASCII(domainToUnicode(input))fails on Node v26.10.0 too forxn--a.xn--zca, which becomesxn--a.ß. The file now expects""for such a row, with a comment.url.test.tsrow needs ICU 76 (the Unicode 15.1 rule for a decodedxn--prefix inside a non-ASCII host) and is guarded.test/js/web/url/,test/js/node/url/,test/js/web/fetch/fetch.tls.wildcard.test.ts, and the vendoredtest-whatwg-url-*,test-url-*,test-icu-punycode.jsandtest-tls-check-server-identity.js.url-canParse-whatwg.test.jstimes out on main too with a debug build.no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/node/url/url.test.ts