From 77641003fa3827da6444c43fa8b87371549f9eed Mon Sep 17 00:00:00 2001
From: terrafrost <terrafrost@php.net>
Date: Sat, 10 May 2014 19:52:38 -0500
Subject: [PATCH 1/4] SFTP: add file_exists, is_dir and is_file functions

also expand caching layer
---
 phpseclib/Net/SFTP.php | 187 ++++++++++++++++++++++++++++++++++-------
 1 file changed, 156 insertions(+), 31 deletions(-)

diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php
index c891db57..a0f1038f 100644
--- a/phpseclib/Net/SFTP.php
+++ b/phpseclib/Net/SFTP.php
@@ -229,18 +229,18 @@ class Net_SFTP extends Net_SSH2
     var $sftp_errors = array();
 
     /**
-     * Directory Cache
+     * Cache
      *
      * Rather than always having to open a directory and close it immediately there after to see if a file is a directory or
      * rather than always
      *
-     * @see Net_SFTP::_save_dir()
-     * @see Net_SFTP::_remove_dir()
-     * @see Net_SFTP::_is_dir()
+     * @see Net_SFTP::_update_cache()
+     * @see Net_SFTP::_remove_from_cache()
+     * @see Net_SFTP::_query_cache()
      * @var Array
      * @access private
      */
-    var $dirs = array();
+    var $cache = array();
 
     /**
      * Max SFTP Packet Size
@@ -252,6 +252,16 @@ class Net_SFTP extends Net_SSH2
      */
     var $max_sftp_packet;
 
+    /**
+     * Cache Flag
+     *
+     * @see Net_SFTP::disableCache()
+     * @see Net_SFTP::enableCache()
+     * @var Boolean
+     * @access private
+     */
+    var $use_cache = true;
+
     /**
      * Default Constructor.
      *
@@ -522,11 +532,31 @@ class Net_SFTP extends Net_SSH2
 
         $this->pwd = $this->_realpath('.');
 
-        $this->_save_dir($this->pwd);
+        $this->_update_cache($this->pwd, array());
 
         return true;
     }
 
+    /**
+     * Disable the cache
+     *
+     * @access public
+     */
+    function disableCache()
+    {
+        $this->use_cache = false;
+    }
+
+    /**
+     * Enable the cache
+     *
+     * @access public
+     */
+    function enableCache()
+    {
+        $this->use_cache = true;
+    }
+
     /**
      * Returns the current directory name
      *
@@ -645,7 +675,7 @@ class Net_SFTP extends Net_SSH2
         $dir = $this->_realpath($dir);
 
         // confirm that $dir is, in fact, a valid directory
-        if ($this->_is_dir($dir)) {
+        if ($this->use_cache && is_array($this->_query_cache($dir))) {
             $this->pwd = $dir;
             return true;
         }
@@ -677,7 +707,7 @@ class Net_SFTP extends Net_SSH2
             return false;
         }
 
-        $this->_save_dir($dir);
+        $this->_update_cache($dir, array());
 
         $this->pwd = $dir;
         return true;
@@ -752,7 +782,7 @@ class Net_SFTP extends Net_SSH2
                 return false;
         }
 
-        $this->_save_dir($dir);
+        $this->_update_cache($dir, array());
 
         $contents = array();
         while (true) {
@@ -786,7 +816,9 @@ class Net_SFTP extends Net_SSH2
                         }
 
                         if (isset($attributes['type']) && $attributes['type'] == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) {
-                            $this->_save_dir($dir . '/' . $shortname);
+                            $this->_update_cache($dir . '/' . $shortname, array());
+                        } else {
+                            $this->_update_cache($dir . '/' . $shortname, 1);
                         }
                         // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the
                         // final SSH_FXP_STATUS packet should tell us that, already.
@@ -836,36 +868,41 @@ class Net_SFTP extends Net_SSH2
     }
 
     /**
-     * Save directories to cache
+     * Save files / directories to cache
      *
-     * @param String $dir
+     * @param String $path
+     * @param optional Boolean $file
      * @access private
      */
-    function _save_dir($dir)
+    function _update_cache($path, $value)
     {
-        // preg_replace('#^/|/(?=/)|/$#', '', $dir) == str_replace('//', '/', trim($dir, '/'))
-        $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir));
+        // preg_replace('#^/|/(?=/)|/$#', '', $dir) == str_replace('//', '/', trim($path, '/'))
+        $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
 
