From 0ef6a7a0e9ddeb5723922a86f2f341d254238a16 Mon Sep 17 00:00:00 2001 From: Nahiyan Khan Date: Wed, 26 Feb 2025 20:43:45 -0500 Subject: [PATCH] check file type with validation --- ui/desktop/src/main.ts | 27 ++++++++++++++++++++++-- ui/desktop/src/utils/fileUtils.ts | 34 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 ui/desktop/src/utils/fileUtils.ts diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index b4c9255d88..d428dbc57d 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -12,8 +12,10 @@ import { powerSaveBlocker, Tray, } from 'electron'; +import { isTextFile } from './utils/fileUtils'; import started from 'electron-squirrel-startup'; import path from 'node:path'; +import fs from 'node:fs'; import { startGoosed } from './goosed'; import { getBinaryPath } from './utils/binaryPath'; import { loadShellEnv } from './utils/loadEnv'; @@ -315,14 +317,35 @@ process.on('unhandledRejection', (error) => { handleFatalError(error instanceof Error ? error : new Error(String(error))); }); -// Add file/directory selection handler ipcMain.handle('select-file-or-directory', async () => { const result = await dialog.showOpenDialog({ properties: ['openFile', 'openDirectory'], + title: 'Select a text file or directory', }); if (!result.canceled && result.filePaths.length > 0) { - return result.filePaths[0]; + const path = result.filePaths[0]; + const stats = await fs.promises.stat(path); + + // If it's a directory, allow it + if (stats.isDirectory()) { + return path; + } + + // If it's a file, check if it's text + if (stats.isFile()) { + if (isTextFile(path)) { + return path; + } else { + dialog.showMessageBox({ + type: 'error', + title: 'Unsupported File Type', + message: 'Only text files can be attached.', + detail: 'The selected file appears to be binary or non-text content.', + }); + return null; + } + } } return null; }); diff --git a/ui/desktop/src/utils/fileUtils.ts b/ui/desktop/src/utils/fileUtils.ts new file mode 100644 index 0000000000..d32a478ef3 --- /dev/null +++ b/ui/desktop/src/utils/fileUtils.ts @@ -0,0 +1,34 @@ +import fs from 'node:fs'; +import { Buffer } from 'node:buffer'; + +/** + * Checks if a file is likely to be a text file by reading its first chunk + * and checking for null bytes and valid UTF-8 encoding + */ +export function isTextFile(filePath: string): boolean { + try { + // Read the first 8KB of the file + const buffer = Buffer.alloc(8192); + const fd = fs.openSync(filePath, 'r'); + const bytesRead = fs.readSync(fd, buffer, 0, 8192, 0); + fs.closeSync(fd); + + // If file is empty, consider it text + if (bytesRead === 0) return true; + + // Check for null bytes which usually indicate binary content + for (let i = 0; i < bytesRead; i++) { + if (buffer[i] === 0) return false; + } + + // Try to decode as UTF-8 + const content = buffer.slice(0, bytesRead).toString('utf8'); + + // If we can decode it as UTF-8 and it contains printable characters + // we'll consider it a text file + return content.length > 0 && /^[\x20-\x7E\t\n\r\x80-\xFF]*$/.test(content); + } catch (error) { + console.error('Error checking file type:', error); + return false; + } +}