extract(unpack('Nstatus', $this->_string_shift($response, 4)));
if ($status != NET_SFTP_STATUS_OK) {
$this->_logError($response, $status);
public function _close_handle($handle)
if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
// "The client MUST release all resources associated with the handle regardless of the status."
// -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3
$response = $this->_get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
user_error('Expected SSH_FXP_STATUS');
extract(unpack('Nstatus', $this->_string_shift($response, 4)));
if ($status != NET_SFTP_STATUS_OK) {
$this->_logError($response, $status);
* Downloads a file from the SFTP server.
* Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
* the operation was unsuccessful. If $local_file is defined, returns true or false depending on the success of the
* $offset and $length can be used to download files in chunks.
* @param String $remote_file
* @param optional String $local_file
* @param optional Integer $offset
* @param optional Integer $length
public function get($remote_file, $local_file = false, $offset = 0, $length = -1)
if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
$remote_file = $this->_realpath($remote_file);
if ($remote_file === false) {
$packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_READ, 0);
if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
$response = $this->_get_sftp_packet();
switch ($this->packet_type) {
$handle = substr($response, 4);
case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
$this->_logError($response);
user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
if ($local_file !== false) {
$fp = fopen($local_file, 'wb');
$size = $this->max_sftp_packet < $length || $length < 0 ? $this->max_sftp_packet : $length;
$packet = pack('Na*N3', strlen($handle), $handle, $offset / 4294967296, $offset, $size);
if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet)) {
if ($local_file !== false) {
$response = $this->_get_sftp_packet();
switch ($this->packet_type) {
$temp = substr($response, 4);
$offset += strlen($temp);
if ($local_file === false) {
// could, in theory, return false if !strlen($content) but we'll hold off for the time being
$this->_logError($response);
user_error('Expected SSH_FXP_DATA or SSH_FXP_STATUS');
if ($local_file !== false) {
if ($length > 0 && $length <= $offset - $start) {
if ($length > 0 && $length <= $offset - $start) {
if ($local_file === false) {
$content = substr($content, 0, $length);
if ($local_file !== false) {
if (!$this->_close_handle($handle)) {
// if $content isn't set that means a file was written to
return isset($content) ? $content : true;
* Deletes a file on the SFTP server.
* @param Boolean $recursive
public function delete($path, $recursive = true)
if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
$path = $this->_realpath($path);
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path))) {
$response = $this->_get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
user_error('Expected SSH_FXP_STATUS');
// if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
extract(unpack('Nstatus', $this->_string_shift($response, 4)));
if ($status != NET_SFTP_STATUS_OK) {
$this->_logError($response, $status);
$result = $this->_delete_recursive($path, $i);
$this->_read_put_responses($i);
$this->_remove_from_stat_cache($path);
* Recursively deletes directories on the SFTP server
* Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
public function _delete_recursive($path, &$i)
if (!$this->_read_put_responses($i)) {
$entries = $this->_list($path, true, false);
// normally $entries would have at least . and .. but it might not if the directories
// permissions didn't allow reading
foreach ($entries as $filename => $props) {
if ($filename == '.' || $filename == '..') {
if (!isset($props['type'])) {
$temp = $path.'/'.$filename;
if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
if (!$this->_delete_recursive($temp, $i)) {
if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($temp), $temp))) {
if ($i >= NET_SFTP_QUEUE_SIZE) {
if (!$this->_read_put_responses($i)) {
$this->_remove_from_stat_cache($path);
if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($path), $path))) {
if ($i >= NET_SFTP_QUEUE_SIZE) {
if (!$this->_read_put_responses($i)) {
* Checks whether a file or directory exists
public function file_exists($path)
if ($this->use_stat_cache) {
$path = $this->_realpath($path);
$result = $this->_query_stat_cache($path);
// return true if $result is an array or if it's int(1)
return $result !== false;
return $this->stat($path) !== false;
* Tells whether the filename is a directory
public function is_dir($path)
$result = $this->_get_stat_cache_prop($path, 'type');
return $result === NET_SFTP_TYPE_DIRECTORY;
* Tells whether the filename is a regular file
public function is_file($path)
$result = $this->_get_stat_cache_prop($path, 'type');
return $result === NET_SFTP_TYPE_REGULAR;
* Tells whether the filename is a symbolic link
public function is_link($path)
$result = $this->_get_stat_cache_prop($path, 'type');
return $result === NET_SFTP_TYPE_SYMLINK;
* Gets last access time of file
public function fileatime($path)
return $this->_get_stat_cache_prop($path, 'atime');
* Gets file modification time
public function filemtime($path)
return $this->_get_stat_cache_prop($path, 'mtime');
public function fileperms($path)
return $this->_get_stat_cache_prop($path, 'permissions');
public function fileowner($path)
return $this->_get_stat_cache_prop($path, 'uid');
public function filegroup($path)
return $this->_get_stat_cache_prop($path, 'gid');
public function filesize($path)
return $this->_get_stat_cache_prop($path, 'size');
public function filetype($path)
$type = $this->_get_stat_cache_prop($path, 'type');
case NET_SFTP_BLOCK_DEVICE:
case NET_SFTP_TYPE_CHAR_DEVICE:
case NET_SFTP_TYPE_DIRECTORY:
case NET_SFTP_TYPE_REGULAR:
case NET_SFTP_TYPE_SYMLINK:
* Return a stat properity
* Uses cache if appropriate.
public function _get_stat_cache_prop($path, $prop)
if ($this->use_stat_cache) {
$path = $this->_realpath($path);
$result = $this->_query_stat_cache($path);
if (is_object($result) && isset($result->$prop)) {
$result = $this->stat($path);