-        $temp = &$this->dirs;
+        $temp = &$this->cache;
         foreach ($dirs as $dir) {
             if (!isset($temp[$dir])) {
                 $temp[$dir] = array();
             }
+            if ($dir == end($dirs)) {
+                $temp[$dir] = $value;
+            }
             $temp = &$temp[$dir];
         }
     }
 
     /**
-     * Remove directories from cache
+     * Remove files / directories from cache
      *
-     * @param String $dir
+     * @param String $path
+     * @param optional Boolean $file
      * @access private
      */
-    function _remove_dir($dir)
+    function _remove_from_cache($path)
     {
-        $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir));
+        $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
 
-        $temp = &$this->dirs;
+        $temp = &$this->cache;
         foreach ($dirs as $dir) {
             if ($dir == end($dirs)) {
                 unset($temp[$dir]);
@@ -879,26 +916,26 @@ class Net_SFTP extends Net_SSH2
     }
 
     /**
-     * Checks cache for directory
+     * Checks cache for path
      *
-     * Mainly used by chdir, which is, in turn, also used for determining whether or not an individual
-     * file is a directory or not by stat() and lstat()
+     * Mainly used by file_exists
      *
      * @param String $dir
+     * @return Mixed
      * @access private
      */
-    function _is_dir($dir)
+    function _query_cache($path)
     {
-        $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir));
+        $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
 
-        $temp = &$this->dirs;
+        $temp = &$this->cache;
         foreach ($dirs as $dir) {
             if (!isset($temp[$dir])) {
                 return false;
             }
             $temp = &$temp[$dir];
         }
