mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-09-11 17:11:23 +00:00
🐛 fix: Name provisioned images by their stored MIME type
Image uploads are converted to appConfig.imageOutputType while the file record keeps the original filename, so code-env provisioning shipped webp bytes as photo.jpg and extension-sniffing tools mis-handled them. Uploads now rename known converted image types to match the persisted MIME type.
This commit is contained in:
parent
82040ea5c6
commit
7e458649ef
2 changed files with 76 additions and 2 deletions
|
|
@ -29,6 +29,24 @@ async function buildCodeApiHeaders({ apiKey, req }) {
|
|||
};
|
||||
}
|
||||
|
||||
/** Image uploads are converted to appConfig.imageOutputType while the record keeps
|
||||
* the original filename; rename so sandbox decoders match the stored bytes. */
|
||||
function provisionFilename(file) {
|
||||
if (!file.type?.startsWith('image/')) {
|
||||
return file.filename;
|
||||
}
|
||||
const subtype = file.type.slice('image/'.length);
|
||||
if (!['webp', 'png', 'jpeg', 'gif'].includes(subtype)) {
|
||||
return file.filename;
|
||||
}
|
||||
const accepted = subtype === 'jpeg' ? ['.jpg', '.jpeg'] : [`.${subtype}`];
|
||||
const currentExt = path.extname(file.filename).toLowerCase();
|
||||
if (accepted.includes(currentExt)) {
|
||||
return file.filename;
|
||||
}
|
||||
return `${path.basename(file.filename, path.extname(file.filename))}${accepted[0]}`;
|
||||
}
|
||||
|
||||
/** Env var holding the code-execution API key (symmetric with LIBRECHAT_CODE_BASEURL). */
|
||||
const CODE_API_KEY_FIELD = 'LIBRECHAT_CODE_API_KEY';
|
||||
|
||||
|
|
@ -79,7 +97,7 @@ async function provisionToCodeEnv({ req, file, entity_id }) {
|
|||
const uploaded = await uploadCodeEnvFile({
|
||||
req,
|
||||
stream,
|
||||
filename: file.filename,
|
||||
filename: provisionFilename(file),
|
||||
kind,
|
||||
id,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ jest.mock('./strategies', () => ({
|
|||
|
||||
const { loadAuthValues } = require('~/server/services/Tools/credentials');
|
||||
const { getCodeApiAuthHeaders, __codeAxios } = require('@librechat/api');
|
||||
const { loadCodeApiKey, checkSessionsAlive } = require('./provision');
|
||||
const { getStrategyFunctions } = require('./strategies');
|
||||
const { loadCodeApiKey, checkSessionsAlive, provisionToCodeEnv } = require('./provision');
|
||||
|
||||
describe('loadCodeApiKey', () => {
|
||||
afterEach(() => jest.clearAllMocks());
|
||||
|
|
@ -102,3 +103,58 @@ describe('checkSessionsAlive', () => {
|
|||
expect(__codeAxios.mock.calls[0][0].headers['X-API-Key']).toBe('legacy-key');
|
||||
});
|
||||
});
|
||||
|
||||
describe('provisionToCodeEnv', () => {
|
||||
afterEach(() => jest.clearAllMocks());
|
||||
|
||||
const setupStrategies = (uploadCodeEnvFile) => {
|
||||
const getDownloadStream = jest.fn().mockResolvedValue({ pipe: jest.fn() });
|
||||
getStrategyFunctions.mockImplementation((source) =>
|
||||
source === 'execute_code' ? { handleFileUpload: uploadCodeEnvFile } : { getDownloadStream },
|
||||
);
|
||||
};
|
||||
|
||||
it('renames converted images to match the stored MIME type', async () => {
|
||||
const uploadCodeEnvFile = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ storage_session_id: 's1', file_id: 'r1' });
|
||||
setupStrategies(uploadCodeEnvFile);
|
||||
|
||||
const result = await provisionToCodeEnv({
|
||||
req: { user: { id: 'u1' } },
|
||||
file: {
|
||||
file_id: 'f1',
|
||||
filename: 'photo.jpg',
|
||||
type: 'image/webp',
|
||||
source: 'local',
|
||||
filepath: '/x/photo.jpg',
|
||||
metadata: {},
|
||||
},
|
||||
});
|
||||
|
||||
expect(uploadCodeEnvFile).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ filename: 'photo.webp' }),
|
||||
);
|
||||
expect(result.codeEnvRef.file_id).toBe('r1');
|
||||
});
|
||||
|
||||
it('keeps filenames untouched when the extension already matches or the file is not an image', async () => {
|
||||
const uploadCodeEnvFile = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ storage_session_id: 's1', file_id: 'r1' });
|
||||
setupStrategies(uploadCodeEnvFile);
|
||||
const baseFile = { file_id: 'f1', source: 'local', filepath: '/x/f', metadata: {} };
|
||||
|
||||
await provisionToCodeEnv({
|
||||
req: { user: { id: 'u1' } },
|
||||
file: { ...baseFile, filename: 'data.csv', type: 'text/csv' },
|
||||
});
|
||||
await provisionToCodeEnv({
|
||||
req: { user: { id: 'u1' } },
|
||||
file: { ...baseFile, filename: 'pic.webp', type: 'image/webp' },
|
||||
});
|
||||
|
||||
expect(uploadCodeEnvFile.mock.calls[0][0].filename).toBe('data.csv');
|
||||
expect(uploadCodeEnvFile.mock.calls[1][0].filename).toBe('pic.webp');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue