SFTP: Revamp file type detection and add truncate method

Also clean up some code
This commit is contained in:
terrafrost 2013-03-08 00:53:34 -06:00
parent d4f176b434
commit 51d106b6ec

View File

@ -225,15 +225,6 @@ class Net_SFTP extends Net_SSH2 {
*/ */
var $sftp_errors = array(); var $sftp_errors = array();
/**
* File Type
*
* @see Net_SFTP::_parseLongname()
* @var Integer
* @access private
*/
var $fileType = 0;
/** /**
* Directory Cache * Directory Cache
* *
@ -338,7 +329,14 @@ class Net_SFTP extends Net_SSH2 {
1 => 'NET_SFTP_TYPE_REGULAR', 1 => 'NET_SFTP_TYPE_REGULAR',
2 => 'NET_SFTP_TYPE_DIRECTORY', 2 => 'NET_SFTP_TYPE_DIRECTORY',
3 => 'NET_SFTP_TYPE_SYMLINK', 3 => 'NET_SFTP_TYPE_SYMLINK',
4 => 'NET_SFTP_TYPE_SPECIAL' 4 => 'NET_SFTP_TYPE_SPECIAL',
5 => 'NET_SFTP_TYPE_UNKNOWN',
// the followin types were first defined for use in SFTPv5+
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
6 => 'NET_SFTP_TYPE_SOCKET',
7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
9 => 'NET_SFTP_TYPE_FIFO'
); );
$this->_define_array( $this->_define_array(
$this->packet_types, $this->packet_types,
@ -587,6 +585,11 @@ class Net_SFTP extends Net_SSH2 {
return true; return true;
} }
// we could do a stat on the alleged $dir to see if it's a directory but that doesn't tell us
// the currently logged in user has the appropriate permissions or not. maybe you could see if
// the file's uid / gid match the currently logged in user's uid / gid but how there's no easy
// way to get those with SFTP
if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) { if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
return false; return false;
} }
@ -716,21 +719,21 @@ class Net_SFTP extends Net_SSH2 {
$shortname = $this->_string_shift($response, $length); $shortname = $this->_string_shift($response, $length);
extract(unpack('Nlength', $this->_string_shift($response, 4))); extract(unpack('Nlength', $this->_string_shift($response, 4)));
$longname = $this->_string_shift($response, $length); $longname = $this->_string_shift($response, $length);
$attributes = $this->_parseAttributes($response); // we also don't care about the attributes $attributes = $this->_parseAttributes($response);
$fileType = $this->_parseLongname($longname); if (!isset($attributes['type'])) {
$fileType = $this->_parseLongname($longname);
if ($fileType) {
$attributes['type'] = $fileType;
}
}
if (!$raw) { if (!$raw) {
$contents[] = $shortname; $contents[] = $shortname;
} else { } else {
$contents[$shortname] = $attributes; $contents[$shortname] = $attributes;
} }
if ($fileType) { if (isset($attributes['type']) && $attributes['type'] == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) {
if ($fileType == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) { $this->_save_dir($dir . '/' . $shortname);
$this->_save_dir($dir . '/' . $shortname);
}
if ($raw) {
$contents[$shortname]['type'] = $fileType;
}
} }
// SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the // 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. // final SSH_FXP_STATUS packet should tell us that, already.
@ -883,6 +886,9 @@ class Net_SFTP extends Net_SSH2 {
if ($stat === false) { if ($stat === false) {
return false; return false;
} }
if (isset($stat['type'])) {
return $stat;
}
$pwd = $this->pwd; $pwd = $this->pwd;
$stat['type'] = $this->chdir($filename) ? $stat['type'] = $this->chdir($filename) ?
@ -917,6 +923,10 @@ class Net_SFTP extends Net_SSH2 {
if ($lstat === false) { if ($lstat === false) {
return false; return false;
} }
if (isset($lstat['type'])) {
return $lstat;
}
$stat = $this->_stat($filename, NET_SFTP_STAT); $stat = $this->_stat($filename, NET_SFTP_STAT);
if ($lstat != $stat) { if ($lstat != $stat) {
@ -954,11 +964,7 @@ class Net_SFTP extends Net_SSH2 {
$response = $this->_get_sftp_packet(); $response = $this->_get_sftp_packet();
switch ($this->packet_type) { switch ($this->packet_type) {
case NET_SFTP_ATTRS: case NET_SFTP_ATTRS:
$attributes = $this->_parseAttributes($response); return $this->_parseAttributes($response);
if ($this->fileType) {
$attributes['type'] = $this->fileType;
}
return $attributes;
case NET_SFTP_STATUS: case NET_SFTP_STATUS:
$this->_logError($response); $this->_logError($response);
return false; return false;
@ -968,33 +974,6 @@ class Net_SFTP extends Net_SSH2 {
return false; return false;
} }
/**
* Attempt to identify the file type
*
* @param String $path
* @param Array $stat
* @param Array $lstat
* @return Integer
* @access private
*/
function _identify_type($path, $stat1, $stat2)
{
$stat1 = $this->_stat($path, $stat1);
$stat2 = $this->_stat($path, $stat2);
if ($stat1 != $stat2) {
return array_merge($stat1, array('type' => NET_SFTP_TYPE_SYMLINK));
}
$pwd = $this->pwd;
$stat1['type'] = $this->chdir($path) ?
NET_SFTP_TYPE_DIRECTORY :
NET_SFTP_TYPE_REGULAR;
$this->pwd = $pwd;
return $stat1;
}
/** /**
* Returns the file size, in bytes, or false, on failure * Returns the file size, in bytes, or false, on failure
* *
@ -1013,6 +992,21 @@ class Net_SFTP extends Net_SSH2 {
return isset($result['size']) ? $result['size'] : -1; return isset($result['size']) ? $result['size'] : -1;
} }
/**
* Truncates a file to a given length
*
* @param String $filename
* @param Integer $new_size
* @return Boolean
* @access public
*/
function truncate($filename, $new_size)
{
$attr = pack('N3', NET_SFTP_ATTR_SIZE, 0, $new_size);
return $this->_setstat($filename, $attr, false);
}
/** /**
* Sets access and modification time of file. * Sets access and modification time of file.
* *
@ -1079,24 +1073,7 @@ class Net_SFTP extends Net_SSH2 {
return false; return false;
} }
$attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime); return $this->_setstat($filename, $attr, false);
if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($filename), $filename, $attr))) {
return false;
}
$response = $this->_get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
user_error('Expected SSH_FXP_STATUS');
return false;
}
extract(unpack('Nstatus', $this->_string_shift($response, 4)));
if ($status != NET_SFTP_STATUS_OK) {
$this->_logError($response, $status);
return false;
}
return true;
} }
/** /**
@ -1914,6 +1891,10 @@ class Net_SFTP extends Net_SSH2 {
break; break;
case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004 case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
$attr+= unpack('Npermissions', $this->_string_shift($response, 4)); $attr+= unpack('Npermissions', $this->_string_shift($response, 4));
$fileType = $this->_parseMode($attr['permissions']);
if ($filetype !== false) {
$attr+= array('type' => $fileType);
}
break; break;
case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008 case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
$attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8)); $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8));
@ -1931,6 +1912,47 @@ class Net_SFTP extends Net_SSH2 {
return $attr; return $attr;
} }
/**
* Attempt to identify the file type
*
* Quoting the SFTP RFC, "Implementations MUST NOT send bits that are not defined" but they seem to anyway
*
* @param Integer $mode
* @return Integer
* @access private
*/
function _parseMode($mode)
{
// values come from http://lxr.free-electrons.com/source/include/uapi/linux/stat.h#L12
// see, also, http://linux.die.net/man/2/stat
switch ($mode & 0170000) {// ie. 1111 0000 0000 0000
case 0000000: // no file type specified - figure out the file type using alternative means
return false;
case 0040000:
return NET_SFTP_TYPE_DIRECTORY;
case 0100000:
return NET_SFTP_TYPE_REGULAR;
case 0120000:
return NET_SFTP_TYPE_SYMLINK;
// new types introduced in SFTPv5+
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
case 0010000: // named pipe (fifo)
return NET_SFTP_TYPE_FIFO;
case 0020000: // character special
return NET_SFTP_TYPE_CHAR_DEVICE;
case 0060000: // block special
return NET_SFTP_BLOCK_DEVICE;
case 0140000: // socket
return NET_SFTP_TYPE_SOCKET;
case 0160000: // whiteout
// "SPECIAL should be used for files that are of
// a known type which cannot be expressed in the protocol"
return NET_SFTP_TYPE_SPECIAL;
default:
return NET_SFTP_TYPE_UNKNOWN;
}
}
/** /**
* Parse Longname * Parse Longname
* *