Treat SVG attachments as files
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
getMimeTypeFromPath,
|
||||
getRasterImageMimeTypeFromPath,
|
||||
isRasterImageFile,
|
||||
isRasterImageMimeType,
|
||||
isRasterImagePath,
|
||||
RASTER_IMAGE_FILE_EXTENSIONS,
|
||||
} from "./file-types";
|
||||
|
||||
describe("attachment file types", () => {
|
||||
it("keeps SVG as a file while treating raster image files as images", () => {
|
||||
expect(getMimeTypeFromPath("/tmp/logo.svg")).toBe("image/svg+xml");
|
||||
expect(isRasterImagePath("/tmp/logo.svg")).toBe(false);
|
||||
expect(isRasterImageMimeType("image/svg+xml")).toBe(false);
|
||||
expect(isRasterImageFile(new File(["<svg />"], "logo.svg", { type: "image/svg+xml" }))).toBe(
|
||||
false,
|
||||
);
|
||||
|
||||
expect(getRasterImageMimeTypeFromPath("/tmp/screenshot.PNG?cache=1")).toBe("image/png");
|
||||
expect(isRasterImagePath("/tmp/screenshot.PNG?cache=1")).toBe(true);
|
||||
expect(isRasterImageMimeType("image/png; charset=binary")).toBe(true);
|
||||
expect(isRasterImageFile(new File([new Uint8Array([0])], "screenshot.png"))).toBe(true);
|
||||
});
|
||||
|
||||
it("does not offer SVG in the image picker extension list", () => {
|
||||
expect(new Set(RASTER_IMAGE_FILE_EXTENSIONS)).toEqual(
|
||||
new Set(["png", "jpg", "jpeg", "gif", "webp", "bmp", "heic", "heif", "avif", "tif", "tiff"]),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
const MIME_TYPE_BY_EXTENSION: Record<string, string> = {
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".gif": "image/gif",
|
||||
".webp": "image/webp",
|
||||
".bmp": "image/bmp",
|
||||
".svg": "image/svg+xml",
|
||||
".heic": "image/heic",
|
||||
".heif": "image/heif",
|
||||
".avif": "image/avif",
|
||||
".tif": "image/tiff",
|
||||
".tiff": "image/tiff",
|
||||
".pdf": "application/pdf",
|
||||
".txt": "text/plain",
|
||||
".md": "text/markdown",
|
||||
".json": "application/json",
|
||||
".js": "text/javascript",
|
||||
".ts": "text/typescript",
|
||||
".tsx": "text/typescript-jsx",
|
||||
".jsx": "text/javascript",
|
||||
".html": "text/html",
|
||||
".css": "text/css",
|
||||
".xml": "text/xml",
|
||||
".csv": "text/csv",
|
||||
".zip": "application/zip",
|
||||
".gz": "application/gzip",
|
||||
".tar": "application/x-tar",
|
||||
".mp3": "audio/mpeg",
|
||||
".mp4": "video/mp4",
|
||||
".mov": "video/quicktime",
|
||||
".webm": "video/webm",
|
||||
".wav": "audio/wav",
|
||||
".ogg": "audio/ogg",
|
||||
".doc": "application/msword",
|
||||
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
".xls": "application/vnd.ms-excel",
|
||||
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
".ppt": "application/vnd.ms-powerpoint",
|
||||
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
};
|
||||
|
||||
const RASTER_IMAGE_MIME_TYPE_BY_EXTENSION: Record<string, string> = {
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".gif": "image/gif",
|
||||
".webp": "image/webp",
|
||||
".bmp": "image/bmp",
|
||||
".heic": "image/heic",
|
||||
".heif": "image/heif",
|
||||
".avif": "image/avif",
|
||||
".tif": "image/tiff",
|
||||
".tiff": "image/tiff",
|
||||
};
|
||||
|
||||
const RASTER_IMAGE_MIME_TYPES = new Set(Object.values(RASTER_IMAGE_MIME_TYPE_BY_EXTENSION));
|
||||
|
||||
export const RASTER_IMAGE_FILE_EXTENSIONS = Object.keys(RASTER_IMAGE_MIME_TYPE_BY_EXTENSION).map(
|
||||
(extension) => extension.slice(1),
|
||||
);
|
||||
|
||||
export function getFileExtension(path: string): string {
|
||||
const normalizedPath = path.split("#", 1)[0]?.split("?", 1)[0] ?? path;
|
||||
const extensionIndex = normalizedPath.lastIndexOf(".");
|
||||
if (extensionIndex < 0) {
|
||||
return "";
|
||||
}
|
||||
return normalizedPath.slice(extensionIndex).toLowerCase();
|
||||
}
|
||||
|
||||
export function getMimeTypeFromPath(path: string): string {
|
||||
return MIME_TYPE_BY_EXTENSION[getFileExtension(path)] ?? "application/octet-stream";
|
||||
}
|
||||
|
||||
export function getRasterImageMimeTypeFromPath(path: string): string | null {
|
||||
return RASTER_IMAGE_MIME_TYPE_BY_EXTENSION[getFileExtension(path)] ?? null;
|
||||
}
|
||||
|
||||
export function isRasterImagePath(path: string): boolean {
|
||||
return getRasterImageMimeTypeFromPath(path) !== null;
|
||||
}
|
||||
|
||||
export function isRasterImageMimeType(mimeType: string | null | undefined): boolean {
|
||||
const normalized = mimeType?.split(";", 1)[0]?.trim().toLowerCase();
|
||||
return Boolean(normalized && RASTER_IMAGE_MIME_TYPES.has(normalized));
|
||||
}
|
||||
|
||||
export function isRasterImageFile(file: Pick<File, "name" | "type">): boolean {
|
||||
if (isRasterImageMimeType(file.type)) {
|
||||
return true;
|
||||
}
|
||||
return file.type.trim().length === 0 && isRasterImagePath(file.name);
|
||||
}
|
||||
@@ -53,4 +53,8 @@ describe("parseImageDataUrl", () => {
|
||||
it("ignores non-image data URLs", () => {
|
||||
expect(parseImageDataUrl("data:text/plain;base64,SGVsbG8=")).toBeNull();
|
||||
});
|
||||
|
||||
it("ignores SVG data URLs", () => {
|
||||
expect(parseImageDataUrl("data:image/svg+xml;base64,PHN2ZyAvPg==")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { generateMessageId } from "@/types/stream";
|
||||
import { isAbsolutePath } from "@/utils/path";
|
||||
import { isRasterImageMimeType } from "./file-types";
|
||||
|
||||
export function generateAttachmentId(): string {
|
||||
return `att_${generateMessageId()}`;
|
||||
@@ -52,7 +53,7 @@ export function parseImageDataUrl(
|
||||
|
||||
try {
|
||||
const parsed = parseDataUrl(uri);
|
||||
if (!parsed.mimeType.toLowerCase().startsWith("image/")) {
|
||||
if (!isRasterImageMimeType(parsed.mimeType)) {
|
||||
return null;
|
||||
}
|
||||
const fingerprint = `${parsed.mimeType}\0${parsed.base64.length}\0${parsed.base64.slice(0, 64)}\0${parsed.base64.slice(-64)}`;
|
||||
|
||||
Reference in New Issue
Block a user