From e06b60b5d52010b3880087f49c499af181b4deb0 Mon Sep 17 00:00:00 2001 From: VELLANKI SANTHOSH Date: Tue, 13 Jan 2026 15:11:03 +0000 Subject: [PATCH 1/2] fix: correct file input field mapping for PDF and other file uploads Fix a copy-paste bug where fileInputFieldFromExt was incorrectly used instead of fileInputFieldFromMimeType in the fallback condition. This caused PDF files to be processed with TextLoader instead of PDFLoader, resulting in 'Unable to upload documents' errors. The bug affected file uploads across multiple features: - Agent file attachments (createAttachment) - Vector store uploads (upsertVector) - Chatflow file inputs (buildChatflow) - Document store uploads (documentstore) Why it failed for PDFs but not Word/Excel: - .docx and .xlsx extensions are properly mapped in mapExtToInputField() - .pdf extension was not mapped, so it fell to the MIME type fallback - The fallback incorrectly assigned the ext value instead of MIME value - This caused PDFs to use 'txtFile' instead of 'pdfFile' loader Fixes: PDF upload failures in Agentflows with full file upload enabled Related: CVE-2025-61687 file validation improvements --- packages/server/src/services/documentstore/index.ts | 2 +- packages/server/src/utils/buildChatflow.ts | 2 +- packages/server/src/utils/createAttachment.ts | 2 +- packages/server/src/utils/upsertVector.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/server/src/services/documentstore/index.ts b/packages/server/src/services/documentstore/index.ts index 845bf9f1f98..fd27013dc8c 100644 --- a/packages/server/src/services/documentstore/index.ts +++ b/packages/server/src/services/documentstore/index.ts @@ -1861,7 +1861,7 @@ const upsertDocStore = async ( if (fileInputFieldFromExt !== 'txtFile') { fileInputField = fileInputFieldFromExt } else if (fileInputFieldFromMimeType !== 'txtFile') { - fileInputField = fileInputFieldFromExt + fileInputField = fileInputFieldFromMimeType } if (loaderId === 'unstructuredFileLoader') { diff --git a/packages/server/src/utils/buildChatflow.ts b/packages/server/src/utils/buildChatflow.ts index c54c009c43a..02cd62ab205 100644 --- a/packages/server/src/utils/buildChatflow.ts +++ b/packages/server/src/utils/buildChatflow.ts @@ -448,7 +448,7 @@ export const executeFlow = async ({ if (fileInputFieldFromExt !== 'txtFile') { fileInputField = fileInputFieldFromExt } else if (fileInputFieldFromMimeType !== 'txtFile') { - fileInputField = fileInputFieldFromExt + fileInputField = fileInputFieldFromMimeType } if (overrideConfig[fileInputField]) { diff --git a/packages/server/src/utils/createAttachment.ts b/packages/server/src/utils/createAttachment.ts index 2daec1e2e1d..2cc759c2bf2 100644 --- a/packages/server/src/utils/createAttachment.ts +++ b/packages/server/src/utils/createAttachment.ts @@ -177,7 +177,7 @@ export const createFileAttachment = async (req: Request) => { if (fileInputFieldFromExt !== 'txtFile') { fileInputField = fileInputFieldFromExt } else if (fileInputFieldFromMimeType !== 'txtFile') { - fileInputField = fileInputFieldFromExt + fileInputField = fileInputFieldFromMimeType } await removeSpecificFileFromUpload(file.path ?? file.key) diff --git a/packages/server/src/utils/upsertVector.ts b/packages/server/src/utils/upsertVector.ts index b89e82928e0..55e051c5106 100644 --- a/packages/server/src/utils/upsertVector.ts +++ b/packages/server/src/utils/upsertVector.ts @@ -96,7 +96,7 @@ export const executeUpsert = async ({ if (fileInputFieldFromExt !== 'txtFile') { fileInputField = fileInputFieldFromExt } else if (fileInputFieldFromMimeType !== 'txtFile') { - fileInputField = fileInputFieldFromExt + fileInputField = fileInputFieldFromMimeType } if (overrideConfig[fileInputField]) { From e47ded4bebd8111ffe43dadcac26b3f69861f601 Mon Sep 17 00:00:00 2001 From: VELLANKI SANTHOSH Date: Tue, 13 Jan 2026 15:18:00 +0000 Subject: [PATCH 2/2] refactor: simplify file input field mapping logic Apply code review suggestion to use clearer linear fallback instead of nested conditionals. This makes the intent more obvious and prevents future copy-paste errors. Changes: - Refactored mapping logic in all 4 affected files to use: let fileInputField = mapExtToInputField(fileExtension) if (fileInputField === 'txtFile') { fileInputField = mapMimeTypeToInputField(file.mimetype) } - Added comprehensive regression tests to verify PDF, Word, and Excel file mappings work correctly with the new logic - Tests ensure .pdf files correctly map to 'pdfFile' loader via MIME type fallback The simplified logic is more maintainable and self-documenting: 1. Try file extension mapping first 2. If unrecognized (returns 'txtFile'), fall back to MIME type Co-authored-by: Gemini Code Assist --- .../src/services/documentstore/index.ts | 14 +-- packages/server/src/utils/buildChatflow.ts | 14 +-- packages/server/src/utils/createAttachment.ts | 14 +-- packages/server/src/utils/upsertVector.ts | 14 +-- packages/server/test/index.test.ts | 2 + .../test/utils/file-mapping.util.test.ts | 109 ++++++++++++++++++ 6 files changed, 127 insertions(+), 40 deletions(-) create mode 100644 packages/server/test/utils/file-mapping.util.test.ts diff --git a/packages/server/src/services/documentstore/index.ts b/packages/server/src/services/documentstore/index.ts index fd27013dc8c..a850b5516ca 100644 --- a/packages/server/src/services/documentstore/index.ts +++ b/packages/server/src/services/documentstore/index.ts @@ -1850,18 +1850,12 @@ const upsertDocStore = async ( const mimePrefix = 'data:' + file.mimetype + ';base64' const storagePath = mimePrefix + ',' + fileBuffer.toString('base64') + `,filename:${file.originalname}` - const fileInputFieldFromMimeType = mapMimeTypeToInputField(file.mimetype) - const fileExtension = path.extname(file.originalname) - const fileInputFieldFromExt = mapExtToInputField(fileExtension) - - let fileInputField = 'txtFile' - - if (fileInputFieldFromExt !== 'txtFile') { - fileInputField = fileInputFieldFromExt - } else if (fileInputFieldFromMimeType !== 'txtFile') { - fileInputField = fileInputFieldFromMimeType + // Try to map by file extension first, fall back to MIME type if extension is not recognized + let fileInputField = mapExtToInputField(fileExtension) + if (fileInputField === 'txtFile') { + fileInputField = mapMimeTypeToInputField(file.mimetype) } if (loaderId === 'unstructuredFileLoader') { diff --git a/packages/server/src/utils/buildChatflow.ts b/packages/server/src/utils/buildChatflow.ts index 02cd62ab205..711e925a927 100644 --- a/packages/server/src/utils/buildChatflow.ts +++ b/packages/server/src/utils/buildChatflow.ts @@ -437,18 +437,12 @@ export const executeFlow = async ({ ) await updateStorageUsage(orgId, workspaceId, totalSize, usageCacheManager) - const fileInputFieldFromMimeType = mapMimeTypeToInputField(file.mimetype) - const fileExtension = path.extname(file.originalname) - const fileInputFieldFromExt = mapExtToInputField(fileExtension) - - let fileInputField = 'txtFile' - - if (fileInputFieldFromExt !== 'txtFile') { - fileInputField = fileInputFieldFromExt - } else if (fileInputFieldFromMimeType !== 'txtFile') { - fileInputField = fileInputFieldFromMimeType + // Try to map by file extension first, fall back to MIME type if extension is not recognized + let fileInputField = mapExtToInputField(fileExtension) + if (fileInputField === 'txtFile') { + fileInputField = mapMimeTypeToInputField(file.mimetype) } if (overrideConfig[fileInputField]) { diff --git a/packages/server/src/utils/createAttachment.ts b/packages/server/src/utils/createAttachment.ts index 2cc759c2bf2..7ae03f945de 100644 --- a/packages/server/src/utils/createAttachment.ts +++ b/packages/server/src/utils/createAttachment.ts @@ -166,18 +166,12 @@ export const createFileAttachment = async (req: Request) => { ) await updateStorageUsage(orgId, workspaceId, totalSize, appServer.usageCacheManager) - const fileInputFieldFromMimeType = mapMimeTypeToInputField(file.mimetype) - const fileExtension = path.extname(file.originalname) - const fileInputFieldFromExt = mapExtToInputField(fileExtension) - - let fileInputField = 'txtFile' - - if (fileInputFieldFromExt !== 'txtFile') { - fileInputField = fileInputFieldFromExt - } else if (fileInputFieldFromMimeType !== 'txtFile') { - fileInputField = fileInputFieldFromMimeType + // Try to map by file extension first, fall back to MIME type if extension is not recognized + let fileInputField = mapExtToInputField(fileExtension) + if (fileInputField === 'txtFile') { + fileInputField = mapMimeTypeToInputField(file.mimetype) } await removeSpecificFileFromUpload(file.path ?? file.key) diff --git a/packages/server/src/utils/upsertVector.ts b/packages/server/src/utils/upsertVector.ts index 55e051c5106..2fee256ef60 100644 --- a/packages/server/src/utils/upsertVector.ts +++ b/packages/server/src/utils/upsertVector.ts @@ -85,18 +85,12 @@ export const executeUpsert = async ({ ) await updateStorageUsage(orgId, workspaceId, totalSize, usageCacheManager) - const fileInputFieldFromMimeType = mapMimeTypeToInputField(file.mimetype) - const fileExtension = path.extname(file.originalname) - const fileInputFieldFromExt = mapExtToInputField(fileExtension) - - let fileInputField = 'txtFile' - - if (fileInputFieldFromExt !== 'txtFile') { - fileInputField = fileInputFieldFromExt - } else if (fileInputFieldFromMimeType !== 'txtFile') { - fileInputField = fileInputFieldFromMimeType + // Try to map by file extension first, fall back to MIME type if extension is not recognized + let fileInputField = mapExtToInputField(fileExtension) + if (fileInputField === 'txtFile') { + fileInputField = mapMimeTypeToInputField(file.mimetype) } if (overrideConfig[fileInputField]) { diff --git a/packages/server/test/index.test.ts b/packages/server/test/index.test.ts index 8c038f44f62..c733bc9a7d9 100644 --- a/packages/server/test/index.test.ts +++ b/packages/server/test/index.test.ts @@ -3,6 +3,7 @@ import { getRunningExpressApp } from '../src/utils/getRunningExpressApp' import { organizationUserRouteTest } from './routes/v1/organization-user.route.test' import { userRouteTest } from './routes/v1/user.route.test' import { apiKeyTest } from './utils/api-key.util.test' +import { fileMappingTest } from './utils/file-mapping.util.test' // ⏱️ Extend test timeout to 6 minutes for long setups (increase as tests grow) jest.setTimeout(360000) @@ -25,4 +26,5 @@ describe('Routes Test', () => { describe('Utils Test', () => { apiKeyTest() + fileMappingTest() }) diff --git a/packages/server/test/utils/file-mapping.util.test.ts b/packages/server/test/utils/file-mapping.util.test.ts new file mode 100644 index 00000000000..69c48d4f248 --- /dev/null +++ b/packages/server/test/utils/file-mapping.util.test.ts @@ -0,0 +1,109 @@ +import { mapExtToInputField, mapMimeTypeToInputField } from 'flowise-components' + +export function fileMappingTest() { + describe('File Input Field Mapping', () => { + describe('Extension-based mapping', () => { + it('should map .pdf extension to pdfFile', () => { + const result = mapExtToInputField('.pdf') + expect(result).toEqual('pdfFile') + }) + + it('should map .docx extension to docxFile', () => { + const result = mapExtToInputField('.docx') + expect(result).toEqual('docxFile') + }) + + it('should map .xlsx extension to csvFile', () => { + const result = mapExtToInputField('.xlsx') + expect(result).toEqual('csvFile') + }) + + it('should return txtFile for unknown extensions', () => { + const result = mapExtToInputField('.unknown') + expect(result).toEqual('txtFile') + }) + }) + + describe('MIME type-based mapping', () => { + it('should map application/pdf to pdfFile', () => { + const result = mapMimeTypeToInputField('application/pdf') + expect(result).toEqual('pdfFile') + }) + + it('should map Word document MIME types to docxFile', () => { + expect(mapMimeTypeToInputField('application/vnd.openxmlformats-officedocument.wordprocessingml.document')).toEqual( + 'docxFile' + ) + expect(mapMimeTypeToInputField('application/msword')).toEqual('docxFile') + }) + + it('should map Excel MIME types to excelFile', () => { + expect(mapMimeTypeToInputField('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')).toEqual( + 'excelFile' + ) + expect(mapMimeTypeToInputField('application/vnd.ms-excel')).toEqual('excelFile') + }) + + it('should return txtFile for unknown MIME types', () => { + const result = mapMimeTypeToInputField('application/unknown') + expect(result).toEqual('txtFile') + }) + }) + + describe('Fallback logic (regression test for PDF bug)', () => { + it('should correctly handle PDF files using fallback logic', () => { + // Simulate the file input field determination logic + const fileExtension = '.pdf' + const mimeType = 'application/pdf' + + // Try extension first + let fileInputField = mapExtToInputField(fileExtension) + + // Fall back to MIME type if extension returns txtFile + if (fileInputField === 'txtFile') { + fileInputField = mapMimeTypeToInputField(mimeType) + } + + // PDF should be correctly mapped to pdfFile + expect(fileInputField).toEqual('pdfFile') + }) + + it('should correctly handle Word documents using extension', () => { + const fileExtension = '.docx' + const mimeType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' + + let fileInputField = mapExtToInputField(fileExtension) + if (fileInputField === 'txtFile') { + fileInputField = mapMimeTypeToInputField(mimeType) + } + + expect(fileInputField).toEqual('docxFile') + }) + + it('should correctly handle Excel documents using extension', () => { + const fileExtension = '.xlsx' + const mimeType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + + let fileInputField = mapExtToInputField(fileExtension) + if (fileInputField === 'txtFile') { + fileInputField = mapMimeTypeToInputField(mimeType) + } + + expect(fileInputField).toEqual('csvFile') + }) + + it('should fall back to MIME type for unrecognized extensions', () => { + const fileExtension = '.unknown' + const mimeType = 'application/pdf' + + let fileInputField = mapExtToInputField(fileExtension) + if (fileInputField === 'txtFile') { + fileInputField = mapMimeTypeToInputField(mimeType) + } + + // Should use MIME type fallback and detect it as PDF + expect(fileInputField).toEqual('pdfFile') + }) + }) + }) +}