<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
ini_set('log_errors', 1);
// Start output buffering to catch any unexpected output
ob_start();
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit(0);
}
class SFTPConnector {
private $connection;
private $sftp;
public function __construct() {
// Check if SSH2 extension is available
if (!extension_loaded('ssh2')) {
throw new Exception('SSH2 extension is not installed');
}
}
public function connect($host, $port, $username, $password) {
try {
$this->connection = ssh2_connect($host, $port);
if (!$this->connection) {
throw new Exception("Could not connect to $host on port $port");
}
if (!ssh2_auth_password($this->connection, $username, $password)) {
throw new Exception("Authentication failed for user $username");
}
$this->sftp = ssh2_sftp($this->connection);
if (!$this->sftp) {
throw new Exception("Could not initialize SFTP subsystem");
}
return true;
} catch (Exception $e) {
return $e->getMessage();
}
}
public function listDirectory($path = '.') {
try {
$handle = opendir("ssh2.sftp://{$this->sftp}$path");
if (!$handle) {
throw new Exception("Could not open directory: $path");
}
$files = [];
while (($file = readdir($handle)) !== false) {
if ($file !== '.' && $file !== '..') {
$fullPath = rtrim($path, '/') . '/' . $file;
$stat = ssh2_sftp_stat($this->sftp, $fullPath);
if ($stat !== false) {
$files[] = [
'name' => $file,
'path' => $fullPath,
'size' => $stat['size'],
'modified' => date('Y-m-d H:i:s', $stat['mtime']),
'is_dir' => is_dir("ssh2.sftp://{$this->sftp}$fullPath")
];
}
}
}
closedir($handle);
// Sort: directories first, then files
usort($files, function($a, $b) {
if ($a['is_dir'] && !$b['is_dir']) return -1;
if (!$a['is_dir'] && $b['is_dir']) return 1;
return strcasecmp($a['name'], $b['name']);
});
return $files;
} catch (Exception $e) {
return false;
}
}
public function uploadFile($localFile, $remotePath) {
try {
$stream = fopen("ssh2.sftp://{$this->sftp}$remotePath", 'w');
if (!$stream) {
throw new Exception("Could not open remote file for writing");
}
$data = file_get_contents($localFile);
if (fwrite($stream, $data) === false) {
throw new Exception("Could not write data to remote file");
}
fclose($stream);
return true;
} catch (Exception $e) {
return $e->getMessage();
}
}
public function downloadFile($remotePath, $localPath) {
try {
$remoteStream = fopen("ssh2.sftp://{$this->sftp}$remotePath", 'r');
if (!$remoteStream) {
throw new Exception("Could not open remote file for reading");
}
$localStream = fopen($localPath, 'w');
if (!$localStream) {
throw new Exception("Could not open local file for writing");
}
while (!feof($remoteStream)) {
fwrite($localStream, fread($remoteStream, 8192));
}
fclose($remoteStream);
fclose($localStream);
return true;
} catch (Exception $e) {
return $e->getMessage();
}
}
public function deleteFile($remotePath) {
try {
if (ssh2_sftp_unlink($this->sftp, $remotePath)) {
return true;
}
return "Failed to delete file";
} catch (Exception $e) {
return $e->getMessage();
}
}
public function createDirectory($path) {
try {
if (ssh2_sftp_mkdir($this->sftp, $path)) {
return true;
}
return "Failed to create directory";
} catch (Exception $e) {
return $e->getMessage();
}
}
public function disconnect() {
if ($this->connection) {
ssh2_disconnect($this->connection);
}
}
}
// Alternative system command approach for servers without SSH2 extension
class SystemSFTPConnector {
private $host;
private $port;
private $username;
private $password;
private $connected = false;
public function connect($host, $port, $username, $password) {
$this->host = $host;
$this->port = $port;
$this->username = $username;
$this->password = $password;
if (!function_exists('exec')) {
return 'System commands are disabled on this server';
}
// Test SFTP connectivity
$testCommand = "echo 'quit' | sftp -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -P {$port} {$username}@{$host} 2>&1";
$output = [];
$returnCode = null;
exec($testCommand, $output, $returnCode);
if ($returnCode === 0 || strpos(implode(' ', $output), 'Connected to') !== false) {
$this->connected = true;
return true;
}
return 'Cannot connect to SFTP server: ' . implode(' ', $output);
}
public function listDirectory($path = '/') {
if (!$this->connected) {
return false;
}
$scriptFile = tempnam(sys_get_temp_dir(), 'sftp_script');
$script = "cd {$path}\nls -la\nquit\n";
file_put_contents($scriptFile, $script);
$command = "sftp -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -P {$this->port} -b {$scriptFile} {$this->username}@{$this->host} 2>&1";
$output = [];
exec($command, $output);
unlink($scriptFile);
// Parse the output
$files = [];
$inListing = false;
foreach ($output as $line) {
if (strpos($line, 'sftp>') !== false && strpos($line, 'ls') !== false) {
$inListing = true;
continue;
}
if ($inListing && strpos($line, 'sftp>') !== false) {
break;
}
if ($inListing && preg_match('/^([drwx-]+)\s+\d+\s+\w+\s+\w+\s+(\d+)\s+(\w+\s+\d+\s+[\d:]+)\s+(.+)$/', $line, $matches)) {
$name = $matches[4];
if ($name !== '.' && $name !== '..') {
$files[] = [
'name' => $name,
'path' => rtrim($path, '/') . '/' . $name,
'size' => (int)$matches[2],
'modified' => $matches[3],
'is_dir' => substr($matches[1], 0, 1) === 'd'
];
}
}
}
return $files;
}
public function deleteFile($remotePath) {
if (!$this->connected) {
return 'Not connected';
}
$scriptFile = tempnam(sys_get_temp_dir(), 'sftp_script');
$script = "rm {$remotePath}\nquit\n";
file_put_contents($scriptFile, $script);
$command = "sftp -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -P {$this->port} -b {$scriptFile} {$this->username}@{$this->host} 2>&1";
$output = [];
exec($command, $output);
unlink($scriptFile);
if (strpos(implode(' ', $output), 'Removing') !== false || empty(array_filter($output))) {
return true;
}
return 'Delete failed: ' . implode(' ', $output);
}
public function createDirectory($path) {
if (!$this->connected) {
return 'Not connected';
}
$scriptFile = tempnam(sys_get_temp_dir(), 'sftp_script');
$script = "mkdir {$path}\nquit\n";
file_put_contents($scriptFile, $script);
$command = "sftp -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -P {$this->port} -b {$scriptFile} {$this->username}@{$this->host} 2>&1";
$output = [];
exec($command, $output);
unlink($scriptFile);
return true;
}
}
// Handle API requests
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
if (json_last_error() !== JSON_ERROR_NONE) {
ob_clean();
echo json_encode([
'success' => false,
'message' => 'Invalid JSON input: ' . json_last_error_msg(),
]);
exit;
}
$action = $input['action'] ?? '';
$response = ['success' => false, 'message' => '', 'data' => null];
try {
// Try SSH2 extension first, fall back to system commands
if (extension_loaded('ssh2')) {
$connector = new SFTPConnector();
$connectorType = 'SSH2 Extension';
} else if (function_exists('exec')) {
$connector = new SystemSFTPConnector();
$connectorType = 'System Commands';
} else {
throw new Exception('Neither SSH2 extension nor system commands are available');
}
switch ($action) {
case 'connect':
$result = $connector->connect(
$input['host'] ?? '',
$input['port'] ?? 22,
$input['username'] ?? '',
$input['password'] ?? ''
);
if ($result === true) {
session_start();
$_SESSION['sftp_connected'] = true;
$_SESSION['sftp_config'] = [
'host' => $input['host'],
'port' => $input['port'] ?? 22,
'username' => $input['username'],
'password' => $input['password']
];
$response['success'] = true;
$response['message'] = "Connected successfully via {$connectorType}";
} else {
$response['message'] = $result;
}
break;
case 'list':
session_start();
if (isset($_SESSION['sftp_connected'])) {
$config = $_SESSION['sftp_config'];
// Reconnect for each request
if (extension_loaded('ssh2')) {
$connector = new SFTPConnector();
} else {
$connector = new SystemSFTPConnector();
}
$connectResult = $connector->connect(
$config['host'],
$config['port'],
$config['username'],
$config['password']
);
if ($connectResult !== true) {
$response['message'] = 'Reconnection failed: ' . $connectResult;
break;
}
$files = $connector->listDirectory($input['path'] ?? '/');
if ($files !== false) {
$response['success'] = true;
$response['data'] = $files;
} else {
$response['message'] = 'Failed to list directory';
}
} else {
$response['message'] = 'Not connected to SFTP server';
}
break;
case 'delete':
session_start();
if (isset($_SESSION['sftp_connected'])) {
$config = $_SESSION['sftp_config'];
if (extension_loaded('ssh2')) {
$connector = new SFTPConnector();
} else {
$connector = new SystemSFTPConnector();
}
$connectResult = $connector->connect(
$config['host'],
$config['port'],
$config['username'],
$config['password']
);
if ($connectResult !== true) {
$response['message'] = 'Reconnection failed: ' . $connectResult;
break;
}
$result = $connector->deleteFile($input['path'] ?? '');
if ($result === true) {
$response['success'] = true;
$response['message'] = 'File deleted successfully';
} else {
$response['message'] = $result;
}
} else {
$response['message'] = 'Not connected to SFTP server';
}
break;
case 'create_folder':
session_start();
if (isset($_SESSION['sftp_connected'])) {
$config = $_SESSION['sftp_config'];
if (extension_loaded('ssh2')) {
$connector = new SFTPConnector();
} else {
$connector = new SystemSFTPConnector();
}
$connectResult = $connector->connect(
$config['host'],
$config['port'],
$config['username'],
$config['password']
);
if ($connectResult !== true) {
$response['message'] = 'Reconnection failed: ' . $connectResult;
break;
}
$result = $connector->createDirectory($input['path'] ?? '');
if ($result === true) {
$response['success'] = true;
$response['message'] = 'Directory created successfully';
} else {
$response['message'] = $result;
}
} else {
$response['message'] = 'Not connected to SFTP server';
}
break;
case 'disconnect':
session_start();
unset($_SESSION['sftp_connected']);
unset($_SESSION['sftp_config']);
if (isset($connector)) {
$connector->disconnect();
}
$response['success'] = true;
$response['message'] = 'Disconnected successfully';
break;
case 'test':
$response['success'] = true;
$response['message'] = 'SFTP connector responding';
$response['data'] = [
'ssh2_loaded' => extension_loaded('ssh2'),
'exec_available' => function_exists('exec'),
'system_available' => function_exists('system'),
'shell_exec_available' => function_exists('shell_exec'),
'php_version' => PHP_VERSION,
'time' => date('Y-m-d H:i:s'),
'preferred_method' => extension_loaded('ssh2') ? 'SSH2 Extension' : (function_exists('exec') ? 'System Commands' : 'None Available')
];
break;
default:
$response['message'] = 'Invalid action: ' . $action;
}
} catch (Exception $e) {
$response['message'] = 'Server error: ' . $e->getMessage();
$response['debug'] = $e->getTraceAsString();
}
ob_clean();
echo json_encode($response);
} else {
ob_clean();
echo json_encode([
'success' => false,
'message' => 'Only POST requests are allowed',
'debug' => 'Method: ' . $_SERVER['REQUEST_METHOD']
]);
}
?>