-        return true;
+        return $temp;
     }
 
     /**
@@ -923,8 +960,10 @@ class Net_SFTP extends Net_SSH2
 
         $stat = $this->_stat($filename, NET_SFTP_STAT);
         if ($stat === false) {
+            $this->_update_cache($filename, 0);
             return false;
         }
+        $this->_update_cache($filename, 1);
         if (isset($stat['type'])) {
             return $stat;
         }
@@ -955,6 +994,7 @@ class Net_SFTP extends Net_SSH2
 
         $filename = $this->_realpath($filename);
         if ($filename === false) {
+            $this->_update_cache($filename, 0);
             return false;
         }
 
@@ -962,6 +1002,7 @@ class Net_SFTP extends Net_SSH2
         if ($lstat === false) {
             return false;
         }
+        $this->_update_cache($filename, 1);
         if (isset($lstat['type'])) {
             return $lstat;
         }
@@ -1094,6 +1135,8 @@ class Net_SFTP extends Net_SSH2
                 return false;
         }
 
+        $this->_update_cache($filename, 1);
+
         return $this->_setstat($filename, $attr, false);
     }
 
@@ -1375,7 +1418,7 @@ class Net_SFTP extends Net_SSH2
             return false;
         }
 
-        $this->_save_dir($dir);
+        $this->_update_cache($dir, array());
 
         return true;
     }
@@ -1415,7 +1458,8 @@ class Net_SFTP extends Net_SSH2
             return false;
         }
 
-        $this->_remove_dir($dir);
+        //$this->_remove_from_cache($dir);
+        $this->_update_cache($dir, 0);
 
         return true;
     }
@@ -1568,6 +1612,8 @@ class Net_SFTP extends Net_SSH2
             fclose($fp);
         }
 
+        $this->_update_cache($remote_file, 1);
+
         return $this->_close_handle($handle);
     }
 
@@ -1739,6 +1785,8 @@ class Net_SFTP extends Net_SSH2
             return false;
         }
 
+        $this->_update_cache($remote_file, 1);
+
         // if $content isn't set that means a file was written to
         return isset($content) ? $content : true;
     }
@@ -1786,6 +1834,8 @@ class Net_SFTP extends Net_SSH2
             return $result;
         }
 
+        $this->_update_cache($path, 0);
+
         return true;
     }
 
@@ -1841,12 +1891,13 @@ class Net_SFTP extends Net_SSH2
                     $i = 0;
                 }
             }
+            //$this->_remove_from_cache($path);
+            $this->_update_cache($path, 0);
         }
 
         if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($path), $path))) {
             return false;
         }
-        $this->_remove_dir($path);
 
         $i++;
 
@@ -1860,6 +1911,77 @@ class Net_SFTP extends Net_SSH2
         return true;
     }
 
+    /**
+     * Checks whether a file or directory exists
+     *
+     * @param String $path
+     * @return Boolean
+     * @access public
+     */
+    function file_exists($path)
+    {
+        if ($this->use_cache) {
+            $path = $this->_realpath($path);
+
+            $result = $this->_query_cache($path);
+
+            if ($result !== false) {
+                // return true if $result is an array or if it's int(1)
+                return $result !== 0;
+            }
+        }
+
+        return $this->stat($path) !== false;
+    }
+
+    /**
+     * Tells whether the filename is a directory
+     *
+     * @param String $path
+     * @return Boolean
+     * @access public
+     */
+    function is_dir($path)
+    {
+        if ($this->use_cache) {
+            $path = $this->_realpath($path);
+
+            $result = $this->_query_cache($path);
+
+            if ($result !== false) {
+                return is_array($result);
+            }
+        }
+
+        $result = $this->stat($path);
+
+        return $result['type'] === NET_SFTP_TYPE_DIRECTORY;
+    }
+
+    /**
+     * Tells whether the filename is a regular file
+     *
+     * @param String $path
+     * @return Boolean
+     * @access public
+     */
+    function is_file($path)
+    {
+        if ($this->use_cache) {
+            $path = $this->_realpath($path);
+
+            $result = $this->_query_cache($path);
+
+            if ($result !== false) {
+                return $result === 1;
+            }
+        }
+
+        $result = $this->stat($path);
+
+        return $result['type'] === NET_SFTP_TYPE_REGULAR;
+    }
+
     /**
      * Renames a file or a directory on the SFTP server
      *
@@ -1899,6 +2021,9 @@ class Net_SFTP extends Net_SSH2
             return false;
         }
 
+        $this->_update_cache($oldname, 0);
+        $this->_update_cache($newname, 1);
+
         return true;
     }
 

From 0a0398268a28226aa879571bb96478430638a255 Mon Sep 17 00:00:00 2001
From: terrafrost <terrafrost@php.net>
Date: Tue, 13 May 2014 17:10:32 -0500
Subject: [PATCH 2/4] SFTP: add support for recursive nlist and rawlist

---
 phpseclib/Net/SFTP.php | 72 +++++++++++++++++++++++++++++++++++++++---
 1 file changed, 68 insertions(+), 4 deletions(-)

diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php
index a0f1038f..c50761c4 100644
--- a/phpseclib/Net/SFTP.php
+++ b/phpseclib/Net/SFTP.php
@@ -717,24 +717,88 @@ class Net_SFTP extends Net_SSH2
      * Returns a list of files in the given directory
      *
      * @param optional String $dir
+     * @param optional Boolean $recursive
      * @return Mixed
      * @access public
      */
-    function nlist($dir = '.')
+    function nlist($dir = '.', $recursive = false)
     {
-        return $this->_list($dir, false);
+        $dir = $this->_realpath($dir . '/');
+        switch (true) {
+            case !$this->use_cache:
+            case !is_array($result = $this->_query_cache($dir)):
+            case !isset($result['.']):
+            case $recursive:
+                break;
+            default:
+                return array_keys($result);
+        }
+        $files = $this->_list($dir, false);
+
+        if (!$recursive) {
+            return $files;
+        }
+
+        static $relativeDir = '';
+
+        $result = array();
+        foreach ($files as $value) {
+            if ($value == '.' || $value == '..') {
+                if ($relativeDir == '') {
+                    $result[] = $value;
+                }
+                continue;
+            }
+            if (is_array($this->_query_cache($this->_realpath($dir . '/' . $value)))) {
+                $oldDir = $relativeDir;
+                $relativeDir.= $value . '/';
+                $temp = $this->nlist($dir . '/' . $value, true);
+                $result = array_merge($result, $temp);
+                $relativeDir = $oldDir;
+            } else {
+                $result[] = $relativeDir . $value;
+            }
+        }
+
+        return $result;
     }
 
     /**
      * Returns a detailed list of files in the given directory
      *
      * @param optional String $dir
+     * @param optional Boolean $recursive
      * @return Mixed
      * @access public
      */
