SFTP: Redo mkdir() and _realpath()

Also make it so nlist() caches directories as well
This commit is contained in:
terrafrost 2013-02-27 00:47:17 -06:00
parent 956c1e5bfd
commit 44864874e5

View File

@ -461,7 +461,7 @@ class Net_SFTP extends Net_SSH2 {
return false; return false;
} }
$this->pwd = $this->_realpath('.', false); $this->pwd = $this->_realpath('.');
$this->_save_dir($this->pwd); $this->_save_dir($this->pwd);
@ -508,65 +508,15 @@ class Net_SFTP extends Net_SSH2 {
* the absolute (canonicalized) path. * the absolute (canonicalized) path.
* *
* @see Net_SFTP::chdir() * @see Net_SFTP::chdir()
* @param String $dir * @param String $path
* @return Mixed * @return Mixed
* @access private * @access private
*/ */
function _realpath($dir, $check_dir = true) function _realpath($path)
{ {
if ($check_dir && $this->_is_dir($dir)) { if ($this->pwd === false) {
return true;
}
/*
"This protocol represents file names as strings. File names are
assumed to use the slash ('/') character as a directory separator.
File names starting with a slash are "absolute", and are relative to
the root of the file system. Names starting with any other character
are relative to the user's default directory (home directory). Note
that identifying the user is assumed to take place outside of this
protocol."
-- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-6
*/
$file = '';
if ($this->pwd !== false) {
// if the SFTP server returned the canonicalized path even for non-existant files this wouldn't be necessary
// on OpenSSH it isn't necessary but on other SFTP servers it is. that and since the specs say nothing on
// the subject, we'll go ahead and work around it with the following.
if (empty($dir) || $dir[strlen($dir) - 1] != '/') {
$file = basename($dir);
$dir = dirname($dir);
}
$dir = $dir[0] == '/' ? '/' . rtrim(substr($dir, 1), '/') : rtrim($dir, '/');
if ($dir == '.' || $dir == $this->pwd) {
$temp = $this->pwd;
if (!empty($file)) {
$temp.= '/' . $file;
}
return $temp;
}
if ($dir[0] != '/') {
$dir = $this->pwd . '/' . $dir;
}
// on the surface it seems like maybe resolving a path beginning with / is unnecessary, but such paths
// can contain .'s and ..'s just like any other. we could parse those out as appropriate or we can let
// the server do it. we'll do the latter.
}
/*
that SSH_FXP_REALPATH returns SSH_FXP_NAME does not necessarily mean that anything actually exists at the
specified path. generally speaking, no attributes are returned with this particular SSH_FXP_NAME packet
regardless of whether or not a file actually exists. and in SFTPv3, the longname field and the filename
field match for this particular SSH_FXP_NAME packet. for other SSH_FXP_NAME packets, this will likely
not be the case, but for this one, it is.
*/
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($dir), $dir))) { if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($path), $path))) {
return false; return false;
} }
@ -578,13 +528,7 @@ class Net_SFTP extends Net_SSH2 {
// at is the first part and that part is defined the same in SFTP versions 3 through 6. // at is the first part and that part is defined the same in SFTP versions 3 through 6.
$this->_string_shift($response, 4); // skip over the count - it should be 1, anyway $this->_string_shift($response, 4); // skip over the count - it should be 1, anyway
extract(unpack('Nlength', $this->_string_shift($response, 4))); extract(unpack('Nlength', $this->_string_shift($response, 4)));
$realpath = $this->_string_shift($response, $length); return $this->_string_shift($response, $length);
// the following is SFTPv3 only code. see Net_SFTP::_parseLongname() for more information.
// per the above comment, this is a shot in the dark that, on most servers, won't help us in determining
// the file type for Net_SFTP::stat() and Net_SFTP::lstat() but it's worth a shot.
extract(unpack('Nlength', $this->_string_shift($response, 4)));
$this->fileType = $this->_parseLongname($this->_string_shift($response, $length));
break;
case NET_SFTP_STATUS: case NET_SFTP_STATUS:
$this->_logError($response); $this->_logError($response);
return false; return false;
@ -592,14 +536,29 @@ class Net_SFTP extends Net_SSH2 {
user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS'); user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
return false; return false;
} }
// if $this->pwd isn't set than the only thing $realpath could be is for '.', which is pretty much guaranteed to
// be a bonafide directory
if (!empty($file)) {
$realpath.= '/' . $file;
} }
return $realpath; if ($path[0] != '/') {
$path = $this->pwd . '/' . $path;
}
$path = explode('/', $path);
$new = array();
foreach ($path as $dir) {
if (!strlen($dir)) {
continue;
}
switch ($dir) {
case '..':
array_pop($new);
case '.':
break;
default:
$new[] = $dir;
}
}
return '/' . implode('/', $new);
} }
/** /**
@ -619,19 +578,14 @@ class Net_SFTP extends Net_SSH2 {
$dir.= '/'; $dir.= '/';
} }
$dir = $this->_realpath($dir);
// confirm that $dir is, in fact, a valid directory // confirm that $dir is, in fact, a valid directory
if ($this->_is_dir($dir)) { if ($this->_is_dir($dir)) {
$this->pwd = $dir; $this->pwd = $dir;
return true; return true;
} }
$dir = $this->_realpath($dir, false);
if ($this->_is_dir($dir)) {
$this->pwd = $dir;
return true;
}
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;
} }
@ -762,15 +716,18 @@ class Net_SFTP extends Net_SSH2 {
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); // we also don't care about the attributes
$fileType = $this->_parseLongname($longname);
if (!$raw) { if (!$raw) {
$contents[] = $shortname; $contents[] = $shortname;
} else { } else {
$contents[$shortname] = $attributes; $contents[$shortname] = $attributes;
$fileType = $this->_parseLongname($longname); }
if ($fileType) { if ($fileType) {
if ($fileType == 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; $contents[$shortname]['type'] = $fileType;
} }
} }
@ -881,6 +838,9 @@ class Net_SFTP extends Net_SSH2 {
/** /**
* Checks cache for directory * Checks cache for directory
* *
* 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()
*
* @param String $dir * @param String $dir
* @access private * @access private
*/ */
@ -895,6 +855,7 @@ class Net_SFTP extends Net_SSH2 {
} }
$temp = &$temp[$dir]; $temp = &$temp[$dir];
} }
return true;
} }
/** /**
@ -1362,33 +1323,30 @@ class Net_SFTP extends Net_SSH2 {
* @return Boolean * @return Boolean
* @access public * @access public
*/ */
function mkdir($dir) function mkdir($dir, $mode = -1, $recursive = false)
{ {
if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
return false; return false;
} }
if ($dir[0] != '/') { $dir = $this->_realpath($dir);
$dir = $this->_realpath(rtrim($dir, '/')); $attr = $mode == -1 ? chr(0) : pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
if ($dir === false) {
return false; if ($recursive) {
$dirs = explode('/', preg_replace('#/(?=/)|/$#', '', $dir));
if (empty($dirs[0])) {
array_shift($dirs);
$dirs[0] = '/' . $dirs[0];
} }
if (!$this->_mkdir_helper($dir)) { for ($i = 0; $i < count($dirs); $i++) {
return false; $temp = array_slice($dirs, 0, $i + 1);
} $temp = implode('/', $temp);
} else { $result = $this->_mkdir_helper($temp, $attr);
$dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir));
$temp = '';
foreach ($dirs as $dir) {
$temp.= '/' . $dir;
$result = $this->_mkdir_helper($temp);
}
if (!$result) {
return false;
} }
return $result;
} }
return true; return $this->_mkdir_helper($dir, $attr);
} }
/** /**
@ -1398,11 +1356,11 @@ class Net_SFTP extends Net_SSH2 {
* @return Boolean * @return Boolean
* @access private * @access private
*/ */
function _mkdir_helper($dir) function _mkdir_helper($dir, $attr)
{ {
// by not providing any permissions, hopefully the server will use the logged in users umask - their // by not providing any permissions, hopefully the server will use the logged in users umask - their
// default permissions. // default permissions.
if (!$this->_send_sftp_packet(NET_SFTP_MKDIR, pack('Na*N', strlen($dir), $dir, 0))) { if (!$this->_send_sftp_packet(NET_SFTP_MKDIR, pack('Na*a*', strlen($dir), $dir, $attr))) {
return false; return false;
} }