Files
wehub-resource-sync e115934061
Publish `@librechat/data-schemas` to NPM / pack (push) Failing after 8s
Publish `@librechat/client` to NPM / pack (push) Failing after 0s
GitNexus Index / index (push) Failing after 1s
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Has been skipped
Sync Helm Chart Tags / Sync chart tags (push) Failing after 2s
Publish `librechat-data-provider` to NPM / pack (push) Failing after 1s
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Failing after 1s
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Failing after 0s
Sync Helm Chart Tags / Ignore non-main push (push) Has been skipped
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Failing after 1s
Publish `@librechat/client` to NPM / publish-npm (push) Has been cancelled
Publish `librechat-data-provider` to NPM / publish-npm (push) Has been cancelled
GitNexus Index / post-index (push) Has been cancelled
Publish `@librechat/data-schemas` to NPM / publish-npm (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:08:12 +08:00

96 lines
2.7 KiB
JavaScript

const sharp = require('sharp');
/**
* Determines the file type of a buffer
* @param {Buffer} dataBuffer
* @param {boolean} [returnFileType=false] - Optional. If true, returns the file type instead of the file extension.
* @returns {Promise<string|null|import('file-type').FileTypeResult>} - Returns the file extension if found, else null
* */
const determineFileType = async (dataBuffer, returnFileType) => {
const fileType = await import('file-type');
const type = await fileType.fileTypeFromBuffer(dataBuffer);
if (returnFileType) {
return type;
}
return type ? type.ext : null; // Returns extension if found, else null
};
/**
* Get buffer metadata
* @param {Buffer} buffer
* @returns {Promise<{ bytes: number, type: string, dimensions: Record<string, number>, extension: string}>}
*/
const getBufferMetadata = async (buffer) => {
const fileType = await determineFileType(buffer, true);
const bytes = buffer.length;
let extension = fileType ? fileType.ext : 'unknown';
/** @type {Record<string, number>} */
let dimensions = {};
if (fileType && fileType.mime.startsWith('image/') && extension !== 'unknown') {
const imageMetadata = await sharp(buffer).metadata();
dimensions = {
width: imageMetadata.width,
height: imageMetadata.height,
};
}
return {
bytes,
type: fileType?.mime ?? 'unknown',
dimensions,
extension,
};
};
/**
* Removes UUID prefix from filename for clean display
* Pattern: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx__filename.ext
* @param {string} fileName - The filename to clean
* @returns {string} - The cleaned filename without UUID prefix
*/
const cleanFileName = (fileName) => {
if (!fileName) {
return fileName;
}
// Remove UUID pattern: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx__
const cleaned = fileName.replace(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}__/i,
'',
);
return cleaned;
};
const encodeRFC5987ValueChars = (value) =>
encodeURIComponent(value).replace(
/['()*]/g,
(char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`,
);
const getAsciiFilenameFallback = (fileName) => {
const fallback = fileName
.normalize('NFKD')
.replace(/[^\x20-\x7e]/g, '_')
.replace(/["\\\r\n]/g, '_');
return fallback || 'download';
};
const getContentDisposition = (fileName, disposition = 'attachment') => {
const cleanedFilename = cleanFileName(fileName) || 'download';
const asciiFallback = getAsciiFilenameFallback(cleanedFilename);
const encodedFilename = encodeRFC5987ValueChars(cleanedFilename);
return `${disposition}; filename="${asciiFallback}"; filename*=UTF-8''${encodedFilename}`;
};
module.exports = {
determineFileType,
getBufferMetadata,
cleanFileName,
getContentDisposition,
};