-    function rawlist($dir = '.')
+    function rawlist($dir = '.', $recursive = false)
     {
-        return $this->_list($dir, true);
+        $files = $this->_list($dir, true);
+        if (!$recursive || $files === false) {
+            return $files;
+        }
+
+        static $depth = 0;
+
+        foreach ($files as $key=>$value) {
+            if ($depth != 0 && $key == '..') {
+                unset($files[$key]);
+                continue;
+            }
+            if ($key != '.' && $key != '..' && is_array($this->_query_cache($this->_realpath($dir . '/' . $key)))) {
+                $depth++;
+                $files[$key] = $this->rawlist($dir . '/' . $key, true);
+                $depth--;
+            } else {
+                $temp = new StdClass();
+                foreach ($value as $subkey=>$subvalue) {
+                    $temp->$subkey = $subvalue;
+                }
+                $files[$key] = $temp;
+            }
+        }
+
+        return $files;
     }
 
     /**

From e09a6968da704dd26729e369b8f7a2b4a3df1a0a Mon Sep 17 00:00:00 2001
From: terrafrost <terrafrost@php.net>
Date: Sun, 18 May 2014 15:34:10 -0500
Subject: [PATCH 3/4] SFTP: switch from using file existence cache to stat
 cache, like PHP

also add a few new functions - is_link and filesize
---
 phpseclib/Net/SFTP.php | 214 +++++++++++++++++++++++++++--------------
 1 file changed, 140 insertions(+), 74 deletions(-)

diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php
index c50761c4..34b82bdb 100644
--- a/phpseclib/Net/SFTP.php
+++ b/phpseclib/Net/SFTP.php
@@ -229,18 +229,18 @@ class Net_SFTP extends Net_SSH2
     var $sftp_errors = array();
 
     /**
-     * Cache
+     * Stat Cache
      *
-     * Rather than always having to open a directory and close it immediately there after to see if a file is a directory or
-     * rather than always
+     * Rather than always having to open a directory and close it immediately there after to see if a file is a directory
+     * we'll cache the results.
      *
-     * @see Net_SFTP::_update_cache()
-     * @see Net_SFTP::_remove_from_cache()
-     * @see Net_SFTP::_query_cache()
+     * @see Net_SFTP::_update_stat_cache()
+     * @see Net_SFTP::_remove_from_stat_cache()
+     * @see Net_SFTP::_query_stat_cache()
      * @var Array
      * @access private
      */
-    var $cache = array();
+    var $stat_cache = array();
 
     /**
      * Max SFTP Packet Size
@@ -253,14 +253,14 @@ class Net_SFTP extends Net_SSH2
     var $max_sftp_packet;
 
     /**
-     * Cache Flag
+     * Stat Cache Flag
      *
-     * @see Net_SFTP::disableCache()
-     * @see Net_SFTP::enableCache()
+     * @see Net_SFTP::disableStatCache()
+     * @see Net_SFTP::enableStatCache()
      * @var Boolean
      * @access private
      */
-    var $use_cache = true;
+    var $use_stat_cache = true;
 
     /**
      * Default Constructor.
@@ -532,7 +532,7 @@ class Net_SFTP extends Net_SSH2
 
         $this->pwd = $this->_realpath('.');
 
-        $this->_update_cache($this->pwd, array());
+        $this->_update_stat_cache($this->pwd, array());
 
         return true;
     }
@@ -542,9 +542,9 @@ class Net_SFTP extends Net_SSH2
      *
      * @access public
      */
-    function disableCache()
+    function disableStatCache()
     {
-        $this->use_cache = false;
+        $this->use_stat_cache = false;
     }
 
     /**
@@ -552,9 +552,9 @@ class Net_SFTP extends Net_SSH2
      *
      * @access public
      */
