mirror of
https://github.com/phpseclib/phpseclib.git
synced 2024-11-12 00:16:33 +00:00
Merge pull request #1697 from terrafrost/sftpv456-3.0
add SFTP v4/5/6 support to phpseclib v3
This commit is contained in:
commit
d8ea63dbdb
@ -70,6 +70,7 @@ abstract class Strings
|
||||
* C = byte
|
||||
* b = boolean (true/false)
|
||||
* N = uint32
|
||||
* Q = uint64
|
||||
* s = string
|
||||
* i = mpint
|
||||
* L = name-list
|
||||
@ -100,6 +101,12 @@ abstract class Strings
|
||||
throw new \LengthException('At least four byte needs to be present for successful N / i / s / L decodes');
|
||||
}
|
||||
break;
|
||||
case 'Q':
|
||||
if (strlen($data) < 8) {
|
||||
throw new \LengthException('At least eight byte needs to be present for successful N / i / s / L decodes');
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException('$format contains an invalid character');
|
||||
}
|
||||
@ -114,6 +121,19 @@ abstract class Strings
|
||||
list(, $temp) = unpack('N', self::shift($data, 4));
|
||||
$result[] = $temp;
|
||||
continue 2;
|
||||
case 'Q':
|
||||
// pack() added support for Q in PHP 5.6.3 and PHP 5.6 is phpseclib 3's minimum version
|
||||
// so in theory we could support this BUT, "64-bit format codes are not available for
|
||||
// 32-bit versions" and phpseclib works on 32-bit installs. on 32-bit installs
|
||||
// 64-bit floats can be used to get larger numbers then 32-bit signed ints would allow
|
||||
// for. sure, you're not gonna get the full precision of 64-bit numbers but just because
|
||||
// you need > 32-bit precision doesn't mean you need the full 64-bit precision
|
||||
extract(unpack('Nupper/Nlower', self::shift($data, 8)));
|
||||
$temp = $upper ? 4294967296 * $upper : 0;
|
||||
$temp+= $lower < 0 ? ($lower & 0x7FFFFFFFF) + 0x80000000 : $lower;
|
||||
// $temp = hexdec(bin2hex(self::shift($data, 8)));
|
||||
$result[] = $temp;
|
||||
continue 2;
|
||||
}
|
||||
list(, $length) = unpack('N', self::shift($data, 4));
|
||||
if (strlen($data) < $length) {
|
||||
@ -165,6 +185,13 @@ abstract class Strings
|
||||
}
|
||||
$result.= $element ? "\1" : "\0";
|
||||
break;
|
||||
case 'Q':
|
||||
if (!is_int($element) && !is_float($element)) {
|
||||
throw new \InvalidArgumentException('An integer was expected.');
|
||||
}
|
||||
// 4294967296 == 1 << 32
|
||||
$result.= pack('NN', $element / 4294967296, $element);
|
||||
break;
|
||||
case 'N':
|
||||
if (is_float($element)) {
|
||||
$element = (int) $element;
|
||||
|
@ -5,9 +5,7 @@
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* Currently only supports SFTPv2 and v3, which, according to wikipedia.org, "is the most widely used version,
|
||||
* implemented by the popular OpenSSH SFTP server". If you want SFTPv4/5/6 support, provide me with access
|
||||
* to an SFTPv4/5/6 server.
|
||||
* Supports SFTPv2/3/4/5/6. Defaults to v3.
|
||||
*
|
||||
* The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
|
||||
*
|
||||
@ -169,6 +167,24 @@ class SFTP extends SSH2
|
||||
*/
|
||||
private $version;
|
||||
|
||||
/**
|
||||
* Default Server SFTP version
|
||||
*
|
||||
* @var int
|
||||
* @see self::_initChannel()
|
||||
* @access private
|
||||
*/
|
||||
private $defaultVersion;
|
||||
|
||||
/**
|
||||
* Preferred SFTP version
|
||||
*
|
||||
* @var int
|
||||
* @see self::_initChannel()
|
||||
* @access private
|
||||
*/
|
||||
private $preferredVersion = 3;
|
||||
|
||||
/**
|
||||
* Current working directory
|
||||
*
|
||||
@ -297,7 +313,7 @@ class SFTP extends SSH2
|
||||
* @var bool
|
||||
* @access private
|
||||
*/
|
||||
var $allow_arbitrary_length_packets = false;
|
||||
private $allow_arbitrary_length_packets = false;
|
||||
|
||||
/**
|
||||
* Was the last packet due to the channels being closed or not?
|
||||
@ -309,6 +325,14 @@ class SFTP extends SSH2
|
||||
*/
|
||||
private $channel_close = false;
|
||||
|
||||
/**
|
||||
* Has the SFTP channel been partially negotiated?
|
||||
*
|
||||
* @var bool
|
||||
* @access private
|
||||
*/
|
||||
private $partial_init = false;
|
||||
|
||||
/**
|
||||
* Default Constructor.
|
||||
*
|
||||
@ -329,15 +353,13 @@ class SFTP extends SSH2
|
||||
$this->packet_types = [
|
||||
1 => 'NET_SFTP_INIT',
|
||||
2 => 'NET_SFTP_VERSION',
|
||||
/* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+:
|
||||
SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1
|
||||
pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */
|
||||
3 => 'NET_SFTP_OPEN',
|
||||
4 => 'NET_SFTP_CLOSE',
|
||||
5 => 'NET_SFTP_READ',
|
||||
6 => 'NET_SFTP_WRITE',
|
||||
7 => 'NET_SFTP_LSTAT',
|
||||
9 => 'NET_SFTP_SETSTAT',
|
||||
10 => 'NET_SFTP_FSETSTAT',
|
||||
11 => 'NET_SFTP_OPENDIR',
|
||||
12 => 'NET_SFTP_READDIR',
|
||||
13 => 'NET_SFTP_REMOVE',
|
||||
@ -345,18 +367,13 @@ class SFTP extends SSH2
|
||||
15 => 'NET_SFTP_RMDIR',
|
||||
16 => 'NET_SFTP_REALPATH',
|
||||
17 => 'NET_SFTP_STAT',
|
||||
/* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+:
|
||||
SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
|
||||
pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */
|
||||
18 => 'NET_SFTP_RENAME',
|
||||
19 => 'NET_SFTP_READLINK',
|
||||
20 => 'NET_SFTP_SYMLINK',
|
||||
21 => 'NET_SFTP_LINK',
|
||||
|
||||
101=> 'NET_SFTP_STATUS',
|
||||
102=> 'NET_SFTP_HANDLE',
|
||||
/* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+:
|
||||
SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4
|
||||
pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */
|
||||
103=> 'NET_SFTP_DATA',
|
||||
104=> 'NET_SFTP_NAME',
|
||||
105=> 'NET_SFTP_ATTRS',
|
||||
@ -402,8 +419,20 @@ class SFTP extends SSH2
|
||||
$this->attributes = [
|
||||
0x00000001 => 'NET_SFTP_ATTR_SIZE',
|
||||
0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
|
||||
0x00000080 => 'NET_SFTP_ATTR_OWNERGROUP', // defined in SFTPv4+
|
||||
0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
|
||||
0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
|
||||
0x00000010 => 'NET_SFTP_ATTR_CREATETIME', // SFTPv4+
|
||||
0x00000020 => 'NET_SFTP_ATTR_MODIFYTIME',
|
||||
0x00000040 => 'NET_SFTP_ATTR_ACL',
|
||||
0x00000100 => 'NET_SFTP_ATTR_SUBSECOND_TIMES',
|
||||
0x00000200 => 'NET_SFTP_ATTR_BITS', // SFTPv5+
|
||||
0x00000400 => 'NET_SFTP_ATTR_ALLOCATION_SIZE', // SFTPv6+
|
||||
0x00000800 => 'NET_SFTP_ATTR_TEXT_HINT',
|
||||
0x00001000 => 'NET_SFTP_ATTR_MIME_TYPE',
|
||||
0x00002000 => 'NET_SFTP_ATTR_LINK_COUNT',
|
||||
0x00004000 => 'NET_SFTP_ATTR_UNTRANSLATED_NAME',
|
||||
0x00008000 => 'NET_SFTP_ATTR_CTIME',
|
||||
// 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
|
||||
// yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in
|
||||
// two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000.
|
||||
@ -419,7 +448,32 @@ class SFTP extends SSH2
|
||||
0x00000004 => 'NET_SFTP_OPEN_APPEND',
|
||||
0x00000008 => 'NET_SFTP_OPEN_CREATE',
|
||||
0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
|
||||
0x00000020 => 'NET_SFTP_OPEN_EXCL'
|
||||
0x00000020 => 'NET_SFTP_OPEN_EXCL',
|
||||
0x00000040 => 'NET_SFTP_OPEN_TEXT' // defined in SFTPv4
|
||||
];
|
||||
// SFTPv5+ changed the flags up:
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-8.1.1.3
|
||||
$this->open_flags5 = [
|
||||
// when SSH_FXF_ACCESS_DISPOSITION is a 3 bit field that controls how the file is opened
|
||||
0x00000000 => 'NET_SFTP_OPEN_CREATE_NEW',
|
||||
0x00000001 => 'NET_SFTP_OPEN_CREATE_TRUNCATE',
|
||||
0x00000002 => 'NET_SFTP_OPEN_OPEN_EXISTING',
|
||||
0x00000003 => 'NET_SFTP_OPEN_OPEN_OR_CREATE',
|
||||
0x00000004 => 'NET_SFTP_OPEN_TRUNCATE_EXISTING',
|
||||
// the rest of the flags are not supported
|
||||
0x00000008 => 'NET_SFTP_OPEN_APPEND_DATA', // "the offset field of SS_FXP_WRITE requests is ignored"
|
||||
0x00000010 => 'NET_SFTP_OPEN_APPEND_DATA_ATOMIC',
|
||||
0x00000020 => 'NET_SFTP_OPEN_TEXT_MODE',
|
||||
0x00000040 => 'NET_SFTP_OPEN_BLOCK_READ',
|
||||
0x00000080 => 'NET_SFTP_OPEN_BLOCK_WRITE',
|
||||
0x00000100 => 'NET_SFTP_OPEN_BLOCK_DELETE',
|
||||
0x00000200 => 'NET_SFTP_OPEN_BLOCK_ADVISORY',
|
||||
0x00000400 => 'NET_SFTP_OPEN_NOFOLLOW',
|
||||
0x00000800 => 'NET_SFTP_OPEN_DELETE_ON_CLOSE',
|
||||
0x00001000 => 'NET_SFTP_OPEN_ACCESS_AUDIT_ALARM_INFO',
|
||||
0x00002000 => 'NET_SFTP_OPEN_ACCESS_BACKUP',
|
||||
0x00004000 => 'NET_SFTP_OPEN_BACKUP_STREAM',
|
||||
0x00008000 => 'NET_SFTP_OPEN_OVERRIDE_OWNER',
|
||||
];
|
||||
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
|
||||
// see \phpseclib3\Net\SFTP::_parseLongname() for an explanation
|
||||
@ -441,6 +495,7 @@ class SFTP extends SSH2
|
||||
$this->status_codes,
|
||||
$this->attributes,
|
||||
$this->open_flags,
|
||||
$this->open_flags5,
|
||||
$this->file_types
|
||||
);
|
||||
|
||||
@ -453,31 +508,32 @@ class SFTP extends SSH2
|
||||
}
|
||||
|
||||
/**
|
||||
* Login
|
||||
* Check a few things before SFTP functions are called
|
||||
*
|
||||
* @return bool
|
||||
* @access public
|
||||
*/
|
||||
private function precheck()
|
||||
{
|
||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->pwd === false) {
|
||||
return $this->init_sftp_connection();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Partially initialize an SFTP connection
|
||||
*
|
||||
* @param string $username
|
||||
* @param string|AsymmetricKey|array[]|Agent|null ...$args
|
||||
* @throws \UnexpectedValueException on receipt of unexpected packets
|
||||
* @return bool
|
||||
* @access public
|
||||
*/
|
||||
public function login($username, ...$args)
|
||||
{
|
||||
if (!parent::login(...func_get_args())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->init_sftp_connection();
|
||||
}
|
||||
|
||||
/**
|
||||
* (Re)initializes the SFTP channel
|
||||
*
|
||||
* @throws \UnexpectedValueException on receipt of unexpected packets
|
||||
* @return bool
|
||||
* @access private
|
||||
*/
|
||||
private function init_sftp_connection()
|
||||
private function partial_init_sftp_connection()
|
||||
{
|
||||
$this->window_size_server_to_client[self::CHANNEL] = $this->window_size;
|
||||
|
||||
@ -548,27 +604,30 @@ class SFTP extends SSH2
|
||||
. 'Got packet type: ' . $this->packet_type);
|
||||
}
|
||||
|
||||
list($this->version) = Strings::unpackSSH2('N', $response);
|
||||
$this->use_request_id = true;
|
||||
|
||||
list($this->defaultVersion) = Strings::unpackSSH2('N', $response);
|
||||
while (!empty($response)) {
|
||||
list($key, $value) = Strings::unpackSSH2('ss', $response);
|
||||
$this->extensions[$key] = $value;
|
||||
}
|
||||
|
||||
/*
|
||||
SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com',
|
||||
however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's
|
||||
not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for
|
||||
one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that
|
||||
'newline@vandyke.com' would.
|
||||
*/
|
||||
/*
|
||||
if (isset($this->extensions['newline@vandyke.com'])) {
|
||||
$this->extensions['newline'] = $this->extensions['newline@vandyke.com'];
|
||||
unset($this->extensions['newline@vandyke.com']);
|
||||
}
|
||||
*/
|
||||
$this->partial_init = true;
|
||||
|
||||
$this->use_request_id = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* (Re)initializes the SFTP channel
|
||||
*
|
||||
* @return bool
|
||||
* @access private
|
||||
*/
|
||||
private function init_sftp_connection()
|
||||
{
|
||||
if (!$this->partial_init && !$this->partial_init_sftp_connection()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
A Note on SFTPv4/5/6 support:
|
||||
@ -593,14 +652,56 @@ class SFTP extends SSH2
|
||||
in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what \phpseclib3\Net\SFTP would do is close the
|
||||
channel and reopen it with a new and updated SSH_FXP_INIT packet.
|
||||
*/
|
||||
switch ($this->version) {
|
||||
case 2:
|
||||
case 3:
|
||||
$this->version = $this->defaultVersion;
|
||||
if (isset($this->extensions['versions']) && (!$this->preferredVersion || $this->preferredVersion != $this->version)) {
|
||||
$versions = explode(',', $this->extensions['versions']);
|
||||
$supported = [6, 5, 4];
|
||||
if ($this->preferredVersion) {
|
||||
$supported = array_diff($supported, [$this->preferredVersion]);
|
||||
array_unshift($supported, $this->preferredVersion);
|
||||
}
|
||||
foreach ($supported as $ver) {
|
||||
if (in_array($ver, $versions)) {
|
||||
if ($ver === $this->version) {
|
||||
break;
|
||||
default:
|
||||
}
|
||||
$this->version = (int) $ver;
|
||||
$packet = Strings::packSSH2('ss', 'version-select', "$ver");
|
||||
$this->send_sftp_packet(NET_SFTP_EXTENDED, $packet);
|
||||
$response = $this->get_sftp_packet();
|
||||
if ($this->packet_type != NET_SFTP_STATUS) {
|
||||
throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
|
||||
. 'Got packet type: ' . $this->packet_type);
|
||||
}
|
||||
list($status) = Strings::unpackSSH2('N', $response);
|
||||
if ($status != NET_SFTP_STATUS_OK) {
|
||||
$this->logError($response, $status);
|
||||
throw new \UnexpectedValueException('Expected NET_SFTP_STATUS_OK. '
|
||||
. ' Got ' . $status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com',
|
||||
however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's
|
||||
not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for
|
||||
one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that
|
||||
'newline@vandyke.com' would.
|
||||
*/
|
||||
/*
|
||||
if (isset($this->extensions['newline@vandyke.com'])) {
|
||||
$this->extensions['newline'] = $this->extensions['newline@vandyke.com'];
|
||||
unset($this->extensions['newline@vandyke.com']);
|
||||
}
|
||||
*/
|
||||
if ($this->version < 2 || $this->version > 6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->pwd = true;
|
||||
$this->pwd = $this->realpath('.');
|
||||
|
||||
$this->update_stat_cache($this->pwd, []);
|
||||
@ -686,6 +787,10 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function pwd()
|
||||
{
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->pwd;
|
||||
}
|
||||
|
||||
@ -729,11 +834,15 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function realpath($path)
|
||||
{
|
||||
if ($this->precheck() === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->canonicalize_paths) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
if ($this->pwd === false) {
|
||||
if ($this->pwd === true) {
|
||||
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
|
||||
$this->send_sftp_packet(NET_SFTP_REALPATH, Strings::packSSH2('s', $path));
|
||||
|
||||
@ -787,7 +896,7 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function chdir($dir)
|
||||
{
|
||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -941,7 +1050,7 @@ class SFTP extends SSH2
|
||||
*/
|
||||
private function readlist($dir, $raw = true)
|
||||
{
|
||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -984,9 +1093,14 @@ class SFTP extends SSH2
|
||||
case NET_SFTP_NAME:
|
||||
list($count) = Strings::unpackSSH2('N', $response);
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
list($shortname, $longname) = Strings::unpackSSH2('ss', $response);
|
||||
list($shortname) = Strings::unpackSSH2('s', $response);
|
||||
// SFTPv4 "removed the long filename from the names structure-- it can now be
|
||||
// built from information available in the attrs structure."
|
||||
if ($this->version < 4) {
|
||||
list($longname) = Strings::unpackSSH2('s', $response);
|
||||
}
|
||||
$attributes = $this->parseAttributes($response);
|
||||
if (!isset($attributes['type'])) {
|
||||
if (!isset($attributes['type']) && $this->version < 4) {
|
||||
$fileType = $this->parseLongname($longname);
|
||||
if ($fileType) {
|
||||
$attributes['type'] = $fileType;
|
||||
@ -1240,7 +1354,7 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function stat($filename)
|
||||
{
|
||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1297,7 +1411,7 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function lstat($filename)
|
||||
{
|
||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1392,7 +1506,7 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function truncate($filename, $new_size)
|
||||
{
|
||||
$attr = pack('N3', NET_SFTP_ATTR_SIZE, $new_size / 4294967296, $new_size); // 4294967296 == 0x100000000 == 1<<32
|
||||
$attr = Strings::packSSH2('NQ', NET_SFTP_ATTR_SIZE, $new_size);
|
||||
|
||||
return $this->setstat($filename, $attr, false);
|
||||
}
|
||||
@ -1411,7 +1525,7 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function touch($filename, $time = null, $atime = null)
|
||||
{
|
||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1427,9 +1541,16 @@ class SFTP extends SSH2
|
||||
$atime = $time;
|
||||
}
|
||||
|
||||
$flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL;
|
||||
$attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime);
|
||||
$packet = Strings::packSSH2('sN', $filename, $flags) . $attr;
|
||||
$attr = $this->version < 4 ?
|
||||
pack('N3', NET_SFTP_ATTR_ACCESSTIME, $atime, $time) :
|
||||
Strings::packSSH2('NQ2', NET_SFTP_ATTR_ACCESSTIME | NET_SFTP_ATTR_MODIFYTIME, $atime, $time);
|
||||
|
||||
$packet = Strings::packSSH2('s', $filename);
|
||||
$packet.= $this->version >= 5 ?
|
||||
pack('N2', 0, NET_SFTP_OPEN_OPEN_EXISTING) :
|
||||
pack('N', NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL);
|
||||
$packet.= $attr;
|
||||
|
||||
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
|
||||
|
||||
$response = $this->get_sftp_packet();
|
||||
@ -1450,19 +1571,47 @@ class SFTP extends SSH2
|
||||
/**
|
||||
* Changes file or directory owner
|
||||
*
|
||||
* $uid should be an int for SFTPv3 and a string for SFTPv4+. Ideally the string
|
||||
* would be of the form "user@dns_domain" but it does not need to be.
|
||||
* `$sftp->getSupportedVersions()['version']` will return the specific version
|
||||
* that's being used.
|
||||
*
|
||||
* Returns true on success or false on error.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param int $uid
|
||||
* @param int|string $uid
|
||||
* @param bool $recursive
|
||||
* @return bool
|
||||
* @access public
|
||||
*/
|
||||
public function chown($filename, $uid, $recursive = false)
|
||||
{
|
||||
// quoting from <http://www.kernel.org/doc/man-pages/online/pages/man2/chown.2.html>,
|
||||
/*
|
||||
quoting <https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.5>,
|
||||
|
||||
"To avoid a representation that is tied to a particular underlying
|
||||
implementation at the client or server, the use of UTF-8 strings has
|
||||
been chosen. The string should be of the form "user@dns_domain".
|
||||
This will allow for a client and server that do not use the same
|
||||
local representation the ability to translate to a common syntax that
|
||||
can be interpreted by both. In the case where there is no
|
||||
translation available to the client or server, the attribute value
|
||||
must be constructed without the "@"."
|
||||
|
||||
phpseclib _could_ auto append the dns_domain to $uid BUT what if it shouldn't
|
||||
have one? phpseclib would have no way of knowing so rather than guess phpseclib
|
||||
will just use whatever value the user provided
|
||||
*/
|
||||
|
||||
$attr = $this->version < 4 ?
|
||||
// quoting <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);
|
||||
pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1) :
|
||||
// quoting <https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.5>,
|
||||
// "If either the owner or group field is zero length, the field should be
|
||||
// considered absent, and no change should be made to that specific field
|
||||
// during a modification operation"
|
||||
Strings::packSSH2('Nss', NET_SFTP_ATTR_OWNERGROUP, $uid, '');
|
||||
|
||||
return $this->setstat($filename, $attr, $recursive);
|
||||
}
|
||||
@ -1470,17 +1619,24 @@ class SFTP extends SSH2
|
||||
/**
|
||||
* Changes file or directory group
|
||||
*
|
||||
* $gid should be an int for SFTPv3 and a string for SFTPv4+. Ideally the string
|
||||
* would be of the form "user@dns_domain" but it does not need to be.
|
||||
* `$sftp->getSupportedVersions()['version']` will return the specific version
|
||||
* that's being used.
|
||||
*
|
||||
* Returns true on success or false on error.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param int $gid
|
||||
* @param int|string $gid
|
||||
* @param bool $recursive
|
||||
* @return bool
|
||||
* @access public
|
||||
*/
|
||||
public function chgrp($filename, $gid, $recursive = false)
|
||||
{
|
||||
$attr = pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid);
|
||||
$attr = $this->version < 4 ?
|
||||
pack('N3', NET_SFTP_ATTR_UIDGID, $gid, -1) :
|
||||
Strings::packSSH2('Nss', NET_SFTP_ATTR_OWNERGROUP, '', $gid);
|
||||
|
||||
return $this->setstat($filename, $attr, $recursive);
|
||||
}
|
||||
@ -1547,7 +1703,7 @@ class SFTP extends SSH2
|
||||
*/
|
||||
private function setstat($filename, $attr, $recursive)
|
||||
{
|
||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1565,9 +1721,11 @@ class SFTP extends SSH2
|
||||
return $result;
|
||||
}
|
||||
|
||||
// SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to
|
||||
// SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT.
|
||||
$this->send_sftp_packet(NET_SFTP_SETSTAT, Strings::packSSH2('s', $filename) . $attr);
|
||||
$packet = Strings::packSSH2('s', $filename);
|
||||
$packet.= $this->version >= 4 ?
|
||||
pack('a*Ca*', substr($attr, 0, 4), NET_SFTP_TYPE_UNKNOWN, substr($attr, 4)) :
|
||||
$attr;
|
||||
$this->send_sftp_packet(NET_SFTP_SETSTAT, $packet);
|
||||
|
||||
/*
|
||||
"Because some systems must use separate system calls to set various attributes, it is possible that a failure
|
||||
@ -1632,7 +1790,11 @@ class SFTP extends SSH2
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->send_sftp_packet(NET_SFTP_SETSTAT, Strings::packSSH2('s', $temp) . $attr);
|
||||
$packet = Strings::packSSH2('s', $temp);
|
||||
$packet.= $this->version >= 4 ?
|
||||
pack('Ca*', NET_SFTP_TYPE_UNKNOWN, $attr) :
|
||||
$attr;
|
||||
$this->send_sftp_packet(NET_SFTP_SETSTAT, $packet);
|
||||
|
||||
$i++;
|
||||
|
||||
@ -1645,7 +1807,11 @@ class SFTP extends SSH2
|
||||
}
|
||||
}
|
||||
|
||||
$this->send_sftp_packet(NET_SFTP_SETSTAT, Strings::packSSH2('s', $path) . $attr);
|
||||
$packet = Strings::packSSH2('s', $path);
|
||||
$packet.= $this->version >= 4 ?
|
||||
pack('Ca*', NET_SFTP_TYPE_UNKNOWN, $attr) :
|
||||
$atr;
|
||||
$this->send_sftp_packet(NET_SFTP_SETSTAT, $packet);
|
||||
|
||||
$i++;
|
||||
|
||||
@ -1669,7 +1835,7 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function readlink($link)
|
||||
{
|
||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1713,15 +1879,44 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function symlink($target, $link)
|
||||
{
|
||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//$target = $this->realpath($target);
|
||||
$link = $this->realpath($link);
|
||||
|
||||
$packet = Strings::packSSH2('ss', $target, $link);
|
||||
$this->send_sftp_packet(NET_SFTP_SYMLINK, $packet);
|
||||
/* quoting https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-09#section-12.1 :
|
||||
|
||||
Changed the SYMLINK packet to be LINK and give it the ability to
|
||||
create hard links. Also change it's packet number because many
|
||||
implementation implemented SYMLINK with the arguments reversed.
|
||||
Hopefully the new argument names make it clear which way is which.
|
||||
*/
|
||||
if ($this->version == 6) {
|
||||
$type = NET_SFTP_LINK;
|
||||
$packet = Strings::packSSH2('ssC', $link, $target, 1);
|
||||
} else {
|
||||
$type = NET_SFTP_SYMLINK;
|
||||
/* quoting http://bxr.su/OpenBSD/usr.bin/ssh/PROTOCOL#347 :
|
||||
|
||||
3.1. sftp: Reversal of arguments to SSH_FXP_SYMLINK
|
||||
|
||||
When OpenSSH's sftp-server was implemented, the order of the arguments
|
||||
to the SSH_FXP_SYMLINK method was inadvertently reversed. Unfortunately,
|
||||
the reversal was not noticed until the server was widely deployed. Since
|
||||
fixing this to follow the specification would cause incompatibility, the
|
||||
current order was retained. For correct operation, clients should send
|
||||
SSH_FXP_SYMLINK as follows:
|
||||
|
||||
uint32 id
|
||||
string targetpath
|
||||
string linkpath */
|
||||
$packet = substr($this->server_identifier, 0, 15) == 'SSH-2.0-OpenSSH' ?
|
||||
Strings::packSSH2('ss', $target, $link) :
|
||||
Strings::packSSH2('ss', $link, $target);
|
||||
}
|
||||
$this->send_sftp_packet($type, $packet);
|
||||
|
||||
$response = $this->get_sftp_packet();
|
||||
if ($this->packet_type != NET_SFTP_STATUS) {
|
||||
@ -1749,7 +1944,7 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function mkdir($dir, $mode = -1, $recursive = false)
|
||||
{
|
||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1814,7 +2009,7 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function rmdir($dir)
|
||||
{
|
||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1858,7 +2053,8 @@ class SFTP extends SSH2
|
||||
* contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how
|
||||
* large $remote_file will be, as well.
|
||||
*
|
||||
* Setting $mode to self::SOURCE_CALLBACK will use $data as callback function, which gets only one parameter -- number of bytes to return, and returns a string if there is some data or null if there is no more data
|
||||
* Setting $mode to self::SOURCE_CALLBACK will use $data as callback function, which gets only one parameter -- number
|
||||
* of bytes to return, and returns a string if there is some data or null if there is no more data
|
||||
*
|
||||
* If $data is a resource then it'll be used as a resource instead.
|
||||
*
|
||||
@ -1898,7 +2094,7 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $local_start = -1, $progressCallback = null)
|
||||
{
|
||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1907,10 +2103,16 @@ class SFTP extends SSH2
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->remove_from_stat_cache($remote_file);
|
||||
|
||||
if ($this->version >= 5) {
|
||||
$flags = NET_SFTP_OPEN_OPEN_OR_CREATE;
|
||||
} else {
|
||||
$flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE;
|
||||
// according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file."
|
||||
// in practice, it doesn't seem to do that.
|
||||
//$flags|= ($mode & self::RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE;
|
||||
}
|
||||
|
||||
if ($start >= 0) {
|
||||
$offset = $start;
|
||||
@ -1920,12 +2122,19 @@ class SFTP extends SSH2
|
||||
$offset = $size !== false ? $size : 0;
|
||||
} else {
|
||||
$offset = 0;
|
||||
if ($this->version >= 5) {
|
||||
$flags = NET_SFTP_OPEN_CREATE_TRUNCATE;
|
||||
} else {
|
||||
$flags|= NET_SFTP_OPEN_TRUNCATE;
|
||||
}
|
||||
}
|
||||
|
||||
$this->remove_from_stat_cache($remote_file);
|
||||
|
||||
$packet = Strings::packSSH2('sNN', $remote_file, $flags, 0);
|
||||
$packet = Strings::packSSH2('s', $remote_file);
|
||||
$packet.= $this->version >= 5 ?
|
||||
pack('N3', 0, $flags, 0) :
|
||||
pack('N2', $flags, 0);
|
||||
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
|
||||
|
||||
$response = $this->get_sftp_packet();
|
||||
@ -2032,6 +2241,8 @@ class SFTP extends SSH2
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->close_handle($handle);
|
||||
|
||||
if (!$this->read_put_responses($i)) {
|
||||
if ($mode & self::SOURCE_LOCAL_FILE) {
|
||||
fclose($fp);
|
||||
@ -2040,18 +2251,23 @@ class SFTP extends SSH2
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($mode & self::SOURCE_LOCAL_FILE) {
|
||||
if ($this->preserveTime) {
|
||||
$stat = fstat($fp);
|
||||
$this->touch($remote_file, $stat['mtime'], $stat['atime']);
|
||||
}
|
||||
|
||||
if ($mode & SFTP::SOURCE_LOCAL_FILE) {
|
||||
if (isset($fp) && is_resource($fp)) {
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
if ($this->preserveTime) {
|
||||
$stat = stat($data);
|
||||
$attr = $this->version < 4 ?
|
||||
pack('N3', NET_SFTP_ATTR_ACCESSTIME, $stat['atime'], $stat['time']) :
|
||||
Strings::packSSH2('NQ2', NET_SFTP_ATTR_ACCESSTIME | NET_SFTP_ATTR_MODIFYTIME, $stat['atime'], $stat['time']);
|
||||
if (!$this->setstat($remote_file, $attr, false)) {
|
||||
throw new \RuntimeException('Error setting file time');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->close_handle($handle);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2133,7 +2349,7 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function get($remote_file, $local_file = false, $offset = 0, $length = -1, $progressCallback = null)
|
||||
{
|
||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2142,7 +2358,10 @@ class SFTP extends SSH2
|
||||
return false;
|
||||
}
|
||||
|
||||
$packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_READ, 0);
|
||||
$packet = Strings::packSSH2('s', $remote_file);
|
||||
$packet.= $this->version >= 5 ?
|
||||
pack('N3', 0, NET_SFTP_OPEN_OPEN_EXISTING, 0) :
|
||||
pack('N2', NET_SFTP_OPEN_READ, 0);
|
||||
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
|
||||
|
||||
$response = $this->get_sftp_packet();
|
||||
@ -2243,6 +2462,7 @@ class SFTP extends SSH2
|
||||
fclose($fp);
|
||||
}
|
||||
if ($this->channel_close) {
|
||||
$this->partial_init = false;
|
||||
$this->init_sftp_connection();
|
||||
return false;
|
||||
} else {
|
||||
@ -2294,7 +2514,7 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function delete($path, $recursive = true)
|
||||
{
|
||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2415,6 +2635,10 @@ class SFTP extends SSH2
|
||||
public function file_exists($path)
|
||||
{
|
||||
if ($this->use_stat_cache) {
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$path = $this->realpath($path);
|
||||
|
||||
$result = $this->query_stat_cache($path);
|
||||
@ -2485,6 +2709,10 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function is_readable($path)
|
||||
{
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_READ, 0);
|
||||
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
|
||||
|
||||
@ -2509,6 +2737,10 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function is_writable($path)
|
||||
{
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_WRITE, 0);
|
||||
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
|
||||
|
||||
@ -2685,6 +2917,10 @@ class SFTP extends SSH2
|
||||
*/
|
||||
private function get_xstat_cache_prop($path, $prop, $type)
|
||||
{
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->use_stat_cache) {
|
||||
$path = $this->realpath($path);
|
||||
|
||||
@ -2705,7 +2941,9 @@ class SFTP extends SSH2
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames a file or a directory on the SFTP server
|
||||
* Renames a file or a directory on the SFTP server.
|
||||
*
|
||||
* If the file already exists this will return false
|
||||
*
|
||||
* @param string $oldname
|
||||
* @param string $newname
|
||||
@ -2715,7 +2953,7 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function rename($oldname, $newname)
|
||||
{
|
||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2727,6 +2965,18 @@ class SFTP extends SSH2
|
||||
|
||||
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
|
||||
$packet = Strings::packSSH2('ss', $oldname, $newname);
|
||||
if ($this->version >= 5) {
|
||||
/* quoting https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-05#section-6.5 ,
|
||||
|
||||
'flags' is 0 or a combination of:
|
||||
|
||||
SSH_FXP_RENAME_OVERWRITE 0x00000001
|
||||
SSH_FXP_RENAME_ATOMIC 0x00000002
|
||||
SSH_FXP_RENAME_NATIVE 0x00000004
|
||||
|
||||
(none of these are currently supported) */
|
||||
$packet.= "\0\0\0\0";
|
||||
}
|
||||
$this->send_sftp_packet(NET_SFTP_RENAME, $packet);
|
||||
|
||||
$response = $this->get_sftp_packet();
|
||||
@ -2751,6 +3001,27 @@ class SFTP extends SSH2
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Time
|
||||
*
|
||||
* See '7.7. Times' of draft-ietf-secsh-filexfer-13 for more info.
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $flags
|
||||
* @param string $response
|
||||
* @return array
|
||||
* @access private
|
||||
*/
|
||||
private function parseTime($key, $flags, &$response)
|
||||
{
|
||||
$attr = [];
|
||||
list($attr[$key]) = Strings::unpackSSH2('Q', $response);
|
||||
if ($flags & NET_SFTP_ATTR_SUBSECOND_TIMES) {
|
||||
list($attr[key . '-nseconds']) = Strings::unpackSSH2('N', $response);
|
||||
}
|
||||
return $attr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Attributes
|
||||
*
|
||||
@ -2762,10 +3033,12 @@ class SFTP extends SSH2
|
||||
*/
|
||||
protected function parseAttributes(&$response)
|
||||
{
|
||||
$attr = [];
|
||||
if ($this->version >= 4) {
|
||||
list($flags, $attr['type']) = Strings::unpackSSH2('NC', $response);
|
||||
} else {
|
||||
list($flags) = Strings::unpackSSH2('N', $response);
|
||||
}
|
||||
|
||||
// SFTPv4+ have a type field (a byte) that follows the above flag field
|
||||
foreach ($this->attributes as $key => $value) {
|
||||
switch ($flags & $key) {
|
||||
case NET_SFTP_ATTR_SIZE: // 0x00000001
|
||||
@ -2775,9 +3048,7 @@ class SFTP extends SSH2
|
||||
// IEEE 754 binary64 "double precision" on such platforms and
|
||||
// as such can represent integers of at least 2^50 without loss
|
||||
// of precision. Interpreted in filesize, 2^50 bytes = 1024 TiB.
|
||||
list($upper, $size) = Strings::unpackSSH2('NN', $response);
|
||||
$attr['size'] = $upper ? 4294967296 * $upper : 0;
|
||||
$attr['size']+= $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
|
||||
list($attr['size']) = Strings::unpackSSH2('Q', $response);
|
||||
break;
|
||||
case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
|
||||
list($attr['uid'], $attr['gid']) = Strings::unpackSSH2('NN', $response);
|
||||
@ -2785,13 +3056,78 @@ class SFTP extends SSH2
|
||||
case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
|
||||
list($attr['mode']) = Strings::unpackSSH2('N', $response);
|
||||
$fileType = $this->parseMode($attr['mode']);
|
||||
if ($fileType !== false) {
|
||||
if ($this->version < 4 && $fileType !== false) {
|
||||
$attr+= ['type' => $fileType];
|
||||
}
|
||||
break;
|
||||
case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
|
||||
if ($this->version >= 4) {
|
||||
$attr+= $this->parseTime('atime', $flags, $response);
|
||||
break;
|
||||
}
|
||||
list($attr['atime'], $attr['mtime']) = Strings::unpackSSH2('NN', $response);
|
||||
break;
|
||||
case NET_SFTP_ATTR_CREATETIME: // 0x00000010 (SFTPv4+)
|
||||
$attr+= $this->parseTime('createtime', $flags, $response);
|
||||
break;
|
||||
case NET_SFTP_ATTR_MODIFYTIME: // 0x00000020
|
||||
$attr+= $this->parseTime('mtime', $flags, $response);
|
||||
break;
|
||||
case NET_SFTP_ATTR_ACL: // 0x00000040
|
||||
// access control list
|
||||
// see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-04#section-5.7
|
||||
// currently unsupported
|
||||
list($count) = Strings::unpackSSH2('N', $response);
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
list($type, $flag, $mask, $who) = Strings::unpackSSH2('N3s', $result);
|
||||
}
|
||||
break;
|
||||
case NET_SFTP_ATTR_OWNERGROUP: // 0x00000080
|
||||
list($attr['owner'], $attr['$group']) = Strings::unpackSSH2('ss', $response);
|
||||
break;
|
||||
case NET_SFTP_ATTR_SUBSECOND_TIMES: // 0x00000100
|
||||
break;
|
||||
case NET_SFTP_ATTR_BITS: // 0x00000200 (SFTPv5+)
|
||||
// see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-05#section-5.8
|
||||
// currently unsupported
|
||||
// tells if you file is:
|
||||
// readonly, system, hidden, case inensitive, archive, encrypted, compressed, sparse
|
||||
// append only, immutable, sync
|
||||
list($attrib_bits, $attrib_bits_valid) = Strings::unpackSSH2('N2', $response);
|
||||
// if we were actually gonna implement the above it ought to be
|
||||
// $attr['attrib-bits'] and $attr['attrib-bits-valid']
|
||||
// eg. - instead of _
|
||||
break;
|
||||
case NET_SFTP_ATTR_ALLOCATION_SIZE: // 0x00000400 (SFTPv6+)
|
||||
// see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.4
|
||||
// represents the number of bytes that the file consumes on the disk. will
|
||||
// usually be larger than the 'size' field
|
||||
list($attr['allocation-size']) = Strings::unpack('Q', $response);
|
||||
break;
|
||||
case NET_SFTP_ATTR_TEXT_HINT: // 0x00000800
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.10
|
||||
// currently unsupported
|
||||
// tells if file is "known text", "guessed text", "known binary", "guessed binary"
|
||||
list($text_hint) = Strings::unpackSSH2('C', $response);
|
||||
// the above should be $attr['text-hint']
|
||||
break;
|
||||
case NET_SFTP_ATTR_MIME_TYPE: // 0x00001000
|
||||
// see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.11
|
||||
list($attr['mime-type']) = Strings::unpackSSH2('s', $response);
|
||||
break;
|
||||
case NET_SFTP_ATTR_LINK_COUNT: // 0x00002000
|
||||
// see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.12
|
||||
list($attr['link-count']) = Strings::unpackSS2('N', $response);
|
||||
break;
|
||||
case NET_SFTP_ATTR_UNTRANSLATED_NAME:// 0x00004000
|
||||
// see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.13
|
||||
list($attr['untranslated-name']) = Strings::unpackSSH2('s', $response);
|
||||
break;
|
||||
case NET_SFTP_ATTR_CTIME: // 0x00008000
|
||||
// 'ctime' contains the last time the file attributes were changed. The
|
||||
// exact meaning of this field depends on the server.
|
||||
$attr+= $this->parseTime('ctime', $flags, $response);
|
||||
break;
|
||||
case NET_SFTP_ATTR_EXTENDED: // 0x80000000
|
||||
list($count) = Strings::unpackSSH2('N', $response);
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
@ -3116,13 +3452,51 @@ class SFTP extends SSH2
|
||||
*/
|
||||
public function getSupportedVersions()
|
||||
{
|
||||
$temp = ['version' => $this->version];
|
||||
if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->partial_init) {
|
||||
$this->partial_init_sftp_connection();
|
||||
}
|
||||
|
||||
$temp = ['version' => $this->defaultVersion];
|
||||
if (isset($this->extensions['versions'])) {
|
||||
$temp['extensions'] = $this->extensions['versions'];
|
||||
}
|
||||
return $temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get supported SFTP versions
|
||||
*
|
||||
* @return array
|
||||
* @access public
|
||||
*/
|
||||
public function getNegotiatedVersion()
|
||||
{
|
||||
if (!$this->precheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set preferred version
|
||||
*
|
||||
* If you're preferred version isn't supported then the highest supported
|
||||
* version of SFTP will be utilized. Set to null or false or int(0) to
|
||||
* unset the preferred version
|
||||
*
|
||||
* @param int $version
|
||||
* @access public
|
||||
*/
|
||||
public function setPreferredVersion($version)
|
||||
{
|
||||
$this->preferredVersion = $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect
|
||||
*
|
||||
|
@ -216,7 +216,7 @@ class SSH2
|
||||
* @var array|false
|
||||
* @access private
|
||||
*/
|
||||
private $server_identifier = false;
|
||||
protected $server_identifier = false;
|
||||
|
||||
/**
|
||||
* Key Exchange Algorithms
|
||||
|
Loading…
Reference in New Issue
Block a user