$result = strcasecmp($a['filename'], $b['filename']);
return $order === SORT_DESC ? -$result : $result;
if ($a[$sort] === $b[$sort]) {
return $order === SORT_ASC ? $a[$sort] - $b[$sort] : $b[$sort] - $a[$sort];
* Defines how nlist() and rawlist() will be sorted - if at all.
* If sorting is enabled directories and files will be sorted independently with
* directories appearing before files in the resultant array that is returned.
* Any parameter returned by stat is a valid sort parameter for this function.
* Filename comparisons are case insensitive.
* $sftp->setListOrder('filename', SORT_ASC);
* $sftp->setListOrder('size', SORT_DESC, 'filename', SORT_ASC);
* $sftp->setListOrder(true);
* Separates directories from files but doesn't do any sorting beyond that
* Don't do any sort of sorting
public function setListOrder()
$this->sortOptions = array();
$len = count($args) & 0x7FFFFFFE;
for ($i = 0; $i < $len; $i += 2) {
$this->sortOptions[$args[$i]] = $args[$i + 1];
if (!count($this->sortOptions)) {
$this->sortOptions = array('bogus' => true);
* Returns the file size, in bytes, or false, on failure
* Files larger than 4GB will show up as being exactly 4GB.
* @param String $filename
public function size($filename)
if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
$result = $this->stat($filename);
return isset($result['size']) ? $result['size'] : -1;
* Save files / directories to cache
public function _update_stat_cache($path, $value)
// preg_replace('#^/|/(?=/)|/$#', '', $dir) == str_replace('//', '/', trim($path, '/'))
$dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
$temp = &$this->stat_cache;
foreach ($dirs as $dir) {
if (!isset($temp[$dir])) {
if ($dir == end($dirs)) {
* Remove files / directories from cache
public function _remove_from_stat_cache($path)
$dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
$temp = &$this->stat_cache;
foreach ($dirs as $dir) {
if ($dir == end($dirs)) {
if (!isset($temp[$dir])) {
* Mainly used by file_exists
public function _query_stat_cache($path)
$dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
$temp = &$this->stat_cache;
foreach ($dirs as $dir) {
if (!isset($temp[$dir])) {
* Returns general information about a file.
* Returns an array on success and false otherwise.
* @param String $filename
public function stat($filename)
if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
$filename = $this->_realpath($filename);
if ($filename === false) {
if ($this->use_stat_cache) {
$result = $this->_query_stat_cache($filename);
if (is_array($result) && isset($result['.'])) {
return (array) $result['.'];
if (is_object($result)) {
$stat = $this->_stat($filename, NET_SFTP_STAT);
$this->_remove_from_stat_cache($filename);
if (isset($stat['type'])) {
if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
$this->_update_stat_cache($filename, (object) $stat);
$stat['type'] = $this->chdir($filename) ?
NET_SFTP_TYPE_DIRECTORY :
if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
$this->_update_stat_cache($filename, (object) $stat);
* Returns general information about a file or symbolic link.
* Returns an array on success and false otherwise.
* @param String $filename
public function lstat($filename)
if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
$filename = $this->_realpath($filename);
if ($filename === false) {
if ($this->use_stat_cache) {
$result = $this->_query_stat_cache($filename);
if (is_array($result) && isset($result['.'])) {
return (array) $result['.'];
if (is_object($result)) {
$lstat = $this->_stat($filename, NET_SFTP_LSTAT);
$this->_remove_from_stat_cache($filename);
if (isset($lstat['type'])) {
if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
$this->_update_stat_cache($filename, (object) $lstat);
$stat = $this->_stat($filename, NET_SFTP_STAT);
$lstat = array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK));
$this->_update_stat_cache($filename, (object) $lstat);
$lstat['type'] = $this->chdir($filename) ?
NET_SFTP_TYPE_DIRECTORY :
if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
$this->_update_stat_cache($filename, (object) $lstat);
* Returns general information about a file or symbolic link
* Determines information without calling Net_SFTP::_realpath().
* The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT.
* @param String $filename
public function _stat($filename, $type)
// SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
$packet = pack('Na*', strlen($filename), $filename);
if (!$this->_send_sftp_packet($type, $packet)) {
$response = $this->_get_sftp_packet();
switch ($this->packet_type) {
return $this->_parseAttributes($response);
$this->_logError($response);
user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
* Truncates a file to a given length
* @param String $filename
* @param Integer $new_size
public function truncate($filename, $new_size)
$attr = pack('N3', NET_SFTP_ATTR_SIZE, $new_size / 4294967296, $new_size); // 4294967296 == 0x100000000 == 1<<32
return $this->_setstat($filename, $attr, false);
* Sets access and modification time of file.
* If the file does not exist, it will be created.
* @param String $filename
* @param optional Integer $time
* @param optional Integer $atime
public function touch($filename, $time = null, $atime = null)
if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
$filename = $this->_realpath($filename);
if ($filename === false) {
$flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL;
$attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime);
$packet = pack('Na*Na*', strlen($filename), $filename, $flags, $attr);
if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
$response = $this->_get_sftp_packet();
switch ($this->packet_type) {
return $this->_close_handle(substr($response, 4));
$this->_logError($response);
user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
return $this->_setstat($filename, $attr, false);
* Changes file or directory owner
* Returns true on success or false on error.
* @param String $filename
* @param optional Boolean $recursive
public function chown($filename, $uid, $recursive = false)
// quoting from <http://www.kernel.org/doc/man-pages/online/pages/man2/chown.2.html>,
// "if the owner or group is specified as -1, then that ID is not changed"
$attr = pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1);
return $this->_setstat($filename, $attr, $recursive);
* Changes file or directory group
* Returns true on success or false on error.
* @param String $filename
* @param optional Boolean $recursive
public function chgrp($filename, $gid, $recursive = false)
$attr = pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid);
return $this->_setstat($filename, $attr, $recursive);
* Set permissions on a file.
* Returns the new file permissions on success or false on error.
* If $recursive is true than this just returns true or false.
* @param String $filename
* @param optional Boolean $recursive
public function chmod($mode, $filename, $recursive = false)
if (is_string($mode) && is_int($filename)) {
$attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
if (!$this->_setstat($filename, $attr, $recursive)) {
// rather than return what the permissions *should* be, we'll return what they actually are. this will also
// tell us if the file actually exists.
// incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
$packet = pack('Na*', strlen($filename), $filename);
if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) {
$response = $this->_get_sftp_packet();
switch ($this->packet_type) {
$attrs = $this->_parseAttributes($response);
return $attrs['permissions'];
$this->_logError($response);
user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
* Sets information about a file
* @param String $filename
* @param Boolean $recursive
public function _setstat($filename, $attr, $recursive)
if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
$filename = $this->_realpath($filename);
if ($filename === false) {