fix: auto-append file extension based on magic bytes or mime type if missing
This commit is contained in:
+30
-15
@@ -1,7 +1,7 @@
|
||||
import { nanoid } from 'nanoid';
|
||||
import { db, files as fileSchema } from '../db';
|
||||
import { config } from '../env';
|
||||
import { checkFileSize, extractMimeType, getFileType } from '../utils/file';
|
||||
import { checkFileSize, ensureExtension, extractMimeType, getFileType } from '../utils/file';
|
||||
import logger from '../utils/logger';
|
||||
import { forwardToStorage, getBot } from '../utils/telegram';
|
||||
|
||||
@@ -38,18 +38,23 @@ const handleMultipartUpload = async (req: Request): Promise<Response> => {
|
||||
|
||||
const fileBytes = await file.arrayBuffer();
|
||||
const fileBuffer = Buffer.from(fileBytes);
|
||||
const mimeType = file.type || extractMimeType({}, req) || 'application/octet-stream';
|
||||
const fileType = getFileType(mimeType, fileName);
|
||||
const rawMimeType = file.type || extractMimeType({}, req) || 'application/octet-stream';
|
||||
const { fileName: finalFileName, mimeType } = ensureExtension(
|
||||
fileName,
|
||||
fileBuffer,
|
||||
rawMimeType,
|
||||
);
|
||||
const fileType = getFileType(mimeType, finalFileName);
|
||||
|
||||
if (!checkFileSize(fileBuffer.byteLength, fileType)) {
|
||||
return Response.json({ error: `File size exceeds ${fileType} limit` }, { status: 400 });
|
||||
}
|
||||
|
||||
const isDocument =
|
||||
fileName.endsWith('.pdf') ||
|
||||
fileName.endsWith('.txt') ||
|
||||
finalFileName.endsWith('.pdf') ||
|
||||
finalFileName.endsWith('.txt') ||
|
||||
!['photo', 'video', 'audio', 'voice', 'animation'].includes(fileType);
|
||||
const result = await forwardToStorage(fileBuffer, fileName, isDocument);
|
||||
const result = await forwardToStorage(fileBuffer, finalFileName, isDocument);
|
||||
const bot = getBot();
|
||||
const fileInfo = (await bot.telegram.getFile(result.telegramFileId)) as any;
|
||||
|
||||
@@ -59,7 +64,7 @@ const handleMultipartUpload = async (req: Request): Promise<Response> => {
|
||||
telegramFileUniqueId: result.telegramFileUniqueId,
|
||||
storageChatId: config.storageChatId,
|
||||
storageMessageId: result.storageMessageId,
|
||||
fileName: fileName,
|
||||
fileName: finalFileName,
|
||||
mimeType: fileInfo.mime_type || mimeType || 'application/octet-stream',
|
||||
sizeBytes: fileInfo.file_size || fileBuffer.byteLength,
|
||||
fileType: fileType,
|
||||
@@ -103,22 +108,32 @@ const handleJSONUpload = async (req: Request): Promise<Response> => {
|
||||
);
|
||||
}
|
||||
|
||||
const fileBytes = Buffer.from(file, 'base64');
|
||||
const mimeType = 'application/octet-stream';
|
||||
let base64Data = file;
|
||||
let rawMimeType = 'application/octet-stream';
|
||||
if (file.startsWith('data:')) {
|
||||
const match = file.match(/^data:([^;]+);base64,(.+)$/);
|
||||
if (match) {
|
||||
rawMimeType = match[1];
|
||||
base64Data = match[2];
|
||||
}
|
||||
}
|
||||
|
||||
const fileBytes = Buffer.from(base64Data, 'base64');
|
||||
const { fileName: finalFileName, mimeType } = ensureExtension(fileName, fileBytes, rawMimeType);
|
||||
const fileType =
|
||||
getFileType(mimeType, fileName) === 'application'
|
||||
getFileType(mimeType, finalFileName) === 'application'
|
||||
? 'document'
|
||||
: getFileType(mimeType, fileName);
|
||||
: getFileType(mimeType, finalFileName);
|
||||
|
||||
if (!checkFileSize(fileBytes.byteLength, fileType)) {
|
||||
return Response.json({ error: `File size exceeds ${fileType} limit` }, { status: 400 });
|
||||
}
|
||||
|
||||
const isDocument =
|
||||
fileName.endsWith('.pdf') ||
|
||||
fileName.endsWith('.txt') ||
|
||||
finalFileName.endsWith('.pdf') ||
|
||||
finalFileName.endsWith('.txt') ||
|
||||
!['photo', 'video', 'audio', 'voice', 'animation'].includes(fileType);
|
||||
const result = await forwardToStorage(fileBytes, fileName, isDocument);
|
||||
const result = await forwardToStorage(fileBytes, finalFileName, isDocument);
|
||||
const bot = getBot();
|
||||
const fileInfo = (await bot.telegram.getFile(result.telegramFileId)) as any;
|
||||
|
||||
@@ -128,7 +143,7 @@ const handleJSONUpload = async (req: Request): Promise<Response> => {
|
||||
telegramFileUniqueId: result.telegramFileUniqueId,
|
||||
storageChatId: config.storageChatId,
|
||||
storageMessageId: result.storageMessageId,
|
||||
fileName: fileName,
|
||||
fileName: finalFileName,
|
||||
mimeType: fileInfo.mime_type || mimeType || 'application/octet-stream',
|
||||
sizeBytes: fileInfo.file_size || fileBytes.byteLength,
|
||||
fileType: fileType,
|
||||
|
||||
@@ -26,6 +26,48 @@ export const checkFileSize = (sizeBytes: number, fileType: string): boolean => {
|
||||
return sizeBytes <= limit;
|
||||
};
|
||||
|
||||
export const ensureExtension = (
|
||||
fileName: string,
|
||||
buffer: Buffer,
|
||||
detectedMime?: string,
|
||||
): { fileName: string; mimeType: string } => {
|
||||
const mimeMap: Record<string, string> = {
|
||||
'application/pdf': 'pdf',
|
||||
'image/png': 'png',
|
||||
'image/jpeg': 'jpg',
|
||||
'image/gif': 'gif',
|
||||
'text/plain': 'txt',
|
||||
'application/zip': 'zip',
|
||||
};
|
||||
|
||||
let ext: string | null = null;
|
||||
if (buffer.subarray(0, 4).toString() === '%PDF') {
|
||||
ext = 'pdf';
|
||||
} else if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47) {
|
||||
ext = 'png';
|
||||
} else if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
|
||||
ext = 'jpg';
|
||||
} else if (buffer.subarray(0, 4).toString() === 'GIF8') {
|
||||
ext = 'gif';
|
||||
} else if (detectedMime) {
|
||||
ext = mimeMap[detectedMime.toLowerCase()] || null;
|
||||
}
|
||||
|
||||
let finalFileName = fileName;
|
||||
const hasExtension = fileName.includes('.') && fileName.split('.').pop()!.length >= 2;
|
||||
if (!hasExtension && ext) {
|
||||
finalFileName = `${fileName}.${ext}`;
|
||||
}
|
||||
|
||||
const mimeType = ext
|
||||
? Object.keys(mimeMap).find((k) => mimeMap[k] === ext) ||
|
||||
detectedMime ||
|
||||
'application/octet-stream'
|
||||
: detectedMime || 'application/octet-stream';
|
||||
|
||||
return { fileName: finalFileName, mimeType };
|
||||
};
|
||||
|
||||
export const extractFileName = (msg: any, request: any): string => {
|
||||
if (request?.headers?.['x-file-name']) {
|
||||
return request.headers['x-file-name'];
|
||||
|
||||
Reference in New Issue
Block a user