fix(browser): keep malformed Content-Length declarations unknown (#2773)
* fix(browser): keep malformed Content-Length declarations unknown Response metadata normalized Content-Length with Number(), truncation and clamping, so "-5", "0x10", "1e3" and "+12" became observed lengths 0, 16, 1000 and 12. RFC 9110 section 8.6 defines the field as 1*DIGIT; any other spelling declares no length. Parse only decimal digits within the safe integer range and keep every other value null. Fixes #2596 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(browser): cover the largest accepted Content-Length Pin the safe-integer boundary beside the overflow case so a future change cannot reject the largest representable declared length unnoticed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4c79127fcb
commit
c22d7cd756
|
|
@ -289,4 +289,25 @@ describe("safe CDP response metadata referrer policy", () => {
|
|||
expect(captured.response.policies.referrer_policy).toBe(expected);
|
||||
expect(captured.response.policies.coop).toBe("same-origin");
|
||||
});
|
||||
|
||||
it.each<[string | number | undefined, number | null]>([
|
||||
["100", 100],
|
||||
[" 0 ", 0],
|
||||
[100, 100],
|
||||
["-5", null],
|
||||
["+12", null],
|
||||
["0x10", null],
|
||||
["1e3", null],
|
||||
["9007199254740991", 9007199254740991],
|
||||
["9007199254740992", null],
|
||||
[undefined, null],
|
||||
])("declares Content-Length %j only for decimal digits", (raw, expected) => {
|
||||
const captured = safeResponseMetadata(
|
||||
"request-length",
|
||||
`${origin}/api`,
|
||||
{ headers: raw === undefined ? {} : { "Content-Length": raw } },
|
||||
new Set([origin]),
|
||||
);
|
||||
expect(captured.response.content_length).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
import type { WebPageInspection } from "../domain/browserObservationSchemas.js";
|
||||
import { sanitizeBrowserUrl } from "../domain/browserObservation.js";
|
||||
import {
|
||||
numberValue,
|
||||
cdpStringValue,
|
||||
type UnknownRecord,
|
||||
} from "./CdpCaptureValues.js";
|
||||
import { cdpStringValue, type UnknownRecord } from "./CdpCaptureValues.js";
|
||||
|
||||
type ResponseMetadata = WebPageInspection["metadata"]["responses"][number];
|
||||
type LinkMetadata = ResponseMetadata["links"][number];
|
||||
|
|
@ -43,7 +39,7 @@ export const safeResponseMetadata = (
|
|||
request_id: requestId,
|
||||
url,
|
||||
mime_type: boundedHeader(cdpStringValue(response.mimeType)),
|
||||
content_length: nonnegativeInteger(headers.get("content-length")),
|
||||
content_length: contentLength(headers.get("content-length")),
|
||||
content_encoding: boundedHeader(headers.get("content-encoding")),
|
||||
csp,
|
||||
links,
|
||||
|
|
@ -343,9 +339,12 @@ const policyToken = (value: string | undefined): string | null => {
|
|||
return /^[a-z][a-z0-9_.\x2d]*$/u.test(token) ? token : null;
|
||||
};
|
||||
|
||||
const nonnegativeInteger = (value: string | undefined): number | null => {
|
||||
const number = numberValue(value === undefined ? undefined : Number(value));
|
||||
return number === undefined ? null : Math.max(0, Math.trunc(number));
|
||||
// RFC 9110 section 8.6: Content-Length = 1*DIGIT. Other spellings declare no length.
|
||||
const contentLength = (value: string | undefined): number | null => {
|
||||
const token = (value ?? "").trim();
|
||||
if (!/^[0-9]+$/u.test(token)) return null;
|
||||
const length = Number(token);
|
||||
return Number.isSafeInteger(length) ? length : null;
|
||||
};
|
||||
|
||||
const boundedHeader = (value: string | undefined): string | null =>
|
||||
|
|
|
|||
Loading…
Reference in New Issue