返回 DeepSeek-Reasonix
localFileUrl.ts
根目录 / desktop / frontend / src / lib / localFileUrl.ts
1 // Canonical parsing and validation for local file URLs used by Markdown links.
2
3 export function hasDisallowedWindowsPathSyntax(path: string): boolean {
4 const slashPath = path.replace(/\\/g, "/");
5 if (slashPath.includes("\0")) return true;
6 if (/^\/\/[.?](?:\/|$)/.test(slashPath)) return true;
7 const isDrivePath = /^[A-Za-z]:\//.test(slashPath);
8 const isUncPath = slashPath.startsWith("//");
9 if (!isDrivePath && !isUncPath) return false;
10 const remainder = slashPath.slice(2);
11 if (remainder.includes(":")) return true;
12 return remainder.split("/").some((component) => {
13 const base = component.trimEnd().replace(/\.+$/, "").split(".", 1)[0]?.toUpperCase() ?? "";
14 return /^(?:CON|PRN|AUX|NUL|CLOCK\$|CONIN\$|CONOUT\$|COM[1-9¹²³]|LPT[1-9¹²³])$/.test(base);
15 });
16 }
17
18 function hasDisallowedRawFileUrlSyntax(href: string): boolean {
19 let rawPath: string;
20 try {
21 rawPath = decodeURIComponent(href.slice("file:".length)).replace(/\\/g, "/");
22 } catch {
23 return true;
24 }
25 // URL normalisation removes dot segments. Reject Windows device authorities
26 // before constructing URL so file:////./PhysicalDrive0 cannot become the
27 // apparently ordinary //PhysicalDrive0 UNC path.
28 return /^\/\/[.?](?:\/|$)/.test(rawPath) || /^\/{4,}[.?](?:\/|$)/.test(rawPath);
29 }
30
31 /**
32 * Returns the decoded filesystem path represented by a local file URL.
33 * The scheme check is intentionally case-sensitive so `FILE:` cannot bypass
34 * the Markdown URL allowlist and reach a browser or native opener.
35 */
36 export function localPathFromHref(href?: string): string | null {
37 if (!href || !href.startsWith("file://")) return null;
38 if (hasDisallowedRawFileUrlSyntax(href)) return null;
39
40 try {
41 const url = new URL(href);
42 if (url.protocol !== "file:") return null;
43 if (url.username || url.password || url.port) return null;
44 if (url.search || url.hash) return null;
45 if (url.hostname === "." || url.hostname === "?") return null;
46
47 let path = decodeURIComponent(url.pathname);
48 if (url.hostname) path = `//${url.hostname}${path}`;
49
50 // file:///D:/... has a URL root slash that is not part of the Windows
51 // drive path. Multiple leading slashes are the slash-form UNC variant.
52 if (/^\/[A-Za-z]:\//.test(path)) path = path.slice(1);
53 if (path.startsWith("//")) path = `//${path.replace(/^\/+/, "")}`;
54 if (hasDisallowedWindowsPathSyntax(path)) return null;
55 if (/^[A-Za-z]:\//.test(path)) return path;
56 if (!path.startsWith("/")) return null;
57 return path;
58 } catch {
59 return null;
60 }
61 }
62
63 export function isLocalFileHref(href?: string): boolean {
64 return localPathFromHref(href) !== null;
65 }
66
66 lines TYPESCRIPT