URLPattern: use encoded username/password when matching URL inputs

Closed
RO
1mo ago
+46-4

URLPattern#exec / #test percent-decoded the username and password of URL-string inputs before matching, so a pattern built from a URL's own canonical userinfo failed to match that URL, and exec() echoed reserved bytes raw. Dict-form inputs behaved correctly, so Bun disagreed with itself. Node 26, Deno, and the spec all keep the encoded form.

const m = new URLPattern({}).exec("https://u%20x:%2F%3A%40@h.example/");
m.username.input;  // bun: "u x"     node/deno: "u%20x"
m.password.input;  // bun: "/:@"     node/deno: "%2F%3A%40"

new URLPattern({ username: "u%20x", password: "%2F%3A%40" })
  .test("https://u%20x:%2F%3A%40@h.example/");  // bun: false   node/deno: true

const p = new URLPattern({ password: "paß" });
p.password;                                   // "pa%C3%9F" everywhere
p.test("https://user:pa%C3%9F@h.example/");   // bun: false   node/deno: true
p.test({ password: "paß" });                  // true everywhere (dict form)

Cause

matchHelperAssignInputsFromURL in URLPattern.cpp read input.user() / input.password(), which are WTF::URL's escape-decoding accessors. The spec's match step 11 takes the parsed URL's serialized (still-encoded) username and password, which is what encodedUser() / encodedPassword() return. The baseURL path of processInit had the same bug for the same reason.

Fix

Switch both sites to encodedUser() / encodedPassword(). Upstream WebCore's URLPattern.cpp has the same defect at the same two sites.

Verification

5 new cases in test/js/web/urlpattern/urlpattern.test.ts cover URL-string input, baseURL input, matching against encoded and non-ASCII patterns, and agreement with new URL(). All 5 fail on the released Bun; all 413 tests in the file pass with this change. Every expected value matches Node 26.3.


[review] gate passed · iteration 0 · 2 files touched

fails on main (without fix)
ASAN without fix: 5 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/web/urlpattern/urlpattern.test.ts
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (75265af8b)

test/js/web/urlpattern/urlpattern.test.ts:
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: [{"pathname":"/foo/bar"}] [33.73ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: [{"pathname":"/foo/ba"}] [7.67ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: [{"pathname":"/foo/bar/"}] [7.27ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: [{"pathname":"/foo/bar/baz"}] [8.36ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: ["https://example.com/foo/bar"] [13.46ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: ["https://example.com/foo/bar/baz"] [9.07m
... (truncated)

release without fix: 5 FAILED
bun test v1.4.0-canary.1 (1498d7b77)

test/js/web/urlpattern/urlpattern.test.ts:
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: [{"pathname":"/foo/bar"}] [0.57ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: [{"pathname":"/foo/ba"}] [0.05ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: [{"pathname":"/foo/bar/"}] [0.04ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: [{"pathname":"/foo/bar/baz"}] [0.06ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: ["https://example.com/foo/bar"] [0.27ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: ["https://example.com/foo/bar/baz"] [0.08ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: [{"hostname":"example.com","pathname":"/foo/bar"}] [0.14ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: [{"hostname":"example.com","pathname":"/foo/bar/baz"}] [0.06ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: [{"pathname":"/foo/bar","baseURL":"https://example.com"}] [0.06ms]
(pass) 
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/web/urlpattern/urlpattern.test.ts
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (75265af8b)

test/js/web/urlpattern/urlpattern.test.ts:
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: [{"pathname":"/foo/bar"}] [34.35ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: [{"pathname":"/foo/ba"}] [7.76ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: [{"pathname":"/foo/bar/"}] [7.59ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: [{"pathname":"/foo/bar/baz"}] [8.11ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: ["https://example.com/foo/bar"] [13.37ms]
(pass) URLPattern > WPT tests > Pattern: [{"pathname":"/foo/bar"}] Inputs: ["https://example.com/foo/bar/baz"] [9.01m
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
[configured] bun-profile → bun (stripped) in 752ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/119] gen generated_host_exports.rs
generated_host_exports.rs: 91 exports (host=3, lazy=10, generic=78, rust=0); 243 extern-C blocks audited
[2/119] gen cpp.rs (cppbind)
[2/119] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: component rust-std is up to date

  nightly-2026-05-06-x86_64-unknown-linux-gnu unchanged - rustc 1.97.0-nightly (e95e73209 2026-05-05)

info: checking for self-update (current version: 1.29.0)
    Blocking waiting for file lock on build directory
   Compiling bun_runtime v0.0.0 (/
... (truncated)
diff hotspot
src/jsc/bindings/webcore/URLPattern.cpp   |  8 +++---
 test/js/web/urlpattern/urlpattern.test.ts | 42 +++++++++++++++++++++++++++++++
 2 files changed, 46 insertions(+), 4 deletions(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                       reads  edits  tests
src/jsc/bindings/webcore/URLPattern.cpp        1      2      0
test/js/web/urlpattern/urlpattern.test.ts      1      1      0