mirror of
https://github.com/danog/phpseclib.git
synced 2024-12-13 09:37:37 +01:00
Merge branch 'sftpv455-2.0' into sftpv456-3.0
This commit is contained in:
commit
0dbbeb39ce
@ -70,6 +70,7 @@ abstract class Strings
|
|||||||
* C = byte
|
* C = byte
|
||||||
* b = boolean (true/false)
|
* b = boolean (true/false)
|
||||||
* N = uint32
|
* N = uint32
|
||||||
|
* Q = uint64
|
||||||
* s = string
|
* s = string
|
||||||
* i = mpint
|
* i = mpint
|
||||||
* L = name-list
|
* 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');
|
throw new \LengthException('At least four byte needs to be present for successful N / i / s / L decodes');
|
||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
throw new \InvalidArgumentException('$format contains an invalid character');
|
throw new \InvalidArgumentException('$format contains an invalid character');
|
||||||
}
|
}
|
||||||
@ -114,6 +121,19 @@ abstract class Strings
|
|||||||
list(, $temp) = unpack('N', self::shift($data, 4));
|
list(, $temp) = unpack('N', self::shift($data, 4));
|
||||||
$result[] = $temp;
|
$result[] = $temp;
|
||||||
continue 2;
|
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
|
||||||
|
list(, $upper, $lower) = unpack('NN', self::shift($data, 8));
|
||||||
|
$temp = $upper ? 4294967296 * $lower : 0;
|
||||||
|
$temp+= $lower < 0 ? ($temp & 0x7FFFFFFFF) + 0x80000000 : $temp;
|
||||||
|
// $temp = hexdec(bin2hex(self::shift($data, 8)));
|
||||||
|
$result[] = $temp;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
list(, $length) = unpack('N', self::shift($data, 4));
|
list(, $length) = unpack('N', self::shift($data, 4));
|
||||||
if (strlen($data) < $length) {
|
if (strlen($data) < $length) {
|
||||||
@ -165,6 +185,13 @@ abstract class Strings
|
|||||||
}
|
}
|
||||||
$result.= $element ? "\1" : "\0";
|
$result.= $element ? "\1" : "\0";
|
||||||
break;
|
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':
|
case 'N':
|
||||||
if (is_float($element)) {
|
if (is_float($element)) {
|
||||||
$element = (int) $element;
|
$element = (int) $element;
|
||||||
|
@ -5,9 +5,7 @@
|
|||||||
*
|
*
|
||||||
* PHP version 5
|
* PHP version 5
|
||||||
*
|
*
|
||||||
* Currently only supports SFTPv2 and v3, which, according to wikipedia.org, "is the most widely used version,
|
* Supports SFTPv2/3/4/5/6. Defaults to v3.
|
||||||
* implemented by the popular OpenSSH SFTP server". If you want SFTPv4/5/6 support, provide me with access
|
|
||||||
* to an SFTPv4/5/6 server.
|
|
||||||
*
|
*
|
||||||
* The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
|
* 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;
|
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
|
* Current working directory
|
||||||
*
|
*
|
||||||
@ -297,7 +313,7 @@ class SFTP extends SSH2
|
|||||||
* @var bool
|
* @var bool
|
||||||
* @access private
|
* @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?
|
* Was the last packet due to the channels being closed or not?
|
||||||
@ -309,6 +325,14 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
private $channel_close = false;
|
private $channel_close = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has the SFTP channel been partially negotiated?
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
private $partial_init = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default Constructor.
|
* Default Constructor.
|
||||||
*
|
*
|
||||||
@ -329,15 +353,13 @@ class SFTP extends SSH2
|
|||||||
$this->packet_types = [
|
$this->packet_types = [
|
||||||
1 => 'NET_SFTP_INIT',
|
1 => 'NET_SFTP_INIT',
|
||||||
2 => 'NET_SFTP_VERSION',
|
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',
|
3 => 'NET_SFTP_OPEN',
|
||||||
4 => 'NET_SFTP_CLOSE',
|
4 => 'NET_SFTP_CLOSE',
|
||||||
5 => 'NET_SFTP_READ',
|
5 => 'NET_SFTP_READ',
|
||||||
6 => 'NET_SFTP_WRITE',
|
6 => 'NET_SFTP_WRITE',
|
||||||
7 => 'NET_SFTP_LSTAT',
|
7 => 'NET_SFTP_LSTAT',
|
||||||
9 => 'NET_SFTP_SETSTAT',
|
9 => 'NET_SFTP_SETSTAT',
|
||||||
|
10 => 'NET_SFTP_FSETSTAT',
|
||||||
11 => 'NET_SFTP_OPENDIR',
|
11 => 'NET_SFTP_OPENDIR',
|
||||||
12 => 'NET_SFTP_READDIR',
|
12 => 'NET_SFTP_READDIR',
|
||||||
13 => 'NET_SFTP_REMOVE',
|
13 => 'NET_SFTP_REMOVE',
|
||||||
@ -345,18 +367,13 @@ class SFTP extends SSH2
|
|||||||
15 => 'NET_SFTP_RMDIR',
|
15 => 'NET_SFTP_RMDIR',
|
||||||
16 => 'NET_SFTP_REALPATH',
|
16 => 'NET_SFTP_REALPATH',
|
||||||
17 => 'NET_SFTP_STAT',
|
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',
|
18 => 'NET_SFTP_RENAME',
|
||||||
19 => 'NET_SFTP_READLINK',
|
19 => 'NET_SFTP_READLINK',
|
||||||
20 => 'NET_SFTP_SYMLINK',
|
20 => 'NET_SFTP_SYMLINK',
|
||||||
|
21 => 'NET_SFTP_LINK',
|
||||||
|
|
||||||
101=> 'NET_SFTP_STATUS',
|
101=> 'NET_SFTP_STATUS',
|
||||||
102=> 'NET_SFTP_HANDLE',
|
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',
|
103=> 'NET_SFTP_DATA',
|
||||||
104=> 'NET_SFTP_NAME',
|
104=> 'NET_SFTP_NAME',
|
||||||
105=> 'NET_SFTP_ATTRS',
|
105=> 'NET_SFTP_ATTRS',
|
||||||
@ -402,8 +419,20 @@ class SFTP extends SSH2
|
|||||||
$this->attributes = [
|
$this->attributes = [
|
||||||
0x00000001 => 'NET_SFTP_ATTR_SIZE',
|
0x00000001 => 'NET_SFTP_ATTR_SIZE',
|
||||||
0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
|
0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
|
||||||
|
0x00000080 => 'NET_SFTP_ATTR_OWNERGROUP', // defined in SFTPv4+
|
||||||
0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
|
0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
|
||||||
0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
|
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
|
// 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
|
// 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.
|
// 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',
|
0x00000004 => 'NET_SFTP_OPEN_APPEND',
|
||||||
0x00000008 => 'NET_SFTP_OPEN_CREATE',
|
0x00000008 => 'NET_SFTP_OPEN_CREATE',
|
||||||
0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
|
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
|
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
|
||||||
// see \phpseclib3\Net\SFTP::_parseLongname() for an explanation
|
// see \phpseclib3\Net\SFTP::_parseLongname() for an explanation
|
||||||
@ -441,6 +495,7 @@ class SFTP extends SSH2
|
|||||||
$this->status_codes,
|
$this->status_codes,
|
||||||
$this->attributes,
|
$this->attributes,
|
||||||
$this->open_flags,
|
$this->open_flags,
|
||||||
|
$this->open_flags5,
|
||||||
$this->file_types
|
$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
|
* @throws \UnexpectedValueException on receipt of unexpected packets
|
||||||
* @return bool
|
* @return bool
|
||||||
* @access public
|
* @access public
|
||||||
*/
|
*/
|
||||||
public function login($username, ...$args)
|
private function partial_init_sftp_connection()
|
||||||
{
|
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
$this->window_size_server_to_client[self::CHANNEL] = $this->window_size;
|
$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);
|
. '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)) {
|
while (!empty($response)) {
|
||||||
list($key, $value) = Strings::unpackSSH2('ss', $response);
|
list($key, $value) = Strings::unpackSSH2('ss', $response);
|
||||||
$this->extensions[$key] = $value;
|
$this->extensions[$key] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
$this->partial_init = true;
|
||||||
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->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:
|
A Note on SFTPv4/5/6 support:
|
||||||
@ -593,11 +652,55 @@ 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
|
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.
|
channel and reopen it with a new and updated SSH_FXP_INIT packet.
|
||||||
*/
|
*/
|
||||||
switch ($this->version) {
|
$this->version = $this->defaultVersion;
|
||||||
case 2:
|
if (isset($this->extensions['versions']) && (!$this->preferredVersion || $this->preferredVersion != $this->version)) {
|
||||||
case 3:
|
$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;
|
break;
|
||||||
default:
|
}
|
||||||
|
$this->version = (int) $ver;
|
||||||
|
$packet = Strings::packSSH2('ss', 'version-select', $ver);
|
||||||
|
if (!$this->send_sftp_packet(NET_SFTP_EXTENDED, $packet)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -686,6 +789,10 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function pwd()
|
public function pwd()
|
||||||
{
|
{
|
||||||
|
if (!$this->precheck()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return $this->pwd;
|
return $this->pwd;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -729,6 +836,10 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function realpath($path)
|
public function realpath($path)
|
||||||
{
|
{
|
||||||
|
if (!$this->precheck()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!$this->canonicalize_paths) {
|
if (!$this->canonicalize_paths) {
|
||||||
return $path;
|
return $path;
|
||||||
}
|
}
|
||||||
@ -787,7 +898,7 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function chdir($dir)
|
public function chdir($dir)
|
||||||
{
|
{
|
||||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
if (!$this->precheck()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -941,7 +1052,7 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
private function readlist($dir, $raw = true)
|
private function readlist($dir, $raw = true)
|
||||||
{
|
{
|
||||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
if (!$this->precheck()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -984,9 +1095,14 @@ class SFTP extends SSH2
|
|||||||
case NET_SFTP_NAME:
|
case NET_SFTP_NAME:
|
||||||
list($count) = Strings::unpackSSH2('N', $response);
|
list($count) = Strings::unpackSSH2('N', $response);
|
||||||
for ($i = 0; $i < $count; $i++) {
|
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);
|
$attributes = $this->parseAttributes($response);
|
||||||
if (!isset($attributes['type'])) {
|
if (!isset($attributes['type']) && $this->version < 4) {
|
||||||
$fileType = $this->parseLongname($longname);
|
$fileType = $this->parseLongname($longname);
|
||||||
if ($fileType) {
|
if ($fileType) {
|
||||||
$attributes['type'] = $fileType;
|
$attributes['type'] = $fileType;
|
||||||
@ -1240,7 +1356,7 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function stat($filename)
|
public function stat($filename)
|
||||||
{
|
{
|
||||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
if (!$this->precheck()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1297,7 +1413,7 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function lstat($filename)
|
public function lstat($filename)
|
||||||
{
|
{
|
||||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
if (!$this->precheck()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1392,7 +1508,7 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function truncate($filename, $new_size)
|
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('Q', NET_SFTP_ATTR_SIZE, $new_size);
|
||||||
|
|
||||||
return $this->setstat($filename, $attr, false);
|
return $this->setstat($filename, $attr, false);
|
||||||
}
|
}
|
||||||
@ -1411,7 +1527,7 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function touch($filename, $time = null, $atime = null)
|
public function touch($filename, $time = null, $atime = null)
|
||||||
{
|
{
|
||||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
if (!$this->precheck()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1427,9 +1543,16 @@ class SFTP extends SSH2
|
|||||||
$atime = $time;
|
$atime = $time;
|
||||||
}
|
}
|
||||||
|
|
||||||
$flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL;
|
$attr = $this->version < 4 ?
|
||||||
$attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime);
|
pack('N3', NET_SFTP_ATTR_ACCESSTIME, $atime, $time) :
|
||||||
$packet = Strings::packSSH2('sN', $filename, $flags) . $attr;
|
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);
|
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
|
||||||
|
|
||||||
$response = $this->get_sftp_packet();
|
$response = $this->get_sftp_packet();
|
||||||
@ -1450,19 +1573,47 @@ class SFTP extends SSH2
|
|||||||
/**
|
/**
|
||||||
* Changes file or directory owner
|
* 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.
|
* Returns true on success or false on error.
|
||||||
*
|
*
|
||||||
* @param string $filename
|
* @param string $filename
|
||||||
* @param int $uid
|
* @param int|string $uid
|
||||||
* @param bool $recursive
|
* @param bool $recursive
|
||||||
* @return bool
|
* @return bool
|
||||||
* @access public
|
* @access public
|
||||||
*/
|
*/
|
||||||
public function chown($filename, $uid, $recursive = false)
|
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"
|
// "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"
|
||||||
|
pack('NNa*Na*', NET_SFTP_ATTR_OWNERGROUP, strlen($uid), $uid, 0, '');
|
||||||
|
|
||||||
return $this->setstat($filename, $attr, $recursive);
|
return $this->setstat($filename, $attr, $recursive);
|
||||||
}
|
}
|
||||||
@ -1470,17 +1621,24 @@ class SFTP extends SSH2
|
|||||||
/**
|
/**
|
||||||
* Changes file or directory group
|
* 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.
|
* Returns true on success or false on error.
|
||||||
*
|
*
|
||||||
* @param string $filename
|
* @param string $filename
|
||||||
* @param int $gid
|
* @param int|string $gid
|
||||||
* @param bool $recursive
|
* @param bool $recursive
|
||||||
* @return bool
|
* @return bool
|
||||||
* @access public
|
* @access public
|
||||||
*/
|
*/
|
||||||
public function chgrp($filename, $gid, $recursive = false)
|
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) :
|
||||||
|
pack('NNa*Na*', NET_SFTP_ATTR_OWNERGROUP, 0, '', strlen($gid), $gid);
|
||||||
|
|
||||||
return $this->setstat($filename, $attr, $recursive);
|
return $this->setstat($filename, $attr, $recursive);
|
||||||
}
|
}
|
||||||
@ -1547,7 +1705,7 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
private function setstat($filename, $attr, $recursive)
|
private function setstat($filename, $attr, $recursive)
|
||||||
{
|
{
|
||||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
if (!$this->precheck()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1565,9 +1723,11 @@ class SFTP extends SSH2
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to
|
$packet = Strings:packSSH2('s', $filename);
|
||||||
// SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT.
|
$packet.= $this->version >= 4 ?
|
||||||
$this->send_sftp_packet(NET_SFTP_SETSTAT, Strings::packSSH2('s', $filename) . $attr);
|
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
|
"Because some systems must use separate system calls to set various attributes, it is possible that a failure
|
||||||
@ -1632,7 +1792,11 @@ class SFTP extends SSH2
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} 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++;
|
$i++;
|
||||||
|
|
||||||
@ -1645,7 +1809,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++;
|
$i++;
|
||||||
|
|
||||||
@ -1669,7 +1837,7 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function readlink($link)
|
public function readlink($link)
|
||||||
{
|
{
|
||||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
if (!$this->precheck()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1713,15 +1881,44 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function symlink($target, $link)
|
public function symlink($target, $link)
|
||||||
{
|
{
|
||||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
if (!$this->precheck()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//$target = $this->realpath($target);
|
//$target = $this->realpath($target);
|
||||||
$link = $this->realpath($link);
|
$link = $this->realpath($link);
|
||||||
|
|
||||||
$packet = Strings::packSSH2('ss', $target, $link);
|
/* quoting https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-09#section-12.1 :
|
||||||
$this->send_sftp_packet(NET_SFTP_SYMLINK, $packet);
|
|
||||||
|
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();
|
$response = $this->get_sftp_packet();
|
||||||
if ($this->packet_type != NET_SFTP_STATUS) {
|
if ($this->packet_type != NET_SFTP_STATUS) {
|
||||||
@ -1749,7 +1946,7 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function mkdir($dir, $mode = -1, $recursive = false)
|
public function mkdir($dir, $mode = -1, $recursive = false)
|
||||||
{
|
{
|
||||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
if (!$this->precheck()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1814,7 +2011,7 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function rmdir($dir)
|
public function rmdir($dir)
|
||||||
{
|
{
|
||||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
if (!$this->precheck()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1858,7 +2055,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
|
* 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.
|
* 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.
|
* If $data is a resource then it'll be used as a resource instead.
|
||||||
*
|
*
|
||||||
@ -1898,7 +2096,7 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $local_start = -1, $progressCallback = null)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1907,10 +2105,16 @@ class SFTP extends SSH2
|
|||||||
return false;
|
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;
|
$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."
|
// 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.
|
// in practice, it doesn't seem to do that.
|
||||||
//$flags|= ($mode & self::RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE;
|
//$flags|= ($mode & self::RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE;
|
||||||
|
}
|
||||||
|
|
||||||
if ($start >= 0) {
|
if ($start >= 0) {
|
||||||
$offset = $start;
|
$offset = $start;
|
||||||
@ -1920,12 +2124,19 @@ class SFTP extends SSH2
|
|||||||
$offset = $size !== false ? $size : 0;
|
$offset = $size !== false ? $size : 0;
|
||||||
} else {
|
} else {
|
||||||
$offset = 0;
|
$offset = 0;
|
||||||
|
if ($this->version >= 5) {
|
||||||
|
$flags = NET_SFTP_OPEN_CREATE_TRUNCATE;
|
||||||
|
} else {
|
||||||
$flags|= NET_SFTP_OPEN_TRUNCATE;
|
$flags|= NET_SFTP_OPEN_TRUNCATE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->remove_from_stat_cache($remote_file);
|
$this->remove_from_stat_cache($remote_file);
|
||||||
|
|
||||||
$packet = Strings::packSSH2('sNN', $remote_file, $flags, 0);
|
$packet = Strings::packSSH2('s', $remote_filename);
|
||||||
|
$packet.= $this->version >= 5 ?
|
||||||
|
pack('N3', 0, $flags, 0) :
|
||||||
|
pack('N2', $flags, 0);
|
||||||
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
|
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
|
||||||
|
|
||||||
$response = $this->get_sftp_packet();
|
$response = $this->get_sftp_packet();
|
||||||
@ -2032,6 +2243,8 @@ class SFTP extends SSH2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$result = $this->close_handle($handle);
|
||||||
|
|
||||||
if (!$this->read_put_responses($i)) {
|
if (!$this->read_put_responses($i)) {
|
||||||
if ($mode & self::SOURCE_LOCAL_FILE) {
|
if ($mode & self::SOURCE_LOCAL_FILE) {
|
||||||
fclose($fp);
|
fclose($fp);
|
||||||
@ -2040,18 +2253,23 @@ class SFTP extends SSH2
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($mode & self::SOURCE_LOCAL_FILE) {
|
if ($mode & SFTP::SOURCE_LOCAL_FILE) {
|
||||||
if ($this->preserveTime) {
|
|
||||||
$stat = fstat($fp);
|
|
||||||
$this->touch($remote_file, $stat['mtime'], $stat['atime']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($fp) && is_resource($fp)) {
|
if (isset($fp) && is_resource($fp)) {
|
||||||
fclose($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 +2351,7 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function get($remote_file, $local_file = false, $offset = 0, $length = -1, $progressCallback = null)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2142,7 +2360,10 @@ class SFTP extends SSH2
|
|||||||
return false;
|
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);
|
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
|
||||||
|
|
||||||
$response = $this->get_sftp_packet();
|
$response = $this->get_sftp_packet();
|
||||||
@ -2243,6 +2464,7 @@ class SFTP extends SSH2
|
|||||||
fclose($fp);
|
fclose($fp);
|
||||||
}
|
}
|
||||||
if ($this->channel_close) {
|
if ($this->channel_close) {
|
||||||
|
$this->partial_init = false;
|
||||||
$this->init_sftp_connection();
|
$this->init_sftp_connection();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@ -2294,7 +2516,7 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function delete($path, $recursive = true)
|
public function delete($path, $recursive = true)
|
||||||
{
|
{
|
||||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
if (!$this->precheck()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2415,6 +2637,10 @@ class SFTP extends SSH2
|
|||||||
public function file_exists($path)
|
public function file_exists($path)
|
||||||
{
|
{
|
||||||
if ($this->use_stat_cache) {
|
if ($this->use_stat_cache) {
|
||||||
|
if (!$this->precheck()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$path = $this->realpath($path);
|
$path = $this->realpath($path);
|
||||||
|
|
||||||
$result = $this->query_stat_cache($path);
|
$result = $this->query_stat_cache($path);
|
||||||
@ -2485,6 +2711,10 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function is_readable($path)
|
public function is_readable($path)
|
||||||
{
|
{
|
||||||
|
if (!$this->precheck()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_READ, 0);
|
$packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_READ, 0);
|
||||||
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
|
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
|
||||||
|
|
||||||
@ -2509,6 +2739,10 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function is_writable($path)
|
public function is_writable($path)
|
||||||
{
|
{
|
||||||
|
if (!$this->precheck()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_WRITE, 0);
|
$packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_WRITE, 0);
|
||||||
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
|
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
|
||||||
|
|
||||||
@ -2685,6 +2919,10 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
private function get_xstat_cache_prop($path, $prop, $type)
|
private function get_xstat_cache_prop($path, $prop, $type)
|
||||||
{
|
{
|
||||||
|
if (!$this->precheck()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->use_stat_cache) {
|
if ($this->use_stat_cache) {
|
||||||
$path = $this->realpath($path);
|
$path = $this->realpath($path);
|
||||||
|
|
||||||
@ -2705,7 +2943,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 $oldname
|
||||||
* @param string $newname
|
* @param string $newname
|
||||||
@ -2715,7 +2955,7 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function rename($oldname, $newname)
|
public function rename($oldname, $newname)
|
||||||
{
|
{
|
||||||
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
if (!$this->precheck()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2727,6 +2967,18 @@ class SFTP extends SSH2
|
|||||||
|
|
||||||
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
|
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
|
||||||
$packet = Strings::packSSH2('ss', $oldname, $newname);
|
$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);
|
$this->send_sftp_packet(NET_SFTP_RENAME, $packet);
|
||||||
|
|
||||||
$response = $this->get_sftp_packet();
|
$response = $this->get_sftp_packet();
|
||||||
@ -2751,6 +3003,27 @@ class SFTP extends SSH2
|
|||||||
return true;
|
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
|
* Parse Attributes
|
||||||
*
|
*
|
||||||
@ -2762,10 +3035,12 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
protected function parseAttributes(&$response)
|
protected function parseAttributes(&$response)
|
||||||
{
|
{
|
||||||
$attr = [];
|
if ($this->version >= 4) {
|
||||||
|
list($flags, $attr['type']) = Strings::unpackSSH2('NC', $response);
|
||||||
|
} else {
|
||||||
list($flags) = Strings::unpackSSH2('N', $response);
|
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) {
|
foreach ($this->attributes as $key => $value) {
|
||||||
switch ($flags & $key) {
|
switch ($flags & $key) {
|
||||||
case NET_SFTP_ATTR_SIZE: // 0x00000001
|
case NET_SFTP_ATTR_SIZE: // 0x00000001
|
||||||
@ -2775,9 +3050,7 @@ class SFTP extends SSH2
|
|||||||
// IEEE 754 binary64 "double precision" on such platforms and
|
// IEEE 754 binary64 "double precision" on such platforms and
|
||||||
// as such can represent integers of at least 2^50 without loss
|
// as such can represent integers of at least 2^50 without loss
|
||||||
// of precision. Interpreted in filesize, 2^50 bytes = 1024 TiB.
|
// of precision. Interpreted in filesize, 2^50 bytes = 1024 TiB.
|
||||||
list($upper, $size) = Strings::unpackSSH2('NN', $response);
|
list($attr['size']) = Strings::unpackSSH2('Q', $response);
|
||||||
$attr['size'] = $upper ? 4294967296 * $upper : 0;
|
|
||||||
$attr['size']+= $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
|
|
||||||
break;
|
break;
|
||||||
case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
|
case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
|
||||||
list($attr['uid'], $attr['gid']) = Strings::unpackSSH2('NN', $response);
|
list($attr['uid'], $attr['gid']) = Strings::unpackSSH2('NN', $response);
|
||||||
@ -2785,13 +3058,78 @@ class SFTP extends SSH2
|
|||||||
case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
|
case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
|
||||||
list($attr['mode']) = Strings::unpackSSH2('N', $response);
|
list($attr['mode']) = Strings::unpackSSH2('N', $response);
|
||||||
$fileType = $this->parseMode($attr['mode']);
|
$fileType = $this->parseMode($attr['mode']);
|
||||||
if ($fileType !== false) {
|
if ($version < 4 && $fileType !== false) {
|
||||||
$attr+= ['type' => $fileType];
|
$attr+= ['type' => $fileType];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
|
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);
|
list($attr['atime'], $attr['mtime']) = Strings::unpackSSH2('NN', $response);
|
||||||
break;
|
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
|
case NET_SFTP_ATTR_EXTENDED: // 0x80000000
|
||||||
list($count) = Strings::unpackSSH2('N', $response);
|
list($count) = Strings::unpackSSH2('N', $response);
|
||||||
for ($i = 0; $i < $count; $i++) {
|
for ($i = 0; $i < $count; $i++) {
|
||||||
@ -3116,13 +3454,51 @@ class SFTP extends SSH2
|
|||||||
*/
|
*/
|
||||||
public function getSupportedVersions()
|
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'])) {
|
if (isset($this->extensions['versions'])) {
|
||||||
$temp['extensions'] = $this->extensions['versions'];
|
$temp['extensions'] = $this->extensions['versions'];
|
||||||
}
|
}
|
||||||
return $temp;
|
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
|
* Disconnect
|
||||||
*
|
*
|
||||||
|
Loading…
Reference in New Issue
Block a user