mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(browser): preserve media type parameter spelling (#6985)
* fix(browser): preserve media type parameter spelling * test(verifiers): follow shared command output and identity
This commit is contained in:
1 parent
1ca7d38764
commit
85bc8c8977
2 files changed
+80
-2
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { MIMEType } from "node:util";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { safeResponseMetadata } from "./CdpSafeMetadata.js";
|
||||
@@ -69,6 +70,70 @@ describe("structured COOP and COEP declarations", () => {
|
||||
);
|
||||
});
|
||||
|
||||
describe("media type parameter preservation", () => {
|
||||
it.each([
|
||||
[
|
||||
'Multipart/Mixed; boundary="AaB03x"',
|
||||
'multipart/mixed; boundary="AaB03x"',
|
||||
],
|
||||
[
|
||||
'Application/LD+JSON; profile="https://api.example/Profiles/Book;V=One,Two"',
|
||||
'application/ld+json; profile="https://api.example/Profiles/Book;V=One,Two"',
|
||||
],
|
||||
[
|
||||
'Application/X-Protobuf; type="Example.Message"',
|
||||
'application/x-protobuf; type="Example.Message"',
|
||||
],
|
||||
])(
|
||||
"preserves case-sensitive media type parameters in %s",
|
||||
(raw, expected) => {
|
||||
const linkType = raw.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
|
||||
const captured = safeResponseMetadata(
|
||||
"request-media-type",
|
||||
`${origin}/page`,
|
||||
{
|
||||
mimeType: ` ${raw} `,
|
||||
headers: {
|
||||
"Content-Encoding": " GZip ",
|
||||
Link: `</resource>; rel="PreLoad MCP"; type="${linkType}"; as=SCRIPT; crossorigin=ANONYMOUS`,
|
||||
},
|
||||
},
|
||||
new Set([origin]),
|
||||
);
|
||||
|
||||
for (const projected of [
|
||||
captured.response.mime_type,
|
||||
captured.response.links[0]?.type,
|
||||
]) {
|
||||
expect(projected).toBe(expected);
|
||||
expect([...new MIMEType(projected ?? "").params]).toEqual([
|
||||
...new MIMEType(raw).params,
|
||||
]);
|
||||
}
|
||||
expect(captured.response.content_encoding).toBe("gzip");
|
||||
expect(captured.response.links).toMatchObject([
|
||||
{
|
||||
href: `${origin}/resource`,
|
||||
destination_scope: "approved",
|
||||
context_url: `${origin}/page`,
|
||||
context_scope: "approved",
|
||||
rel: ["preload", "mcp"],
|
||||
as: "script",
|
||||
crossorigin: "anonymous",
|
||||
},
|
||||
]);
|
||||
expect(captured.agentHints).toEqual([
|
||||
{
|
||||
mechanism: "link_rel",
|
||||
declaration: "preload mcp",
|
||||
url: `${origin}/resource`,
|
||||
trust: "page-declared-untrusted",
|
||||
},
|
||||
]);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("safe CDP response metadata", () => {
|
||||
it("drops credential headers and preserves complete local link URLs", () => {
|
||||
const captured = safeResponseMetadata(
|
||||
|
||||
@@ -53,7 +53,7 @@ export const safeResponseMetadata = (
|
||||
response: {
|
||||
request_id: requestId,
|
||||
url,
|
||||
mime_type: boundedHeader(cdpStringValue(response.mimeType)),
|
||||
mime_type: mediaTypeDeclaration(cdpStringValue(response.mimeType)),
|
||||
content_length: contentLength(field("content-length")),
|
||||
content_encoding: boundedHeader(field("content-encoding")),
|
||||
csp,
|
||||
@@ -245,7 +245,9 @@ const parseLinks = (
|
||||
context_scope: context.scope,
|
||||
rel: rel.toLowerCase().split(/\s+/u).filter(Boolean),
|
||||
as: boundedHeader(lastLinkParameter(parameters, "as")?.value),
|
||||
type: boundedHeader(firstLinkParameter(parameters, "type")?.value),
|
||||
type: mediaTypeDeclaration(
|
||||
firstLinkParameter(parameters, "type")?.value,
|
||||
),
|
||||
crossorigin: boundedHeader(
|
||||
lastLinkParameter(parameters, "crossorigin")?.value,
|
||||
),
|
||||
@@ -430,6 +432,17 @@ const contentLength = (value: string | undefined): number | null => {
|
||||
const boundedHeader = (value: string | undefined): string | null =>
|
||||
value === undefined ? null : value.trim().toLowerCase();
|
||||
|
||||
// RFC 9110 section 8.3.1: type/subtype are case-insensitive; parameters may not be.
|
||||
const mediaTypeDeclaration = (value: string | undefined): string | null => {
|
||||
if (value === undefined) return null;
|
||||
const declaration = value.trim();
|
||||
const separator = declaration.indexOf(";");
|
||||
return separator < 0
|
||||
? declaration.toLowerCase()
|
||||
: declaration.slice(0, separator).toLowerCase() +
|
||||
declaration.slice(separator);
|
||||
};
|
||||
|
||||
const decodeLinkParameter = (value: string): string => {
|
||||
if (!value.startsWith('"')) return value;
|
||||
let decoded = "";
|
||||
|
||||
Reference in new issue
Block a user