Skip to content

WHATWG URL class instance properties + setters broken #650

Description

@proggeramlug

Repro

const u = new URL("https://user:pass@example.com:8080/p/q?k=v#frag");
console.log("host:", u.host);
console.log("hostname:", u.hostname);
console.log("origin:", u.origin);
console.log("password:", u.password);
console.log("username:", u.username);
console.log("toString:", u.toString());
console.log("toJSON:", u.toJSON());

const u2 = new URL("https://example.com/");
u2.pathname = "/changed";
u2.search = "?x=1";
u2.hash = "#h";
console.log("after mutation:", u2.href);

console.log("URL.canParse('https://x'):", URL.canParse("https://x"));
console.log("URL.parse('https://x'):", URL.parse("https://x")?.href);

const sp = new URLSearchParams("a=1&b=2");
console.log("sp.size:", sp.size);
const sp2 = new URLSearchParams([["a", "1"], ["b", "2"]]);
console.log("sp2 from iterable:", sp2.toString());

Actual (Perry)

host: user:pass@example.com:8080         ← userinfo leaks into host
hostname: user:pass@example.com          ← userinfo leaks into hostname
origin: https://user:pass@example.com:8080  ← userinfo leaks into origin
password: undefined                      ← parser doesn't extract userinfo
username: undefined                      ← parser doesn't extract userinfo
toString: [object Object]                ← stringifier missing
toJSON: 1970-01-01T00:00:00.000Z         ← treated as Date instead of URL
after mutation: https://example.com/     ← setters don't apply
URL.canParse('https://x'): undefined     ← static method missing
URL.parse('https://x'): undefined        ← static method missing
sp.size: undefined                       ← getter missing
sp2 from iterable: <Perry hangs / silently exits>  ← iterable ctor broken

Expected (Node)

host: example.com:8080
hostname: example.com
origin: https://example.com:8080
password: pass
username: user
toString: https://user:pass@example.com:8080/p/q?k=v#frag
toJSON: https://user:pass@example.com:8080/p/q?k=v#frag
after mutation: https://example.com/changed?x=1#h
URL.canParse('https://x'): true
URL.parse('https://x'): https://x/
sp.size: 2
sp2 from iterable: a=1&b=2

Impact

The WHATWG URL class is foundational — every fetch(url), new URL(href), new URLSearchParams(), URL.canParse(s) call relies on it. Bugs here cascade into:

  • Authentication — code that does const u = new URL(authUrl); req.setHeader('Authorization', \Basic ${btoa(`${u.username}:${u.password}`)}`)` reads undefined for both fields.
  • Hostname-based routing — code that does if (u.hostname === 'api.example.com') matches the wrong string when userinfo is present.
  • URL serializationJSON.stringify(url) produces "1970-01-01T00:00:00.000Z" instead of the URL string. Any object containing a URL field round-trips wrong.
  • Routing libraries — most web frameworks parse request.url via WHATWG URL; broken here means broken for hono, koa, etc.

The bugs cluster into a few categories that may share root causes:

  1. WHATWG parser doesn't extract authority componentshost, hostname, origin include the userinfo string; username/password come back undefined. Looks like the URL parser stops at :// and doesn't split out the user:pass@ before the host.
  2. toString() returns [object Object] — the URL prototype's toString getter is unwired.
  3. toJSON() returns 1970-01-01T00:00:00.000Z — the URL instance is being incorrectly classified as a Date by js_jsvalue_to_string's tag-based dispatch.
  4. Setters are no-opsu.pathname = "/x" doesn't update the underlying URL state; subsequent .href reads the original.
  5. URL.canParse / URL.parse static methods missing entirely.
  6. URLSearchParams.size getter missing.
  7. new URLSearchParams([['a','1']]) iterable form silently exits the process — earlier reported as a stand-alone bug; symptom is process exit 0 after the constructor call without printing anything further. Workaround: caller must construct via string or object literal form.

Acceptance

The 18-line repro above prints output byte-identical to Node.

Related

  • Test file: test-files/test_parity_url.ts (47 of 60 expected output lines today; 77 diff lines).
  • The toJSON: Date bug shares its root cause with typeof process returns "number" instead of "object" (default-import + global) #623 (NaN-box tag dispatch picking the wrong type for an unrecognized object kind).
  • The [object Object] in toString() is a similar pattern — Perry's value-to-string falls through to a generic Object renderer instead of consulting the URL prototype's own toString.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions