mirror of
https://github.com/abue-ammar/tinycast.git
synced 2026-10-02 08:14:38 +08:00
Validate URL schemes in fs path arguments like Node (#713)
fsPath degraded any URL to its decoded pathname, so fs.existsSync accepted a vscode-remote:// workspace whose stripped pathname exists locally — an SSH host opened at / always does — and Raycast's Visual Studio Code extension handed that URI to fileURLToPath, crashing Search Recent Projects with "The URL must be of scheme file". URL arguments now go through fileURLToPath: a non-file scheme throws ERR_INVALID_URL_SCHEME like Node, existsSync counts that as absence, and the remote entries render as remote items.
This commit is contained in:
@@ -170,6 +170,7 @@ export default function Command() {
|
||||
const nodeSource = `
|
||||
import path from "node:path";
|
||||
import os from "node:os";
|
||||
import fs from "node:fs";
|
||||
import crypto from "node:crypto";
|
||||
import { Buffer } from "node:buffer";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
@@ -236,6 +237,15 @@ export default function Command() {
|
||||
errorCode(() => fileURLToPath("file://user@localhost/tmp/a")),
|
||||
errorCode(() => fileURLToPath("file://localhost:/tmp/a")),
|
||||
errorCode(() => fileURLToPath("file:///C:/a", { windows: true })),
|
||||
// fs validates URL schemes the way Node does: a vscode-remote:// workspace URI whose stripped
|
||||
// pathname exists locally ("/" always does) must not pass existsSync — Raycast's Search Recent
|
||||
// Projects relies on that guard before handing the URI to fileURLToPath.
|
||||
String(fs.existsSync(new URL("vscode-remote://ssh-remote%2Bucg/"))),
|
||||
String(fs.existsSync(new URL("vscode-remote://ssh-remote%2Bserver/etc/docker/daemon.json"))),
|
||||
String(fs.existsSync(new URL("file:///etc/hosts"))),
|
||||
String(fs.existsSync("/etc/hosts")),
|
||||
errorCode(() => fs.statSync(new URL("https://example.com/a"))),
|
||||
errorCode(() => fs.readFileSync(new URL("https://example.com/a"))),
|
||||
];
|
||||
return <Detail markdown={parts.join("\\n")} />;
|
||||
}
|
||||
@@ -744,6 +754,12 @@ export async function runFixtures() {
|
||||
"ERR_INVALID_URL",
|
||||
"ERR_INVALID_URL",
|
||||
"Error",
|
||||
"false",
|
||||
"false",
|
||||
"true",
|
||||
"true",
|
||||
"ERR_INVALID_URL_SCHEME",
|
||||
"ERR_INVALID_URL_SCHEME",
|
||||
];
|
||||
expected.forEach((value, index) => check(`shim ${index}: ${value}`, markdown[index] === value, markdown[index]));
|
||||
});
|
||||
|
||||
@@ -330,7 +330,10 @@ class Dirent {
|
||||
}
|
||||
|
||||
function fsPath(input) {
|
||||
if (input instanceof URL) return decodeURIComponent(input.pathname);
|
||||
// Node validates URL inputs through fileURLToPath: a non-file scheme (say a VS Code
|
||||
// vscode-remote:// workspace URI) must throw ERR_INVALID_URL_SCHEME rather than quietly
|
||||
// degrading to its pathname — extensions like Search Recent Projects guard on that failure.
|
||||
if (input instanceof URL) return fileURLToPath(input);
|
||||
if (input instanceof Uint8Array) return utf8Decode(input);
|
||||
return String(input);
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -586,6 +586,13 @@ host rather than returning a wrong path. Node's `windows` override is absent: Ti
|
||||
macOS, so drive-letter and UNC output would be unreachable. `url.pathToFileURL` escapes `?` and `#`
|
||||
so a filename holding either survives the round trip.
|
||||
|
||||
The `fs` functions hand URL arguments to that same validator: a URL whose scheme is not `file:`
|
||||
throws `ERR_INVALID_URL_SCHEME` instead of degrading to its pathname, and `fs.existsSync` counts
|
||||
that as absence, like Node. Raycast's Visual Studio Code extension leans on the guard — a
|
||||
`vscode-remote://` workspace whose stripped pathname exists locally (an SSH host opened at `/`
|
||||
always does) would otherwise pass `isFolderEntry` and reach `fileURLToPath`, which took the whole
|
||||
Search Recent Projects command down.
|
||||
|
||||
A bundle that ships its own HTTP client rather than calling `fetch` — node-fetch travels inside
|
||||
`@raycast/utils`, and axios has a Node adapter — reaches the network through `http.request`, so the
|
||||
shim answers it: one request when the body ends, one response chunk when the bridge replies. The
|
||||
|
||||
Reference in New Issue
Block a user