-    function enableCache()
+    function enableStatCache()
     {
-        $this->use_cache = true;
+        $this->use_stat_cache = true;
     }
 
     /**
@@ -675,7 +675,7 @@ class Net_SFTP extends Net_SSH2
         $dir = $this->_realpath($dir);
 
         // confirm that $dir is, in fact, a valid directory
-        if ($this->use_cache && is_array($this->_query_cache($dir))) {
+        if ($this->use_cache && is_array($this->_query_stat_cache($dir))) {
             $this->pwd = $dir;
             return true;
         }
@@ -707,7 +707,7 @@ class Net_SFTP extends Net_SSH2
             return false;
         }
 
-        $this->_update_cache($dir, array());
+        $this->_update_stat_cache($dir, array());
 
         $this->pwd = $dir;
         return true;
@@ -723,24 +723,26 @@ class Net_SFTP extends Net_SSH2
      */
     function nlist($dir = '.', $recursive = false)
     {
-        $dir = $this->_realpath($dir . '/');
-        switch (true) {
-            case !$this->use_cache:
-            case !is_array($result = $this->_query_cache($dir)):
-            case !isset($result['.']):
-            case $recursive:
-                break;
-            default:
-                return array_keys($result);
-        }
+        return $this->_nlist_helper($dir, $recursive, '');
+    }
+
+    /**
+     * Helper method for nlist
+     *
+     * @param String $dir
+     * @param Boolean $recursive
+     * @param String $relativeDir
+     * @return Mixed
+     * @access private
+     */
+    function _nlist_helper($dir, $recursive, $relativeDir)
+    {
         $files = $this->_list($dir, false);
 
         if (!$recursive) {
             return $files;
         }
 
-        static $relativeDir = '';
-
         $result = array();
         foreach ($files as $value) {
             if ($value == '.' || $value == '..') {
@@ -749,12 +751,9 @@ class Net_SFTP extends Net_SSH2
                 }
                 continue;
             }
-            if (is_array($this->_query_cache($this->_realpath($dir . '/' . $value)))) {
-                $oldDir = $relativeDir;
-                $relativeDir.= $value . '/';
-                $temp = $this->nlist($dir . '/' . $value, true);
+            if (is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $value)))) {
+                $temp = $this->_nlist_helper($dir . '/' . $value, true, $relativeDir . $value . '/');
                 $result = array_merge($result, $temp);
-                $relativeDir = $oldDir;
             } else {
                 $result[] = $relativeDir . $value;
             }
@@ -785,16 +784,12 @@ class Net_SFTP extends Net_SSH2
                 unset($files[$key]);
                 continue;
             }
-            if ($key != '.' && $key != '..' && is_array($this->_query_cache($this->_realpath($dir . '/' . $key)))) {
+            if ($key != '.' && $key != '..' && is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $key)))) {
                 $depth++;
                 $files[$key] = $this->rawlist($dir . '/' . $key, true);
                 $depth--;
             } else {
-                $temp = new StdClass();
-                foreach ($value as $subkey=>$subvalue) {
-                    $temp->$subkey = $subvalue;
-                }
-                $files[$key] = $temp;
+                $files[$key] = (object) $value;
             }
         }
 
@@ -846,7 +841,7 @@ class Net_SFTP extends Net_SSH2
                 return false;
         }
 
-        $this->_update_cache($dir, array());
+        $this->_update_stat_cache($dir, array());
 
         $contents = array();
         while (true) {
@@ -880,9 +875,9 @@ class Net_SFTP extends Net_SSH2
                         }
 
                         if (isset($attributes['type']) && $attributes['type'] == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) {
-                            $this->_update_cache($dir . '/' . $shortname, array());
+                            $this->_update_stat_cache($dir . '/' . $shortname, array());
                         } else {
-                            $this->_update_cache($dir . '/' . $shortname, 1);
+                            $this->_update_stat_cache($dir . '/' . $shortname, (object) $attributes);
                         }
                         // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the
                         // final SSH_FXP_STATUS packet should tell us that, already.
@@ -938,7 +933,7 @@ class Net_SFTP extends Net_SSH2
      * @param optional Boolean $file
      * @access private
      */
