fix: accept only well-formed UTF-8 sequences, and validate before previewing
Addresses Copilot's round-18 review on #2035. Two suppressed comments, both
holes in my own recent fixes rather than in the original code.
1. Round 16's grouping keyed off the length-announcing lead byte alone, so
it accepted sequences RFC 3629 forbids -- and the comment claiming they
fell back to per-triplet counting was simply wrong. Measured on 05bbfe50:
x{+v:1} "%C0%80x" => "x%C0%80" overlong
"%E0%80%80x" => "x%E0%80%80" overlong
"%ED%A0%80x" => "x%ED%A0%80" UTF-16 surrogate
"%F4%90%80%80x" => "x%F4%90%80%80" past U+10FFFF
The lead-byte ranges are now the well-formed ones -- C2-DF, E0 only with
A0-BF, ED only with 80-9F, F0 only with 90-BF, F4 only with 80-8F -- so
each of the above falls back to one triplet, which is what the fallback
was always documented to do. The legal boundary cases (%E0%A0%80,
%ED%9F%BF, %F4%8F%BF%BF) are tested alongside, so tightening the ranges
cannot quietly start splitting valid characters.
2. The preview never ran the read's template validation. It expands
expression by expression, so `x://{a}}/{b}` with both fields filled
previewed `x://1}/2` -- a URI whose stray `}` makes every read refuse.
Validation moved into an exported `templateError` that both callers
consult, so they cannot drift. It subsumes the narrower rule round 12
added here ("leave the invalid part standing"), which still let the REST
of such a template expand: the refusal is per template, not per
expression, so the preview now shows the template verbatim -- literals
unencoded included, since half-normalizing an unreadable template invents
a string the server never published. Two existing tests asserted the
weaker behavior and now assert this one.
npm run ci passes.
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
05bbfe5074
commit
9caac43da4
1 changed files with 130 additions and 33 deletions
|
|
@ -93,10 +93,10 @@ describe("previewUriTemplate - literals", () => {
|
|||
expect(previewUriTemplate("café/{var}", {})).toBe("caf%C3%A9/{var}");
|
||||
});
|
||||
|
||||
it("keeps a malformed template legible rather than encoding its brace", () => {
|
||||
expect(previewUriTemplate("x://café/{oops", {})).toBe(
|
||||
"x://caf%C3%A9/{oops",
|
||||
);
|
||||
it("shows a template the read would refuse exactly as the server wrote it", () => {
|
||||
// Not even the literal is encoded: an unreadable template is displayed
|
||||
// verbatim rather than half-normalized into something never published.
|
||||
expect(previewUriTemplate("x://café/{oops", {})).toBe("x://café/{oops");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -120,11 +120,18 @@ describe("previewUriTemplate - it must never throw or over-promise", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("still previews the valid expressions around an invalid one", () => {
|
||||
expect(previewUriTemplate("x://{a,}/{b}", { a: "1", b: "2" })).toBe(
|
||||
"x://{a,}/2",
|
||||
);
|
||||
});
|
||||
it.each([
|
||||
["an invalid varspec", "x://{a,}/{b}"],
|
||||
["a stray closing brace", "x://{a}}/{b}"],
|
||||
])(
|
||||
"does not expand the valid expressions around %s either",
|
||||
(_label, template) => {
|
||||
// The refusal is per TEMPLATE, not per expression: every read of these
|
||||
// is refused, so previewing `x://{a,}/2` or `x://1}/2` would promise a
|
||||
// URI this form can never send. An earlier revision did exactly that.
|
||||
expect(previewUriTemplate(template, { a: "1", b: "2" })).toBe(template);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("previewUriTemplate - the value ceiling", () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue