fix: 完善本地与网络图片渲染
This commit is contained in:
@@ -80,7 +80,7 @@ describe("共享应用服务", () => {
|
||||
const roots = await createThemeFixture();
|
||||
const service = createApplicationService(roots);
|
||||
|
||||
const document = service.render({
|
||||
const document = await service.render({
|
||||
markdown: "# 文档\n\n<script>alert('xss')</script>",
|
||||
language: "zh-CN"
|
||||
});
|
||||
@@ -94,7 +94,7 @@ describe("共享应用服务", () => {
|
||||
const roots = await createThemeFixture();
|
||||
const service = createApplicationService(roots);
|
||||
|
||||
expect(() => service.render({ markdown: 42 })).toThrow(
|
||||
await expect(service.render({ markdown: 42 })).rejects.toEqual(
|
||||
expect.objectContaining<ApplicationRequestError>({
|
||||
statusCode: 400,
|
||||
code: "INVALID_MARKDOWN"
|
||||
@@ -102,6 +102,39 @@ describe("共享应用服务", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("解析上传素材与桌面文档目录中的相对图片", async () => {
|
||||
const roots = await createThemeFixture();
|
||||
const service = createApplicationService(roots);
|
||||
const image = Buffer.from([
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a
|
||||
]);
|
||||
const assetRoot = join(temporaryDirectory!, "document");
|
||||
await mkdir(join(assetRoot, "文档.assets"), { recursive: true });
|
||||
await writeFile(join(assetRoot, "文档.assets", "本地.png"), image);
|
||||
|
||||
const local = await service.render(
|
||||
{
|
||||
markdown:
|
||||
""
|
||||
},
|
||||
{ localRoot: assetRoot }
|
||||
);
|
||||
const uploaded = await service.render({
|
||||
markdown: "",
|
||||
resources: [
|
||||
{
|
||||
path: "文档.assets/网页.png",
|
||||
data: image.toString("base64")
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
expect(local.articleHtml).toContain("data:image/png;base64,");
|
||||
expect(uploaded.articleHtml).toContain("data:image/png;base64,");
|
||||
expect(local.articleHtml).toContain("md-document-image");
|
||||
expect(uploaded.warnings).toEqual([]);
|
||||
});
|
||||
|
||||
it("列出主题并使用调用方提供的资源 URL", async () => {
|
||||
const roots = await createThemeFixture();
|
||||
const service = createApplicationService({
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
createImageResourceResolver,
|
||||
downloadRemoteImage,
|
||||
normalizeDocumentAssetPath
|
||||
} from "../src/index.js";
|
||||
|
||||
const png = Buffer.from([
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a
|
||||
]);
|
||||
|
||||
describe("Markdown 图片资源安全", () => {
|
||||
it("解码合法相对路径并拒绝越界与绝对路径", () => {
|
||||
expect(
|
||||
normalizeDocumentAssetPath(
|
||||
"./%E6%96%87%E6%A1%A3.assets/a%20b.png?raw=1"
|
||||
)
|
||||
).toBe("文档.assets/a b.png");
|
||||
expect(() => normalizeDocumentAssetPath("../secret.png")).toThrow(
|
||||
"越过文档目录"
|
||||
);
|
||||
expect(() => normalizeDocumentAssetPath("C:/secret.png")).toThrow(
|
||||
"绝对图片路径"
|
||||
);
|
||||
});
|
||||
|
||||
it("在发起请求前拒绝直接指向本机的远程地址", async () => {
|
||||
const fetcher = vi.fn<typeof fetch>();
|
||||
await expect(
|
||||
downloadRemoteImage("http://127.0.0.1/private.png", fetcher)
|
||||
).rejects.toThrow("非公网 IP");
|
||||
expect(fetcher).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("缓存成功的远程图片并把失败转换为可诊断占位", async () => {
|
||||
const remoteLoader = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
content: png,
|
||||
contentType: "image/png"
|
||||
})
|
||||
.mockRejectedValueOnce(new Error("网络不可用"));
|
||||
const resolve = createImageResourceResolver({ remoteLoader });
|
||||
const first = await resolve(
|
||||
"\n",
|
||||
undefined
|
||||
);
|
||||
const failed = await resolve(
|
||||
"",
|
||||
undefined
|
||||
);
|
||||
|
||||
expect(remoteLoader).toHaveBeenCalledTimes(2);
|
||||
expect(first.sources.get("https://example.com/a.png")).toMatch(
|
||||
/^data:image\/png;base64,/u
|
||||
);
|
||||
expect(failed.sources.get("https://example.com/b.png")).toMatch(
|
||||
/^data:image\/svg\+xml;base64,/u
|
||||
);
|
||||
expect(failed.warnings[0]).toContain("网络不可用");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user