-    function _update_cache($path, $value)
+    function _update_stat_cache($path, $value)
     {
         // preg_replace('#^/|/(?=/)|/$#', '', $dir) == str_replace('//', '/', trim($path, '/'))
         $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
@@ -962,7 +957,7 @@ class Net_SFTP extends Net_SSH2
      * @param optional Boolean $file
      * @access private
      */
-    function _remove_from_cache($path)
+    function _remove_from_stat_cache($path)
     {
         $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
 
@@ -988,7 +983,7 @@ class Net_SFTP extends Net_SSH2
      * @return Mixed
      * @access private
      */
-    function _query_cache($path)
+    function _query_stat_cache($path)
     {
         $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
 
@@ -1022,13 +1017,23 @@ class Net_SFTP extends Net_SSH2
             return false;
         }
 
+        if ($this->use_stat_cache) {
+            $result = $this->_query_stat_cache($filename);
+            if (is_array($result)) {
+                return (array) $result['.'];
+            }
+            if ($result !== false) {
+                return (array) $result;
+            }
+        }
+
         $stat = $this->_stat($filename, NET_SFTP_STAT);
         if ($stat === false) {
-            $this->_update_cache($filename, 0);
+            $this->_update_stat_cache($filename, 0);
             return false;
         }
-        $this->_update_cache($filename, 1);
         if (isset($stat['type'])) {
+            $this->_update_stat_cache($filename, $stat);
             return $stat;
         }
 
@@ -1038,6 +1043,8 @@ class Net_SFTP extends Net_SSH2
             NET_SFTP_TYPE_REGULAR;
         $this->pwd = $pwd;
 
+        $this->_update_stat_cache($filename, $stat);
+
         return $stat;
     }
 
@@ -1058,23 +1065,34 @@ class Net_SFTP extends Net_SSH2
 
         $filename = $this->_realpath($filename);
         if ($filename === false) {
-            $this->_update_cache($filename, 0);
             return false;
         }
 
+        if ($this->use_stat_cache) {
+            $result = $this->_query_stat_cache($filename);
+            if (is_array($result)) {
+                return (array) $result['.'];
+            }
+            if ($result !== false) {
+                return (array) $result;
+            }
+        }
+
         $lstat = $this->_stat($filename, NET_SFTP_LSTAT);
         if ($lstat === false) {
             return false;
         }
-        $this->_update_cache($filename, 1);
         if (isset($lstat['type'])) {
+            $this->_update_stat_cache($filename, $lstat);
             return $lstat;
         }
 
         $stat = $this->_stat($filename, NET_SFTP_STAT);
 
         if ($lstat != $stat) {
-            return array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK));
+            $lstat = array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK));
+            $this->_update_stat_cache($filename, $lstat);
+            return $stat;
         }
 
         $pwd = $this->pwd;
@@ -1083,6 +1101,8 @@ class Net_SFTP extends Net_SSH2
             NET_SFTP_TYPE_REGULAR;
         $this->pwd = $pwd;
 
+        $this->_update_stat_cache($filename, $lstat);
+
         return $lstat;
     }
 
@@ -1199,8 +1219,6 @@ class Net_SFTP extends Net_SSH2
                 return false;
         }
 
-        $this->_update_cache($filename, 1);
-
         return $this->_setstat($filename, $attr, false);
     }
 
@@ -1312,6 +1330,8 @@ class Net_SFTP extends Net_SSH2
             return false;
         }
 
+        $this->_remove_from_stat_cache($filename);
+
         if ($recursive) {
             $i = 0;
             $result = $this->_setstat_recursive($filename, $attr, $i);
@@ -1482,8 +1502,6 @@ class Net_SFTP extends Net_SSH2
             return false;
         }
 
-        $this->_update_cache($dir, array());
-
         return true;
     }
 
@@ -1522,8 +1540,11 @@ class Net_SFTP extends Net_SSH2
             return false;
         }
 
-        //$this->_remove_from_cache($dir);
-        $this->_update_cache($dir, 0);
+        $this->_remove_from_stat_cache($dir);
+        // the following will do a soft delete, which would be useful if you deleted a file
+        // and then tried to do a stat on the deleted file. the above, in contrast, does
+        // a hard delete
+        //$this->_update_stat_cache($dir, 0);
 
         return true;
     }
@@ -1676,8 +1697,6 @@ class Net_SFTP extends Net_SSH2
             fclose($fp);
         }
 
-        $this->_update_cache($remote_file, 1);
-
         return $this->_close_handle($handle);
     }
 
@@ -1849,8 +1868,6 @@ class Net_SFTP extends Net_SSH2
             return false;
         }
 
-        $this->_update_cache($remote_file, 1);
-
         // if $content isn't set that means a file was written to
         return isset($content) ? $content : true;
     }
@@ -1898,7 +1915,7 @@ class Net_SFTP extends Net_SSH2
             return $result;
         }
 
-        $this->_update_cache($path, 0);
+        $this->_remove_from_stat_cache($path);
 
         return true;
     }
