FEAT - OPS ATTACHMENTS: multi-format upload, stable card drop zone and text previews
This commit is contained in:
@@ -10,6 +10,30 @@ import { fileTypeFromBuffer } from "file-type";
|
||||
import type { TFileMetaDataLite, TFileSignedURLResponse } from "@plane/types";
|
||||
import { DANGEROUS_EXTENSIONS } from "@plane/constants";
|
||||
|
||||
const TEXT_MIME_TYPES_BY_EXTENSION: Readonly<Record<string, string>> = {
|
||||
cfg: "text/plain",
|
||||
conf: "text/plain",
|
||||
csv: "text/csv",
|
||||
ini: "text/plain",
|
||||
json: "application/json",
|
||||
jsonl: "application/x-ndjson",
|
||||
log: "text/plain",
|
||||
markdown: "text/markdown",
|
||||
md: "text/markdown",
|
||||
mdown: "text/markdown",
|
||||
mkd: "text/markdown",
|
||||
mkdn: "text/markdown",
|
||||
ndjson: "application/x-ndjson",
|
||||
toml: "application/toml",
|
||||
tsv: "text/tab-separated-values",
|
||||
txt: "text/plain",
|
||||
xml: "application/xml",
|
||||
yaml: "application/yaml",
|
||||
yml: "application/yaml",
|
||||
};
|
||||
|
||||
const getFileExtension = (filename: string): string => filename.split(".").pop()?.trim().toLowerCase() ?? "";
|
||||
|
||||
/**
|
||||
* @description Filename validation - checks for double extensions and dangerous patterns
|
||||
* @param {string} filename
|
||||
@@ -82,8 +106,10 @@ const detectMimeTypeFromSignature = async (file: File): Promise<string> => {
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Validate and detect the MIME type of a file using signature detection
|
||||
* Also performs basic security checks on filename
|
||||
* @description Validate and detect the MIME type of a file.
|
||||
* Binary signatures take precedence. Text formats are resolved from a conservative
|
||||
* extension map because they do not have a binary signature. Browser MIME metadata
|
||||
* and application/octet-stream provide compatibility for other safe attachments.
|
||||
* @param {File} file
|
||||
* @returns {Promise<string>} validated and detected MIME type
|
||||
*/
|
||||
@@ -91,7 +117,7 @@ const validateAndDetectFileType = async (file: File): Promise<string> => {
|
||||
// Basic filename validation
|
||||
const filenameError = validateFilename(file.name);
|
||||
if (filenameError) {
|
||||
console.warn(`File validation warning: ${filenameError}`);
|
||||
throw new Error(filenameError);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -103,8 +129,15 @@ const validateAndDetectFileType = async (file: File): Promise<string> => {
|
||||
console.warn("Error detecting file type from signature:", _error);
|
||||
}
|
||||
|
||||
// fallback for unknown files
|
||||
return "";
|
||||
const extensionMimeType = TEXT_MIME_TYPES_BY_EXTENSION[getFileExtension(file.name)];
|
||||
if (extensionMimeType) return extensionMimeType;
|
||||
|
||||
const browserMimeType = file.type.split(";", 1)[0]?.trim().toLowerCase();
|
||||
if (browserMimeType) return browserMimeType;
|
||||
|
||||
// Preserve generic attachment support when neither the file signature nor the
|
||||
// browser can identify a safe filename. The API still enforces its MIME allowlist.
|
||||
return "application/octet-stream";
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user