fix: reject control characters in manifest URLs
This commit is contained in:
@@ -95,6 +95,17 @@ const validCatalog = (): Record<string, unknown> => ({
|
||||
apps: [{ manifest: "./apps/example/toolbox-app.json", enabled: true }],
|
||||
});
|
||||
|
||||
const asciiUrlControls = [
|
||||
...Array.from({ length: 0x20 }, (_, codePoint) => codePoint),
|
||||
0x7f,
|
||||
].map(
|
||||
(codePoint) =>
|
||||
[
|
||||
`U+${codePoint.toString(16).toUpperCase().padStart(4, "0")}`,
|
||||
String.fromCodePoint(codePoint),
|
||||
] as const,
|
||||
);
|
||||
|
||||
describe("canonical schema and runtime parser parity", () => {
|
||||
const acceptedApps: Array<[string, () => Record<string, unknown>]> = [
|
||||
["the baseline app", validApp],
|
||||
@@ -170,6 +181,48 @@ describe("canonical schema and runtime parser parity", () => {
|
||||
expectParity(validateApp, parseToolboxApp, fixture(), false);
|
||||
});
|
||||
|
||||
it.each(asciiUrlControls)(
|
||||
"rejects %s embedded in an app URL reference",
|
||||
(_label, control) => {
|
||||
expectParity(
|
||||
validateApp,
|
||||
parseToolboxApp,
|
||||
{ ...validApp(), entry: `./before${control}after` },
|
||||
false,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["TAB", "icon", "\t"],
|
||||
["LF", "privacy URL", "\n"],
|
||||
["CR", "repository URL", "\r"],
|
||||
["DEL", "action URL", "\u007f"],
|
||||
] as const)("rejects %s in an app %s", (_name, field, control) => {
|
||||
const fixture = validApp();
|
||||
if (field === "icon") fixture.icon = `./icon${control}.svg`;
|
||||
if (field === "privacy URL") {
|
||||
fixture.privacy = {
|
||||
processing: "local",
|
||||
fileUploads: false,
|
||||
telemetry: false,
|
||||
url: `./privacy${control}.html`,
|
||||
};
|
||||
}
|
||||
if (field === "repository URL") {
|
||||
fixture.source = {
|
||||
repository: `https://example.test/source${control}code`,
|
||||
license: "MIT",
|
||||
};
|
||||
}
|
||||
if (field === "action URL") {
|
||||
fixture.actions = [
|
||||
{ id: "docs", label: "Docs", url: `./docs${control}.html` },
|
||||
];
|
||||
}
|
||||
expectParity(validateApp, parseToolboxApp, fixture, false);
|
||||
});
|
||||
|
||||
const acceptedCatalogs: Array<[string, () => Record<string, unknown>]> = [
|
||||
["the baseline catalog", validCatalog],
|
||||
[
|
||||
@@ -258,4 +311,45 @@ describe("canonical schema and runtime parser parity", () => {
|
||||
it.each(rejectedCatalogs)("rejects %s", (_label, fixture) => {
|
||||
expectParity(validateCatalog, parseToolboxCatalog, fixture(), false);
|
||||
});
|
||||
|
||||
it.each(asciiUrlControls)(
|
||||
"rejects %s embedded in a catalog URL reference",
|
||||
(_label, control) => {
|
||||
expectParity(
|
||||
validateCatalog,
|
||||
parseToolboxCatalog,
|
||||
{ ...validCatalog(), home: `./before${control}after` },
|
||||
false,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["TAB", "manifest", "\t"],
|
||||
["LF", "external entry", "\n"],
|
||||
["CR", "home", "\r"],
|
||||
["DEL", "manifest", "\u007f"],
|
||||
] as const)("rejects %s in a catalog %s URL", (_name, field, control) => {
|
||||
const fixture = validCatalog();
|
||||
if (field === "home") fixture.home = `./home${control}page`;
|
||||
if (field === "manifest") {
|
||||
fixture.apps = [
|
||||
{
|
||||
manifest: `./apps/example${control}/toolbox-app.json`,
|
||||
enabled: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
if (field === "external entry") {
|
||||
fixture.apps = [
|
||||
{
|
||||
name: "External",
|
||||
entry: `https://example.test/tool${control}page`,
|
||||
launch: "new-tab",
|
||||
enabled: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
expectParity(validateCatalog, parseToolboxCatalog, fixture, false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user