Refuse the endpoint spellings WHATWG URL repairs and System.Uri does not
`isLoopbackEndpoint` handed the raw endpoint to `URL` and read the host it got back. `URL` is a repairing parser and `System.Uri` is not, so seven spellings were loopback here and remote in C#: a trailing dot on the host, which `URL` drops; `ws:127.0.0.1`, `ws:/127.0.0.1` and `ws:///127.0.0.1`, whose authority `URL` infers; a backslash path separator, which `URL` reads as a separator; fullwidth digits and letters in the host, which `URL` folds to ASCII; and a tab in the host, which `URL` deletes. Each of those is a local-development bypass: a route the C# consumer would have demanded a certificate for opened unsigned here. `refusedByCSharpUriShape` refuses exactly those, on the raw string, before `URL` sees it. Leading and trailing C0 controls and spaces around the whole endpoint are stripped first, because both sides accept them. The spellings Hands probed earlier and C# accepts -- `127.1`, hex, decimal, `[::FFFF:7F00:1]`, userinfo, `localhost:8443` -- are untouched. The doc comment claimed equivalence with `System.Uri.IsLoopback`. It is not equivalent and now says so: two divergences remain, both unreachable from a browser CultMesh route, and both named. An IPv6 zone id throws in `URL` and is a host-local interface name no browser socket can dial; `file:` and `mailto:` have no `URL` host and are refused by `openSocket`, which takes `ws:` and `wss:` only. The test is Soul's endpoint list with the C# answer as the expectation: 82 of the 88 it probed, the six unreachable ones named in the doc comment instead. Mutations: the trailing-dot refusal dropped, the scheme-and-authority shape inverted, the backslash refusal dropped, and the non-ASCII host refusal dropped -- each killed by the table. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
c4c2330805
commit
803ee12a7a
3 changed files with 172 additions and 22 deletions
|
|
@ -296,6 +296,40 @@ function ownedBytes(view: Uint8Array): ArrayBuffer {
|
|||
return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength) as ArrayBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* `System.Uri` refuses spellings that WHATWG `URL` repairs, and a repaired
|
||||
* spelling would be loopback here and not in C#. This is that refusal, on the
|
||||
* raw string, before `URL` sees it: the scheme must be followed by `//` and a
|
||||
* non-empty authority, the string carries no backslash, and the host is ASCII,
|
||||
* free of whitespace, and does not end in a dot.
|
||||
*
|
||||
* Each clause answers one spelling Soul found `System.Uri` refusing and `URL`
|
||||
* accepting on 2026-09-16: `ws://127.0.0.1.` (the trailing dot, which `URL`
|
||||
* drops), `ws:127.0.0.1`, `ws:/127.0.0.1` and `ws:///127.0.0.1` (the missing or
|
||||
* empty authority, which `URL` infers), `ws://127.0.0.1\mesh` (the backslash,
|
||||
* which `URL` reads as a path separator), `ws://127.0.0.1` and
|
||||
* `ws://localhost` (fullwidth forms, which `URL` folds to ASCII), and
|
||||
* `ws://localhost\t/mesh` (the tab, which `URL` deletes).
|
||||
*
|
||||
* Leading and trailing C0 control characters and spaces around the whole
|
||||
* endpoint are stripped first, because `URL` strips them and `System.Uri`
|
||||
* accepts them: " ws://127.0.0.1/mesh" is loopback to both.
|
||||
*/
|
||||
function refusedByCSharpUriShape(value: string): boolean {
|
||||
const raw = value.replace(/^[ | ||||