From 6271532b2ea44fc10791edb674179ac933189233 Mon Sep 17 00:00:00 2001 From: Braux Martinez Date: Mon, 19 Oct 2026 07:51:04 -0600 Subject: [PATCH 1/2] 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 6d1fbc524c046..68ee0da0e2f77 100644 --- a/apps/files/src/services/DropService.ts +++ b/apps/files/src/services/DropService.ts @@ -62,7 +62,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 09a8cf4f717904ed70c8562f3846002d44ba38d4 Mon Sep 17 00:00:00 2001 From: Braux Martinez Date: Sat, 24 Oct 2026 01:39:33 -0600 Subject: [PATCH 2/2] 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 68ee0da0e2f77..91f6e78e974b9 100644 --- a/apps/files/src/services/DropService.ts +++ b/apps/files/src/services/DropService.ts @@ -64,7 +64,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 () => {