From 419a073fe4d3a4fa4429e9d69a69c2facc65d043 Mon Sep 17 00:00:00 2001 From: Braux Martinez Date: Mon, 19 Oct 2026 07:51:04 -0600 Subject: [PATCH 1/3] fix(files): upload files with empty mime type The drop handler discarded files whose MIME type arrives empty from the browser (common in Chromium for unknown extensions), silently skipping them from the upload queue. Keep files with an unknown type and let the server detect it on upload (content sniffing + extension map). Directories are still skipped with a warning. Assisted-by: opencode:MiMo-V2.6-Flash Signed-off-by: Braux Martinez --- apps/files/src/services/DropService.ts | 4 +- .../src/services/DropServiceUtils.spec.ts | 42 ++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/apps/files/src/services/DropService.ts b/apps/files/src/services/DropService.ts index e5978c53cad94..ce24474e969de 100644 --- a/apps/files/src/services/DropService.ts +++ b/apps/files/src/services/DropService.ts @@ -61,7 +61,9 @@ export async function dataTransferToFileTree(items: DataTransferItem[]): Promise // Warn the user that the browser does not support the Filesystem API // we therefore cannot upload directories recursively. - if (file.type === 'httpd/unix-directory' || !file.type) { + // Note: files with an unknown type are kept on purpose, the server + // detects their mime type on upload (content sniffing + extension map). + if (file.type === 'httpd/unix-directory') { if (!warned) { logger.warn('Browser does not support Filesystem API. Directories will not be uploaded') showWarning(t('files', 'Your browser does not support the Filesystem API. Directories will not be uploaded')) diff --git a/apps/files/src/services/DropServiceUtils.spec.ts b/apps/files/src/services/DropServiceUtils.spec.ts index e8d2e2ad33d69..eb4ca713bf181 100644 --- a/apps/files/src/services/DropServiceUtils.spec.ts +++ b/apps/files/src/services/DropServiceUtils.spec.ts @@ -196,7 +196,7 @@ describe('DropService dataTransferToFileTree', () => { expect(logger.warn).not.toBeCalled() }) - it('Should return a RootDirectory with legacy File API ignoring recursive directories', async () => { + it('Should return a RootDirectory with legacy File API keeping unknown types', async () => { vi.spyOn(logger, 'error').mockImplementation(() => vi.fn()) vi.spyOn(logger, 'warn').mockImplementation(() => vi.fn()) @@ -206,7 +206,9 @@ describe('DropService dataTransferToFileTree', () => { expect(fileTree.name).toBe('root') expect(fileTree).toBeInstanceOf(Directory) - expect(fileTree.contents).toHaveLength(1) + // file0.txt plus the two directories as empty files: unknown types are + // kept on purpose, the server detects their mime type on upload + expect(fileTree.contents).toHaveLength(3) // The file tree should be recursive when using the Filesystem API expect(fileTree.contents[0]).not.toBeInstanceOf(Directory) @@ -215,8 +217,38 @@ describe('DropService dataTransferToFileTree', () => { expect(logger.error).not.toBeCalled() expect(logger.warn).toHaveBeenNthCalledWith(1, 'Could not get FilesystemEntry of item, falling back to file') expect(logger.warn).toHaveBeenNthCalledWith(2, 'Could not get FilesystemEntry of item, falling back to file') - expect(logger.warn).toHaveBeenNthCalledWith(3, 'Browser does not support Filesystem API. Directories will not be uploaded') - expect(logger.warn).toHaveBeenNthCalledWith(4, 'Could not get FilesystemEntry of item, falling back to file') - expect(logger.warn).toHaveBeenCalledTimes(4) + expect(logger.warn).toHaveBeenNthCalledWith(3, 'Could not get FilesystemEntry of item, falling back to file') + expect(logger.warn).toHaveBeenCalledTimes(3) + }) + + it('Should keep files with an unknown mime type for server-side detection', async () => { + const { showWarning } = await import('@nextcloud/dialogs') + const file = new File(['data'], 'notes.unknown-ext', { type: '' }) + const item = Object.create(DataTransferItemMock.prototype) + Object.defineProperties(item, { + kind: { value: 'file' }, + getAsFile: { value: () => file }, + }) + + const fileTree = await dataTransferToFileTree([item as unknown as DataTransferItem]) + + expect(fileTree.contents).toHaveLength(1) + expect(fileTree.contents[0].name).toBe('notes.unknown-ext') + expect(showWarning).not.toHaveBeenCalled() + }) + + it('Should still skip unix directories with a warning', async () => { + const { showWarning } = await import('@nextcloud/dialogs') + const file = new File([], 'mydir', { type: 'httpd/unix-directory' }) + const item = Object.create(DataTransferItemMock.prototype) + Object.defineProperties(item, { + kind: { value: 'file' }, + getAsFile: { value: () => file }, + }) + + const fileTree = await dataTransferToFileTree([item as unknown as DataTransferItem]) + + expect(fileTree.contents).toHaveLength(0) + expect(showWarning).toHaveBeenCalledTimes(1) }) }) From 8ccef2b972663e708922122a20b71d2804b63d9d Mon Sep 17 00:00:00 2001 From: Braux Martinez Date: Sat, 24 Oct 2026 01:39:33 -0600 Subject: [PATCH 2/3] fix(files): keep unknown mime files but still skip empty directories - Distinguish empty directories from files with unknown mime via file.size - Restore original test for recursive directory ignoring - Add dedicated tests for unknown mime and unix directory handling Assisted-by: opencode:opencode Signed-off-by: Braux Martinez --- apps/files/src/services/DropService.ts | 2 +- apps/files/src/services/DropServiceUtils.spec.ts | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/files/src/services/DropService.ts b/apps/files/src/services/DropService.ts index ce24474e969de..dbcffa5d3bbaa 100644 --- a/apps/files/src/services/DropService.ts +++ b/apps/files/src/services/DropService.ts @@ -63,7 +63,7 @@ export async function dataTransferToFileTree(items: DataTransferItem[]): Promise // we therefore cannot upload directories recursively. // Note: files with an unknown type are kept on purpose, the server // detects their mime type on upload (content sniffing + extension map). - if (file.type === 'httpd/unix-directory') { + if (file.type === 'httpd/unix-directory' || (!file.type && file.size === 0)) { if (!warned) { logger.warn('Browser does not support Filesystem API. Directories will not be uploaded') showWarning(t('files', 'Your browser does not support the Filesystem API. Directories will not be uploaded')) diff --git a/apps/files/src/services/DropServiceUtils.spec.ts b/apps/files/src/services/DropServiceUtils.spec.ts index eb4ca713bf181..3fc76656bca0c 100644 --- a/apps/files/src/services/DropServiceUtils.spec.ts +++ b/apps/files/src/services/DropServiceUtils.spec.ts @@ -196,7 +196,7 @@ describe('DropService dataTransferToFileTree', () => { expect(logger.warn).not.toBeCalled() }) - it('Should return a RootDirectory with legacy File API keeping unknown types', async () => { + it('Should return a RootDirectory with legacy File API ignoring recursive directories', async () => { vi.spyOn(logger, 'error').mockImplementation(() => vi.fn()) vi.spyOn(logger, 'warn').mockImplementation(() => vi.fn()) @@ -206,9 +206,7 @@ describe('DropService dataTransferToFileTree', () => { expect(fileTree.name).toBe('root') expect(fileTree).toBeInstanceOf(Directory) - // file0.txt plus the two directories as empty files: unknown types are - // kept on purpose, the server detects their mime type on upload - expect(fileTree.contents).toHaveLength(3) + expect(fileTree.contents).toHaveLength(1) // The file tree should be recursive when using the Filesystem API expect(fileTree.contents[0]).not.toBeInstanceOf(Directory) @@ -217,8 +215,9 @@ describe('DropService dataTransferToFileTree', () => { expect(logger.error).not.toBeCalled() expect(logger.warn).toHaveBeenNthCalledWith(1, 'Could not get FilesystemEntry of item, falling back to file') expect(logger.warn).toHaveBeenNthCalledWith(2, 'Could not get FilesystemEntry of item, falling back to file') - expect(logger.warn).toHaveBeenNthCalledWith(3, 'Could not get FilesystemEntry of item, falling back to file') - expect(logger.warn).toHaveBeenCalledTimes(3) + expect(logger.warn).toHaveBeenNthCalledWith(3, 'Browser does not support Filesystem API. Directories will not be uploaded') + expect(logger.warn).toHaveBeenNthCalledWith(4, 'Could not get FilesystemEntry of item, falling back to file') + expect(logger.warn).toHaveBeenCalledTimes(4) }) it('Should keep files with an unknown mime type for server-side detection', async () => { From 00b310f806f3969761c8103ea5566fd230a8ad94 Mon Sep 17 00:00:00 2001 From: Braux Martinez Date: Mon, 26 Oct 2026 07:11:19 -0600 Subject: [PATCH 3/3] test(files): clear mocks between drop tests Assisted-by: opencode:deepseek-v4.1-flash-free Signed-off-by: Braux Martinez --- apps/files/src/services/DropServiceUtils.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/files/src/services/DropServiceUtils.spec.ts b/apps/files/src/services/DropServiceUtils.spec.ts index 3fc76656bca0c..c5a269a1f191d 100644 --- a/apps/files/src/services/DropServiceUtils.spec.ts +++ b/apps/files/src/services/DropServiceUtils.spec.ts @@ -175,6 +175,10 @@ describe('DropService dataTransferToFileTree', () => { window.DataTransferItem = DataTransferItemMock }) + beforeEach(() => { + vi.clearAllMocks() + }) + it('Should return a RootDirectory with Filesystem API', async () => { vi.spyOn(logger, 'error').mockImplementation(() => vi.fn()) vi.spyOn(logger, 'warn').mockImplementation(() => vi.fn())