fix(reference): preserve historical backslash import bases

This commit is contained in:
morluto
2026-10-24 07:43:56 +08:00
parent c2455152c9
commit 5940600a22
4 changed files with 53 additions and 3 deletions
+7
View File
@@ -505,6 +505,13 @@ by file order. Omit `--evidence-bundle` when the request supplies inline Evidenc
## Import historical source
Historical source inventories preserve exact filenames, including literal backslashes
on POSIX hosts. Relative import lookup uses the recorded source file's owning
directory and decodes URL percent escapes once; encoded slash, NUL and malformed
escapes remain unresolved. A decoded backslash remains part of a filename rather
than a directory separator. This inventory lookup does not claim that Node can
load that filename or apply Node's runtime file-URL admission policy.
Import an older source tree to compare with the current artifact:
```bash
@@ -3,10 +3,10 @@ import { createHash } from "node:crypto";
import { posix } from "node:path";
import type { JavaScriptPackageType } from "../domain/javascript/javascriptSourceParser.js";
import { decodeJavaScriptModuleUrlPath } from "../domain/javascript/javascriptModuleSpecifier.js";
import { logicalPathEscapesRoot } from "../domain/artifactIdentity.js";
import {
createReferenceSourcePathLookup,
decodeReferenceSourceModuleUrlPath,
isPortableAbsoluteReferenceTarget,
type ReferenceSourcePathLookup,
} from "../domain/referenceSourcePathIdentity.js";
@@ -322,7 +322,7 @@ const importLookupPath = (
);
if (!target.pathname.startsWith("/rea-reference/"))
return err("The module URL escapes the selected source inventory.");
const decoded = decodeJavaScriptModuleUrlPath(
const decoded = decodeReferenceSourceModuleUrlPath(
target.pathname.slice("/rea-reference/".length),
);
return decoded.ok
+22 -1
View File
@@ -1,6 +1,9 @@
import { expect, it } from "vitest";
import { createReferenceSourcePathLookup } from "./referenceSourcePathIdentity.js";
import {
createReferenceSourcePathLookup,
decodeReferenceSourceModuleUrlPath,
} from "./referenceSourcePathIdentity.js";
it("reports ambiguous canonical identities while preserving exact paths", () => {
const first = "package/a\u0301\u0323/package.json";
@@ -13,3 +16,21 @@ it("reports ambiguous canonical identities while preserving exact paths", () =>
expect(lookup.resolve(first)).toBe(first);
expect(lookup.resolve(second)).toBe(second);
});
it.each([
["literal%5Cdir/dep.js", "literal\\dir/dep.js"],
["literal%255Cdir/dep.js", "literal%5Cdir/dep.js"],
["literal%252Fdir/dep.js", "literal%2Fdir/dep.js"],
])("decodes historical inventory bytes once: %s", (encoded, path) => {
expect(decodeReferenceSourceModuleUrlPath(encoded)).toEqual({
ok: true,
value: path,
});
});
it.each(["dir%2Fdep.js", "dir%2fdep.js", "dir/%00dep.js", "dir/%invalid.js"])(
"refuses malformed historical module paths: %s",
(encoded) => {
expect(decodeReferenceSourceModuleUrlPath(encoded).ok).toBe(false);
},
);
+22
View File
@@ -1,4 +1,5 @@
import { posix } from "node:path";
import { err, ok, type Result } from "./result.js";
export interface ReferenceSourcePathLookup {
/** Whether an exact or canonical identity exists, including ambiguous ones. */
@@ -53,3 +54,24 @@ export const isPortableAbsoluteReferenceTarget = (target: string): boolean =>
target.startsWith("/") ||
/^[A-Za-z]:[\\/]/u.test(target) ||
/^\\\\[^\\/]+[\\/][^\\/]+/u.test(target);
/** Decode one historical inventory URL path; backslash remains a filename byte. */
export const decodeReferenceSourceModuleUrlPath = (
value: string,
): Result<string, string> => {
if (/%2f/iu.test(value))
return err(
"Historical module URL paths cannot contain percent-encoded slash bytes.",
);
try {
const decoded = decodeURIComponent(value);
return decoded.includes("\0")
? err("The decoded historical module URL path contains NUL.")
: ok(decoded);
} catch (cause: unknown) {
if (!(cause instanceof URIError)) throw cause;
return err(
"The historical module URL path contains malformed percent encoding.",
);
}
};