53 lines
2.0 KiB
TypeScript
53 lines
2.0 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import path from "node:path";
|
|
import { resolveSafeI18nSectionDir } from "../../../src/lib/docsI18nPath.ts";
|
|
|
|
const DOCS_ROOT = path.resolve("/srv/app", "docs");
|
|
const I18N_ROOT = path.join(DOCS_ROOT, "i18n");
|
|
|
|
test("resolveSafeI18nSectionDir rejects traversal in the locale (cookie-controlled)", () => {
|
|
const badLocales = ["..", "../..", "/etc", "es/../../etc", "es\0", "..%2f", "en.", "es/x"];
|
|
for (const locale of badLocales) {
|
|
assert.strictEqual(
|
|
resolveSafeI18nSectionDir(DOCS_ROOT, locale, ["overview"]),
|
|
null,
|
|
`Should reject bad locale: ${JSON.stringify(locale)}`
|
|
);
|
|
}
|
|
});
|
|
|
|
test("resolveSafeI18nSectionDir rejects traversal in slug segments", () => {
|
|
const badSlugs = [[".."], ["..", "foo"], ["/etc", "passwd"], ["foo", "..", "bar"], ["a/b"]];
|
|
for (const slug of badSlugs) {
|
|
assert.strictEqual(
|
|
resolveSafeI18nSectionDir(DOCS_ROOT, "pt-BR", slug),
|
|
null,
|
|
`Should reject bad slug: ${slug.join("/")}`
|
|
);
|
|
}
|
|
});
|
|
|
|
test("resolveSafeI18nSectionDir confines the resolved dir to docs/i18n", () => {
|
|
// Valid input → a dir strictly under i18nRoot.
|
|
const ok = resolveSafeI18nSectionDir(DOCS_ROOT, "pt-BR", ["architecture", "overview"]);
|
|
assert.ok(ok, "valid locale+slug should resolve");
|
|
assert.ok(
|
|
ok === I18N_ROOT || ok.startsWith(I18N_ROOT + path.sep),
|
|
`resolved dir must stay under i18nRoot, got ${ok}`
|
|
);
|
|
assert.strictEqual(ok, path.join(I18N_ROOT, "pt-BR", "docs", "architecture"));
|
|
});
|
|
|
|
test("resolveSafeI18nSectionDir allows valid locale/slug patterns", () => {
|
|
const goodLocales = ["en", "pt-BR", "zh-CN", "es"];
|
|
for (const locale of goodLocales) {
|
|
assert.ok(
|
|
resolveSafeI18nSectionDir(DOCS_ROOT, locale, ["getting-started"]),
|
|
`Should allow good locale: ${locale}`
|
|
);
|
|
}
|
|
assert.ok(resolveSafeI18nSectionDir(DOCS_ROOT, "es", ["v1-migration"]));
|
|
assert.strictEqual(resolveSafeI18nSectionDir(DOCS_ROOT, "es", []), null, "empty slug → null");
|
|
});
|