import express from 'express'; import cors from 'cors'; import {Client as SSHClient} from 'ssh2'; import chalk from "chalk"; const app = express(); app.use(cors({ origin: '*', methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'] })); // Increase JSON body parser limit for larger file uploads app.use(express.json({ limit: '100mb' })); app.use(express.urlencoded({ limit: '100mb', extended: true })); // Add raw body parser for very large files app.use(express.raw({ limit: '200mb', type: 'application/octet-stream' })); const sshIconSymbol = '📁'; const getTimeStamp = (): string => chalk.gray(`[${new Date().toLocaleTimeString()}]`); const formatMessage = (level: string, colorFn: chalk.Chalk, message: string): string => { return `${getTimeStamp()} ${colorFn(`[${level.toUpperCase()}]`)} ${chalk.hex('#1e3a8a')(`[${sshIconSymbol}]`)} ${message}`; }; const logger = { info: (msg: string): void => { console.log(formatMessage('info', chalk.cyan, msg)); }, warn: (msg: string): void => { console.warn(formatMessage('warn', chalk.yellow, msg)); }, error: (msg: string, err?: unknown): void => { console.error(formatMessage('error', chalk.redBright, msg)); if (err) console.error(err); }, success: (msg: string): void => { console.log(formatMessage('success', chalk.greenBright, msg)); }, debug: (msg: string): void => { if (process.env.NODE_ENV !== 'production') { console.debug(formatMessage('debug', chalk.magenta, msg)); } } }; interface SSHSession { client: SSHClient; isConnected: boolean; lastActive: number; timeout?: NodeJS.Timeout; } const sshSessions: Record = {}; const SESSION_TIMEOUT_MS = 10 * 60 * 1000; function cleanupSession(sessionId: string) { const session = sshSessions[sessionId]; if (session) { try { session.client.end(); } catch { } clearTimeout(session.timeout); delete sshSessions[sessionId]; } } function scheduleSessionCleanup(sessionId: string) { const session = sshSessions[sessionId]; if (session) { if (session.timeout) clearTimeout(session.timeout); session.timeout = setTimeout(() => cleanupSession(sessionId), SESSION_TIMEOUT_MS); } } app.post('/ssh/file_manager/ssh/connect', (req, res) => { const {sessionId, ip, port, username, password, sshKey, keyPassword} = req.body; if (!sessionId || !ip || !username || !port) { return res.status(400).json({error: 'Missing SSH connection parameters'}); } if (sshSessions[sessionId]?.isConnected) cleanupSession(sessionId); const client = new SSHClient(); const config: any = { host: ip, port: port || 22, username, readyTimeout: 20000, keepaliveInterval: 10000, keepaliveCountMax: 3, algorithms: { kex: [ 'diffie-hellman-group14-sha256', 'diffie-hellman-group14-sha1', 'diffie-hellman-group1-sha1', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group-exchange-sha1', 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521' ], cipher: [ 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-gcm@openssh.com', 'aes256-gcm@openssh.com', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc', '3des-cbc' ], hmac: [ 'hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1', 'hmac-md5' ], compress: [ 'none', 'zlib@openssh.com', 'zlib' ] } }; if (sshKey && sshKey.trim()) { config.privateKey = sshKey; if (keyPassword) config.passphrase = keyPassword; } else if (password && password.trim()) { config.password = password; } else { return res.status(400).json({error: 'Either password or SSH key must be provided'}); } let responseSent = false; client.on('ready', () => { if (responseSent) return; responseSent = true; sshSessions[sessionId] = {client, isConnected: true, lastActive: Date.now()}; scheduleSessionCleanup(sessionId); res.json({status: 'success', message: 'SSH connection established'}); }); client.on('error', (err) => { if (responseSent) return; responseSent = true; logger.error(`SSH connection error for session ${sessionId}:`, err.message); res.status(500).json({status: 'error', message: err.message}); }); client.on('close', () => { if (sshSessions[sessionId]) sshSessions[sessionId].isConnected = false; cleanupSession(sessionId); }); client.connect(config); }); app.post('/ssh/file_manager/ssh/disconnect', (req, res) => { const {sessionId} = req.body; cleanupSession(sessionId); res.json({status: 'success', message: 'SSH connection disconnected'}); }); app.get('/ssh/file_manager/ssh/status', (req, res) => { const sessionId = req.query.sessionId as string; const isConnected = !!sshSessions[sessionId]?.isConnected; res.json({status: 'success', connected: isConnected}); }); app.get('/ssh/file_manager/ssh/listFiles', (req, res) => { const sessionId = req.query.sessionId as string; const sshConn = sshSessions[sessionId]; const sshPath = decodeURIComponent((req.query.path as string) || '/'); if (!sessionId) { return res.status(400).json({error: 'Session ID is required'}); } if (!sshConn?.isConnected) { return res.status(400).json({error: 'SSH connection not established'}); } sshConn.lastActive = Date.now(); scheduleSessionCleanup(sessionId); const escapedPath = sshPath.replace(/'/g, "'\"'\"'"); sshConn.client.exec(`ls -la '${escapedPath}'`, (err, stream) => { if (err) { logger.error('SSH listFiles error:', err); return res.status(500).json({error: err.message}); } let data = ''; let errorData = ''; stream.on('data', (chunk: Buffer) => { data += chunk.toString(); }); stream.stderr.on('data', (chunk: Buffer) => { errorData += chunk.toString(); }); stream.on('close', (code) => { if (code !== 0) { logger.error(`SSH listFiles command failed with code ${code}: ${errorData.replace(/\n/g, ' ').trim()}`); return res.status(500).json({error: `Command failed: ${errorData}`}); } const lines = data.split('\n').filter(line => line.trim()); const files = []; for (let i = 1; i < lines.length; i++) { const line = lines[i]; const parts = line.split(/\s+/); if (parts.length >= 9) { const permissions = parts[0]; const name = parts.slice(8).join(' '); const isDirectory = permissions.startsWith('d'); const isLink = permissions.startsWith('l'); if (name === '.' || name === '..') continue; files.push({ name, type: isDirectory ? 'directory' : (isLink ? 'link' : 'file') }); } } res.json(files); }); }); }); app.get('/ssh/file_manager/ssh/readFile', (req, res) => { const sessionId = req.query.sessionId as string; const sshConn = sshSessions[sessionId]; const filePath = decodeURIComponent(req.query.path as string); if (!sessionId) { return res.status(400).json({error: 'Session ID is required'}); } if (!sshConn?.isConnected) { return res.status(400).json({error: 'SSH connection not established'}); } if (!filePath) { return res.status(400).json({error: 'File path is required'}); } sshConn.lastActive = Date.now(); scheduleSessionCleanup(sessionId); const escapedPath = filePath.replace(/'/g, "'\"'\"'"); sshConn.client.exec(`cat '${escapedPath}'`, (err, stream) => { if (err) { logger.error('SSH readFile error:', err); return res.status(500).json({error: err.message}); } let data = ''; let errorData = ''; stream.on('data', (chunk: Buffer) => { data += chunk.toString(); }); stream.stderr.on('data', (chunk: Buffer) => { errorData += chunk.toString(); }); stream.on('close', (code) => { if (code !== 0) { logger.error(`SSH readFile command failed with code ${code}: ${errorData.replace(/\n/g, ' ').trim()}`); return res.status(500).json({error: `Command failed: ${errorData}`}); } res.json({content: data, path: filePath}); }); }); }); app.post('/ssh/file_manager/ssh/writeFile', (req, res) => { const {sessionId, path: filePath, content} = req.body; const sshConn = sshSessions[sessionId]; if (!sessionId) { return res.status(400).json({error: 'Session ID is required'}); } if (!sshConn?.isConnected) { return res.status(400).json({error: 'SSH connection not established'}); } if (!filePath) { return res.status(400).json({error: 'File path is required'}); } if (content === undefined) { return res.status(400).json({error: 'File content is required'}); } sshConn.lastActive = Date.now(); scheduleSessionCleanup(sessionId); const commandTimeout = setTimeout(() => { logger.error(`SSH writeFile command timed out for session: ${sessionId}`); if (!res.headersSent) { res.status(500).json({error: 'SSH command timed out'}); } }, 60000); // Increased timeout to 60 seconds // Try SFTP first, fallback to command line if it fails const trySFTP = () => { try { sshConn.client.sftp((err, sftp) => { if (err) { logger.warn(`SFTP failed, trying fallback method: ${err.message}`); tryFallbackMethod(); return; } // Convert content to buffer let fileBuffer; try { if (typeof content === 'string') { fileBuffer = Buffer.from(content, 'utf8'); } else if (Buffer.isBuffer(content)) { fileBuffer = content; } else { fileBuffer = Buffer.from(content); } } catch (bufferErr) { clearTimeout(commandTimeout); logger.error('Buffer conversion error:', bufferErr); if (!res.headersSent) { return res.status(500).json({error: 'Invalid file content format'}); } return; } // Create write stream with error handling const writeStream = sftp.createWriteStream(filePath); let hasError = false; let hasFinished = false; writeStream.on('error', (streamErr) => { if (hasError || hasFinished) return; hasError = true; logger.warn(`SFTP write failed, trying fallback method: ${streamErr.message}`); tryFallbackMethod(); }); writeStream.on('finish', () => { if (hasError || hasFinished) return; hasFinished = true; clearTimeout(commandTimeout); logger.success(`File written successfully via SFTP: ${filePath}`); if (!res.headersSent) { res.json({message: 'File written successfully', path: filePath}); } }); writeStream.on('close', () => { if (hasError || hasFinished) return; hasFinished = true; clearTimeout(commandTimeout); logger.success(`File written successfully via SFTP: ${filePath}`); if (!res.headersSent) { res.json({message: 'File written successfully', path: filePath}); } }); // Write the buffer to the stream try { writeStream.write(fileBuffer); writeStream.end(); } catch (writeErr) { if (hasError || hasFinished) return; hasError = true; logger.warn(`SFTP write operation failed, trying fallback method: ${writeErr.message}`); tryFallbackMethod(); } }); } catch (sftpErr) { logger.warn(`SFTP connection error, trying fallback method: ${sftpErr.message}`); tryFallbackMethod(); } }; // Fallback method using command line const tryFallbackMethod = () => { try { const base64Content = Buffer.from(content, 'utf8').toString('base64'); const escapedPath = filePath.replace(/'/g, "'\"'\"'"); const writeCommand = `echo '${base64Content}' | base64 -d > '${escapedPath}' && echo "SUCCESS"`; sshConn.client.exec(writeCommand, (err, stream) => { if (err) { clearTimeout(commandTimeout); logger.error('Fallback write command failed:', err); if (!res.headersSent) { return res.status(500).json({error: `Write failed: ${err.message}`}); } return; } let outputData = ''; let errorData = ''; stream.on('data', (chunk: Buffer) => { outputData += chunk.toString(); }); stream.stderr.on('data', (chunk: Buffer) => { errorData += chunk.toString(); }); stream.on('close', (code) => { clearTimeout(commandTimeout); if (outputData.includes('SUCCESS')) { logger.success(`File written successfully via fallback: ${filePath}`); if (!res.headersSent) { res.json({message: 'File written successfully', path: filePath}); } } else { logger.error(`Fallback write failed with code ${code}: ${errorData}`); if (!res.headersSent) { res.status(500).json({error: `Write failed: ${errorData}`}); } } }); stream.on('error', (streamErr) => { clearTimeout(commandTimeout); logger.error('Fallback write stream error:', streamErr); if (!res.headersSent) { res.status(500).json({error: `Write stream error: ${streamErr.message}`}); } }); }); } catch (fallbackErr) { clearTimeout(commandTimeout); logger.error('Fallback method failed:', fallbackErr); if (!res.headersSent) { res.status(500).json({error: `All write methods failed: ${fallbackErr.message}`}); } } }; // Start with SFTP trySFTP(); }); // Upload file route app.post('/ssh/file_manager/ssh/uploadFile', (req, res) => { const {sessionId, path: filePath, content, fileName} = req.body; const sshConn = sshSessions[sessionId]; if (!sessionId) { return res.status(400).json({error: 'Session ID is required'}); } if (!sshConn?.isConnected) { return res.status(400).json({error: 'SSH connection not established'}); } if (!filePath || !fileName || content === undefined) { return res.status(400).json({error: 'File path, name, and content are required'}); } sshConn.lastActive = Date.now(); scheduleSessionCleanup(sessionId); const fullPath = filePath.endsWith('/') ? filePath + fileName : filePath + '/' + fileName; const commandTimeout = setTimeout(() => { logger.error(`SSH uploadFile command timed out for session: ${sessionId}`); if (!res.headersSent) { res.status(500).json({error: 'SSH command timed out'}); } }, 60000); // Increased timeout to 60 seconds // Try SFTP first, fallback to command line if it fails const trySFTP = () => { try { sshConn.client.sftp((err, sftp) => { if (err) { logger.warn(`SFTP failed, trying fallback method: ${err.message}`); tryFallbackMethod(); return; } // Convert content to buffer let fileBuffer; try { if (typeof content === 'string') { fileBuffer = Buffer.from(content, 'utf8'); } else if (Buffer.isBuffer(content)) { fileBuffer = content; } else { fileBuffer = Buffer.from(content); } } catch (bufferErr) { clearTimeout(commandTimeout); logger.error('Buffer conversion error:', bufferErr); if (!res.headersSent) { return res.status(500).json({error: 'Invalid file content format'}); } return; } // Create write stream with error handling const writeStream = sftp.createWriteStream(fullPath); let hasError = false; let hasFinished = false; writeStream.on('error', (streamErr) => { if (hasError || hasFinished) return; hasError = true; logger.warn(`SFTP write failed, trying fallback method: ${streamErr.message}`); tryFallbackMethod(); }); writeStream.on('finish', () => { if (hasError || hasFinished) return; hasFinished = true; clearTimeout(commandTimeout); logger.success(`File uploaded successfully via SFTP: ${fullPath}`); if (!res.headersSent) { res.json({message: 'File uploaded successfully', path: fullPath}); } }); writeStream.on('close', () => { if (hasError || hasFinished) return; hasFinished = true; clearTimeout(commandTimeout); logger.success(`File uploaded successfully via SFTP: ${fullPath}`); if (!res.headersSent) { res.json({message: 'File uploaded successfully', path: fullPath}); } }); // Write the buffer to the stream try { writeStream.write(fileBuffer); writeStream.end(); } catch (writeErr) { if (hasError || hasFinished) return; hasError = true; logger.warn(`SFTP write operation failed, trying fallback method: ${writeErr.message}`); tryFallbackMethod(); } }); } catch (sftpErr) { logger.warn(`SFTP connection error, trying fallback method: ${sftpErr.message}`); tryFallbackMethod(); } }; // Fallback method using command line with chunked approach const tryFallbackMethod = () => { try { // Convert content to base64 and split into smaller chunks if needed const base64Content = Buffer.from(content, 'utf8').toString('base64'); const chunkSize = 1000000; // 1MB chunks const chunks = []; for (let i = 0; i < base64Content.length; i += chunkSize) { chunks.push(base64Content.slice(i, i + chunkSize)); } if (chunks.length === 1) { // Single chunk - use simple approach const tempFile = `/tmp/upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const escapedTempFile = tempFile.replace(/'/g, "'\"'\"'"); const escapedPath = fullPath.replace(/'/g, "'\"'\"'"); const writeCommand = `echo '${chunks[0]}' | base64 -d > '${escapedPath}' && echo "SUCCESS"`; sshConn.client.exec(writeCommand, (err, stream) => { if (err) { clearTimeout(commandTimeout); logger.error('Fallback upload command failed:', err); if (!res.headersSent) { return res.status(500).json({error: `Upload failed: ${err.message}`}); } return; } let outputData = ''; let errorData = ''; stream.on('data', (chunk: Buffer) => { outputData += chunk.toString(); }); stream.stderr.on('data', (chunk: Buffer) => { errorData += chunk.toString(); }); stream.on('close', (code) => { clearTimeout(commandTimeout); if (outputData.includes('SUCCESS')) { logger.success(`File uploaded successfully via fallback: ${fullPath}`); if (!res.headersSent) { res.json({message: 'File uploaded successfully', path: fullPath}); } } else { logger.error(`Fallback upload failed with code ${code}: ${errorData}`); if (!res.headersSent) { res.status(500).json({error: `Upload failed: ${errorData}`}); } } }); stream.on('error', (streamErr) => { clearTimeout(commandTimeout); logger.error('Fallback upload stream error:', streamErr); if (!res.headersSent) { res.status(500).json({error: `Upload stream error: ${streamErr.message}`}); } }); }); } else { // Multiple chunks - use chunked approach const tempFile = `/tmp/upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const escapedTempFile = tempFile.replace(/'/g, "'\"'\"'"); const escapedPath = fullPath.replace(/'/g, "'\"'\"'"); let writeCommand = `> '${escapedPath}'`; // Start with empty file chunks.forEach((chunk, index) => { writeCommand += ` && echo '${chunk}' | base64 -d >> '${escapedPath}'`; }); writeCommand += ` && echo "SUCCESS"`; sshConn.client.exec(writeCommand, (err, stream) => { if (err) { clearTimeout(commandTimeout); logger.error('Chunked fallback upload failed:', err); if (!res.headersSent) { return res.status(500).json({error: `Chunked upload failed: ${err.message}`}); } return; } let outputData = ''; let errorData = ''; stream.on('data', (chunk: Buffer) => { outputData += chunk.toString(); }); stream.stderr.on('data', (chunk: Buffer) => { errorData += chunk.toString(); }); stream.on('close', (code) => { clearTimeout(commandTimeout); if (outputData.includes('SUCCESS')) { logger.success(`File uploaded successfully via chunked fallback: ${fullPath}`); if (!res.headersSent) { res.json({message: 'File uploaded successfully', path: fullPath}); } } else { logger.error(`Chunked fallback upload failed with code ${code}: ${errorData}`); if (!res.headersSent) { res.status(500).json({error: `Chunked upload failed: ${errorData}`}); } } }); stream.on('error', (streamErr) => { clearTimeout(commandTimeout); logger.error('Chunked fallback upload stream error:', streamErr); if (!res.headersSent) { res.status(500).json({error: `Chunked upload stream error: ${streamErr.message}`}); } }); }); } } catch (fallbackErr) { clearTimeout(commandTimeout); logger.error('Fallback method failed:', fallbackErr); if (!res.headersSent) { res.status(500).json({error: `All upload methods failed: ${fallbackErr.message}`}); } } }; // Start with SFTP trySFTP(); }); // Create new file route app.post('/ssh/file_manager/ssh/createFile', (req, res) => { const {sessionId, path: filePath, fileName, content = ''} = req.body; const sshConn = sshSessions[sessionId]; if (!sessionId) { return res.status(400).json({error: 'Session ID is required'}); } if (!sshConn?.isConnected) { return res.status(400).json({error: 'SSH connection not established'}); } if (!filePath || !fileName) { return res.status(400).json({error: 'File path and name are required'}); } sshConn.lastActive = Date.now(); scheduleSessionCleanup(sessionId); const fullPath = filePath.endsWith('/') ? filePath + fileName : filePath + '/' + fileName; const escapedPath = fullPath.replace(/'/g, "'\"'\"'"); const commandTimeout = setTimeout(() => { logger.error(`SSH createFile command timed out for session: ${sessionId}`); if (!res.headersSent) { res.status(500).json({error: 'SSH command timed out'}); } }, 15000); const createCommand = `touch '${escapedPath}' && echo "SUCCESS" && exit 0`; sshConn.client.exec(createCommand, (err, stream) => { if (err) { clearTimeout(commandTimeout); logger.error('SSH createFile error:', err); if (!res.headersSent) { return res.status(500).json({error: err.message}); } return; } let outputData = ''; let errorData = ''; stream.on('data', (chunk: Buffer) => { outputData += chunk.toString(); }); stream.stderr.on('data', (chunk: Buffer) => { errorData += chunk.toString(); if (chunk.toString().includes('Permission denied')) { clearTimeout(commandTimeout); logger.error(`Permission denied creating file: ${fullPath}`); if (!res.headersSent) { return res.status(403).json({ error: `Permission denied: Cannot create file ${fullPath}. Check directory permissions.` }); } return; } }); stream.on('close', (code) => { clearTimeout(commandTimeout); if (outputData.includes('SUCCESS')) { if (!res.headersSent) { res.json({message: 'File created successfully', path: fullPath}); } return; } if (code !== 0) { logger.error(`SSH createFile command failed with code ${code}: ${errorData.replace(/\n/g, ' ').trim()}`); if (!res.headersSent) { return res.status(500).json({error: `Command failed: ${errorData}`}); } return; } if (!res.headersSent) { res.json({message: 'File created successfully', path: fullPath}); } }); stream.on('error', (streamErr) => { clearTimeout(commandTimeout); logger.error('SSH createFile stream error:', streamErr); if (!res.headersSent) { res.status(500).json({error: `Stream error: ${streamErr.message}`}); } }); }); }); // Create folder route app.post('/ssh/file_manager/ssh/createFolder', (req, res) => { const {sessionId, path: folderPath, folderName} = req.body; const sshConn = sshSessions[sessionId]; if (!sessionId) { return res.status(400).json({error: 'Session ID is required'}); } if (!sshConn?.isConnected) { return res.status(400).json({error: 'SSH connection not established'}); } if (!folderPath || !folderName) { return res.status(400).json({error: 'Folder path and name are required'}); } sshConn.lastActive = Date.now(); scheduleSessionCleanup(sessionId); const fullPath = folderPath.endsWith('/') ? folderPath + folderName : folderPath + '/' + folderName; const escapedPath = fullPath.replace(/'/g, "'\"'\"'"); const commandTimeout = setTimeout(() => { logger.error(`SSH createFolder command timed out for session: ${sessionId}`); if (!res.headersSent) { res.status(500).json({error: 'SSH command timed out'}); } }, 15000); const createCommand = `mkdir -p '${escapedPath}' && echo "SUCCESS" && exit 0`; sshConn.client.exec(createCommand, (err, stream) => { if (err) { clearTimeout(commandTimeout); logger.error('SSH createFolder error:', err); if (!res.headersSent) { return res.status(500).json({error: err.message}); } return; } let outputData = ''; let errorData = ''; stream.on('data', (chunk: Buffer) => { outputData += chunk.toString(); }); stream.stderr.on('data', (chunk: Buffer) => { errorData += chunk.toString(); if (chunk.toString().includes('Permission denied')) { clearTimeout(commandTimeout); logger.error(`Permission denied creating folder: ${fullPath}`); if (!res.headersSent) { return res.status(403).json({ error: `Permission denied: Cannot create folder ${fullPath}. Check directory permissions.` }); } return; } }); stream.on('close', (code) => { clearTimeout(commandTimeout); if (outputData.includes('SUCCESS')) { if (!res.headersSent) { res.json({message: 'Folder created successfully', path: fullPath}); } return; } if (code !== 0) { logger.error(`SSH createFolder command failed with code ${code}: ${errorData.replace(/\n/g, ' ').trim()}`); if (!res.headersSent) { return res.status(500).json({error: `Command failed: ${errorData}`}); } return; } if (!res.headersSent) { res.json({message: 'Folder created successfully', path: fullPath}); } }); stream.on('error', (streamErr) => { clearTimeout(commandTimeout); logger.error('SSH createFolder stream error:', streamErr); if (!res.headersSent) { res.status(500).json({error: `Stream error: ${streamErr.message}`}); } }); }); }); // Delete file/folder route app.delete('/ssh/file_manager/ssh/deleteItem', (req, res) => { const {sessionId, path: itemPath, isDirectory} = req.body; const sshConn = sshSessions[sessionId]; if (!sessionId) { return res.status(400).json({error: 'Session ID is required'}); } if (!sshConn?.isConnected) { return res.status(400).json({error: 'SSH connection not established'}); } if (!itemPath) { return res.status(400).json({error: 'Item path is required'}); } sshConn.lastActive = Date.now(); scheduleSessionCleanup(sessionId); const escapedPath = itemPath.replace(/'/g, "'\"'\"'"); const commandTimeout = setTimeout(() => { logger.error(`SSH deleteItem command timed out for session: ${sessionId}`); if (!res.headersSent) { res.status(500).json({error: 'SSH command timed out'}); } }, 15000); const deleteCommand = isDirectory ? `rm -rf '${escapedPath}' && echo "SUCCESS" && exit 0` : `rm -f '${escapedPath}' && echo "SUCCESS" && exit 0`; sshConn.client.exec(deleteCommand, (err, stream) => { if (err) { clearTimeout(commandTimeout); logger.error('SSH deleteItem error:', err); if (!res.headersSent) { return res.status(500).json({error: err.message}); } return; } let outputData = ''; let errorData = ''; stream.on('data', (chunk: Buffer) => { outputData += chunk.toString(); }); stream.stderr.on('data', (chunk: Buffer) => { errorData += chunk.toString(); if (chunk.toString().includes('Permission denied')) { clearTimeout(commandTimeout); logger.error(`Permission denied deleting: ${itemPath}`); if (!res.headersSent) { return res.status(403).json({ error: `Permission denied: Cannot delete ${itemPath}. Check file permissions.` }); } return; } }); stream.on('close', (code) => { clearTimeout(commandTimeout); if (outputData.includes('SUCCESS')) { if (!res.headersSent) { res.json({message: 'Item deleted successfully', path: itemPath}); } return; } if (code !== 0) { logger.error(`SSH deleteItem command failed with code ${code}: ${errorData.replace(/\n/g, ' ').trim()}`); if (!res.headersSent) { return res.status(500).json({error: `Command failed: ${errorData}`}); } return; } if (!res.headersSent) { res.json({message: 'Item deleted successfully', path: itemPath}); } }); stream.on('error', (streamErr) => { clearTimeout(commandTimeout); logger.error('SSH deleteItem stream error:', streamErr); if (!res.headersSent) { res.status(500).json({error: `Stream error: ${streamErr.message}`}); } }); }); }); // Rename file/folder route app.put('/ssh/file_manager/ssh/renameItem', (req, res) => { const {sessionId, oldPath, newName} = req.body; const sshConn = sshSessions[sessionId]; if (!sessionId) { return res.status(400).json({error: 'Session ID is required'}); } if (!sshConn?.isConnected) { return res.status(400).json({error: 'SSH connection not established'}); } if (!oldPath || !newName) { return res.status(400).json({error: 'Old path and new name are required'}); } sshConn.lastActive = Date.now(); scheduleSessionCleanup(sessionId); const oldDir = oldPath.substring(0, oldPath.lastIndexOf('/') + 1); const newPath = oldDir + newName; const escapedOldPath = oldPath.replace(/'/g, "'\"'\"'"); const escapedNewPath = newPath.replace(/'/g, "'\"'\"'"); const commandTimeout = setTimeout(() => { logger.error(`SSH renameItem command timed out for session: ${sessionId}`); if (!res.headersSent) { res.status(500).json({error: 'SSH command timed out'}); } }, 15000); const renameCommand = `mv '${escapedOldPath}' '${escapedNewPath}' && echo "SUCCESS" && exit 0`; sshConn.client.exec(renameCommand, (err, stream) => { if (err) { clearTimeout(commandTimeout); logger.error('SSH renameItem error:', err); if (!res.headersSent) { return res.status(500).json({error: err.message}); } return; } let outputData = ''; let errorData = ''; stream.on('data', (chunk: Buffer) => { outputData += chunk.toString(); }); stream.stderr.on('data', (chunk: Buffer) => { errorData += chunk.toString(); if (chunk.toString().includes('Permission denied')) { clearTimeout(commandTimeout); logger.error(`Permission denied renaming: ${oldPath}`); if (!res.headersSent) { return res.status(403).json({ error: `Permission denied: Cannot rename ${oldPath}. Check file permissions.` }); } return; } }); stream.on('close', (code) => { clearTimeout(commandTimeout); if (outputData.includes('SUCCESS')) { if (!res.headersSent) { res.json({message: 'Item renamed successfully', oldPath, newPath}); } return; } if (code !== 0) { logger.error(`SSH renameItem command failed with code ${code}: ${errorData.replace(/\n/g, ' ').trim()}`); if (!res.headersSent) { return res.status(500).json({error: `Command failed: ${errorData}`}); } return; } if (!res.headersSent) { res.json({message: 'Item renamed successfully', oldPath, newPath}); } }); stream.on('error', (streamErr) => { clearTimeout(commandTimeout); logger.error('SSH renameItem stream error:', streamErr); if (!res.headersSent) { res.status(500).json({error: `Stream error: ${streamErr.message}`}); } }); }); }); process.on('SIGINT', () => { Object.keys(sshSessions).forEach(cleanupSession); process.exit(0); }); process.on('SIGTERM', () => { Object.keys(sshSessions).forEach(cleanupSession); process.exit(0); }); const PORT = 8084; app.listen(PORT, () => { });