@@ -1955,8 +1972,7 @@ class Net_SFTP extends Net_SSH2
                     $i = 0;
                 }
             }
-            //$this->_remove_from_cache($path);
-            $this->_update_cache($path, 0);
+            $this->_remove_from_stat_cache($path);
         }
 
         if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($path), $path))) {
@@ -1984,10 +2000,10 @@ class Net_SFTP extends Net_SSH2
      */
     function file_exists($path)
     {
-        if ($this->use_cache) {
+        if ($this->use_stat_cache) {
             $path = $this->_realpath($path);
 
-            $result = $this->_query_cache($path);
+            $result = $this->_query_stat_cache($path);
 
             if ($result !== false) {
                 // return true if $result is an array or if it's int(1)
@@ -2007,10 +2023,10 @@ class Net_SFTP extends Net_SSH2
      */
     function is_dir($path)
     {
-        if ($this->use_cache) {
+        if ($this->use_stat_cache) {
             $path = $this->_realpath($path);
 
-            $result = $this->_query_cache($path);
+            $result = $this->_query_stat_cache($path);
 
             if ($result !== false) {
                 return is_array($result);
@@ -2031,13 +2047,13 @@ class Net_SFTP extends Net_SSH2
      */
     function is_file($path)
     {
-        if ($this->use_cache) {
+        if ($this->use_stat_cache) {
             $path = $this->_realpath($path);
 
-            $result = $this->_query_cache($path);
+            $result = $this->_query_stat_cache($path);
 
             if ($result !== false) {
-                return $result === 1;
+                return !is_array($result);
             }
         }
 
@@ -2046,6 +2062,54 @@ class Net_SFTP extends Net_SSH2
         return $result['type'] === NET_SFTP_TYPE_REGULAR;
     }
 
+    /**
+     * Tells whether the filename is a symbolic link
+     *
+     * @param String $path
+     * @return Boolean
+     * @access public
+     */
+    function is_link($path)
+    {
+        if ($this->use_stat_cache) {
+            $path = $this->_realpath($path);
+
+            $result = $this->_query_stat_cache($path);
+
+            if (is_object($result) && isset($result->type)) {
+                return $result->type === NET_SFTP_TYPE_SYMLINK;
+            }
+        }
+
+        $result = $this->stat($path);
+
+        return $result['type'] === NET_SFTP_TYPE_SYMLINK;
+    }
+
+    /**
+     * Gets file size
+     *
+     * @param String $path
+     * @return Boolean
+     * @access public
+     */
+    function filesize($path)
+    {
+        if ($this->use_stat_cache) {
+            $path = $this->_realpath($path);
+
+            $result = $this->_query_stat_cache($path);
+
+            if (is_object($result) && isset($result->size)) {
+                return $result->size;
+            }
+        }
+
+        $result = $this->stat($path);
+
+        return $result['size'];
+    }
+
     /**
      * Renames a file or a directory on the SFTP server
      *
@@ -2085,8 +2149,10 @@ class Net_SFTP extends Net_SSH2
             return false;
         }
 
-        $this->_update_cache($oldname, 0);
-        $this->_update_cache($newname, 1);
+        // don't move the stat cache entry over since this operation could very well change the
+        // atime and mtime attributes
+        //$this->_update_stat_cache($newname, $this->_query_stat_cache($oldname));
+        $this->_remove_from_stat_cache($oldname);
 
         return true;
     }

From 36fa9e4e487d10fce1de9ba0b4065c30cd4fc7b2 Mon Sep 17 00:00:00 2001
From: terrafrost <terrafrost@php.net>
Date: Sun, 18 May 2014 15:55:12 -0500
Subject: [PATCH 4/4] SFTP: use_cache -> use_stat_cache

---
 phpseclib/Net/SFTP.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php
index 34b82bdb..a64b48cd 100644
--- a/phpseclib/Net/SFTP.php
+++ b/phpseclib/Net/SFTP.php
@@ -675,7 +675,7 @@ class Net_SFTP extends Net_SSH2
         $dir = $this->_realpath($dir);
 
         // confirm that $dir is, in fact, a valid directory
-        if ($this->use_cache && is_array($this->_query_stat_cache($dir))) {
+        if ($this->use_stat_cache && is_array($this->_query_stat_cache($dir))) {
             $this->pwd = $dir;
             return true;
         }