mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
fix: replace exec with spawn (#2626)
This commit is contained in:
+170
-55
@@ -31,12 +31,8 @@ import {
|
||||
} from './utils/settings';
|
||||
import * as crypto from 'crypto';
|
||||
import * as electron from 'electron';
|
||||
import { exec as execCallback } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import * as yaml from 'yaml';
|
||||
|
||||
const exec = promisify(execCallback);
|
||||
|
||||
if (started) app.quit();
|
||||
|
||||
app.setAsDefaultProtocolClient('goose');
|
||||
@@ -645,30 +641,56 @@ ipcMain.handle('check-ollama', async () => {
|
||||
try {
|
||||
return new Promise((resolve) => {
|
||||
// Run `ps` and filter for "ollama"
|
||||
exec('ps aux | grep -iw "[o]llama"', (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error('Error executing ps command:', error);
|
||||
return resolve(false); // Process is not running
|
||||
const ps = spawn('ps', ['aux']);
|
||||
const grep = spawn('grep', ['-iw', '[o]llama']);
|
||||
|
||||
let output = '';
|
||||
let errorOutput = '';
|
||||
|
||||
// Pipe ps output to grep
|
||||
ps.stdout.pipe(grep.stdin);
|
||||
|
||||
grep.stdout.on('data', (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
grep.stderr.on('data', (data) => {
|
||||
errorOutput += data.toString();
|
||||
});
|
||||
|
||||
grep.on('close', (code) => {
|
||||
if (code !== null && code !== 0 && code !== 1) {
|
||||
// grep returns 1 when no matches found
|
||||
console.error('Error executing grep command:', errorOutput);
|
||||
return resolve(false);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error('Standard error output from ps command:', stderr);
|
||||
return resolve(false); // Process is not running
|
||||
}
|
||||
|
||||
console.log('Raw stdout from ps command:', stdout);
|
||||
|
||||
// Trim and check if output contains a match
|
||||
const trimmedOutput = stdout.trim();
|
||||
console.log('Raw stdout from ps|grep command:', output);
|
||||
const trimmedOutput = output.trim();
|
||||
console.log('Trimmed stdout:', trimmedOutput);
|
||||
|
||||
const isRunning = trimmedOutput.length > 0; // True if there's any output
|
||||
resolve(isRunning); // Resolve true if running, false otherwise
|
||||
const isRunning = trimmedOutput.length > 0;
|
||||
resolve(isRunning);
|
||||
});
|
||||
|
||||
ps.on('error', (error) => {
|
||||
console.error('Error executing ps command:', error);
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
grep.on('error', (error) => {
|
||||
console.error('Error executing grep command:', error);
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
// Close ps stdin when done
|
||||
ps.stdout.on('end', () => {
|
||||
grep.stdin.end();
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error checking for Ollama:', err);
|
||||
return false; // Return false on error
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -679,36 +701,46 @@ ipcMain.handle('get-binary-path', (_event, binaryName) => {
|
||||
|
||||
ipcMain.handle('read-file', (_event, filePath) => {
|
||||
return new Promise((resolve) => {
|
||||
exec(`cat ${filePath}`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
// File not found
|
||||
resolve({ file: '', filePath, error: null, found: false });
|
||||
const cat = spawn('cat', [filePath]);
|
||||
let output = '';
|
||||
let errorOutput = '';
|
||||
|
||||
cat.stdout.on('data', (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
cat.stderr.on('data', (data) => {
|
||||
errorOutput += data.toString();
|
||||
});
|
||||
|
||||
cat.on('close', (code) => {
|
||||
if (code !== 0) {
|
||||
// File not found or error
|
||||
resolve({ file: '', filePath, error: errorOutput || null, found: false });
|
||||
return;
|
||||
}
|
||||
if (stderr) {
|
||||
console.error('Error output:', stderr);
|
||||
resolve({ file: '', filePath, error, found: false });
|
||||
}
|
||||
resolve({ file: stdout, filePath, error: null, found: true });
|
||||
resolve({ file: output, filePath, error: null, found: true });
|
||||
});
|
||||
|
||||
cat.on('error', (error) => {
|
||||
console.error('Error reading file:', error);
|
||||
resolve({ file: '', filePath, error, found: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle('write-file', (_event, filePath, content) => {
|
||||
return new Promise((resolve) => {
|
||||
const command = `cat << 'EOT' > ${filePath}
|
||||
${content}
|
||||
EOT`;
|
||||
exec(command, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error('Error writing to file:', error);
|
||||
resolve(false);
|
||||
}
|
||||
if (stderr) {
|
||||
console.error('Error output:', stderr);
|
||||
resolve(false);
|
||||
}
|
||||
// Create a write stream to the file
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const fs = require('fs');
|
||||
try {
|
||||
fs.writeFileSync(filePath, content, { encoding: 'utf8' });
|
||||
resolve(true);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error writing to file:', error);
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1037,12 +1069,62 @@ app.whenReady().then(async () => {
|
||||
);
|
||||
|
||||
ipcMain.on('notify', (_event, data) => {
|
||||
console.log('NOTIFY', data);
|
||||
new Notification({ title: data.title, body: data.body }).show();
|
||||
try {
|
||||
// Validate notification data
|
||||
if (!data || typeof data !== 'object') {
|
||||
console.error('Invalid notification data');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate title and body
|
||||
if (typeof data.title !== 'string' || typeof data.body !== 'string') {
|
||||
console.error('Invalid notification title or body');
|
||||
return;
|
||||
}
|
||||
|
||||
// Limit the length of title and body
|
||||
const MAX_LENGTH = 1000;
|
||||
if (data.title.length > MAX_LENGTH || data.body.length > MAX_LENGTH) {
|
||||
console.error('Notification title or body too long');
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove any HTML tags for security
|
||||
const sanitizeText = (text: string) => text.replace(/<[^>]*>/g, '');
|
||||
|
||||
console.log('NOTIFY', data);
|
||||
new Notification({
|
||||
title: sanitizeText(data.title),
|
||||
body: sanitizeText(data.body),
|
||||
}).show();
|
||||
} catch (error) {
|
||||
console.error('Error showing notification:', error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('logInfo', (_event, info) => {
|
||||
log.info('from renderer:', info);
|
||||
try {
|
||||
// Validate log info
|
||||
if (info === undefined || info === null) {
|
||||
console.error('Invalid log info: undefined or null');
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert to string if not already
|
||||
const logMessage = String(info);
|
||||
|
||||
// Limit log message length
|
||||
const MAX_LENGTH = 10000; // 10KB limit
|
||||
if (logMessage.length > MAX_LENGTH) {
|
||||
console.error('Log message too long');
|
||||
return;
|
||||
}
|
||||
|
||||
// Log the sanitized message
|
||||
log.info('from renderer:', logMessage);
|
||||
} catch (error) {
|
||||
console.error('Error logging info:', error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('reload-app', (event) => {
|
||||
@@ -1084,6 +1166,14 @@ app.whenReady().then(async () => {
|
||||
// Handle metadata fetching from main process
|
||||
ipcMain.handle('fetch-metadata', async (_event, url) => {
|
||||
try {
|
||||
// Validate URL
|
||||
const parsedUrl = new URL(url);
|
||||
|
||||
// Only allow http and https protocols
|
||||
if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
|
||||
throw new Error('Invalid URL protocol. Only HTTP and HTTPS are allowed.');
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; Goose/1.0)',
|
||||
@@ -1094,7 +1184,19 @@ app.whenReady().then(async () => {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.text();
|
||||
// Set a reasonable size limit (e.g., 10MB)
|
||||
const MAX_SIZE = 10 * 1024 * 1024; // 10MB
|
||||
const contentLength = parseInt(response.headers.get('content-length') || '0');
|
||||
if (contentLength > MAX_SIZE) {
|
||||
throw new Error('Response too large');
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
if (text.length > MAX_SIZE) {
|
||||
throw new Error('Response too large');
|
||||
}
|
||||
|
||||
return text;
|
||||
} catch (error) {
|
||||
console.error('Error fetching metadata:', error);
|
||||
throw error;
|
||||
@@ -1102,15 +1204,28 @@ app.whenReady().then(async () => {
|
||||
});
|
||||
|
||||
ipcMain.on('open-in-chrome', (_event, url) => {
|
||||
// On macOS, use the 'open' command with Chrome
|
||||
if (process.platform === 'darwin') {
|
||||
spawn('open', ['-a', 'Google Chrome', url]);
|
||||
} else if (process.platform === 'win32') {
|
||||
// On Windows, start is built-in command of cmd.exe
|
||||
spawn('cmd.exe', ['/c', 'start', '', 'chrome', url]);
|
||||
} else {
|
||||
// On Linux, use xdg-open with chrome
|
||||
spawn('xdg-open', [url]);
|
||||
try {
|
||||
// Validate URL
|
||||
const parsedUrl = new URL(url);
|
||||
|
||||
// Only allow http and https protocols
|
||||
if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
|
||||
console.error('Invalid URL protocol. Only HTTP and HTTPS are allowed.');
|
||||
return;
|
||||
}
|
||||
|
||||
// On macOS, use the 'open' command with Chrome
|
||||
if (process.platform === 'darwin') {
|
||||
spawn('open', ['-a', 'Google Chrome', url]);
|
||||
} else if (process.platform === 'win32') {
|
||||
// On Windows, start is built-in command of cmd.exe
|
||||
spawn('cmd.exe', ['/c', 'start', '', 'chrome', url]);
|
||||
} else {
|
||||
// On Linux, use xdg-open with chrome
|
||||
spawn('xdg-open', [url]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error opening URL in Chrome:', error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user