fix: keep a pct-encoded UTF-8 sequence whole, and bound the preview too
Addresses Copilot's round-16 review on #2035. Two suppressed comments, both
real, and both refinements of fixes from the previous two rounds rather than
defects in the original code.
1. Round 11 settled on "a pct-encoded triplet counts as one character", but
a multi-octet character spans several triplets -- `%C3%A9` is one `é`.
Measured on a3dd8563:
x{+v:1} v = "%C3%A9x" => "x%C3" a lone lead byte, decoding to nothing
`prefixUnits` now recognizes a well-formed pct-encoded UTF-8 sequence from
its lead byte (RFC 3629: C2-DF two octets, E0-EF three, F0-F4 four;
continuations 80-BF), longest alternative first so a four-octet sequence
is not read as a two-octet one followed by loose triplets.
A malformed sequence -- a lead byte with too few continuations, or a stray
continuation -- matches no sequence alternative and falls back to
per-triplet counting. That is the conservative direction: it counts as
MORE units, so truncation keeps less rather than emitting octets that
were never in the value.
2. Round 14's per-value ceiling only covered the read. The panel calls
`previewUriTemplate` on every keystroke, and that path expands expression
by expression, so it reached the allocation-heavy encoders with the same
oversized value -- submission refused while the UI froze on the paste
anyway. Both paths now share one exported `valueLengthError`, checked
before any encoding happens.
npm run ci passes.
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
a3dd856380
commit
7d9410cbe0
2 changed files with 102 additions and 13 deletions
|
|
@ -126,3 +126,19 @@ describe("previewUriTemplate - it must never throw or over-promise", () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("previewUriTemplate - the value ceiling", () => {
|
||||
it("falls back before encoding an oversized value", () => {
|
||||
// Without this the guard was half a guard: the read was refused while the
|
||||
// preview still encoded the same value on every keystroke, so a large
|
||||
// paste froze the UI anyway.
|
||||
expect(previewUriTemplate("x://{v}", { v: "a".repeat(1_000_001) })).toBe(
|
||||
"x://{v}",
|
||||
);
|
||||
});
|
||||
|
||||
it("still previews a value at the limit", () => {
|
||||
const value = "a".repeat(1_000_000);
|
||||
expect(previewUriTemplate("x://{v}", { v: value })).toBe(`x://${value}`);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
encodeLiteral,
|
||||
expandTemplateExpression,
|
||||
parseUriTemplate,
|
||||
valueLengthError,
|
||||
} from "@inspector/core/mcp/uriTemplate.js";
|
||||
|
||||
export {
|
||||
|
|
@ -27,6 +28,7 @@ export {
|
|||
templateVariables,
|
||||
tryExpandUriTemplate,
|
||||
unmetRequiredGroups,
|
||||
valueLengthError,
|
||||
} from "@inspector/core/mcp/uriTemplate.js";
|
||||
export type {
|
||||
TemplateExpansion,
|
||||
|
|
@ -71,6 +73,12 @@ export function previewUriTemplate(
|
|||
): string {
|
||||
const defined = definedValues(values);
|
||||
|
||||
// The same ceiling `tryExpandUriTemplate` enforces for the read. Without it
|
||||
// the guard was half a guard: submission was refused while this path still
|
||||
// handed the oversized value to the encoders on every keystroke, so a large
|
||||
// paste froze the UI anyway. Checked before any encoding happens.
|
||||
if (valueLengthError(defined) !== null) return uriTemplate;
|
||||
|
||||
try {
|
||||
return parseUriTemplate(uriTemplate)
|
||||
.map((part) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue