Seditio Source
Root |
./othercms/PHPFusion 9.10.20/includes/elFinder/php/elFinderVolumeSFTPphpseclib.class.php
<?php

/**
 * Simple elFinder driver for SFTP using phpseclib 1
 *
 * @author Dmitry (dio) Levashov
 * @author Cem (discofever), sitecode
 * @reference http://phpseclib.sourceforge.net/sftp/2.0/examples.html
 **/
class elFinderVolumeSFTPphpseclib extends elFinderVolumeFTP {

   
/**
     * Constructor
     * Extend options with required fields
     *
     * @author Dmitry (dio) Levashov
     * @author Cem (DiscoFever)
     */
   
public function __construct()
    {
       
$opts = array(
           
'host' => 'localhost',
           
'user' => '',
           
'pass' => '',
           
'port' => 22,
           
'path' => '/',
           
'timeout' => 20,
           
'owner' => true,
           
'tmbPath' => '',
           
'tmpPath' => '',
           
'separator' => '/',
           
'phpseclibDir' => '../phpseclib/',
           
'connectCallback' => null, //provide your own already instantiated phpseclib $Sftp object returned by this callback
                                       //'connectCallback'=> function($options) {
                                       //     //load and instantiate phpseclib $sftp
                                       //     return $sftp;
                                       // },
           
'checkSubfolders' => -1,
           
'dirMode' => 0755,
           
'fileMode' => 0644,
           
'rootCssClass' => 'elfinder-navbar-root-ftp',
        );
       
$this->options = array_merge($this->options, $opts);
       
$this->options['mimeDetect'] = 'internal';
    }

   
/**
     * Prepare
     * Call from elFinder::netmout() before volume->mount()
     *
     * @param $options
     *
     * @return array volume root options
     * @author Naoki Sawada
     */
   
public function netmountPrepare($options)
    {
       
$options['statOwner'] = true;
       
$options['allowChmodReadOnly'] = true;
       
$options['acceptedName'] = '#^[^/\\?*:|"<>]*[^./\\?*:|"<>]$#';
        return
$options;
    }

   
/*********************************************************************/
    /*                        INIT AND CONFIGURE                         */
    /*********************************************************************/

    /**
     * Prepare SFTP connection
     * Connect to remote server and check if credentials are correct, if so, store the connection
     *
     * @return bool
     * @author Dmitry (dio) Levashov
     * @author Cem (DiscoFever)
     **/
   
protected function init()
    {
        if (!
$this->options['connectCallback']) {
            if (!
$this->options['host']
                || !
$this->options['port']) {
                return
$this->setError('Required options undefined.');
            }

            if (!
$this->options['path']) {
               
$this->options['path'] = '/';
            }

           
// make net mount key
           
$this->netMountKey = md5(join('-', array('sftpphpseclib', $this->options['host'], $this->options['port'], $this->options['path'], $this->options['user'])));

           
set_include_path(get_include_path() . PATH_SEPARATOR . getcwd().'/'.$this->options['phpseclibDir']);
            include_once(
'Net/SFTP.php');

            if (!
class_exists('Net_SFTP')) {
                return
$this->setError('SFTP extension not loaded. Install phpseclib version 1: http://phpseclib.sourceforge.net/ Set option "phpseclibDir" accordingly.');
            }

           
// remove protocol from host
           
$scheme = parse_url($this->options['host'], PHP_URL_SCHEME);

            if (
$scheme) {
               
$this->options['host'] = substr($this->options['host'], strlen($scheme) + 3);
            }
        } else {
           
// make net mount key
           
$this->netMountKey = md5(join('-', array('sftpphpseclib', $this->options['path'])));
        }

       
// normalize root path
       
$this->root = $this->options['path'] = $this->_normpath($this->options['path']);

        if (empty(
$this->options['alias'])) {
           
$this->options['alias'] = $this->options['user'] . '@' . $this->options['host'];
            if (!empty(
$this->options['netkey'])) {
               
elFinder::$instance->updateNetVolumeOption($this->options['netkey'], 'alias', $this->options['alias']);
            }
        }

       
$this->rootName = $this->options['alias'];
       
$this->options['separator'] = '/';

        if (
is_null($this->options['syncChkAsTs'])) {
           
$this->options['syncChkAsTs'] = true;
        }

        return
$this->needOnline? $this->connect() : true;

    }


   
/**
     * Configure after successfull mount.
     *
     * @return void
     * @throws elFinderAbortException
     * @author Dmitry (dio) Levashov
     */
   
protected function configure()
    {
       
parent::configure();

        if (!
$this->tmp) {
           
$this->disabled[] = 'mkfile';
           
$this->disabled[] = 'paste';
           
$this->disabled[] = 'upload';
           
$this->disabled[] = 'edit';
           
//$this->disabled[] = 'archive';
            //$this->disabled[] = 'extract';
       
}

       
$this->disabled[] = 'archive';
       
$this->disabled[] = 'extract';
    }

   
/**
     * Connect to sftp server
     *
     * @return bool
     * @author sitecode
     **/
   
protected function connect()
    {
       
//use ca
       
if ($this->options['connectCallback']) {
           
$this->connect = $this->options['connectCallback']($this->options);
            if (!
$this->connect || !$this->connect->isConnected()) {
                return
$this->setError('Unable to connect successfully');
            }

            return
true;
        }

        try{
           
$host = $this->options['host'] . ($this->options['port'] != 22 ? ':' . $this->options['port'] : '');
           
$this->connect = new Net_SFTP($host);
           
//TODO check fingerprint before login, fail if no match to last time
           
if (!$this->connect->login($this->options['user'], $this->options['pass'])) {
                return
$this->setError('Unable to connect to SFTP server ' . $host);
            }
        } catch (
Exception $e) {
            return
$this->setError('Error while connecting to SFTP server '  . $host . ': ' . $e->getMessage());
        }

        if (!
$this->connect->chdir($this->root)
           
/*|| $this->root != $this->connect->pwd()*/) {
           
//$this->umount();
           
return $this->setError('Unable to open root folder.');
        }

        return
true;
    }

   
/**
     * Call rawlist
     *
     * @param string $path
     *
     * @return array
     */
   
protected function ftpRawList($path)
    {
        return
$this->connect->rawlist($path ?: '.') ?: [];
/*
        $raw = $this->connect->rawlist($path ?: '.') ?: [];
        $raw = array_map(function($key, $value) {
            $value['name'] = $key;
            return $value;
        }, array_keys($raw), $raw);
        return $raw;
*/
   
}

   
/*********************************************************************/
    /*                               FS API                              */
    /*********************************************************************/

    /**
     * Close opened connection
     *
     * @return void
     * @author Dmitry (dio) Levashov
     **/
   
public function umount()
    {
       
$this->connect && $this->connect->disconnect();
    }


   
/**
     * Parse line from rawlist() output and return file stat (array)
     *
     * @param  string $raw line from rawlist() output
     * @param         $base
     * @param bool    $nameOnly
     *
     * @return array
     * @author Dmitry Levashov
     */
   
protected function parseRaw($raw, $base, $nameOnly = false)
    {
       
$info = $raw;
       
$stat = array();

        if (
$info['filename'] == '.' || $info['filename'] == '..') {
            return
false;
        }

       
$name = $info['filename'];

        if (
preg_match('|(.+)\-\>(.+)|', $name, $m)) {
           
$name = trim($m[1]);
           
// check recursive processing
           
if ($this->cacheDirTarget && $this->_joinPath($base, $name) !== $this->cacheDirTarget) {
                return array();
            }
            if (!
$nameOnly) {
               
$target = trim($m[2]);
                if (
substr($target, 0, 1) !== $this->separator) {
                   
$target = $this->getFullPath($target, $base);
                }
               
$target = $this->_normpath($target);
               
$stat['name'] = $name;
               
$stat['target'] = $target;
                return
$stat;
            }
        }

        if (
$nameOnly) {
            return array(
'name' => $name);
        }

       
$stat['ts'] = $info['mtime'];

        if (
$this->options['statOwner']) {
           
$stat['owner'] = $info['uid'];
           
$stat['group'] = $info['gid'];
           
$stat['perm'] = $info['permissions'];
           
$stat['isowner'] = isset($stat['owner']) ? ($this->options['owner'] ? true : ($stat['owner'] == $this->options['user'])) : true;
        }

       
$owner_computed = isset($stat['isowner']) ? $stat['isowner'] : $this->options['owner'];
       
$perm = $this->parsePermissions($info['permissions'], $owner_computed);
       
$stat['name'] = $name;
       
$stat['mime'] = $info['type'] == NET_SFTP_TYPE_DIRECTORY ? 'directory' : $this->mimetype($stat['name'], true);
       
$stat['size'] = $stat['mime'] == 'directory' ? 0 : $info['size'];
       
$stat['read'] = $perm['read'];
       
$stat['write'] = $perm['write'];

        return
$stat;
    }

   
/**
     * Parse permissions string. Return array(read => true/false, write => true/false)
     *
     * @param  int $perm
     *                                             The isowner parameter is computed by the caller.
     *                                             If the owner parameter in the options is true, the user is the actual owner of all objects even if the user used in the ftp Login
     *                                             is different from the file owner id.
     *                                             If the owner parameter is false to understand if the user is the file owner we compare the ftp user with the file owner id.
     * @param Boolean $isowner                     . Tell if the current user is the owner of the object.
     *
     * @return array
     * @author Dmitry (dio) Levashov
     * @author sitecode
     */
   
protected function parsePermissions($permissions, $isowner = true)
    {
       
$permissions = decoct($permissions);
       
$perm = $isowner ? decbin($permissions[-3]) : decbin($permissions[-1]);

        return array(
           
'read' => $perm[-3],
           
'write' => $perm[-2]
        );
    }

   
/**
     * Cache dir contents
     *
     * @param  string $path dir path
     *
     * @return void
     * @author Dmitry Levashov, sitecode
     **/
   
protected function cacheDir($path)
    {
       
$this->dirsCache[$path] = array();
       
$hasDir = false;

       
$list = array();
       
$encPath = $this->convEncIn($path);
        foreach (
$this->ftpRawList($encPath) as $raw) {
            if ((
$stat = $this->parseRaw($raw, $encPath))) {
               
$list[] = $stat;
            }
        }
       
$list = $this->convEncOut($list);
       
$prefix = ($path === $this->separator) ? $this->separator : $path . $this->separator;
       
$targets = array();
        foreach (
$list as $stat) {
           
$p = $prefix . $stat['name'];
            if (isset(
$stat['target'])) {
               
// stat later
               
$targets[$stat['name']] = $stat['target'];
            } else {
               
$stat = $this->updateCache($p, $stat);
                if (empty(
$stat['hidden'])) {
                    if (!
$hasDir && $stat['mime'] === 'directory') {
                       
$hasDir = true;
                    }
                   
$this->dirsCache[$path][] = $p;
                }
            }
        }
       
// stat link targets
       
foreach ($targets as $name => $target) {
           
$stat = array();
           
$stat['name'] = $name;
           
$p = $prefix . $name;
           
$cacheDirTarget = $this->cacheDirTarget;
           
$this->cacheDirTarget = $this->convEncIn($target, true);
            if (
$tstat = $this->stat($target)) {
               
$stat['size'] = $tstat['size'];
               
$stat['alias'] = $target;
               
$stat['thash'] = $tstat['hash'];
               
$stat['mime'] = $tstat['mime'];
               
$stat['read'] = $tstat['read'];
               
$stat['write'] = $tstat['write'];

                if (isset(
$tstat['ts'])) {
                   
$stat['ts'] = $tstat['ts'];
                }
                if (isset(
$tstat['owner'])) {
                   
$stat['owner'] = $tstat['owner'];
                }
                if (isset(
$tstat['group'])) {
                   
$stat['group'] = $tstat['group'];
                }
                if (isset(
$tstat['perm'])) {
                   
$stat['perm'] = $tstat['perm'];
                }
                if (isset(
$tstat['isowner'])) {
                   
$stat['isowner'] = $tstat['isowner'];
                }
            } else {

               
$stat['mime'] = 'symlink-broken';
               
$stat['read'] = false;
               
$stat['write'] = false;
               
$stat['size'] = 0;

            }
           
$this->cacheDirTarget = $cacheDirTarget;
           
$stat = $this->updateCache($p, $stat);
            if (empty(
$stat['hidden'])) {
                if (!
$hasDir && $stat['mime'] === 'directory') {
                   
$hasDir = true;
                }
               
$this->dirsCache[$path][] = $p;
            }
        }

        if (isset(
$this->sessionCache['subdirs'])) {
           
$this->sessionCache['subdirs'][$path] = $hasDir;
        }
    }


   
/***************** file stat ********************/

    /**
     * Return stat for given path.
     * Stat contains following fields:
     * - (int)    size    file size in b. required
     * - (int)    ts      file modification time in unix time. required
     * - (string) mime    mimetype. required for folders, others - optionally
     * - (bool)   read    read permissions. required
     * - (bool)   write   write permissions. required
     * - (bool)   locked  is object locked. optionally
     * - (bool)   hidden  is object hidden. optionally
     * - (string) alias   for symlinks - link target path relative to root path. optionally
     * - (string) target  for symlinks - link target path. optionally
     * If file does not exists - returns empty array or false.
     *
     * @param  string $path file path
     *
     * @return array|false
     * @author Dmitry (dio) Levashov
     **/
   
protected function _stat($path)
    {
       
$outPath = $this->convEncOut($path);
        if (isset(
$this->cache[$outPath])) {
            return
$this->convEncIn($this->cache[$outPath]);
        } else {
           
$this->convEncIn();
        }
        if (
$path === $this->root) {
           
$res = array(
               
'name' => $this->root,
               
'mime' => 'directory',
               
'dirs' => -1
           
);
            if (
$this->needOnline && (($this->ARGS['cmd'] === 'open' && $this->ARGS['target'] === $this->encode($this->root)) || $this->isMyReload())) {
               
$check = array(
                   
'ts' => true,
                   
'dirs' => true,
                );
               
$ts = 0;
                foreach (
$this->ftpRawList($path) as $str) {
                   
$info = preg_split('/\s+/', $str, 9);
                    if (
$info[8] === '.') {
                       
$info[8] = 'root';
                        if (
$stat = $this->parseRaw(join(' ', $info), $path)) {
                            unset(
$stat['name']);
                           
$res = array_merge($res, $stat);
                            if (
$res['ts']) {
                               
$ts = 0;
                                unset(
$check['ts']);
                            }
                        }
                    }
                    if (
$check && ($stat = $this->parseRaw($str, $path))) {
                        if (isset(
$stat['ts']) && !empty($stat['ts'])) {
                           
$ts = max($ts, $stat['ts']);
                        }
                        if (isset(
$stat['dirs']) && $stat['mime'] === 'directory') {
                           
$res['dirs'] = 1;
                            unset(
$stat['dirs']);
                        }
                        if (!
$check) {
                            break;
                        }
                    }
                }
                if (
$ts) {
                   
$res['ts'] = $ts;
                }
               
$this->cache[$outPath] = $res;
            }
            return
$res;
        }

       
$pPath = $this->_dirname($path);
        if (
$this->_inPath($pPath, $this->root)) {
           
$outPPpath = $this->convEncOut($pPath);
            if (!isset(
$this->dirsCache[$outPPpath])) {
               
$parentSubdirs = null;
                if (isset(
$this->sessionCache['subdirs']) && isset($this->sessionCache['subdirs'][$outPPpath])) {
                   
$parentSubdirs = $this->sessionCache['subdirs'][$outPPpath];
                }
               
$this->cacheDir($outPPpath);
                if (
$parentSubdirs) {
                   
$this->sessionCache['subdirs'][$outPPpath] = $parentSubdirs;
                }
            }
        }

       
$stat = $this->convEncIn(isset($this->cache[$outPath]) ? $this->cache[$outPath] : array());
        if (!
$this->mounted) {
           
// dispose incomplete cache made by calling `stat` by 'startPath' option
           
$this->cache = array();
        }

        return
$stat;
    }

   
/**
     * Return true if path is dir and has at least one childs directory
     *
     * @param  string $path dir path
     *
     * @return bool
     * @author Dmitry (dio) Levashov, sitecode
     **/
   
protected function _subdirs($path)
    {
        foreach (
$this->ftpRawList($path) as $info) {
           
$name = $info['filename'];
            if (
$name && $name !== '.' && $name !== '..' && $info['type'] == NET_SFTP_TYPE_DIRECTORY) {
                return
true;
            }
        }

        return
false;
    }


   
/******************** file/dir content *********************/

