import { mkdtemp, mkdir, symlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { expect, test } from "@playwright/test"; import { readTrustedLocalImage, resolveTrustedLocalImageCandidate, } from "../../lib/video-canvas/local-image-input"; test.describe("video canvas element mark local image input", () => { test("maps only loopback local-upload URLs into the public upload directory", () => { const workspaceRoot = "/workspace/project"; expect( resolveTrustedLocalImageCandidate( "http://127.0.0.1:3003/uploads/local/example.png?cache=1", workspaceRoot, ), ).toEqual({ candidatePath: resolve(workspaceRoot, "public/uploads/local/example.png"), uploadRoot: resolve(workspaceRoot, "public/uploads/local"), }); expect( resolveTrustedLocalImageCandidate("/uploads/local/example.png", workspaceRoot), ).toEqual({ candidatePath: resolve(workspaceRoot, "public/uploads/local/example.png"), uploadRoot: resolve(workspaceRoot, "public/uploads/local"), }); expect( resolveTrustedLocalImageCandidate( "https://assets.example.com/uploads/local/example.png", workspaceRoot, ), ).toBeNull(); }); test("rejects traversal and non-upload loopback paths", () => { const workspaceRoot = "/workspace/project"; expect( resolveTrustedLocalImageCandidate( "http://localhost:3003/uploads/local/../../.env", workspaceRoot, ), ).toBeNull(); expect( resolveTrustedLocalImageCandidate( "http://localhost:3003/api/private-image", workspaceRoot, ), ).toBeNull(); }); test("reads a real upload and refuses symlinks escaping the trusted directory", async () => { const workspaceRoot = await mkdtemp(join(tmpdir(), "video-mark-input-")); const uploadRoot = join(workspaceRoot, "public/uploads/local"); await mkdir(uploadRoot, { recursive: true }); await writeFile(join(uploadRoot, "sample.png"), Buffer.from("trusted-image")); await writeFile(join(workspaceRoot, "secret.txt"), Buffer.from("secret")); await symlink(join(workspaceRoot, "secret.txt"), join(uploadRoot, "escape.png")); await expect( readTrustedLocalImage("http://127.0.0.1:3003/uploads/local/sample.png", workspaceRoot), ).resolves.toEqual(Buffer.from("trusted-image")); await expect( readTrustedLocalImage("http://127.0.0.1:3003/uploads/local/escape.png", workspaceRoot), ).rejects.toThrow("本地图片路径不允许访问"); }); });