Archived
Handle missing selected workspace files
This commit is contained in:
@@ -50,13 +50,26 @@ export class FileExplorerController {
|
||||
if (project === undefined || workspace === undefined) return;
|
||||
this.setState({ selectedFilePath: path, selectedFileContent: undefined });
|
||||
try {
|
||||
this.setState({ selectedFileContent: await api.workspaceFile(project.id, workspace.id, path), error: "" });
|
||||
const content = await api.workspaceFile(project.id, workspace.id, path);
|
||||
if (this.getState().selectedFilePath === path) this.setState({ selectedFileContent: content, error: "" });
|
||||
} catch (error) {
|
||||
if (this.getState().selectedFilePath !== path) return;
|
||||
if (isUnavailableFileError(error)) {
|
||||
this.setState({ selectedFilePath: undefined, selectedFileContent: undefined, error: "" });
|
||||
setNamespacedQueryKey(FILES_ROUTE_NAMESPACE, "file", undefined, { replace: true });
|
||||
this.updateUrl({ replace: true });
|
||||
return;
|
||||
}
|
||||
this.setState({ error: String(error) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isUnavailableFileError(error: unknown): boolean {
|
||||
const message = String(error);
|
||||
return message.includes("Path does not exist") || message.includes("ENOENT") || message.includes("no such file or directory");
|
||||
}
|
||||
|
||||
function omitKey<T>(record: Record<string, T>, keyToOmit: string): Record<string, T> {
|
||||
return Object.fromEntries(Object.entries(record).filter(([key]) => key !== keyToOmit));
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ describe("readWorkspaceFile", () => {
|
||||
|
||||
await expect(readWorkspaceFile(root, undefined)).rejects.toThrow("path query parameter is required");
|
||||
await expect(readWorkspaceFile(root, "dir")).rejects.toThrow("Path is not a file");
|
||||
await expect(readWorkspaceFile(root, "missing.txt")).rejects.toThrow("Path does not exist");
|
||||
await expect(readWorkspaceFile(root, "../secret.txt")).rejects.toThrow("Path traversal is not allowed");
|
||||
await expect(readWorkspaceFile(root, "/etc/passwd")).rejects.toThrow("Absolute paths are not allowed");
|
||||
});
|
||||
|
||||
@@ -58,6 +58,7 @@ describe("listWorkspaceTree", () => {
|
||||
await writeFile(join(root, "file.txt"), "content");
|
||||
|
||||
await expect(listWorkspaceTree(root, "file.txt")).rejects.toThrow("Path is not a directory");
|
||||
await expect(listWorkspaceTree(root, "missing-dir")).rejects.toThrow("Path does not exist");
|
||||
await expect(listWorkspaceTree(root, "../outside")).rejects.toThrow("Path traversal is not allowed");
|
||||
await expect(listWorkspaceTree(root, "/tmp")).rejects.toThrow("Absolute paths are not allowed");
|
||||
});
|
||||
|
||||
@@ -5,7 +5,10 @@ export async function resolveInsideWorkspace(rootPath: string, relativePath: str
|
||||
const requested = normalizeRelativePath(relativePath);
|
||||
const root = await realpath(rootPath);
|
||||
const joined = join(root, requested);
|
||||
const target = await realpath(joined);
|
||||
const target = await realpath(joined).catch((error: unknown) => {
|
||||
if (isNodeErrorWithCode(error, "ENOENT")) throw new Error("Path does not exist");
|
||||
throw error;
|
||||
});
|
||||
ensureInside(root, target);
|
||||
return { root, target, relativePath: requested };
|
||||
}
|
||||
@@ -27,6 +30,10 @@ export function normalizeRelativePath(input: string | undefined): string {
|
||||
return parts.join("/");
|
||||
}
|
||||
|
||||
function isNodeErrorWithCode(error: unknown, code: string): error is NodeJS.ErrnoException {
|
||||
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
||||
}
|
||||
|
||||
function ensureInside(root: string, target: string): void {
|
||||
const rel = relative(root, target);
|
||||
if (rel === "") return;
|
||||
|
||||
Reference in New Issue
Block a user