Skip to content

Commit 7abcecd

Browse files
committed
fix(media): anchor sanitizeMimeType regex (#9795)
1 parent 474b08b commit 7abcecd

2 files changed

Lines changed: 46 additions & 2 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { describe, expect, it } from "vitest";
2+
import { sanitizeMimeType } from "./apply.js";
3+
4+
describe("sanitizeMimeType", () => {
5+
it("accepts a plain type/subtype", () => {
6+
expect(sanitizeMimeType("text/plain")).toBe("text/plain");
7+
expect(sanitizeMimeType("application/json")).toBe("application/json");
8+
});
9+
10+
it("lowercases and trims the input", () => {
11+
expect(sanitizeMimeType(" Text/Plain ")).toBe("text/plain");
12+
expect(sanitizeMimeType("IMAGE/PNG")).toBe("image/png");
13+
});
14+
15+
it("strips RFC 7231 parameters while keeping the type/subtype", () => {
16+
expect(sanitizeMimeType("text/plain; charset=utf-8")).toBe("text/plain");
17+
expect(sanitizeMimeType("application/json;charset=utf-8")).toBe("application/json");
18+
expect(sanitizeMimeType("text/plain ; boundary=abc")).toBe("text/plain");
19+
});
20+
21+
it("rejects inputs with invalid characters after the subtype (regression for #9795)", () => {
22+
expect(sanitizeMimeType("text/plain<script>alert(1)</script>")).toBeUndefined();
23+
expect(sanitizeMimeType("text/plain garbage")).toBeUndefined();
24+
expect(sanitizeMimeType("text/plain\nContent-Type: text/html")).toBeUndefined();
25+
});
26+
27+
it("rejects inputs missing a type or subtype", () => {
28+
expect(sanitizeMimeType("textplain")).toBeUndefined();
29+
expect(sanitizeMimeType("/plain")).toBeUndefined();
30+
expect(sanitizeMimeType("text/")).toBeUndefined();
31+
});
32+
33+
it("returns undefined for blank or missing input", () => {
34+
expect(sanitizeMimeType(undefined)).toBeUndefined();
35+
expect(sanitizeMimeType("")).toBeUndefined();
36+
expect(sanitizeMimeType(" ")).toBeUndefined();
37+
});
38+
});

src/media-understanding/apply.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,18 @@ const TEXT_EXT_MIME = new Map<string, string>([
7474
[".xml", "application/xml"],
7575
]);
7676

77-
function sanitizeMimeType(value?: string): string | undefined {
77+
// Exported for direct testing; kept narrow since this is the single MIME
78+
// validator for media-understanding inputs.
79+
export function sanitizeMimeType(value?: string): string | undefined {
7880
const trimmed = normalizeOptionalLowercaseString(value);
7981
if (!trimmed) {
8082
return undefined;
8183
}
82-
const match = trimmed.match(/^([a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+)/);
84+
// Anchor both ends; allow optional RFC 7231 parameters after the type/subtype.
85+
// Trailing garbage (e.g. `text/plain<script>` or `text/plain\ngarbage`) is
86+
// rejected outright instead of silently truncated. Input is already
87+
// lowercased by normalizeOptionalLowercaseString.
88+
const match = trimmed.match(/^([a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+)(?:\s*;.*)?$/);
8389
return match?.[1];
8490
}
8591

0 commit comments

Comments
 (0)