mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 14:55:37 +00:00
fix(export): harden post-merge file handling
This commit is contained in:
@@ -40,6 +40,7 @@ async function makeTempDir() {
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
vi.restoreAllMocks();
|
||||
await Promise.allSettled(
|
||||
tempDirs.splice(0).map((dir) => fs.rm(dir, { force: true, recursive: true })),
|
||||
);
|
||||
@@ -59,4 +60,29 @@ describe("moveExportedTempFile", () => {
|
||||
);
|
||||
await expect(fs.access(tempPath)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("falls back when Windows reports the destination already exists during initial rename", async () => {
|
||||
const dir = await makeTempDir();
|
||||
const tempPath = path.join(dir, "export-temp.mp4");
|
||||
const destinationPath = path.join(dir, "export-final.mp4");
|
||||
await fs.writeFile(tempPath, "new-export");
|
||||
await fs.writeFile(destinationPath, "previous-export");
|
||||
|
||||
const originalRename = fs.rename.bind(fs);
|
||||
const renameSpy = vi.spyOn(fs, "rename");
|
||||
renameSpy.mockImplementation(async (from, to) => {
|
||||
if (from === tempPath && to === destinationPath) {
|
||||
const error = new Error("destination exists") as NodeJS.ErrnoException;
|
||||
error.code = "EEXIST";
|
||||
throw error;
|
||||
}
|
||||
|
||||
return originalRename(from, to);
|
||||
});
|
||||
|
||||
await moveExportedTempFile(tempPath, destinationPath);
|
||||
|
||||
await expect(fs.readFile(destinationPath, "utf8")).resolves.toBe("new-export");
|
||||
await expect(fs.access(tempPath)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,7 +58,12 @@ export async function moveExportedTempFile(tempPath: string, destinationPath: st
|
||||
return;
|
||||
} catch (error) {
|
||||
const code = (error as NodeJS.ErrnoException).code;
|
||||
if (code !== "EXDEV" && code !== "EPERM" && code !== "ENOTEMPTY") {
|
||||
if (
|
||||
code !== "EXDEV" &&
|
||||
code !== "EPERM" &&
|
||||
code !== "ENOTEMPTY" &&
|
||||
code !== "EEXIST"
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
// Cross-device or Windows permission quirks — fall back to copy + unlink so
|
||||
@@ -143,6 +148,10 @@ async function resolveAllowedReadableFilePath(
|
||||
}
|
||||
|
||||
const resolvedPath = path.resolve(filePath);
|
||||
if (!isAllowedLocalReadPath(resolvedPath)) {
|
||||
throw new Error(`${label} is not approved for local reads`);
|
||||
}
|
||||
|
||||
const realPath = await fs.realpath(resolvedPath).catch(() => null);
|
||||
if (!realPath) {
|
||||
throw new Error(`${label} does not exist`);
|
||||
|
||||
Reference in New Issue
Block a user