mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-26 07:45:34 +00:00
fix(ipc): confine recorded video writes
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveRecordedVideoStoragePath } from "./storagePath";
|
||||
|
||||
describe("resolveRecordedVideoStoragePath", () => {
|
||||
const recordingsDir = path.resolve("recordings-root");
|
||||
|
||||
it.each([
|
||||
"recording-0.webm",
|
||||
"recording-1720588800000.mp4",
|
||||
"recording-1720588800000-webcam.webm",
|
||||
"recording-1720588800000-webcam.mp4",
|
||||
])("accepts an app-generated recording name: %s", (fileName) => {
|
||||
expect(resolveRecordedVideoStoragePath(recordingsDir, fileName)).toBe(
|
||||
path.resolve(recordingsDir, fileName),
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
"",
|
||||
"../recording-1.webm",
|
||||
"..\\recording-1.webm",
|
||||
"nested/recording-1.webm",
|
||||
"nested\\recording-1.webm",
|
||||
"/tmp/recording-1.webm",
|
||||
"C:\\temp\\recording-1.webm",
|
||||
"\\\\server\\share\\recording-1.webm",
|
||||
"recording-1.webm:payload",
|
||||
"recording-1.webm\n",
|
||||
"recording-1.webm\0",
|
||||
"recording--1.webm",
|
||||
"recording-1.5.webm",
|
||||
"recording-1.mov",
|
||||
"other-1.webm",
|
||||
"recording-1-WEBCAM.webm",
|
||||
])("rejects an untrusted recording name: %s", (fileName) => {
|
||||
expect(() => resolveRecordedVideoStoragePath(recordingsDir, fileName)).toThrow(
|
||||
"Invalid recording file name",
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ label: "undefined", value: undefined },
|
||||
{ label: "null", value: null },
|
||||
{ label: "number", value: 1 },
|
||||
{ label: "object", value: {} },
|
||||
{ label: "array", value: [] },
|
||||
])("rejects a non-string recording name: $label", ({ value }) => {
|
||||
expect(() => resolveRecordedVideoStoragePath(recordingsDir, value)).toThrow(
|
||||
"Invalid recording file name",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import path from "node:path";
|
||||
|
||||
const RECORDED_VIDEO_FILE_NAME = /^recording-[0-9]+(?:-webcam)?\.(?:webm|mp4)$/;
|
||||
|
||||
export function resolveRecordedVideoStoragePath(recordingsDir: string, fileName: unknown): string {
|
||||
if (typeof fileName !== "string" || RECORDED_VIDEO_FILE_NAME.exec(fileName)?.[0] !== fileName) {
|
||||
throw new Error("Invalid recording file name");
|
||||
}
|
||||
|
||||
const resolvedRecordingsDir = path.resolve(recordingsDir);
|
||||
const candidatePath = path.resolve(resolvedRecordingsDir, fileName);
|
||||
const relativePath = path.relative(resolvedRecordingsDir, candidatePath);
|
||||
|
||||
if (
|
||||
relativePath.length === 0 ||
|
||||
relativePath === ".." ||
|
||||
relativePath.startsWith(`..${path.sep}`) ||
|
||||
path.isAbsolute(relativePath)
|
||||
) {
|
||||
throw new Error("Invalid recording file name");
|
||||
}
|
||||
|
||||
return candidatePath;
|
||||
}
|
||||
@@ -68,6 +68,7 @@ import {
|
||||
waitForNativeCaptureStart,
|
||||
waitForNativeCaptureStop,
|
||||
} from "../recording/mac";
|
||||
import { resolveRecordedVideoStoragePath } from "../recording/storagePath";
|
||||
import {
|
||||
attachWindowsCaptureLifecycle,
|
||||
isNativeWindowsCaptureAvailable,
|
||||
@@ -1747,10 +1748,10 @@ export function registerRecordingHandlers(
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle("store-recorded-video", async (_, videoData: ArrayBuffer, fileName: string) => {
|
||||
ipcMain.handle("store-recorded-video", async (_, videoData: ArrayBuffer, fileName: unknown) => {
|
||||
try {
|
||||
const recordingsDir = await getRecordingsDir();
|
||||
const videoPath = path.join(recordingsDir, fileName);
|
||||
const videoPath = resolveRecordedVideoStoragePath(recordingsDir, fileName);
|
||||
await fs.writeFile(videoPath, Buffer.from(videoData));
|
||||
return await finalizeStoredVideo(videoPath);
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user