    /**
     * Open file and return file pointer
     *
     * @param  string $path file path
     * @param string  $mode
     *
     * @return false|resource
     * @throws elFinderAbortException
     * @internal param bool $write open file for writing
     * @author   Dmitry (dio) Levashov
     */
   
protected function _fopen($path, $mode = 'rb')
    {
        if (
$this->tmp) {
           
$local = $this->getTempFile($path);
           
$this->connect->get($path, $local);
            return @
fopen($local, $mode);
        }

        return
false;
    }

   
/**
     * Close opened file
     *
     * @param  resource $fp file pointer
     * @param string    $path
     *
     * @return void
     * @author Dmitry (dio) Levashov
     */
   
protected function _fclose($fp, $path = '')
    {
       
is_resource($fp) && fclose($fp);
        if (
$path) {
           
unlink($this->getTempFile($path));
        }
    }


   
/********************  file/dir manipulations *************************/

    /**
     * Create dir and return created dir path or false on failed
     *
     * @param  string $path parent dir path
     * @param string  $name new directory name
     *
     * @return string|bool
     * @author Dmitry (dio) Levashov
     **/
   
protected function _mkdir($path, $name)
    {
       
$path = $this->_joinPath($path, $this->_basename($name));
        if (
$this->connect->mkdir($path) === false) {
            return
false;
        }

       
$this->options['dirMode'] && $this->connect->chmod($this->options['dirMode'], $path);
        return
$path;
    }

   
/**
     * Create file and return it's path or false on failed
     *
     * @param  string $path parent dir path
     * @param string  $name new file name
     *
     * @return string|bool
     * @author sitecode
     **/
   
protected function _mkfile($path, $name)
    {
       
$path = $this->_joinPath($path, $this->_basename($name));
        return
$this->connect->put($path, '') ? $path : false;
/*
        if ($this->tmp) {
            $path = $this->_joinPath($path, $name);
            $local = $this->getTempFile();
            $res = touch($local) && $this->connect->put($path, $local, NET_SFTP_LOCAL_FILE);
            unlink($local);
            return $res ? $path : false;
        }

        return false;
 */
   
}

   
/**
     * Copy file into another file
     *
     * @param  string $source    source file path
     * @param  string $targetDir target directory path
     * @param  string $name      new file name
     *
     * @return bool
     * @author Dmitry (dio) Levashov, sitecode
     **/
   
protected function _copy($source, $targetDir, $name)
    {
       
$res = false;

       
$target = $this->_joinPath($targetDir, $this->_basename($name));
        if (
$this->tmp) {
           
$local = $this->getTempFile();

            if (
$this->connect->get($source, $local)
                &&
$this->connect->put($target, $local, NET_SFTP_LOCAL_FILE)) {
               
$res = true;
            }
           
unlink($local);
        } else {
           
//not memory efficient
           
$res = $this->_filePutContents($target, $this->_getContents($source));
        }

        return
$res;
    }

   
/**
     * Move file into another parent dir.
     * Return new file path or false.
     *
     * @param  string $source source file path
     * @param         $targetDir
     * @param  string $name   file name
     *
     * @return bool|string
     * @internal param string $target target dir path
     * @author   Dmitry (dio) Levashov
     */
   
protected function _move($source, $targetDir, $name)
    {
       
$target = $this->_joinPath($targetDir, $this->_basename($name));
        return
$this->connect->rename($source, $target) ? $target : false;
    }

   
/**
     * Remove file
     *
     * @param  string $path file path
     *
     * @return bool
     * @author Dmitry (dio) Levashov
     **/
   
protected function _unlink($path)
    {
        return
$this->connect->delete($path, false);
    }

   
/**
     * Remove dir
     *
     * @param  string $path dir path
     *
     * @return bool
     * @author Dmitry (dio) Levashov
     **/
   
protected function _rmdir($path)
    {
        return
$this->connect->delete($path);
    }

   
/**
     * Create new file and write into it from file pointer.
     * Return new file path or false on error.
     *
     * @param  resource $fp   file pointer
     * @param  string   $dir  target dir path
     * @param  string   $name file name
     * @param  array    $stat file stat (required by some virtual fs)
     *
     * @return bool|string
     * @author Dmitry (dio) Levashov
     **/
   
protected function _save($fp, $dir, $name, $stat)
    {
       
//TODO optionally encrypt $fp before uploading if mime is not already encrypted type
       
$path = $this->_joinPath($dir, $this->_basename($name));
        return
$this->connect->put($path, $fp)
            ?
$path
           
: false;
    }

   
/**
     * Get file contents
     *
     * @param  string $path file path
     *
     * @return string|false
     * @throws elFinderAbortException
     * @author Dmitry (dio) Levashov
     */
   
protected function _getContents($path)
    {
        return
$this->connect->get($path);
    }

   
/**
     * Write a string to a file
     *
     * @param  string $path    file path
     * @param  string $content new file content
     *
     * @return bool
     * @author Dmitry (dio) Levashov
     **/
   
protected function _filePutContents($path, $content)
    {
        return
$this->connect->put($path, $content);
    }

   
/**
     * chmod availability
     *
     * @param string $path
     * @param string $mode
     *
     * @return bool
     */
   
protected function _chmod($path, $mode)
    {
       
$modeOct = is_string($mode) ? octdec($mode) : octdec(sprintf("%04o", $mode));
        return
$this->connect->chmod($modeOct, $path);
    }

   
/**
     * Extract files from archive
     *
     * @param  string $path archive path
     * @param  array  $arc  archiver command and arguments (same as in $this->archivers)
     *
     * @return true
     * @throws elFinderAbortException
     * @author Dmitry (dio) Levashov,
     * @author Alexey Sukhotin
     */
   
protected function _extract($path, $arc)
    {
        return
false; //TODO
   
}

   
/**
     * Create archive and return its path
     *
     * @param  string $dir   target dir
     * @param  array  $files files names list
     * @param  string $name  archive name
     * @param  array  $arc   archiver options
     *
     * @return string|bool
     * @throws elFinderAbortException
     * @author Dmitry (dio) Levashov,
     * @author Alexey Sukhotin
     */
   
protected function _archive($dir, $files, $name, $arc)
    {
        return
false; //TODO
   
}

   
/**
     * Gets an array of absolute remote SFTP paths of files and
     * folders in $remote_directory omitting symbolic links.
     *
     * @param $remote_directory string remote SFTP path to scan for file and folders recursively
     * @param $targets          array  Array of target item. `null` is to get all of items
     *
     * @return array of elements each of which is an array of two elements:
     * <ul>
     * <li>$item['path'] - absolute remote SFTP path</li>
     * <li>$item['type'] - either 'f' for file or 'd' for directory</li>
     * </ul>
     */
   
protected function ftp_scan_dir($remote_directory, $targets = null)
    {
       
$buff = $this->ftpRawList($remote_directory);
       
$items = array();
        if (
$targets && is_array($targets)) {
           
$targets = array_flip($targets);
        } else {
           
$targets = false;
        }
        foreach (
$buff as $info) {
           
$name = $info['filename'];
            if (
$name !== '.' && $name !== '..' && (!$targets || isset($targets[$name]))) {
                switch (
$info['type']) {
                    case
NET_SFTP_TYPE_SYMLINK : //omit symbolic links
                   
case NET_SFTP_TYPE_DIRECTORY :
                       
$remote_file_path = $this->_joinPath($remote_directory, $name);
                       
$item = array();
                       
$item['path'] = $remote_file_path;
                       
$item['type'] = 'd'; // normal file
                       
$items[] = $item;
                       
$items = array_merge($items, $this->ftp_scan_dir($remote_file_path));
                        break;
                    default:
                       
$remote_file_path = $this->_joinPath($remote_directory, $name);
                       
$item = array();
                       
$item['path'] = $remote_file_path;
                       
$item['type'] = 'f'; // normal file
                       
$items[] = $item;
                }
            }
        }
        return
$items;
    }

}
// END class