[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/vendor/phpseclib/phpseclib/phpseclib/Net/ -> SFTP.php (source)

   1  <?php
   2  
   3  /**
   4   * Pure-PHP implementation of SFTP.
   5   *
   6   * PHP version 5
   7   *
   8   * Supports SFTPv2/3/4/5/6. Defaults to v3.
   9   *
  10   * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
  11   *
  12   * Here's a short example of how to use this library:
  13   * <code>
  14   * <?php
  15   *    include 'vendor/autoload.php';
  16   *
  17   *    $sftp = new \phpseclib3\Net\SFTP('www.domain.tld');
  18   *    if (!$sftp->login('username', 'password')) {
  19   *        exit('Login Failed');
  20   *    }
  21   *
  22   *    echo $sftp->pwd() . "\r\n";
  23   *    $sftp->put('filename.ext', 'hello, world!');
  24   *    print_r($sftp->nlist());
  25   * ?>
  26   * </code>
  27   *
  28   * @category  Net
  29   * @package   SFTP
  30   * @author    Jim Wigginton <[email protected]>
  31   * @copyright 2009 Jim Wigginton
  32   * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
  33   * @link      http://phpseclib.sourceforge.net
  34   */
  35  
  36  namespace phpseclib3\Net;
  37  
  38  use phpseclib3\Common\Functions\Strings;
  39  use phpseclib3\Exception\FileNotFoundException;
  40  
  41  /**
  42   * Pure-PHP implementations of SFTP.
  43   *
  44   * @package SFTP
  45   * @author  Jim Wigginton <[email protected]>
  46   * @access  public
  47   */
  48  class SFTP extends SSH2
  49  {
  50      /**
  51       * SFTP channel constant
  52       *
  53       * \phpseclib3\Net\SSH2::exec() uses 0 and \phpseclib3\Net\SSH2::read() / \phpseclib3\Net\SSH2::write() use 1.
  54       *
  55       * @see \phpseclib3\Net\SSH2::send_channel_packet()
  56       * @see \phpseclib3\Net\SSH2::get_channel_packet()
  57       * @access private
  58       */
  59      const CHANNEL = 0x100;
  60  
  61      /**
  62       * Reads data from a local file.
  63       *
  64       * @access public
  65       * @see \phpseclib3\Net\SFTP::put()
  66       */
  67      const SOURCE_LOCAL_FILE = 1;
  68      /**
  69       * Reads data from a string.
  70       *
  71       * @access public
  72       * @see \phpseclib3\Net\SFTP::put()
  73       */
  74      // this value isn't really used anymore but i'm keeping it reserved for historical reasons
  75      const SOURCE_STRING = 2;
  76      /**
  77       * Reads data from callback:
  78       * function callback($length) returns string to proceed, null for EOF
  79       *
  80       * @access public
  81       * @see \phpseclib3\Net\SFTP::put()
  82       */
  83      const SOURCE_CALLBACK = 16;
  84      /**
  85       * Resumes an upload
  86       *
  87       * @access public
  88       * @see \phpseclib3\Net\SFTP::put()
  89       */
  90      const RESUME = 4;
  91      /**
  92       * Append a local file to an already existing remote file
  93       *
  94       * @access public
  95       * @see \phpseclib3\Net\SFTP::put()
  96       */
  97      const RESUME_START = 8;
  98  
  99      /**
 100       * Packet Types
 101       *
 102       * @see self::__construct()
 103       * @var array
 104       * @access private
 105       */
 106      private $packet_types = [];
 107  
 108      /**
 109       * Status Codes
 110       *
 111       * @see self::__construct()
 112       * @var array
 113       * @access private
 114       */
 115      private $status_codes = [];
 116  
 117      /** @var array<int, string> */
 118      private $attributes;
 119  
 120      /** @var array<int, string> */
 121      private $open_flags;
 122  
 123      /** @var array<int, string> */
 124      private $open_flags5;
 125  
 126      /** @var array<int, string> */
 127      private $file_types;
 128  
 129      /**
 130       * The Request ID
 131       *
 132       * The request ID exists in the off chance that a packet is sent out-of-order.  Of course, this library doesn't support
 133       * concurrent actions, so it's somewhat academic, here.
 134       *
 135       * @var boolean
 136       * @see self::_send_sftp_packet()
 137       * @access private
 138       */
 139      private $use_request_id = false;
 140  
 141      /**
 142       * The Packet Type
 143       *
 144       * The request ID exists in the off chance that a packet is sent out-of-order.  Of course, this library doesn't support
 145       * concurrent actions, so it's somewhat academic, here.
 146       *
 147       * @var int
 148       * @see self::_get_sftp_packet()
 149       * @access private
 150       */
 151      private $packet_type = -1;
 152  
 153      /**
 154       * Packet Buffer
 155       *
 156       * @var string
 157       * @see self::_get_sftp_packet()
 158       * @access private
 159       */
 160      private $packet_buffer = '';
 161  
 162      /**
 163       * Extensions supported by the server
 164       *
 165       * @var array
 166       * @see self::_initChannel()
 167       * @access private
 168       */
 169      private $extensions = [];
 170  
 171      /**
 172       * Server SFTP version
 173       *
 174       * @var int
 175       * @see self::_initChannel()
 176       * @access private
 177       */
 178      private $version;
 179  
 180      /**
 181       * Default Server SFTP version
 182       *
 183       * @var int
 184       * @see self::_initChannel()
 185       * @access private
 186       */
 187      private $defaultVersion;
 188  
 189      /**
 190       * Preferred SFTP version
 191       *
 192       * @var int
 193       * @see self::_initChannel()
 194       * @access private
 195       */
 196      private $preferredVersion = 3;
 197  
 198      /**
 199       * Current working directory
 200       *
 201       * @var string|bool
 202       * @see self::realpath()
 203       * @see self::chdir()
 204       * @access private
 205       */
 206      private $pwd = false;
 207  
 208      /**
 209       * Packet Type Log
 210       *
 211       * @see self::getLog()
 212       * @var array
 213       * @access private
 214       */
 215      private $packet_type_log = [];
 216  
 217      /**
 218       * Packet Log
 219       *
 220       * @see self::getLog()
 221       * @var array
 222       * @access private
 223       */
 224      private $packet_log = [];
 225  
 226      /**
 227       * Error information
 228       *
 229       * @see self::getSFTPErrors()
 230       * @see self::getLastSFTPError()
 231       * @var array
 232       * @access private
 233       */
 234      private $sftp_errors = [];
 235  
 236      /**
 237       * Stat Cache
 238       *
 239       * Rather than always having to open a directory and close it immediately there after to see if a file is a directory
 240       * we'll cache the results.
 241       *
 242       * @see self::_update_stat_cache()
 243       * @see self::_remove_from_stat_cache()
 244       * @see self::_query_stat_cache()
 245       * @var array
 246       * @access private
 247       */
 248      private $stat_cache = [];
 249  
 250      /**
 251       * Max SFTP Packet Size
 252       *
 253       * @see self::__construct()
 254       * @see self::get()
 255       * @var int
 256       * @access private
 257       */
 258      private $max_sftp_packet;
 259  
 260      /**
 261       * Stat Cache Flag
 262       *
 263       * @see self::disableStatCache()
 264       * @see self::enableStatCache()
 265       * @var bool
 266       * @access private
 267       */
 268      private $use_stat_cache = true;
 269  
 270      /**
 271       * Sort Options
 272       *
 273       * @see self::_comparator()
 274       * @see self::setListOrder()
 275       * @var array
 276       * @access private
 277       */
 278      protected $sortOptions = [];
 279  
 280      /**
 281       * Canonicalization Flag
 282       *
 283       * Determines whether or not paths should be canonicalized before being
 284       * passed on to the remote server.
 285       *
 286       * @see self::enablePathCanonicalization()
 287       * @see self::disablePathCanonicalization()
 288       * @see self::realpath()
 289       * @var bool
 290       * @access private
 291       */
 292      private $canonicalize_paths = true;
 293  
 294      /**
 295       * Request Buffers
 296       *
 297       * @see self::_get_sftp_packet()
 298       * @var array
 299       * @access private
 300       */
 301      private $requestBuffer = [];
 302  
 303      /**
 304       * Preserve timestamps on file downloads / uploads
 305       *
 306       * @see self::get()
 307       * @see self::put()
 308       * @var bool
 309       * @access private
 310       */
 311      private $preserveTime = false;
 312  
 313      /**
 314       * Arbitrary Length Packets Flag
 315       *
 316       * Determines whether or not packets of any length should be allowed,
 317       * in cases where the server chooses the packet length (such as
 318       * directory listings). By default, packets are only allowed to be
 319       * 256 * 1024 bytes (SFTP_MAX_MSG_LENGTH from OpenSSH's sftp-common.h)
 320       *
 321       * @see self::enableArbitraryLengthPackets()
 322       * @see self::_get_sftp_packet()
 323       * @var bool
 324       * @access private
 325       */
 326      private $allow_arbitrary_length_packets = false;
 327  
 328      /**
 329       * Was the last packet due to the channels being closed or not?
 330       *
 331       * @see self::get()
 332       * @see self::get_sftp_packet()
 333       * @var bool
 334       * @access private
 335       */
 336      private $channel_close = false;
 337  
 338      /**
 339       * Has the SFTP channel been partially negotiated?
 340       *
 341       * @var bool
 342       * @access private
 343       */
 344      private $partial_init = false;
 345  
 346      /**
 347       * Default Constructor.
 348       *
 349       * Connects to an SFTP server
 350       *
 351       * @param string $host
 352       * @param int $port
 353       * @param int $timeout
 354       * @access public
 355       */
 356      public function __construct($host, $port = 22, $timeout = 10)
 357      {
 358          parent::__construct($host, $port, $timeout);
 359  
 360          $this->max_sftp_packet = 1 << 15;
 361  
 362          $this->packet_types = [
 363              1  => 'NET_SFTP_INIT',
 364              2  => 'NET_SFTP_VERSION',
 365              3  => 'NET_SFTP_OPEN',
 366              4  => 'NET_SFTP_CLOSE',
 367              5  => 'NET_SFTP_READ',
 368              6  => 'NET_SFTP_WRITE',
 369              7  => 'NET_SFTP_LSTAT',
 370              9  => 'NET_SFTP_SETSTAT',
 371              10 => 'NET_SFTP_FSETSTAT',
 372              11 => 'NET_SFTP_OPENDIR',
 373              12 => 'NET_SFTP_READDIR',
 374              13 => 'NET_SFTP_REMOVE',
 375              14 => 'NET_SFTP_MKDIR',
 376              15 => 'NET_SFTP_RMDIR',
 377              16 => 'NET_SFTP_REALPATH',
 378              17 => 'NET_SFTP_STAT',
 379              18 => 'NET_SFTP_RENAME',
 380              19 => 'NET_SFTP_READLINK',
 381              20 => 'NET_SFTP_SYMLINK',
 382              21 => 'NET_SFTP_LINK',
 383  
 384              101 => 'NET_SFTP_STATUS',
 385              102 => 'NET_SFTP_HANDLE',
 386              103 => 'NET_SFTP_DATA',
 387              104 => 'NET_SFTP_NAME',
 388              105 => 'NET_SFTP_ATTRS',
 389  
 390              200 => 'NET_SFTP_EXTENDED'
 391          ];
 392          $this->status_codes = [
 393              0 => 'NET_SFTP_STATUS_OK',
 394              1 => 'NET_SFTP_STATUS_EOF',
 395              2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
 396              3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
 397              4 => 'NET_SFTP_STATUS_FAILURE',
 398              5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
 399              6 => 'NET_SFTP_STATUS_NO_CONNECTION',
 400              7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
 401              8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
 402              9 => 'NET_SFTP_STATUS_INVALID_HANDLE',
 403              10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
 404              11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
 405              12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
 406              13 => 'NET_SFTP_STATUS_NO_MEDIA',
 407              14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
 408              15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
 409              16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
 410              17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
 411              18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
 412              19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
 413              20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
 414              21 => 'NET_SFTP_STATUS_LINK_LOOP',
 415              22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
 416              23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
 417              24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
 418              25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
 419              26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
 420              27 => 'NET_SFTP_STATUS_DELETE_PENDING',
 421              28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
 422              29 => 'NET_SFTP_STATUS_OWNER_INVALID',
 423              30 => 'NET_SFTP_STATUS_GROUP_INVALID',
 424              31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK'
 425          ];
 426          // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
 427          // the order, in this case, matters quite a lot - see \phpseclib3\Net\SFTP::_parseAttributes() to understand why
 428          $this->attributes = [
 429              0x00000001 => 'NET_SFTP_ATTR_SIZE',
 430              0x00000002 => 'NET_SFTP_ATTR_UIDGID',          // defined in SFTPv3, removed in SFTPv4+
 431              0x00000080 => 'NET_SFTP_ATTR_OWNERGROUP',      // defined in SFTPv4+
 432              0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
 433              0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
 434              0x00000010 => 'NET_SFTP_ATTR_CREATETIME',      // SFTPv4+
 435              0x00000020 => 'NET_SFTP_ATTR_MODIFYTIME',
 436              0x00000040 => 'NET_SFTP_ATTR_ACL',
 437              0x00000100 => 'NET_SFTP_ATTR_SUBSECOND_TIMES',
 438              0x00000200 => 'NET_SFTP_ATTR_BITS',            // SFTPv5+
 439              0x00000400 => 'NET_SFTP_ATTR_ALLOCATION_SIZE', // SFTPv6+
 440              0x00000800 => 'NET_SFTP_ATTR_TEXT_HINT',
 441              0x00001000 => 'NET_SFTP_ATTR_MIME_TYPE',
 442              0x00002000 => 'NET_SFTP_ATTR_LINK_COUNT',
 443              0x00004000 => 'NET_SFTP_ATTR_UNTRANSLATED_NAME',
 444              0x00008000 => 'NET_SFTP_ATTR_CTIME',
 445              // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
 446              // yields inconsistent behavior depending on how php is compiled.  so we left shift -1 (which, in
 447              // two's compliment, consists of all 1 bits) by 31.  on 64-bit systems this'll yield 0xFFFFFFFF80000000.
 448              // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
 449              (-1 << 31) & 0xFFFFFFFF => 'NET_SFTP_ATTR_EXTENDED'
 450          ];
 451          // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
 452          // the flag definitions change somewhat in SFTPv5+.  if SFTPv5+ support is added to this library, maybe name
 453          // the array for that $this->open5_flags and similarly alter the constant names.
 454          $this->open_flags = [
 455              0x00000001 => 'NET_SFTP_OPEN_READ',
 456              0x00000002 => 'NET_SFTP_OPEN_WRITE',
 457              0x00000004 => 'NET_SFTP_OPEN_APPEND',
 458              0x00000008 => 'NET_SFTP_OPEN_CREATE',
 459              0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
 460              0x00000020 => 'NET_SFTP_OPEN_EXCL',
 461              0x00000040 => 'NET_SFTP_OPEN_TEXT' // defined in SFTPv4
 462          ];
 463          // SFTPv5+ changed the flags up:
 464          // https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-8.1.1.3
 465          $this->open_flags5 = [
 466              // when SSH_FXF_ACCESS_DISPOSITION is a 3 bit field that controls how the file is opened
 467              0x00000000 => 'NET_SFTP_OPEN_CREATE_NEW',
 468              0x00000001 => 'NET_SFTP_OPEN_CREATE_TRUNCATE',
 469              0x00000002 => 'NET_SFTP_OPEN_OPEN_EXISTING',
 470              0x00000003 => 'NET_SFTP_OPEN_OPEN_OR_CREATE',
 471              0x00000004 => 'NET_SFTP_OPEN_TRUNCATE_EXISTING',
 472              // the rest of the flags are not supported
 473              0x00000008 => 'NET_SFTP_OPEN_APPEND_DATA', // "the offset field of SS_FXP_WRITE requests is ignored"
 474              0x00000010 => 'NET_SFTP_OPEN_APPEND_DATA_ATOMIC',
 475              0x00000020 => 'NET_SFTP_OPEN_TEXT_MODE',
 476              0x00000040 => 'NET_SFTP_OPEN_BLOCK_READ',
 477              0x00000080 => 'NET_SFTP_OPEN_BLOCK_WRITE',
 478              0x00000100 => 'NET_SFTP_OPEN_BLOCK_DELETE',
 479              0x00000200 => 'NET_SFTP_OPEN_BLOCK_ADVISORY',
 480              0x00000400 => 'NET_SFTP_OPEN_NOFOLLOW',
 481              0x00000800 => 'NET_SFTP_OPEN_DELETE_ON_CLOSE',
 482              0x00001000 => 'NET_SFTP_OPEN_ACCESS_AUDIT_ALARM_INFO',
 483              0x00002000 => 'NET_SFTP_OPEN_ACCESS_BACKUP',
 484              0x00004000 => 'NET_SFTP_OPEN_BACKUP_STREAM',
 485              0x00008000 => 'NET_SFTP_OPEN_OVERRIDE_OWNER',
 486          ];
 487          // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
 488          // see \phpseclib3\Net\SFTP::_parseLongname() for an explanation
 489          $this->file_types = [
 490              1 => 'NET_SFTP_TYPE_REGULAR',
 491              2 => 'NET_SFTP_TYPE_DIRECTORY',
 492              3 => 'NET_SFTP_TYPE_SYMLINK',
 493              4 => 'NET_SFTP_TYPE_SPECIAL',
 494              5 => 'NET_SFTP_TYPE_UNKNOWN',
 495              // the following types were first defined for use in SFTPv5+
 496              // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
 497              6 => 'NET_SFTP_TYPE_SOCKET',
 498              7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
 499              8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
 500              9 => 'NET_SFTP_TYPE_FIFO'
 501          ];
 502          $this->define_array(
 503              $this->packet_types,
 504              $this->status_codes,
 505              $this->attributes,
 506              $this->open_flags,
 507              $this->open_flags5,
 508              $this->file_types
 509          );
 510  
 511          if (!defined('NET_SFTP_QUEUE_SIZE')) {
 512              define('NET_SFTP_QUEUE_SIZE', 32);
 513          }
 514          if (!defined('NET_SFTP_UPLOAD_QUEUE_SIZE')) {
 515              define('NET_SFTP_UPLOAD_QUEUE_SIZE', 1024);
 516          }
 517      }
 518  
 519      /**
 520       * Check a few things before SFTP functions are called
 521       *
 522       * @return bool
 523       * @access public
 524       */
 525      private function precheck()
 526      {
 527          if (!($this->bitmap & SSH2::MASK_LOGIN)) {
 528              return false;
 529          }
 530  
 531          if ($this->pwd === false) {
 532              return $this->init_sftp_connection();
 533          }
 534  
 535          return true;
 536      }
 537  
 538      /**
 539       * Partially initialize an SFTP connection
 540       *
 541       * @throws \UnexpectedValueException on receipt of unexpected packets
 542       * @return bool
 543       * @access public
 544       */
 545      private function partial_init_sftp_connection()
 546      {
 547          $this->window_size_server_to_client[self::CHANNEL] = $this->window_size;
 548  
 549          $packet = Strings::packSSH2(
 550              'CsN3',
 551              NET_SSH2_MSG_CHANNEL_OPEN,
 552              'session',
 553              self::CHANNEL,
 554              $this->window_size,
 555              0x4000
 556          );
 557  
 558          $this->send_binary_packet($packet);
 559  
 560          $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;
 561  
 562          $response = $this->get_channel_packet(self::CHANNEL, true);
 563          if ($response === true && $this->isTimeout()) {
 564              return false;
 565          }
 566  
 567          $packet = Strings::packSSH2(
 568              'CNsbs',
 569              NET_SSH2_MSG_CHANNEL_REQUEST,
 570              $this->server_channels[self::CHANNEL],
 571              'subsystem',
 572              true,
 573              'sftp'
 574          );
 575          $this->send_binary_packet($packet);
 576  
 577          $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
 578  
 579          $response = $this->get_channel_packet(self::CHANNEL, true);
 580          if ($response === false) {
 581              // from PuTTY's psftp.exe
 582              $command = "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" .
 583                         "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" .
 584                         "exec sftp-server";
 585              // we don't do $this->exec($command, false) because exec() operates on a different channel and plus the SSH_MSG_CHANNEL_OPEN that exec() does
 586              // is redundant
 587              $packet = Strings::packSSH2(
 588                  'CNsCs',
 589                  NET_SSH2_MSG_CHANNEL_REQUEST,
 590                  $this->server_channels[self::CHANNEL],
 591                  'exec',
 592                  1,
 593                  $command
 594              );
 595              $this->send_binary_packet($packet);
 596  
 597              $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
 598  
 599              $response = $this->get_channel_packet(self::CHANNEL, true);
 600              if ($response === false) {
 601                  return false;
 602              }
 603          } elseif ($response === true && $this->isTimeout()) {
 604              return false;
 605          }
 606  
 607          $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;
 608          $this->send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3");
 609  
 610          $response = $this->get_sftp_packet();
 611          if ($this->packet_type != NET_SFTP_VERSION) {
 612              throw new \UnexpectedValueException('Expected NET_SFTP_VERSION. '
 613                                                . 'Got packet type: ' . $this->packet_type);
 614          }
 615  
 616          $this->use_request_id = true;
 617  
 618          list($this->defaultVersion) = Strings::unpackSSH2('N', $response);
 619          while (!empty($response)) {
 620              list($key, $value) = Strings::unpackSSH2('ss', $response);
 621              $this->extensions[$key] = $value;
 622          }
 623  
 624          $this->partial_init = true;
 625  
 626          return true;
 627      }
 628  
 629      /**
 630       * (Re)initializes the SFTP channel
 631       *
 632       * @return bool
 633       * @access private
 634       */
 635      private function init_sftp_connection()
 636      {
 637          if (!$this->partial_init && !$this->partial_init_sftp_connection()) {
 638              return false;
 639          }
 640  
 641          /*
 642           A Note on SFTPv4/5/6 support:
 643           <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.1> states the following:
 644  
 645           "If the client wishes to interoperate with servers that support noncontiguous version
 646            numbers it SHOULD send '3'"
 647  
 648           Given that the server only sends its version number after the client has already done so, the above
 649           seems to be suggesting that v3 should be the default version.  This makes sense given that v3 is the
 650           most popular.
 651  
 652           <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.5> states the following;
 653  
 654           "If the server did not send the "versions" extension, or the version-from-list was not included, the
 655            server MAY send a status response describing the failure, but MUST then close the channel without
 656            processing any further requests."
 657  
 658           So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and
 659           a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4?  If it only implements
 660           v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed
 661           in draft-ietf-secsh-filexfer-13 would be quite impossible.  As such, what \phpseclib3\Net\SFTP would do is close the
 662           channel and reopen it with a new and updated SSH_FXP_INIT packet.
 663          */
 664          $this->version = $this->defaultVersion;
 665          if (isset($this->extensions['versions']) && (!$this->preferredVersion || $this->preferredVersion != $this->version)) {
 666              $versions = explode(',', $this->extensions['versions']);
 667              $supported = [6, 5, 4];
 668              if ($this->preferredVersion) {
 669                  $supported = array_diff($supported, [$this->preferredVersion]);
 670                  array_unshift($supported, $this->preferredVersion);
 671              }
 672              foreach ($supported as $ver) {
 673                  if (in_array($ver, $versions)) {
 674                      if ($ver === $this->version) {
 675                          break;
 676                      }
 677                      $this->version = (int) $ver;
 678                      $packet = Strings::packSSH2('ss', 'version-select', "$ver");
 679                      $this->send_sftp_packet(NET_SFTP_EXTENDED, $packet);
 680                      $response = $this->get_sftp_packet();
 681                      if ($this->packet_type != NET_SFTP_STATUS) {
 682                          throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
 683                              . 'Got packet type: ' . $this->packet_type);
 684                      }
 685                      list($status) = Strings::unpackSSH2('N', $response);
 686                      if ($status != NET_SFTP_STATUS_OK) {
 687                          $this->logError($response, $status);
 688                          throw new \UnexpectedValueException('Expected NET_SFTP_STATUS_OK. '
 689                              . ' Got ' . $status);
 690                      }
 691                      break;
 692                  }
 693              }
 694          }
 695  
 696          /*
 697           SFTPv4+ defines a 'newline' extension.  SFTPv3 seems to have unofficial support for it via '[email protected]',
 698           however, I'm not sure what '[email protected]' is supposed to do (the fact that it's unofficial means that it's
 699           not in the official SFTPv3 specs) and '[email protected]' / 'newline' are likely not drop-in substitutes for
 700           one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that
 701           '[email protected]' would.
 702          */
 703          /*
 704          if (isset($this->extensions['[email protected]'])) {
 705              $this->extensions['newline'] = $this->extensions['[email protected]'];
 706              unset($this->extensions['[email protected]']);
 707          }
 708          */
 709          if ($this->version < 2 || $this->version > 6) {
 710              return false;
 711          }
 712  
 713          $this->pwd = true;
 714          $this->pwd = $this->realpath('.');
 715  
 716          $this->update_stat_cache($this->pwd, []);
 717  
 718          return true;
 719      }
 720  
 721      /**
 722       * Disable the stat cache
 723       *
 724       * @access public
 725       */
 726      public function disableStatCache()
 727      {
 728          $this->use_stat_cache = false;
 729      }
 730  
 731      /**
 732       * Enable the stat cache
 733       *
 734       * @access public
 735       */
 736      public function enableStatCache()
 737      {
 738          $this->use_stat_cache = true;
 739      }
 740  
 741      /**
 742       * Clear the stat cache
 743       *
 744       * @access public
 745       */
 746      public function clearStatCache()
 747      {
 748          $this->stat_cache = [];
 749      }
 750  
 751      /**
 752       * Enable path canonicalization
 753       *
 754       * @access public
 755       */
 756      public function enablePathCanonicalization()
 757      {
 758          $this->canonicalize_paths = true;
 759      }
 760  
 761      /**
 762       * Enable path canonicalization
 763       *
 764       * @access public
 765       */
 766      public function disablePathCanonicalization()
 767      {
 768          $this->canonicalize_paths = false;
 769      }
 770  
 771      /**
 772       * Enable arbitrary length packets
 773       *
 774       * @access public
 775       */
 776      public function enableArbitraryLengthPackets()
 777      {
 778          $this->allow_arbitrary_length_packets = true;
 779      }
 780  
 781      /**
 782       * Disable arbitrary length packets
 783       *
 784       * @access public
 785       */
 786      public function disableArbitraryLengthPackets()
 787      {
 788          $this->allow_arbitrary_length_packets = false;
 789      }
 790  
 791      /**
 792       * Returns the current directory name
 793       *
 794       * @return string|bool
 795       * @access public
 796       */
 797      public function pwd()
 798      {
 799          if (!$this->precheck()) {
 800              return false;
 801          }
 802  
 803          return $this->pwd;
 804      }
 805  
 806      /**
 807       * Logs errors
 808       *
 809       * @param string $response
 810       * @param int $status
 811       * @access private
 812       */
 813      private function logError($response, $status = -1)
 814      {
 815          if ($status == -1) {
 816              list($status) = Strings::unpackSSH2('N', $response);
 817          }
 818  
 819          $error = $this->status_codes[$status];
 820  
 821          if ($this->version > 2) {
 822              list($message) = Strings::unpackSSH2('s', $response);
 823              $this->sftp_errors[] = "$error: $message";
 824          } else {
 825              $this->sftp_errors[] = $error;
 826          }
 827      }
 828  
 829      /**
 830       * Canonicalize the Server-Side Path Name
 831       *
 832       * SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it.  Returns
 833       * the absolute (canonicalized) path.
 834       *
 835       * If canonicalize_paths has been disabled using disablePathCanonicalization(), $path is returned as-is.
 836       *
 837       * @see self::chdir()
 838       * @see self::disablePathCanonicalization()
 839       * @param string $path
 840       * @throws \UnexpectedValueException on receipt of unexpected packets
 841       * @return mixed
 842       * @access public
 843       */
 844      public function realpath($path)
 845      {
 846          if ($this->precheck() === false) {
 847              return false;
 848          }
 849  
 850          if (!$this->canonicalize_paths) {
 851              return $path;
 852          }
 853  
 854          if ($this->pwd === true) {
 855              // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
 856              $this->send_sftp_packet(NET_SFTP_REALPATH, Strings::packSSH2('s', $path));
 857  
 858              $response = $this->get_sftp_packet();
 859              switch ($this->packet_type) {
 860                  case NET_SFTP_NAME:
 861                      // although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following
 862                      // should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks
 863                      // at is the first part and that part is defined the same in SFTP versions 3 through 6.
 864                      list(, $filename) = Strings::unpackSSH2('Ns', $response);
 865                      return $filename;
 866                  case NET_SFTP_STATUS:
 867                      $this->logError($response);
 868                      return false;
 869                  default:
 870                      throw new \UnexpectedValueException('Expected NET_SFTP_NAME or NET_SFTP_STATUS. '
 871                                                        . 'Got packet type: ' . $this->packet_type);
 872              }
 873          }
 874  
 875          if (!strlen($path) || $path[0] != '/') {
 876              $path = $this->pwd . '/' . $path;
 877          }
 878  
 879          $path = explode('/', $path);
 880          $new = [];
 881          foreach ($path as $dir) {
 882              if (!strlen($dir)) {
 883                  continue;
 884              }
 885              switch ($dir) {
 886                  case '..':
 887                      array_pop($new);
 888                      // fall-through
 889                  case '.':
 890                      break;
 891                  default:
 892                      $new[] = $dir;
 893              }
 894          }
 895  
 896          return '/' . implode('/', $new);
 897      }
 898  
 899      /**
 900       * Changes the current directory
 901       *
 902       * @param string $dir
 903       * @throws \UnexpectedValueException on receipt of unexpected packets
 904       * @return bool
 905       * @access public
 906       */
 907      public function chdir($dir)
 908      {
 909          if (!$this->precheck()) {
 910              return false;
 911          }
 912  
 913          // assume current dir if $dir is empty
 914          if ($dir === '') {
 915              $dir = './';
 916          // suffix a slash if needed
 917          } elseif ($dir[strlen($dir) - 1] != '/') {
 918              $dir .= '/';
 919          }
 920  
 921          $dir = $this->realpath($dir);
 922  
 923          // confirm that $dir is, in fact, a valid directory
 924          if ($this->use_stat_cache && is_array($this->query_stat_cache($dir))) {
 925              $this->pwd = $dir;
 926              return true;
 927          }
 928  
 929          // we could do a stat on the alleged $dir to see if it's a directory but that doesn't tell us
 930          // the currently logged in user has the appropriate permissions or not. maybe you could see if
 931          // the file's uid / gid match the currently logged in user's uid / gid but how there's no easy
 932          // way to get those with SFTP
 933  
 934          $this->send_sftp_packet(NET_SFTP_OPENDIR, Strings::packSSH2('s', $dir));
 935  
 936          // see \phpseclib3\Net\SFTP::nlist() for a more thorough explanation of the following
 937          $response = $this->get_sftp_packet();
 938          switch ($this->packet_type) {
 939              case NET_SFTP_HANDLE:
 940                  $handle = substr($response, 4);
 941                  break;
 942              case NET_SFTP_STATUS:
 943                  $this->logError($response);
 944                  return false;
 945              default:
 946                  throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS' .
 947                                                      'Got packet type: ' . $this->packet_type);
 948          }
 949  
 950          if (!$this->close_handle($handle)) {
 951              return false;
 952          }
 953  
 954          $this->update_stat_cache($dir, []);
 955  
 956          $this->pwd = $dir;
 957          return true;
 958      }
 959  
 960      /**
 961       * Returns a list of files in the given directory
 962       *
 963       * @param string $dir
 964       * @param bool $recursive
 965       * @return array|false
 966       * @access public
 967       */
 968      public function nlist($dir = '.', $recursive = false)
 969      {
 970          return $this->nlist_helper($dir, $recursive, '');
 971      }
 972  
 973      /**
 974       * Helper method for nlist
 975       *
 976       * @param string $dir
 977       * @param bool $recursive
 978       * @param string $relativeDir
 979       * @return array|false
 980       * @access private
 981       */
 982      private function nlist_helper($dir, $recursive, $relativeDir)
 983      {
 984          $files = $this->readlist($dir, false);
 985  
 986          if (!$recursive || $files === false) {
 987              return $files;
 988          }
 989  
 990          $result = [];
 991          foreach ($files as $value) {
 992              if ($value == '.' || $value == '..') {
 993                  $result[] = $relativeDir . $value;
 994                  continue;
 995              }
 996              if (is_array($this->query_stat_cache($this->realpath($dir . '/' . $value)))) {
 997                  $temp = $this->nlist_helper($dir . '/' . $value, true, $relativeDir . $value . '/');
 998                  $temp = is_array($temp) ? $temp : [];
 999                  $result = array_merge($result, $temp);
1000              } else {
1001                  $result[] = $relativeDir . $value;
1002              }
1003          }
1004  
1005          return $result;
1006      }
1007  
1008      /**
1009       * Returns a detailed list of files in the given directory
1010       *
1011       * @param string $dir
1012       * @param bool $recursive
1013       * @return array|false
1014       * @access public
1015       */
1016      public function rawlist($dir = '.', $recursive = false)
1017      {
1018          $files = $this->readlist($dir, true);
1019          if (!$recursive || $files === false) {
1020              return $files;
1021          }
1022  
1023          static $depth = 0;
1024  
1025          foreach ($files as $key => $value) {
1026              if ($depth != 0 && $key == '..') {
1027                  unset($files[$key]);
1028                  continue;
1029              }
1030              $is_directory = false;
1031              if ($key != '.' && $key != '..') {
1032                  if ($this->use_stat_cache) {
1033                      $is_directory = is_array($this->query_stat_cache($this->realpath($dir . '/' . $key)));
1034                  } else {
1035                      $stat = $this->lstat($dir . '/' . $key);
1036                      $is_directory = $stat && $stat['type'] === NET_SFTP_TYPE_DIRECTORY;
1037                  }
1038              }
1039  
1040              if ($is_directory) {
1041                  $depth++;
1042                  $files[$key] = $this->rawlist($dir . '/' . $key, true);
1043                  $depth--;
1044              } else {
1045                  $files[$key] = (object) $value;
1046              }
1047          }
1048  
1049          return $files;
1050      }
1051  
1052      /**
1053       * Reads a list, be it detailed or not, of files in the given directory
1054       *
1055       * @param string $dir
1056       * @param bool $raw
1057       * @return array|false
1058       * @throws \UnexpectedValueException on receipt of unexpected packets
1059       * @access private
1060       */
1061      private function readlist($dir, $raw = true)
1062      {
1063          if (!$this->precheck()) {
1064              return false;
1065          }
1066  
1067          $dir = $this->realpath($dir . '/');
1068          if ($dir === false) {
1069              return false;
1070          }
1071  
1072          // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2
1073          $this->send_sftp_packet(NET_SFTP_OPENDIR, Strings::packSSH2('s', $dir));
1074  
1075          $response = $this->get_sftp_packet();
1076          switch ($this->packet_type) {
1077              case NET_SFTP_HANDLE:
1078                  // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2
1079                  // since 'handle' is the last field in the SSH_FXP_HANDLE packet, we'll just remove the first four bytes that
1080                  // represent the length of the string and leave it at that
1081                  $handle = substr($response, 4);
1082                  break;
1083              case NET_SFTP_STATUS:
1084                  // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
1085                  $this->logError($response);
1086                  return false;
1087              default:
1088                  throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. '
1089                                                    . 'Got packet type: ' . $this->packet_type);
1090          }
1091  
1092          $this->update_stat_cache($dir, []);
1093  
1094          $contents = [];
1095          while (true) {
1096              // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2
1097              // why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many
1098              // SSH_MSG_CHANNEL_DATA messages is not known to me.
1099              $this->send_sftp_packet(NET_SFTP_READDIR, Strings::packSSH2('s', $handle));
1100  
1101              $response = $this->get_sftp_packet();
1102              switch ($this->packet_type) {
1103                  case NET_SFTP_NAME:
1104                      list($count) = Strings::unpackSSH2('N', $response);
1105                      for ($i = 0; $i < $count; $i++) {
1106                          list($shortname) = Strings::unpackSSH2('s', $response);
1107                          // SFTPv4 "removed the long filename from the names structure-- it can now be
1108                          //         built from information available in the attrs structure."
1109                          if ($this->version < 4) {
1110                              list($longname) = Strings::unpackSSH2('s', $response);
1111                          }
1112                          $attributes = $this->parseAttributes($response);
1113                          if (!isset($attributes['type']) && $this->version < 4) {
1114                              $fileType = $this->parseLongname($longname);
1115                              if ($fileType) {
1116                                  $attributes['type'] = $fileType;
1117                              }
1118                          }
1119                          $contents[$shortname] = $attributes + ['filename' => $shortname];
1120  
1121                          if (isset($attributes['type']) && $attributes['type'] == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) {
1122                              $this->update_stat_cache($dir . '/' . $shortname, []);
1123                          } else {
1124                              if ($shortname == '..') {
1125                                  $temp = $this->realpath($dir . '/..') . '/.';
1126                              } else {
1127                                  $temp = $dir . '/' . $shortname;
1128                              }
1129                              $this->update_stat_cache($temp, (object) ['lstat' => $attributes]);
1130                          }
1131                          // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the
1132                          // final SSH_FXP_STATUS packet should tell us that, already.
1133                      }
1134                      break;
1135                  case NET_SFTP_STATUS:
1136                      list($status) = Strings::unpackSSH2('N', $response);
1137                      if ($status != NET_SFTP_STATUS_EOF) {
1138                          $this->logError($response, $status);
1139                          return false;
1140                      }
1141                      break 2;
1142                  default:
1143                      throw new \UnexpectedValueException('Expected NET_SFTP_NAME or NET_SFTP_STATUS. '
1144                                                        . 'Got packet type: ' . $this->packet_type);
1145              }
1146          }
1147  
1148          if (!$this->close_handle($handle)) {
1149              return false;
1150          }
1151  
1152          if (count($this->sortOptions)) {
1153              uasort($contents, [&$this, 'comparator']);
1154          }
1155  
1156          return $raw ? $contents : array_map('strval', array_keys($contents));
1157      }
1158  
1159      /**
1160       * Compares two rawlist entries using parameters set by setListOrder()
1161       *
1162       * Intended for use with uasort()
1163       *
1164       * @param array $a
1165       * @param array $b
1166       * @return int
1167       * @access private
1168       */
1169      private function comparator($a, $b)
1170      {
1171          switch (true) {
1172              case $a['filename'] === '.' || $b['filename'] === '.':
1173                  if ($a['filename'] === $b['filename']) {
1174                      return 0;
1175                  }
1176                  return $a['filename'] === '.' ? -1 : 1;
1177              case $a['filename'] === '..' || $b['filename'] === '..':
1178                  if ($a['filename'] === $b['filename']) {
1179                      return 0;
1180                  }
1181                  return $a['filename'] === '..' ? -1 : 1;
1182              case isset($a['type']) && $a['type'] === NET_SFTP_TYPE_DIRECTORY:
1183                  if (!isset($b['type'])) {
1184                      return 1;
1185                  }
1186                  if ($b['type'] !== $a['type']) {
1187                      return -1;
1188                  }
1189                  break;
1190              case isset($b['type']) && $b['type'] === NET_SFTP_TYPE_DIRECTORY:
1191                  return 1;
1192          }
1193          foreach ($this->sortOptions as $sort => $order) {
1194              if (!isset($a[$sort]) || !isset($b[$sort])) {
1195                  if (isset($a[$sort])) {
1196                      return -1;
1197                  }
1198                  if (isset($b[$sort])) {
1199                      return 1;
1200                  }
1201                  return 0;
1202              }
1203              switch ($sort) {
1204                  case 'filename':
1205                      $result = strcasecmp($a['filename'], $b['filename']);
1206                      if ($result) {
1207                          return $order === SORT_DESC ? -$result : $result;
1208                      }
1209                      break;
1210                  case 'mode':
1211                      $a[$sort] &= 07777;
1212                      $b[$sort] &= 07777;
1213                      // fall-through
1214                  default:
1215                      if ($a[$sort] === $b[$sort]) {
1216                          break;
1217                      }
1218                      return $order === SORT_ASC ? $a[$sort] - $b[$sort] : $b[$sort] - $a[$sort];
1219              }
1220          }
1221      }
1222  
1223      /**
1224       * Defines how nlist() and rawlist() will be sorted - if at all.
1225       *
1226       * If sorting is enabled directories and files will be sorted independently with
1227       * directories appearing before files in the resultant array that is returned.
1228       *
1229       * Any parameter returned by stat is a valid sort parameter for this function.
1230       * Filename comparisons are case insensitive.
1231       *
1232       * Examples:
1233       *
1234       * $sftp->setListOrder('filename', SORT_ASC);
1235       * $sftp->setListOrder('size', SORT_DESC, 'filename', SORT_ASC);
1236       * $sftp->setListOrder(true);
1237       *    Separates directories from files but doesn't do any sorting beyond that
1238       * $sftp->setListOrder();
1239       *    Don't do any sort of sorting
1240       *
1241       * @param string ...$args
1242       * @access public
1243       */
1244      public function setListOrder(...$args)
1245      {
1246          $this->sortOptions = [];
1247          if (empty($args)) {
1248              return;
1249          }
1250          $len = count($args) & 0x7FFFFFFE;
1251          for ($i = 0; $i < $len; $i += 2) {
1252              $this->sortOptions[$args[$i]] = $args[$i + 1];
1253          }
1254          if (!count($this->sortOptions)) {
1255              $this->sortOptions = ['bogus' => true];
1256          }
1257      }
1258  
1259      /**
1260       * Save files / directories to cache
1261       *
1262       * @param string $path
1263       * @param mixed $value
1264       * @access private
1265       */
1266      private function update_stat_cache($path, $value)
1267      {
1268          if ($this->use_stat_cache === false) {
1269              return;
1270          }
1271  
1272          // preg_replace('#^/|/(?=/)|/$#', '', $dir) == str_replace('//', '/', trim($path, '/'))
1273          $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
1274  
1275          $temp = &$this->stat_cache;
1276          $max = count($dirs) - 1;
1277          foreach ($dirs as $i => $dir) {
1278              // if $temp is an object that means one of two things.
1279              //  1. a file was deleted and changed to a directory behind phpseclib's back
1280              //  2. it's a symlink. when lstat is done it's unclear what it's a symlink to
1281              if (is_object($temp)) {
1282                  $temp = [];
1283              }
1284              if (!isset($temp[$dir])) {
1285                  $temp[$dir] = [];
1286              }
1287              if ($i === $max) {
1288                  if (is_object($temp[$dir]) && is_object($value)) {
1289                      if (!isset($value->stat) && isset($temp[$dir]->stat)) {
1290                          $value->stat = $temp[$dir]->stat;
1291                      }
1292                      if (!isset($value->lstat) && isset($temp[$dir]->lstat)) {
1293                          $value->lstat = $temp[$dir]->lstat;
1294                      }
1295                  }
1296                  $temp[$dir] = $value;
1297                  break;
1298              }
1299              $temp = &$temp[$dir];
1300          }
1301      }
1302  
1303      /**
1304       * Remove files / directories from cache
1305       *
1306       * @param string $path
1307       * @return bool
1308       * @access private
1309       */
1310      private function remove_from_stat_cache($path)
1311      {
1312          $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
1313  
1314          $temp = &$this->stat_cache;
1315          $max = count($dirs) - 1;
1316          foreach ($dirs as $i => $dir) {
1317              if (!is_array($temp)) {
1318                  return false;
1319              }
1320              if ($i === $max) {
1321                  unset($temp[$dir]);
1322                  return true;
1323              }
1324              if (!isset($temp[$dir])) {
1325                  return false;
1326              }
1327              $temp = &$temp[$dir];
1328          }
1329      }
1330  
1331      /**
1332       * Checks cache for path
1333       *
1334       * Mainly used by file_exists
1335       *
1336       * @param string $path
1337       * @return mixed
1338       * @access private
1339       */
1340      private function query_stat_cache($path)
1341      {
1342          $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
1343  
1344          $temp = &$this->stat_cache;
1345          foreach ($dirs as $dir) {
1346              if (!is_array($temp)) {
1347                  return null;
1348              }
1349              if (!isset($temp[$dir])) {
1350                  return null;
1351              }
1352              $temp = &$temp[$dir];
1353          }
1354          return $temp;
1355      }
1356  
1357      /**
1358       * Returns general information about a file.
1359       *
1360       * Returns an array on success and false otherwise.
1361       *
1362       * @param string $filename
1363       * @return array|false
1364       * @access public
1365       */
1366      public function stat($filename)
1367      {
1368          if (!$this->precheck()) {
1369              return false;
1370          }
1371  
1372          $filename = $this->realpath($filename);
1373          if ($filename === false) {
1374              return false;
1375          }
1376  
1377          if ($this->use_stat_cache) {
1378              $result = $this->query_stat_cache($filename);
1379              if (is_array($result) && isset($result['.']) && isset($result['.']->stat)) {
1380                  return $result['.']->stat;
1381              }
1382              if (is_object($result) && isset($result->stat)) {
1383                  return $result->stat;
1384              }
1385          }
1386  
1387          $stat = $this->stat_helper($filename, NET_SFTP_STAT);
1388          if ($stat === false) {
1389              $this->remove_from_stat_cache($filename);
1390              return false;
1391          }
1392          if (isset($stat['type'])) {
1393              if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1394                  $filename .= '/.';
1395              }
1396              $this->update_stat_cache($filename, (object) ['stat' => $stat]);
1397              return $stat;
1398          }
1399  
1400          $pwd = $this->pwd;
1401          $stat['type'] = $this->chdir($filename) ?
1402              NET_SFTP_TYPE_DIRECTORY :
1403              NET_SFTP_TYPE_REGULAR;
1404          $this->pwd = $pwd;
1405  
1406          if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1407              $filename .= '/.';
1408          }
1409          $this->update_stat_cache($filename, (object) ['stat' => $stat]);
1410  
1411          return $stat;
1412      }
1413  
1414      /**
1415       * Returns general information about a file or symbolic link.
1416       *
1417       * Returns an array on success and false otherwise.
1418       *
1419       * @param string $filename
1420       * @return array|false
1421       * @access public
1422       */
1423      public function lstat($filename)
1424      {
1425          if (!$this->precheck()) {
1426              return false;
1427          }
1428  
1429          $filename = $this->realpath($filename);
1430          if ($filename === false) {
1431              return false;
1432          }
1433  
1434          if ($this->use_stat_cache) {
1435              $result = $this->query_stat_cache($filename);
1436              if (is_array($result) && isset($result['.']) && isset($result['.']->lstat)) {
1437                  return $result['.']->lstat;
1438              }
1439              if (is_object($result) && isset($result->lstat)) {
1440                  return $result->lstat;
1441              }
1442          }
1443  
1444          $lstat = $this->stat_helper($filename, NET_SFTP_LSTAT);
1445          if ($lstat === false) {
1446              $this->remove_from_stat_cache($filename);
1447              return false;
1448          }
1449          if (isset($lstat['type'])) {
1450              if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1451                  $filename .= '/.';
1452              }
1453              $this->update_stat_cache($filename, (object) ['lstat' => $lstat]);
1454              return $lstat;
1455          }
1456  
1457          $stat = $this->stat_helper($filename, NET_SFTP_STAT);
1458  
1459          if ($lstat != $stat) {
1460              $lstat = array_merge($lstat, ['type' => NET_SFTP_TYPE_SYMLINK]);
1461              $this->update_stat_cache($filename, (object) ['lstat' => $lstat]);
1462              return $stat;
1463          }
1464  
1465          $pwd = $this->pwd;
1466          $lstat['type'] = $this->chdir($filename) ?
1467              NET_SFTP_TYPE_DIRECTORY :
1468              NET_SFTP_TYPE_REGULAR;
1469          $this->pwd = $pwd;
1470  
1471          if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1472              $filename .= '/.';
1473          }
1474          $this->update_stat_cache($filename, (object) ['lstat' => $lstat]);
1475  
1476          return $lstat;
1477      }
1478  
1479      /**
1480       * Returns general information about a file or symbolic link
1481       *
1482       * Determines information without calling \phpseclib3\Net\SFTP::realpath().
1483       * The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT.
1484       *
1485       * @param string $filename
1486       * @param int $type
1487       * @throws \UnexpectedValueException on receipt of unexpected packets
1488       * @return array|false
1489       * @access private
1490       */
1491      private function stat_helper($filename, $type)
1492      {
1493          // SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
1494          $packet = Strings::packSSH2('s', $filename);
1495          $this->send_sftp_packet($type, $packet);
1496  
1497          $response = $this->get_sftp_packet();
1498          switch ($this->packet_type) {
1499              case NET_SFTP_ATTRS:
1500                  return $this->parseAttributes($response);
1501              case NET_SFTP_STATUS:
1502                  $this->logError($response);
1503                  return false;
1504          }
1505  
1506          throw new \UnexpectedValueException('Expected NET_SFTP_ATTRS or NET_SFTP_STATUS. '
1507                                            . 'Got packet type: ' . $this->packet_type);
1508      }
1509  
1510      /**
1511       * Truncates a file to a given length
1512       *
1513       * @param string $filename
1514       * @param int $new_size
1515       * @return bool
1516       * @access public
1517       */
1518      public function truncate($filename, $new_size)
1519      {
1520          $attr = Strings::packSSH2('NQ', NET_SFTP_ATTR_SIZE, $new_size);
1521  
1522          return $this->setstat($filename, $attr, false);
1523      }
1524  
1525      /**
1526       * Sets access and modification time of file.
1527       *
1528       * If the file does not exist, it will be created.
1529       *
1530       * @param string $filename
1531       * @param int $time
1532       * @param int $atime
1533       * @throws \UnexpectedValueException on receipt of unexpected packets
1534       * @return bool
1535       * @access public
1536       */
1537      public function touch($filename, $time = null, $atime = null)
1538      {
1539          if (!$this->precheck()) {
1540              return false;
1541          }
1542  
1543          $filename = $this->realpath($filename);
1544          if ($filename === false) {
1545              return false;
1546          }
1547  
1548          if (!isset($time)) {
1549              $time = time();
1550          }
1551          if (!isset($atime)) {
1552              $atime = $time;
1553          }
1554  
1555          $attr = $this->version < 4 ?
1556              pack('N3', NET_SFTP_ATTR_ACCESSTIME, $atime, $time) :
1557              Strings::packSSH2('NQ2', NET_SFTP_ATTR_ACCESSTIME | NET_SFTP_ATTR_MODIFYTIME, $atime, $time);
1558  
1559          $packet = Strings::packSSH2('s', $filename);
1560          $packet .= $this->version >= 5 ?
1561              pack('N2', 0, NET_SFTP_OPEN_OPEN_EXISTING) :
1562              pack('N', NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL);
1563          $packet .= $attr;
1564  
1565          $this->send_sftp_packet(NET_SFTP_OPEN, $packet);
1566  
1567          $response = $this->get_sftp_packet();
1568          switch ($this->packet_type) {
1569              case NET_SFTP_HANDLE:
1570                  return $this->close_handle(substr($response, 4));
1571              case NET_SFTP_STATUS:
1572                  $this->logError($response);
1573                  break;
1574              default:
1575                  throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. '
1576                                                    . 'Got packet type: ' . $this->packet_type);
1577          }
1578  
1579          return $this->setstat($filename, $attr, false);
1580      }
1581  
1582      /**
1583       * Changes file or directory owner
1584       *
1585       * $uid should be an int for SFTPv3 and a string for SFTPv4+. Ideally the string
1586       * would be of the form "user@dns_domain" but it does not need to be.
1587       * `$sftp->getSupportedVersions()['version']` will return the specific version
1588       * that's being used.
1589       *
1590       * Returns true on success or false on error.
1591       *
1592       * @param string $filename
1593       * @param int|string $uid
1594       * @param bool $recursive
1595       * @return bool
1596       * @access public
1597       */
1598      public function chown($filename, $uid, $recursive = false)
1599      {
1600          /*
1601           quoting <https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.5>,
1602  
1603           "To avoid a representation that is tied to a particular underlying
1604            implementation at the client or server, the use of UTF-8 strings has
1605            been chosen.  The string should be of the form "user@dns_domain".
1606            This will allow for a client and server that do not use the same
1607            local representation the ability to translate to a common syntax that
1608            can be interpreted by both.  In the case where there is no
1609            translation available to the client or server, the attribute value
1610            must be constructed without the "@"."
1611  
1612           phpseclib _could_ auto append the dns_domain to $uid BUT what if it shouldn't
1613           have one? phpseclib would have no way of knowing so rather than guess phpseclib
1614           will just use whatever value the user provided
1615         */
1616  
1617          $attr = $this->version < 4 ?
1618              // quoting <http://www.kernel.org/doc/man-pages/online/pages/man2/chown.2.html>,
1619              // "if the owner or group is specified as -1, then that ID is not changed"
1620              pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1) :
1621              // quoting <https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.5>,
1622              // "If either the owner or group field is zero length, the field should be
1623              //  considered absent, and no change should be made to that specific field
1624              //  during a modification operation"
1625              Strings::packSSH2('Nss', NET_SFTP_ATTR_OWNERGROUP, $uid, '');
1626  
1627          return $this->setstat($filename, $attr, $recursive);
1628      }
1629  
1630      /**
1631       * Changes file or directory group
1632       *
1633       * $gid should be an int for SFTPv3 and a string for SFTPv4+. Ideally the string
1634       * would be of the form "user@dns_domain" but it does not need to be.
1635       * `$sftp->getSupportedVersions()['version']` will return the specific version
1636       * that's being used.
1637       *
1638       * Returns true on success or false on error.
1639       *
1640       * @param string $filename
1641       * @param int|string $gid
1642       * @param bool $recursive
1643       * @return bool
1644       * @access public
1645       */
1646      public function chgrp($filename, $gid, $recursive = false)
1647      {
1648          $attr = $this->version < 4 ?
1649              pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid) :
1650              Strings::packSSH2('Nss', NET_SFTP_ATTR_OWNERGROUP, '', $gid);
1651  
1652          return $this->setstat($filename, $attr, $recursive);
1653      }
1654  
1655      /**
1656       * Set permissions on a file.
1657       *
1658       * Returns the new file permissions on success or false on error.
1659       * If $recursive is true than this just returns true or false.
1660       *
1661       * @param int $mode
1662       * @param string $filename
1663       * @param bool $recursive
1664       * @throws \UnexpectedValueException on receipt of unexpected packets
1665       * @return mixed
1666       * @access public
1667       */
1668      public function chmod($mode, $filename, $recursive = false)
1669      {
1670          if (is_string($mode) && is_int($filename)) {
1671              $temp = $mode;
1672              $mode = $filename;
1673              $filename = $temp;
1674          }
1675  
1676          $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
1677          if (!$this->setstat($filename, $attr, $recursive)) {
1678              return false;
1679          }
1680          if ($recursive) {
1681              return true;
1682          }
1683  
1684          $filename = $this->realpath($filename);
1685          // rather than return what the permissions *should* be, we'll return what they actually are.  this will also
1686          // tell us if the file actually exists.
1687          // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
1688          $packet = pack('Na*', strlen($filename), $filename);
1689          $this->send_sftp_packet(NET_SFTP_STAT, $packet);
1690  
1691          $response = $this->get_sftp_packet();
1692          switch ($this->packet_type) {
1693              case NET_SFTP_ATTRS:
1694                  $attrs = $this->parseAttributes($response);
1695                  return $attrs['mode'];
1696              case NET_SFTP_STATUS:
1697                  $this->logError($response);
1698                  return false;
1699          }
1700  
1701          throw new \UnexpectedValueException('Expected NET_SFTP_ATTRS or NET_SFTP_STATUS. '
1702                                            . 'Got packet type: ' . $this->packet_type);
1703      }
1704  
1705      /**
1706       * Sets information about a file
1707       *
1708       * @param string $filename
1709       * @param string $attr
1710       * @param bool $recursive
1711       * @throws \UnexpectedValueException on receipt of unexpected packets
1712       * @return bool
1713       * @access private
1714       */
1715      private function setstat($filename, $attr, $recursive)
1716      {
1717          if (!$this->precheck()) {
1718              return false;
1719          }
1720  
1721          $filename = $this->realpath($filename);
1722          if ($filename === false) {
1723              return false;
1724          }
1725  
1726          $this->remove_from_stat_cache($filename);
1727  
1728          if ($recursive) {
1729              $i = 0;
1730              $result = $this->setstat_recursive($filename, $attr, $i);
1731              $this->read_put_responses($i);
1732              return $result;
1733          }
1734  
1735          $packet = Strings::packSSH2('s', $filename);
1736          $packet .= $this->version >= 4 ?
1737              pack('a*Ca*', substr($attr, 0, 4), NET_SFTP_TYPE_UNKNOWN, substr($attr, 4)) :
1738              $attr;
1739          $this->send_sftp_packet(NET_SFTP_SETSTAT, $packet);
1740  
1741          /*
1742           "Because some systems must use separate system calls to set various attributes, it is possible that a failure
1743            response will be returned, but yet some of the attributes may be have been successfully modified.  If possible,
1744            servers SHOULD avoid this situation; however, clients MUST be aware that this is possible."
1745  
1746            -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6
1747          */
1748          $response = $this->get_sftp_packet();
1749          if ($this->packet_type != NET_SFTP_STATUS) {
1750              throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
1751                                                . 'Got packet type: ' . $this->packet_type);
1752          }
1753  
1754          list($status) = Strings::unpackSSH2('N', $response);
1755          if ($status != NET_SFTP_STATUS_OK) {
1756              $this->logError($response, $status);
1757              return false;
1758          }
1759  
1760          return true;
1761      }
1762  
1763      /**
1764       * Recursively sets information on directories on the SFTP server
1765       *
1766       * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
1767       *
1768       * @param string $path
1769       * @param string $attr
1770       * @param int $i
1771       * @return bool
1772       * @access private
1773       */
1774      private function setstat_recursive($path, $attr, &$i)
1775      {
1776          if (!$this->read_put_responses($i)) {
1777              return false;
1778          }
1779          $i = 0;
1780          $entries = $this->readlist($path, true);
1781  
1782          if ($entries === false) {
1783              return $this->setstat($path, $attr, false);
1784          }
1785  
1786          // normally $entries would have at least . and .. but it might not if the directories
1787          // permissions didn't allow reading
1788          if (empty($entries)) {
1789              return false;
1790          }
1791  
1792          unset($entries['.'], $entries['..']);
1793          foreach ($entries as $filename => $props) {
1794              if (!isset($props['type'])) {
1795                  return false;
1796              }
1797  
1798              $temp = $path . '/' . $filename;
1799              if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
1800                  if (!$this->setstat_recursive($temp, $attr, $i)) {
1801                      return false;
1802                  }
1803              } else {
1804                  $packet = Strings::packSSH2('s', $temp);
1805                  $packet .= $this->version >= 4 ?
1806                      pack('Ca*', NET_SFTP_TYPE_UNKNOWN, $attr) :
1807                      $attr;
1808                  $this->send_sftp_packet(NET_SFTP_SETSTAT, $packet);
1809  
1810                  $i++;
1811  
1812                  if ($i >= NET_SFTP_QUEUE_SIZE) {
1813                      if (!$this->read_put_responses($i)) {
1814                          return false;
1815                      }
1816                      $i = 0;
1817                  }
1818              }
1819          }
1820  
1821          $packet = Strings::packSSH2('s', $path);
1822          $packet .= $this->version >= 4 ?
1823              pack('Ca*', NET_SFTP_TYPE_UNKNOWN, $attr) :
1824              $attr;
1825          $this->send_sftp_packet(NET_SFTP_SETSTAT, $packet);
1826  
1827          $i++;
1828  
1829          if ($i >= NET_SFTP_QUEUE_SIZE) {
1830              if (!$this->read_put_responses($i)) {
1831                  return false;
1832              }
1833              $i = 0;
1834          }
1835  
1836          return true;
1837      }
1838  
1839      /**
1840       * Return the target of a symbolic link
1841       *
1842       * @param string $link
1843       * @throws \UnexpectedValueException on receipt of unexpected packets
1844       * @return mixed
1845       * @access public
1846       */
1847      public function readlink($link)
1848      {
1849          if (!$this->precheck()) {
1850              return false;
1851          }
1852  
1853          $link = $this->realpath($link);
1854  
1855          $this->send_sftp_packet(NET_SFTP_READLINK, Strings::packSSH2('s', $link));
1856  
1857          $response = $this->get_sftp_packet();
1858          switch ($this->packet_type) {
1859              case NET_SFTP_NAME:
1860                  break;
1861              case NET_SFTP_STATUS:
1862                  $this->logError($response);
1863                  return false;
1864              default:
1865                  throw new \UnexpectedValueException('Expected NET_SFTP_NAME or NET_SFTP_STATUS. '
1866                                                    . 'Got packet type: ' . $this->packet_type);
1867          }
1868  
1869          list($count) = Strings::unpackSSH2('N', $response);
1870          // the file isn't a symlink
1871          if (!$count) {
1872              return false;
1873          }
1874  
1875          list($filename) = Strings::unpackSSH2('s', $response);
1876  
1877          return $filename;
1878      }
1879  
1880      /**
1881       * Create a symlink
1882       *
1883       * symlink() creates a symbolic link to the existing target with the specified name link.
1884       *
1885       * @param string $target
1886       * @param string $link
1887       * @throws \UnexpectedValueException on receipt of unexpected packets
1888       * @return bool
1889       * @access public
1890       */
1891      public function symlink($target, $link)
1892      {
1893          if (!$this->precheck()) {
1894              return false;
1895          }
1896  
1897          //$target = $this->realpath($target);
1898          $link = $this->realpath($link);
1899  
1900          /* quoting https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-09#section-12.1 :
1901  
1902             Changed the SYMLINK packet to be LINK and give it the ability to
1903             create hard links.  Also change it's packet number because many
1904             implementation implemented SYMLINK with the arguments reversed.
1905             Hopefully the new argument names make it clear which way is which.
1906          */
1907          if ($this->version == 6) {
1908              $type = NET_SFTP_LINK;
1909              $packet = Strings::packSSH2('ssC', $link, $target, 1);
1910          } else {
1911              $type = NET_SFTP_SYMLINK;
1912              /* quoting http://bxr.su/OpenBSD/usr.bin/ssh/PROTOCOL#347 :
1913  
1914                 3.1. sftp: Reversal of arguments to SSH_FXP_SYMLINK
1915  
1916                 When OpenSSH's sftp-server was implemented, the order of the arguments
1917                 to the SSH_FXP_SYMLINK method was inadvertently reversed. Unfortunately,
1918                 the reversal was not noticed until the server was widely deployed. Since
1919                 fixing this to follow the specification would cause incompatibility, the
1920                 current order was retained. For correct operation, clients should send
1921                 SSH_FXP_SYMLINK as follows:
1922  
1923                     uint32      id
1924                     string      targetpath
1925                     string      linkpath */
1926              $packet = substr($this->server_identifier, 0, 15) == 'SSH-2.0-OpenSSH' ?
1927                  Strings::packSSH2('ss', $target, $link) :
1928                  Strings::packSSH2('ss', $link, $target);
1929          }
1930          $this->send_sftp_packet($type, $packet);
1931  
1932          $response = $this->get_sftp_packet();
1933          if ($this->packet_type != NET_SFTP_STATUS) {
1934              throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
1935                                                . 'Got packet type: ' . $this->packet_type);
1936          }
1937  
1938          list($status) = Strings::unpackSSH2('N', $response);
1939          if ($status != NET_SFTP_STATUS_OK) {
1940              $this->logError($response, $status);
1941              return false;
1942          }
1943  
1944          return true;
1945      }
1946  
1947      /**
1948       * Creates a directory.
1949       *
1950       * @param string $dir
1951       * @param int $mode
1952       * @param bool $recursive
1953       * @return bool
1954       * @access public
1955       */
1956      public function mkdir($dir, $mode = -1, $recursive = false)
1957      {
1958          if (!$this->precheck()) {
1959              return false;
1960          }
1961  
1962          $dir = $this->realpath($dir);
1963  
1964          if ($recursive) {
1965              $dirs = explode('/', preg_replace('#/(?=/)|/$#', '', $dir));
1966              if (empty($dirs[0])) {
1967                  array_shift($dirs);
1968                  $dirs[0] = '/' . $dirs[0];
1969              }
1970              for ($i = 0; $i < count($dirs); $i++) {
1971                  $temp = array_slice($dirs, 0, $i + 1);
1972                  $temp = implode('/', $temp);
1973                  $result = $this->mkdir_helper($temp, $mode);
1974              }
1975              return $result;
1976          }
1977  
1978          return $this->mkdir_helper($dir, $mode);
1979      }
1980  
1981      /**
1982       * Helper function for directory creation
1983       *
1984       * @param string $dir
1985       * @param int $mode
1986       * @return bool
1987       * @access private
1988       */
1989      private function mkdir_helper($dir, $mode)
1990      {
1991          // send SSH_FXP_MKDIR without any attributes (that's what the \0\0\0\0 is doing)
1992          $this->send_sftp_packet(NET_SFTP_MKDIR, Strings::packSSH2('s', $dir) . "\0\0\0\0");
1993  
1994          $response = $this->get_sftp_packet();
1995          if ($this->packet_type != NET_SFTP_STATUS) {
1996              throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
1997                                                . 'Got packet type: ' . $this->packet_type);
1998          }
1999  
2000          list($status) = Strings::unpackSSH2('N', $response);
2001          if ($status != NET_SFTP_STATUS_OK) {
2002              $this->logError($response, $status);
2003              return false;
2004          }
2005  
2006          if ($mode !== -1) {
2007              $this->chmod($mode, $dir);
2008          }
2009  
2010          return true;
2011      }
2012  
2013      /**
2014       * Removes a directory.
2015       *
2016       * @param string $dir
2017       * @throws \UnexpectedValueException on receipt of unexpected packets
2018       * @return bool
2019       * @access public
2020       */
2021      public function rmdir($dir)
2022      {
2023          if (!$this->precheck()) {
2024              return false;
2025          }
2026  
2027          $dir = $this->realpath($dir);
2028          if ($dir === false) {
2029              return false;
2030          }
2031  
2032          $this->send_sftp_packet(NET_SFTP_RMDIR, Strings::packSSH2('s', $dir));
2033  
2034          $response = $this->get_sftp_packet();
2035          if ($this->packet_type != NET_SFTP_STATUS) {
2036              throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
2037                                                . 'Got packet type: ' . $this->packet_type);
2038          }
2039  
2040          list($status) = Strings::unpackSSH2('N', $response);
2041          if ($status != NET_SFTP_STATUS_OK) {
2042              // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED?
2043              $this->logError($response, $status);
2044              return false;
2045          }
2046  
2047          $this->remove_from_stat_cache($dir);
2048          // the following will do a soft delete, which would be useful if you deleted a file
2049          // and then tried to do a stat on the deleted file. the above, in contrast, does
2050          // a hard delete
2051          //$this->update_stat_cache($dir, false);
2052  
2053          return true;
2054      }
2055  
2056      /**
2057       * Uploads a file to the SFTP server.
2058       *
2059       * By default, \phpseclib3\Net\SFTP::put() does not read from the local filesystem.  $data is dumped directly into $remote_file.
2060       * So, for example, if you set $data to 'filename.ext' and then do \phpseclib3\Net\SFTP::get(), you will get a file, twelve bytes
2061       * long, containing 'filename.ext' as its contents.
2062       *
2063       * Setting $mode to self::SOURCE_LOCAL_FILE will change the above behavior.  With self::SOURCE_LOCAL_FILE, $remote_file will
2064       * contain as many bytes as filename.ext does on your local filesystem.  If your filename.ext is 1MB then that is how
2065       * large $remote_file will be, as well.
2066       *
2067       * Setting $mode to self::SOURCE_CALLBACK will use $data as callback function, which gets only one parameter -- number
2068       * of bytes to return, and returns a string if there is some data or null if there is no more data
2069       *
2070       * If $data is a resource then it'll be used as a resource instead.
2071       *
2072       * Currently, only binary mode is supported.  As such, if the line endings need to be adjusted, you will need to take
2073       * care of that, yourself.
2074       *
2075       * $mode can take an additional two parameters - self::RESUME and self::RESUME_START. These are bitwise AND'd with
2076       * $mode. So if you want to resume upload of a 300mb file on the local file system you'd set $mode to the following:
2077       *
2078       * self::SOURCE_LOCAL_FILE | self::RESUME
2079       *
2080       * If you wanted to simply append the full contents of a local file to the full contents of a remote file you'd replace
2081       * self::RESUME with self::RESUME_START.
2082       *
2083       * If $mode & (self::RESUME | self::RESUME_START) then self::RESUME_START will be assumed.
2084       *
2085       * $start and $local_start give you more fine grained control over this process and take precident over self::RESUME
2086       * when they're non-negative. ie. $start could let you write at the end of a file (like self::RESUME) or in the middle
2087       * of one. $local_start could let you start your reading from the end of a file (like self::RESUME_START) or in the
2088       * middle of one.
2089       *
2090       * Setting $local_start to > 0 or $mode | self::RESUME_START doesn't do anything unless $mode | self::SOURCE_LOCAL_FILE.
2091       *
2092       * {@internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - \phpseclib3\Net\SFTP::setMode().}
2093       *
2094       * @param string $remote_file
2095       * @param string|resource $data
2096       * @param int $mode
2097       * @param int $start
2098       * @param int $local_start
2099       * @param callable|null $progressCallback
2100       * @throws \UnexpectedValueException on receipt of unexpected packets
2101       * @throws \BadFunctionCallException if you're uploading via a callback and the callback function is invalid
2102       * @throws \phpseclib3\Exception\FileNotFoundException if you're uploading via a file and the file doesn't exist
2103       * @return bool
2104       * @access public
2105       */
2106      public function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $local_start = -1, $progressCallback = null)
2107      {
2108          if (!$this->precheck()) {
2109              return false;
2110          }
2111  
2112          $remote_file = $this->realpath($remote_file);
2113          if ($remote_file === false) {
2114              return false;
2115          }
2116  
2117          $this->remove_from_stat_cache($remote_file);
2118  
2119          if ($this->version >= 5) {
2120              $flags = NET_SFTP_OPEN_OPEN_OR_CREATE;
2121          } else {
2122              $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE;
2123              // according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file."
2124              // in practice, it doesn't seem to do that.
2125              //$flags|= ($mode & self::RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE;
2126          }
2127  
2128          if ($start >= 0) {
2129              $offset = $start;
2130          } elseif ($mode & self::RESUME) {
2131              // if NET_SFTP_OPEN_APPEND worked as it should _size() wouldn't need to be called
2132              $size = $this->stat($remote_file)['size'];
2133              $offset = $size !== false ? $size : 0;
2134          } else {
2135              $offset = 0;
2136              if ($this->version >= 5) {
2137                  $flags = NET_SFTP_OPEN_CREATE_TRUNCATE;
2138              } else {
2139                  $flags |= NET_SFTP_OPEN_TRUNCATE;
2140              }
2141          }
2142  
2143          $this->remove_from_stat_cache($remote_file);
2144  
2145          $packet = Strings::packSSH2('s', $remote_file);
2146          $packet .= $this->version >= 5 ?
2147              pack('N3', 0, $flags, 0) :
2148              pack('N2', $flags, 0);
2149          $this->send_sftp_packet(NET_SFTP_OPEN, $packet);
2150  
2151          $response = $this->get_sftp_packet();
2152          switch ($this->packet_type) {
2153              case NET_SFTP_HANDLE:
2154                  $handle = substr($response, 4);
2155                  break;
2156              case NET_SFTP_STATUS:
2157                  $this->logError($response);
2158                  return false;
2159              default:
2160                  throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. '
2161                                                    . 'Got packet type: ' . $this->packet_type);
2162          }
2163  
2164          // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3
2165          $dataCallback = false;
2166          switch (true) {
2167              case $mode & self::SOURCE_CALLBACK:
2168                  if (!is_callable($data)) {
2169                      throw new \BadFunctionCallException("\$data should be is_callable() if you specify SOURCE_CALLBACK flag");
2170                  }
2171                  $dataCallback = $data;
2172                  // do nothing
2173                  break;
2174              case is_resource($data):
2175                  $mode = $mode & ~self::SOURCE_LOCAL_FILE;
2176                  $info = stream_get_meta_data($data);
2177                  if ($info['wrapper_type'] == 'PHP' && $info['stream_type'] == 'Input') {
2178                      $fp = fopen('php://memory', 'w+');
2179                      stream_copy_to_stream($data, $fp);
2180                      rewind($fp);
2181                  } else {
2182                      $fp = $data;
2183                  }
2184                  break;
2185              case $mode & self::SOURCE_LOCAL_FILE:
2186                  if (!is_file($data)) {
2187                      throw new FileNotFoundException("$data is not a valid file");
2188                  }
2189                  $fp = @fopen($data, 'rb');
2190                  if (!$fp) {
2191                      return false;
2192                  }
2193          }
2194  
2195          if (isset($fp)) {
2196              $stat = fstat($fp);
2197              $size = !empty($stat) ? $stat['size'] : 0;
2198  
2199              if ($local_start >= 0) {
2200                  fseek($fp, $local_start);
2201                  $size -= $local_start;
2202              }
2203          } elseif ($dataCallback) {
2204              $size = 0;
2205          } else {
2206              $size = strlen($data);
2207          }
2208  
2209          $sent = 0;
2210          $size = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
2211  
2212          $sftp_packet_size = $this->max_sftp_packet;
2213          // make the SFTP packet be exactly the SFTP packet size by including the bytes in the NET_SFTP_WRITE packets "header"
2214          $sftp_packet_size -= strlen($handle) + 25;
2215          $i = $j = 0;
2216          while ($dataCallback || ($size === 0 || $sent < $size)) {
2217              if ($dataCallback) {
2218                  $temp = $dataCallback($sftp_packet_size);
2219                  if (is_null($temp)) {
2220                      break;
2221                  }
2222              } else {
2223                  $temp = isset($fp) ? fread($fp, $sftp_packet_size) : substr($data, $sent, $sftp_packet_size);
2224                  if ($temp === false || $temp === '') {
2225                      break;
2226                  }
2227              }
2228  
2229              $subtemp = $offset + $sent;
2230              $packet = pack('Na*N3a*', strlen($handle), $handle, $subtemp / 4294967296, $subtemp, strlen($temp), $temp);
2231              try {
2232                  $this->send_sftp_packet(NET_SFTP_WRITE, $packet, $j);
2233              } catch (\Exception $e) {
2234                  if ($mode & self::SOURCE_LOCAL_FILE) {
2235                      fclose($fp);
2236                  }
2237                  throw $e;
2238              }
2239              $sent += strlen($temp);
2240              if (is_callable($progressCallback)) {
2241                  $progressCallback($sent);
2242              }
2243  
2244              $i++;
2245              $j++;
2246              if ($i == NET_SFTP_UPLOAD_QUEUE_SIZE) {
2247                  if (!$this->read_put_responses($i)) {
2248                      $i = 0;
2249                      break;
2250                  }
2251                  $i = 0;
2252              }
2253          }
2254  
2255          $result = $this->close_handle($handle);
2256  
2257          if (!$this->read_put_responses($i)) {
2258              if ($mode & self::SOURCE_LOCAL_FILE) {
2259                  fclose($fp);
2260              }
2261              $this->close_handle($handle);
2262              return false;
2263          }
2264  
2265          if ($mode & SFTP::SOURCE_LOCAL_FILE) {
2266              if (isset($fp) && is_resource($fp)) {
2267                  fclose($fp);
2268              }
2269  
2270              if ($this->preserveTime) {
2271                  $stat = stat($data);
2272                  $attr = $this->version < 4 ?
2273                      pack('N3', NET_SFTP_ATTR_ACCESSTIME, $stat['atime'], $stat['time']) :
2274                      Strings::packSSH2('NQ2', NET_SFTP_ATTR_ACCESSTIME | NET_SFTP_ATTR_MODIFYTIME, $stat['atime'], $stat['time']);
2275                  if (!$this->setstat($remote_file, $attr, false)) {
2276                      throw new \RuntimeException('Error setting file time');
2277                  }
2278              }
2279          }
2280  
2281          return $result;
2282      }
2283  
2284      /**
2285       * Reads multiple successive SSH_FXP_WRITE responses
2286       *
2287       * Sending an SSH_FXP_WRITE packet and immediately reading its response isn't as efficient as blindly sending out $i
2288       * SSH_FXP_WRITEs, in succession, and then reading $i responses.
2289       *
2290       * @param int $i
2291       * @return bool
2292       * @throws \UnexpectedValueException on receipt of unexpected packets
2293       * @access private
2294       */
2295      private function read_put_responses($i)
2296      {
2297          while ($i--) {
2298              $response = $this->get_sftp_packet();
2299              if ($this->packet_type != NET_SFTP_STATUS) {
2300                  throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
2301                                                    . 'Got packet type: ' . $this->packet_type);
2302              }
2303  
2304              list($status) = Strings::unpackSSH2('N', $response);
2305              if ($status != NET_SFTP_STATUS_OK) {
2306                  $this->logError($response, $status);
2307                  break;
2308              }
2309          }
2310  
2311          return $i < 0;
2312      }
2313  
2314      /**
2315       * Close handle
2316       *
2317       * @param string $handle
2318       * @return bool
2319       * @throws \UnexpectedValueException on receipt of unexpected packets
2320       * @access private
2321       */
2322      private function close_handle($handle)
2323      {
2324          $this->send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle));
2325  
2326          // "The client MUST release all resources associated with the handle regardless of the status."
2327          //  -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3
2328          $response = $this->get_sftp_packet();
2329          if ($this->packet_type != NET_SFTP_STATUS) {
2330              throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
2331                                                . 'Got packet type: ' . $this->packet_type);
2332          }
2333  
2334          list($status) = Strings::unpackSSH2('N', $response);
2335          if ($status != NET_SFTP_STATUS_OK) {
2336              $this->logError($response, $status);
2337              return false;
2338          }
2339  
2340          return true;
2341      }
2342  
2343      /**
2344       * Downloads a file from the SFTP server.
2345       *
2346       * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
2347       * the operation was unsuccessful.  If $local_file is defined, returns true or false depending on the success of the
2348       * operation.
2349       *
2350       * $offset and $length can be used to download files in chunks.
2351       *
2352       * @param string $remote_file
2353       * @param string|bool|resource|callable $local_file
2354       * @param int $offset
2355       * @param int $length
2356       * @param callable|null $progressCallback
2357       * @throws \UnexpectedValueException on receipt of unexpected packets
2358       * @return string|false
2359       * @access public
2360       */
2361      public function get($remote_file, $local_file = false, $offset = 0, $length = -1, $progressCallback = null)
2362      {
2363          if (!$this->precheck()) {
2364              return false;
2365          }
2366  
2367          $remote_file = $this->realpath($remote_file);
2368          if ($remote_file === false) {
2369              return false;
2370          }
2371  
2372          $packet = Strings::packSSH2('s', $remote_file);
2373          $packet .= $this->version >= 5 ?
2374              pack('N3', 0, NET_SFTP_OPEN_OPEN_EXISTING, 0) :
2375              pack('N2', NET_SFTP_OPEN_READ, 0);
2376          $this->send_sftp_packet(NET_SFTP_OPEN, $packet);
2377  
2378          $response = $this->get_sftp_packet();
2379          switch ($this->packet_type) {
2380              case NET_SFTP_HANDLE:
2381                  $handle = substr($response, 4);
2382                  break;
2383              case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2384                  $this->logError($response);
2385                  return false;
2386              default:
2387                  throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. '
2388                                                    . 'Got packet type: ' . $this->packet_type);
2389          }
2390  
2391          if (is_resource($local_file)) {
2392              $fp = $local_file;
2393              $stat = fstat($fp);
2394              $res_offset = $stat['size'];
2395          } else {
2396              $res_offset = 0;
2397              if ($local_file !== false && !is_callable($local_file)) {
2398                  $fp = fopen($local_file, 'wb');
2399                  if (!$fp) {
2400                      return false;
2401                  }
2402              } else {
2403                  $content = '';
2404              }
2405          }
2406  
2407          $fclose_check = $local_file !== false && !is_callable($local_file) && !is_resource($local_file);
2408  
2409          $start = $offset;
2410          $read = 0;
2411          while (true) {
2412              $i = 0;
2413  
2414              while ($i < NET_SFTP_QUEUE_SIZE && ($length < 0 || $read < $length)) {
2415                  $tempoffset = $start + $read;
2416  
2417                  $packet_size = $length > 0 ? min($this->max_sftp_packet, $length - $read) : $this->max_sftp_packet;
2418  
2419                  $packet = Strings::packSSH2('sN3', $handle, $tempoffset / 4294967296, $tempoffset, $packet_size);
2420                  try {
2421                      $this->send_sftp_packet(NET_SFTP_READ, $packet, $i);
2422                  } catch (\Exception $e) {
2423                      if ($fclose_check) {
2424                          fclose($fp);
2425                      }
2426                      throw $e;
2427                  }
2428                  $packet = null;
2429                  $read += $packet_size;
2430                  $i++;
2431              }
2432  
2433              if (!$i) {
2434                  break;
2435              }
2436  
2437              $packets_sent = $i - 1;
2438  
2439              $clear_responses = false;
2440              while ($i > 0) {
2441                  $i--;
2442  
2443                  if ($clear_responses) {
2444                      $this->get_sftp_packet($packets_sent - $i);
2445                      continue;
2446                  } else {
2447                      $response = $this->get_sftp_packet($packets_sent - $i);
2448                  }
2449  
2450                  switch ($this->packet_type) {
2451                      case NET_SFTP_DATA:
2452                          $temp = substr($response, 4);
2453                          $offset += strlen($temp);
2454                          if ($local_file === false) {
2455                              $content .= $temp;
2456                          } elseif (is_callable($local_file)) {
2457                              $local_file($temp);
2458                          } else {
2459                              fputs($fp, $temp);
2460                          }
2461                          if (is_callable($progressCallback)) {
2462                              call_user_func($progressCallback, $offset);
2463                          }
2464                          $temp = null;
2465                          break;
2466                      case NET_SFTP_STATUS:
2467                          // could, in theory, return false if !strlen($content) but we'll hold off for the time being
2468                          $this->logError($response);
2469                          $clear_responses = true; // don't break out of the loop yet, so we can read the remaining responses
2470                          break;
2471                      default:
2472                          if ($fclose_check) {
2473                              fclose($fp);
2474                          }
2475                          if ($this->channel_close) {
2476                              $this->partial_init = false;
2477                              $this->init_sftp_connection();
2478                              return false;
2479                          } else {
2480                              throw new \UnexpectedValueException('Expected NET_SFTP_DATA or NET_SFTP_STATUS. '
2481                                                                . 'Got packet type: ' . $this->packet_type);
2482                          }
2483                  }
2484                  $response = null;
2485              }
2486  
2487              if ($clear_responses) {
2488                  break;
2489              }
2490          }
2491  
2492          if ($length > 0 && $length <= $offset - $start) {
2493              if ($local_file === false) {
2494                  $content = substr($content, 0, $length);
2495              } else {
2496                  ftruncate($fp, $length + $res_offset);
2497              }
2498          }
2499  
2500          if ($fclose_check) {
2501              fclose($fp);
2502  
2503              if ($this->preserveTime) {
2504                  $stat = $this->stat($remote_file);
2505                  touch($local_file, $stat['mtime'], $stat['atime']);
2506              }
2507          }
2508  
2509          if (!$this->close_handle($handle)) {
2510              return false;
2511          }
2512  
2513          // if $content isn't set that means a file was written to
2514          return isset($content) ? $content : true;
2515      }
2516  
2517      /**
2518       * Deletes a file on the SFTP server.
2519       *
2520       * @param string $path
2521       * @param bool $recursive
2522       * @return bool
2523       * @throws \UnexpectedValueException on receipt of unexpected packets
2524       * @access public
2525       */
2526      public function delete($path, $recursive = true)
2527      {
2528          if (!$this->precheck()) {
2529              return false;
2530          }
2531  
2532          if (is_object($path)) {
2533              // It's an object. Cast it as string before we check anything else.
2534              $path = (string) $path;
2535          }
2536  
2537          if (!is_string($path) || $path == '') {
2538              return false;
2539          }
2540  
2541          $path = $this->realpath($path);
2542          if ($path === false) {
2543              return false;
2544          }
2545  
2546          // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
2547          $this->send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path));
2548  
2549          $response = $this->get_sftp_packet();
2550          if ($this->packet_type != NET_SFTP_STATUS) {
2551              throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
2552                                                . 'Got packet type: ' . $this->packet_type);
2553          }
2554  
2555          // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2556          list($status) = Strings::unpackSSH2('N', $response);
2557          if ($status != NET_SFTP_STATUS_OK) {
2558              $this->logError($response, $status);
2559              if (!$recursive) {
2560                  return false;
2561              }
2562  
2563              $i = 0;
2564              $result = $this->delete_recursive($path, $i);
2565              $this->read_put_responses($i);
2566              return $result;
2567          }
2568  
2569          $this->remove_from_stat_cache($path);
2570  
2571          return true;
2572      }
2573  
2574      /**
2575       * Recursively deletes directories on the SFTP server
2576       *
2577       * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
2578       *
2579       * @param string $path
2580       * @param int $i
2581       * @return bool
2582       * @access private
2583       */
2584      private function delete_recursive($path, &$i)
2585      {
2586          if (!$this->read_put_responses($i)) {
2587              return false;
2588          }
2589          $i = 0;
2590          $entries = $this->readlist($path, true);
2591  
2592          // normally $entries would have at least . and .. but it might not if the directories
2593          // permissions didn't allow reading
2594          if (empty($entries)) {
2595              return false;
2596          }
2597  
2598          unset($entries['.'], $entries['..']);
2599          foreach ($entries as $filename => $props) {
2600              if (!isset($props['type'])) {
2601                  return false;
2602              }
2603  
2604              $temp = $path . '/' . $filename;
2605              if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
2606                  if (!$this->delete_recursive($temp, $i)) {
2607                      return false;
2608                  }
2609              } else {
2610                  $this->send_sftp_packet(NET_SFTP_REMOVE, Strings::packSSH2('s', $temp));
2611                  $this->remove_from_stat_cache($temp);
2612  
2613                  $i++;
2614  
2615                  if ($i >= NET_SFTP_QUEUE_SIZE) {
2616                      if (!$this->read_put_responses($i)) {
2617                          return false;
2618                      }
2619                      $i = 0;
2620                  }
2621              }
2622          }
2623  
2624          $this->send_sftp_packet(NET_SFTP_RMDIR, Strings::packSSH2('s', $path));
2625          $this->remove_from_stat_cache($path);
2626  
2627          $i++;
2628  
2629          if ($i >= NET_SFTP_QUEUE_SIZE) {
2630              if (!$this->read_put_responses($i)) {
2631                  return false;
2632              }
2633              $i = 0;
2634          }
2635  
2636          return true;
2637      }
2638  
2639      /**
2640       * Checks whether a file or directory exists
2641       *
2642       * @param string $path
2643       * @return bool
2644       * @access public
2645       */
2646      public function file_exists($path)
2647      {
2648          if ($this->use_stat_cache) {
2649              if (!$this->precheck()) {
2650                  return false;
2651              }
2652  
2653              $path = $this->realpath($path);
2654  
2655              $result = $this->query_stat_cache($path);
2656  
2657              if (isset($result)) {
2658                  // return true if $result is an array or if it's an stdClass object
2659                  return $result !== false;
2660              }
2661          }
2662  
2663          return $this->stat($path) !== false;
2664      }
2665  
2666      /**
2667       * Tells whether the filename is a directory
2668       *
2669       * @param string $path
2670       * @return bool
2671       * @access public
2672       */
2673      public function is_dir($path)
2674      {
2675          $result = $this->get_stat_cache_prop($path, 'type');
2676          if ($result === false) {
2677              return false;
2678          }
2679          return $result === NET_SFTP_TYPE_DIRECTORY;
2680      }
2681  
2682      /**
2683       * Tells whether the filename is a regular file
2684       *
2685       * @param string $path
2686       * @return bool
2687       * @access public
2688       */
2689      public function is_file($path)
2690      {
2691          $result = $this->get_stat_cache_prop($path, 'type');
2692          if ($result === false) {
2693              return false;
2694          }
2695          return $result === NET_SFTP_TYPE_REGULAR;
2696      }
2697  
2698      /**
2699       * Tells whether the filename is a symbolic link
2700       *
2701       * @param string $path
2702       * @return bool
2703       * @access public
2704       */
2705      public function is_link($path)
2706      {
2707          $result = $this->get_lstat_cache_prop($path, 'type');
2708          if ($result === false) {
2709              return false;
2710          }
2711          return $result === NET_SFTP_TYPE_SYMLINK;
2712      }
2713  
2714      /**
2715       * Tells whether a file exists and is readable
2716       *
2717       * @param string $path
2718       * @return bool
2719       * @access public
2720       */
2721      public function is_readable($path)
2722      {
2723          if (!$this->precheck()) {
2724              return false;
2725          }
2726  
2727          $packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_READ, 0);
2728          $this->send_sftp_packet(NET_SFTP_OPEN, $packet);
2729  
2730          $response = $this->get_sftp_packet();
2731          switch ($this->packet_type) {
2732              case NET_SFTP_HANDLE:
2733                  return true;
2734              case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2735                  return false;
2736              default:
2737                  throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. '
2738                                                    . 'Got packet type: ' . $this->packet_type);
2739          }
2740      }
2741  
2742      /**
2743       * Tells whether the filename is writable
2744       *
2745       * @param string $path
2746       * @return bool
2747       * @access public
2748       */
2749      public function is_writable($path)
2750      {
2751          if (!$this->precheck()) {
2752              return false;
2753          }
2754  
2755          $packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_WRITE, 0);
2756          $this->send_sftp_packet(NET_SFTP_OPEN, $packet);
2757  
2758          $response = $this->get_sftp_packet();
2759          switch ($this->packet_type) {
2760              case NET_SFTP_HANDLE:
2761                  return true;
2762              case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2763                  return false;
2764              default:
2765                  throw new \UnexpectedValueException('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS. '
2766                                                    . 'Got packet type: ' . $this->packet_type);
2767          }
2768      }
2769  
2770      /**
2771       * Tells whether the filename is writeable
2772       *
2773       * Alias of is_writable
2774       *
2775       * @param string $path
2776       * @return bool
2777       * @access public
2778       */
2779      public function is_writeable($path)
2780      {
2781          return $this->is_writable($path);
2782      }
2783  
2784      /**
2785       * Gets last access time of file
2786       *
2787       * @param string $path
2788       * @return mixed
2789       * @access public
2790       */
2791      public function fileatime($path)
2792      {
2793          return $this->get_stat_cache_prop($path, 'atime');
2794      }
2795  
2796      /**
2797       * Gets file modification time
2798       *
2799       * @param string $path
2800       * @return mixed
2801       * @access public
2802       */
2803      public function filemtime($path)
2804      {
2805          return $this->get_stat_cache_prop($path, 'mtime');
2806      }
2807  
2808      /**
2809       * Gets file permissions
2810       *
2811       * @param string $path
2812       * @return mixed
2813       * @access public
2814       */
2815      public function fileperms($path)
2816      {
2817          return $this->get_stat_cache_prop($path, 'mode');
2818      }
2819  
2820      /**
2821       * Gets file owner
2822       *
2823       * @param string $path
2824       * @return mixed
2825       * @access public
2826       */
2827      public function fileowner($path)
2828      {
2829          return $this->get_stat_cache_prop($path, 'uid');
2830      }
2831  
2832      /**
2833       * Gets file group
2834       *
2835       * @param string $path
2836       * @return mixed
2837       * @access public
2838       */
2839      public function filegroup($path)
2840      {
2841          return $this->get_stat_cache_prop($path, 'gid');
2842      }
2843  
2844      /**
2845       * Gets file size
2846       *
2847       * @param string $path
2848       * @return mixed
2849       * @access public
2850       */
2851      public function filesize($path)
2852      {
2853          return $this->get_stat_cache_prop($path, 'size');
2854      }
2855  
2856      /**
2857       * Gets file type
2858       *
2859       * @param string $path
2860       * @return string|false
2861       * @access public
2862       */
2863      public function filetype($path)
2864      {
2865          $type = $this->get_stat_cache_prop($path, 'type');
2866          if ($type === false) {
2867              return false;
2868          }
2869  
2870          switch ($type) {
2871              case NET_SFTP_TYPE_BLOCK_DEVICE:
2872                  return 'block';
2873              case NET_SFTP_TYPE_CHAR_DEVICE:
2874                  return 'char';
2875              case NET_SFTP_TYPE_DIRECTORY:
2876                  return 'dir';
2877              case NET_SFTP_TYPE_FIFO:
2878                  return 'fifo';
2879              case NET_SFTP_TYPE_REGULAR:
2880                  return 'file';
2881              case NET_SFTP_TYPE_SYMLINK:
2882                  return 'link';
2883              default:
2884                  return false;
2885          }
2886      }
2887  
2888      /**
2889       * Return a stat properity
2890       *
2891       * Uses cache if appropriate.
2892       *
2893       * @param string $path
2894       * @param string $prop
2895       * @return mixed
2896       * @access private
2897       */
2898      private function get_stat_cache_prop($path, $prop)
2899      {
2900          return $this->get_xstat_cache_prop($path, $prop, 'stat');
2901      }
2902  
2903      /**
2904       * Return an lstat properity
2905       *
2906       * Uses cache if appropriate.
2907       *
2908       * @param string $path
2909       * @param string $prop
2910       * @return mixed
2911       * @access private
2912       */
2913      private function get_lstat_cache_prop($path, $prop)
2914      {
2915          return $this->get_xstat_cache_prop($path, $prop, 'lstat');
2916      }
2917  
2918      /**
2919       * Return a stat or lstat properity
2920       *
2921       * Uses cache if appropriate.
2922       *
2923       * @param string $path
2924       * @param string $prop
2925       * @param string $type
2926       * @return mixed
2927       * @access private
2928       */
2929      private function get_xstat_cache_prop($path, $prop, $type)
2930      {
2931          if (!$this->precheck()) {
2932              return false;
2933          }
2934  
2935          if ($this->use_stat_cache) {
2936              $path = $this->realpath($path);
2937  
2938              $result = $this->query_stat_cache($path);
2939  
2940              if (is_object($result) && isset($result->$type)) {
2941                  return $result->{$type}[$prop];
2942              }
2943          }
2944  
2945          $result = $this->$type($path);
2946  
2947          if ($result === false || !isset($result[$prop])) {
2948              return false;
2949          }
2950  
2951          return $result[$prop];
2952      }
2953  
2954      /**
2955       * Renames a file or a directory on the SFTP server.
2956       *
2957       * If the file already exists this will return false
2958       *
2959       * @param string $oldname
2960       * @param string $newname
2961       * @return bool
2962       * @throws \UnexpectedValueException on receipt of unexpected packets
2963       * @access public
2964       */
2965      public function rename($oldname, $newname)
2966      {
2967          if (!$this->precheck()) {
2968              return false;
2969          }
2970  
2971          $oldname = $this->realpath($oldname);
2972          $newname = $this->realpath($newname);
2973          if ($oldname === false || $newname === false) {
2974              return false;
2975          }
2976  
2977          // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
2978          $packet = Strings::packSSH2('ss', $oldname, $newname);
2979          if ($this->version >= 5) {
2980              /* quoting https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-05#section-6.5 ,
2981  
2982                 'flags' is 0 or a combination of:
2983  
2984                     SSH_FXP_RENAME_OVERWRITE  0x00000001
2985                     SSH_FXP_RENAME_ATOMIC     0x00000002
2986                     SSH_FXP_RENAME_NATIVE     0x00000004
2987  
2988                 (none of these are currently supported) */
2989              $packet .= "\0\0\0\0";
2990          }
2991          $this->send_sftp_packet(NET_SFTP_RENAME, $packet);
2992  
2993          $response = $this->get_sftp_packet();
2994          if ($this->packet_type != NET_SFTP_STATUS) {
2995              throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
2996                                                . 'Got packet type: ' . $this->packet_type);
2997          }
2998  
2999          // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
3000          list($status) = Strings::unpackSSH2('N', $response);
3001          if ($status != NET_SFTP_STATUS_OK) {
3002              $this->logError($response, $status);
3003              return false;
3004          }
3005  
3006          // don't move the stat cache entry over since this operation could very well change the
3007          // atime and mtime attributes
3008          //$this->update_stat_cache($newname, $this->query_stat_cache($oldname));
3009          $this->remove_from_stat_cache($oldname);
3010          $this->remove_from_stat_cache($newname);
3011  
3012          return true;
3013      }
3014  
3015      /**
3016       * Parse Time
3017       *
3018       * See '7.7.  Times' of draft-ietf-secsh-filexfer-13 for more info.
3019       *
3020       * @param string $key
3021       * @param int $flags
3022       * @param string $response
3023       * @return array
3024       * @access private
3025       */
3026      private function parseTime($key, $flags, &$response)
3027      {
3028          $attr = [];
3029          list($attr[$key]) = Strings::unpackSSH2('Q', $response);
3030          if ($flags & NET_SFTP_ATTR_SUBSECOND_TIMES) {
3031              list($attr[$key . '-nseconds']) = Strings::unpackSSH2('N', $response);
3032          }
3033          return $attr;
3034      }
3035  
3036      /**
3037       * Parse Attributes
3038       *
3039       * See '7.  File Attributes' of draft-ietf-secsh-filexfer-13 for more info.
3040       *
3041       * @param string $response
3042       * @return array
3043       * @access private
3044       */
3045      protected function parseAttributes(&$response)
3046      {
3047          if ($this->version >= 4) {
3048              list($flags, $attr['type']) = Strings::unpackSSH2('NC', $response);
3049          } else {
3050              list($flags) = Strings::unpackSSH2('N', $response);
3051          }
3052  
3053          foreach ($this->attributes as $key => $value) {
3054              switch ($flags & $key) {
3055                  case NET_SFTP_ATTR_UIDGID:
3056                      if ($this->version > 3) {
3057                          continue 2;
3058                      }
3059                      break;
3060                  case NET_SFTP_ATTR_CREATETIME:
3061                  case NET_SFTP_ATTR_MODIFYTIME:
3062                  case NET_SFTP_ATTR_ACL:
3063                  case NET_SFTP_ATTR_OWNERGROUP:
3064                  case NET_SFTP_ATTR_SUBSECOND_TIMES:
3065                      if ($this->version < 4) {
3066                          continue 2;
3067                      }
3068                      break;
3069                  case NET_SFTP_ATTR_BITS:
3070                      if ($this->version < 5) {
3071                          continue 2;
3072                      }
3073                      break;
3074                  case NET_SFTP_ATTR_ALLOCATION_SIZE:
3075                  case NET_SFTP_ATTR_TEXT_HINT:
3076                  case NET_SFTP_ATTR_MIME_TYPE:
3077                  case NET_SFTP_ATTR_LINK_COUNT:
3078                  case NET_SFTP_ATTR_UNTRANSLATED_NAME:
3079                  case NET_SFTP_ATTR_CTIME:
3080                      if ($this->version < 6) {
3081                          continue 2;
3082                      }
3083              }
3084              switch ($flags & $key) {
3085                  case NET_SFTP_ATTR_SIZE:             // 0x00000001
3086                      // The size attribute is defined as an unsigned 64-bit integer.
3087                      // The following will use floats on 32-bit platforms, if necessary.
3088                      // As can be seen in the BigInteger class, floats are generally
3089                      // IEEE 754 binary64 "double precision" on such platforms and
3090                      // as such can represent integers of at least 2^50 without loss
3091                      // of precision. Interpreted in filesize, 2^50 bytes = 1024 TiB.
3092                      list($attr['size']) = Strings::unpackSSH2('Q', $response);
3093                      break;
3094                  case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
3095                      list($attr['uid'], $attr['gid']) = Strings::unpackSSH2('NN', $response);
3096                      break;
3097                  case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
3098                      list($attr['mode']) = Strings::unpackSSH2('N', $response);
3099                      $fileType = $this->parseMode($attr['mode']);
3100                      if ($this->version < 4 && $fileType !== false) {
3101                          $attr += ['type' => $fileType];
3102                      }
3103                      break;
3104                  case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
3105                      if ($this->version >= 4) {
3106                          $attr += $this->parseTime('atime', $flags, $response);
3107                          break;
3108                      }
3109                      list($attr['atime'], $attr['mtime']) = Strings::unpackSSH2('NN', $response);
3110                      break;
3111                  case NET_SFTP_ATTR_CREATETIME:       // 0x00000010 (SFTPv4+)
3112                      $attr += $this->parseTime('createtime', $flags, $response);
3113                      break;
3114                  case NET_SFTP_ATTR_MODIFYTIME:       // 0x00000020
3115                      $attr += $this->parseTime('mtime', $flags, $response);
3116                      break;
3117                  case NET_SFTP_ATTR_ACL:              // 0x00000040
3118                      // access control list
3119                      // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-04#section-5.7
3120                      // currently unsupported
3121                      list($count) = Strings::unpackSSH2('N', $response);
3122                      for ($i = 0; $i < $count; $i++) {
3123                          list($type, $flag, $mask, $who) = Strings::unpackSSH2('N3s', $result);
3124                      }
3125                      break;
3126                  case NET_SFTP_ATTR_OWNERGROUP:       // 0x00000080
3127                      list($attr['owner'], $attr['$group']) = Strings::unpackSSH2('ss', $response);
3128                      break;
3129                  case NET_SFTP_ATTR_SUBSECOND_TIMES:  // 0x00000100
3130                      break;
3131                  case NET_SFTP_ATTR_BITS:             // 0x00000200 (SFTPv5+)
3132                      // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-05#section-5.8
3133                      // currently unsupported
3134                      // tells if you file is:
3135                      // readonly, system, hidden, case inensitive, archive, encrypted, compressed, sparse
3136                      // append only, immutable, sync
3137                      list($attrib_bits, $attrib_bits_valid) = Strings::unpackSSH2('N2', $response);
3138                      // if we were actually gonna implement the above it ought to be
3139                      // $attr['attrib-bits'] and $attr['attrib-bits-valid']
3140                      // eg. - instead of _
3141                      break;
3142                  case NET_SFTP_ATTR_ALLOCATION_SIZE:  // 0x00000400 (SFTPv6+)
3143                      // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.4
3144                      // represents the number of bytes that the file consumes on the disk. will
3145                      // usually be larger than the 'size' field
3146                      list($attr['allocation-size']) = Strings::unpackSSH2('Q', $response);
3147                      break;
3148                  case NET_SFTP_ATTR_TEXT_HINT:        // 0x00000800
3149                      // https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.10
3150                      // currently unsupported
3151                      // tells if file is "known text", "guessed text", "known binary", "guessed binary"
3152                      list($text_hint) = Strings::unpackSSH2('C', $response);
3153                      // the above should be $attr['text-hint']
3154                      break;
3155                  case NET_SFTP_ATTR_MIME_TYPE:        // 0x00001000
3156                      // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.11
3157                      list($attr['mime-type']) = Strings::unpackSSH2('s', $response);
3158                      break;
3159                  case NET_SFTP_ATTR_LINK_COUNT:       // 0x00002000
3160                      // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.12
3161                      list($attr['link-count']) = Strings::unpackSSH2('N', $response);
3162                      break;
3163                  case NET_SFTP_ATTR_UNTRANSLATED_NAME:// 0x00004000
3164                      // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.13
3165                      list($attr['untranslated-name']) = Strings::unpackSSH2('s', $response);
3166                      break;
3167                  case NET_SFTP_ATTR_CTIME:            // 0x00008000
3168                      // 'ctime' contains the last time the file attributes were changed.  The
3169                      // exact meaning of this field depends on the server.
3170                      $attr += $this->parseTime('ctime', $flags, $response);
3171                      break;
3172                  case NET_SFTP_ATTR_EXTENDED: // 0x80000000
3173                      list($count) = Strings::unpackSSH2('N', $response);
3174                      for ($i = 0; $i < $count; $i++) {
3175                          list($key, $value) = Strings::unpackSSH2('ss', $response);
3176                          $attr[$key] = $value;
3177                      }
3178              }
3179          }
3180          return $attr;
3181      }
3182  
3183      /**
3184       * Attempt to identify the file type
3185       *
3186       * Quoting the SFTP RFC, "Implementations MUST NOT send bits that are not defined" but they seem to anyway
3187       *
3188       * @param int $mode
3189       * @return int
3190       * @access private
3191       */
3192      private function parseMode($mode)
3193      {
3194          // values come from http://lxr.free-electrons.com/source/include/uapi/linux/stat.h#L12
3195          // see, also, http://linux.die.net/man/2/stat
3196          switch ($mode & 0170000) {// ie. 1111 0000 0000 0000
3197              case 0000000: // no file type specified - figure out the file type using alternative means
3198                  return false;
3199              case 0040000:
3200                  return NET_SFTP_TYPE_DIRECTORY;
3201              case 0100000:
3202                  return NET_SFTP_TYPE_REGULAR;
3203              case 0120000:
3204                  return NET_SFTP_TYPE_SYMLINK;
3205              // new types introduced in SFTPv5+
3206              // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
3207              case 0010000: // named pipe (fifo)
3208                  return NET_SFTP_TYPE_FIFO;
3209              case 0020000: // character special
3210                  return NET_SFTP_TYPE_CHAR_DEVICE;
3211              case 0060000: // block special
3212                  return NET_SFTP_TYPE_BLOCK_DEVICE;
3213              case 0140000: // socket
3214                  return NET_SFTP_TYPE_SOCKET;
3215              case 0160000: // whiteout
3216                  // "SPECIAL should be used for files that are of
3217                  //  a known type which cannot be expressed in the protocol"
3218                  return NET_SFTP_TYPE_SPECIAL;
3219              default:
3220                  return NET_SFTP_TYPE_UNKNOWN;
3221          }
3222      }
3223  
3224      /**
3225       * Parse Longname
3226       *
3227       * SFTPv3 doesn't provide any easy way of identifying a file type.  You could try to open
3228       * a file as a directory and see if an error is returned or you could try to parse the
3229       * SFTPv3-specific longname field of the SSH_FXP_NAME packet.  That's what this function does.
3230       * The result is returned using the
3231       * {@link http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2 SFTPv4 type constants}.
3232       *
3233       * If the longname is in an unrecognized format bool(false) is returned.
3234       *
3235       * @param string $longname
3236       * @return mixed
3237       * @access private
3238       */
3239      private function parseLongname($longname)
3240      {
3241          // http://en.wikipedia.org/wiki/Unix_file_types
3242          // http://en.wikipedia.org/wiki/Filesystem_permissions#Notation_of_traditional_Unix_permissions
3243          if (preg_match('#^[^/]([r-][w-][xstST-]){3}#', $longname)) {
3244              switch ($longname[0]) {
3245                  case '-':
3246                      return NET_SFTP_TYPE_REGULAR;
3247                  case 'd':
3248                      return NET_SFTP_TYPE_DIRECTORY;
3249                  case 'l':
3250                      return NET_SFTP_TYPE_SYMLINK;
3251                  default:
3252                      return NET_SFTP_TYPE_SPECIAL;
3253              }
3254          }
3255  
3256          return false;
3257      }
3258  
3259      /**
3260       * Sends SFTP Packets
3261       *
3262       * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
3263       *
3264       * @param int $type
3265       * @param string $data
3266       * @param int $request_id
3267       * @see self::_get_sftp_packet()
3268       * @see self::send_channel_packet()
3269       * @return void
3270       * @access private
3271       */
3272      private function send_sftp_packet($type, $data, $request_id = 1)
3273      {
3274          // in SSH2.php the timeout is cumulative per function call. eg. exec() will
3275          // timeout after 10s. but for SFTP.php it's cumulative per packet
3276          $this->curTimeout = $this->timeout;
3277  
3278          $packet = $this->use_request_id ?
3279              pack('NCNa*', strlen($data) + 5, $type, $request_id, $data) :
3280              pack('NCa*', strlen($data) + 1, $type, $data);
3281  
3282          $start = microtime(true);
3283          $this->send_channel_packet(self::CHANNEL, $packet);
3284          $stop = microtime(true);
3285  
3286          if (defined('NET_SFTP_LOGGING')) {
3287              $packet_type = '-> ' . $this->packet_types[$type] .
3288                             ' (' . round($stop - $start, 4) . 's)';
3289              if (NET_SFTP_LOGGING == self::LOG_REALTIME) {
3290                  switch (PHP_SAPI) {
3291                      case 'cli':
3292                          $start = $stop = "\r\n";
3293                          break;
3294                      default:
3295                          $start = '<pre>';
3296                          $stop = '</pre>';
3297                  }
3298                  echo $start . $this->format_log([$data], [$packet_type]) . $stop;
3299                  @flush();
3300                  @ob_flush();
3301              } else {
3302                  $this->packet_type_log[] = $packet_type;
3303                  if (NET_SFTP_LOGGING == self::LOG_COMPLEX) {
3304                      $this->packet_log[] = $data;
3305                  }
3306              }
3307          }
3308      }
3309  
3310      /**
3311       * Resets a connection for re-use
3312       *
3313       * @param int $reason
3314       * @access private
3315       */
3316      protected function reset_connection($reason)
3317      {
3318          parent::reset_connection($reason);
3319          $this->use_request_id = false;
3320          $this->pwd = false;
3321          $this->requestBuffer = [];
3322      }
3323  
3324      /**
3325       * Receives SFTP Packets
3326       *
3327       * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
3328       *
3329       * Incidentally, the number of SSH_MSG_CHANNEL_DATA messages has no bearing on the number of SFTP packets present.
3330       * There can be one SSH_MSG_CHANNEL_DATA messages containing two SFTP packets or there can be two SSH_MSG_CHANNEL_DATA
3331       * messages containing one SFTP packet.
3332       *
3333       * @see self::_send_sftp_packet()
3334       * @return string
3335       * @access private
3336       */
3337      private function get_sftp_packet($request_id = null)
3338      {
3339          $this->channel_close = false;
3340  
3341          if (isset($request_id) && isset($this->requestBuffer[$request_id])) {
3342              $this->packet_type = $this->requestBuffer[$request_id]['packet_type'];
3343              $temp = $this->requestBuffer[$request_id]['packet'];
3344              unset($this->requestBuffer[$request_id]);
3345              return $temp;
3346          }
3347  
3348          // in SSH2.php the timeout is cumulative per function call. eg. exec() will
3349          // timeout after 10s. but for SFTP.php it's cumulative per packet
3350          $this->curTimeout = $this->timeout;
3351  
3352          $start = microtime(true);
3353  
3354          // SFTP packet length
3355          while (strlen($this->packet_buffer) < 4) {
3356              $temp = $this->get_channel_packet(self::CHANNEL, true);
3357              if ($temp === true) {
3358                  if ($this->channel_status[self::CHANNEL] === NET_SSH2_MSG_CHANNEL_CLOSE) {
3359                      $this->channel_close = true;
3360                  }
3361                  $this->packet_type = false;
3362                  $this->packet_buffer = '';
3363                  return false;
3364              }
3365              $this->packet_buffer .= $temp;
3366          }
3367          if (strlen($this->packet_buffer) < 4) {
3368              throw new \RuntimeException('Packet is too small');
3369          }
3370          extract(unpack('Nlength', Strings::shift($this->packet_buffer, 4)));
3371          /** @var integer $length */
3372  
3373          $tempLength = $length;
3374          $tempLength -= strlen($this->packet_buffer);
3375  
3376          // 256 * 1024 is what SFTP_MAX_MSG_LENGTH is set to in OpenSSH's sftp-common.h
3377          if (!$this->allow_arbitrary_length_packets && !$this->use_request_id && $tempLength > 256 * 1024) {
3378              throw new \RuntimeException('Invalid Size');
3379          }
3380  
3381          // SFTP packet type and data payload
3382          while ($tempLength > 0) {
3383              $temp = $this->get_channel_packet(self::CHANNEL, true);
3384              if (is_bool($temp)) {
3385                  $this->packet_type = false;
3386                  $this->packet_buffer = '';
3387                  return false;
3388              }
3389              $this->packet_buffer .= $temp;
3390              $tempLength -= strlen($temp);
3391          }
3392  
3393          $stop = microtime(true);
3394  
3395          $this->packet_type = ord(Strings::shift($this->packet_buffer));
3396  
3397          if ($this->use_request_id) {
3398              extract(unpack('Npacket_id', Strings::shift($this->packet_buffer, 4))); // remove the request id
3399              $length -= 5; // account for the request id and the packet type
3400          } else {
3401              $length -= 1; // account for the packet type
3402          }
3403  
3404          $packet = Strings::shift($this->packet_buffer, $length);
3405  
3406          if (defined('NET_SFTP_LOGGING')) {
3407              $packet_type = '<- ' . $this->packet_types[$this->packet_type] .
3408                             ' (' . round($stop - $start, 4) . 's)';
3409              if (NET_SFTP_LOGGING == self::LOG_REALTIME) {
3410                  switch (PHP_SAPI) {
3411                      case 'cli':
3412                          $start = $stop = "\r\n";
3413                          break;
3414                      default:
3415                          $start = '<pre>';
3416                          $stop = '</pre>';
3417                  }
3418                  echo $start . $this->format_log([$packet], [$packet_type]) . $stop;
3419                  @flush();
3420                  @ob_flush();
3421              } else {
3422                  $this->packet_type_log[] = $packet_type;
3423                  if (NET_SFTP_LOGGING == self::LOG_COMPLEX) {
3424                      $this->packet_log[] = $packet;
3425                  }
3426              }
3427          }
3428  
3429          if (isset($request_id) && $this->use_request_id && $packet_id != $request_id) {
3430              $this->requestBuffer[$packet_id] = [
3431                  'packet_type' => $this->packet_type,
3432                  'packet' => $packet
3433              ];
3434              return $this->get_sftp_packet($request_id);
3435          }
3436  
3437          return $packet;
3438      }
3439  
3440      /**
3441       * Returns a log of the packets that have been sent and received.
3442       *
3443       * Returns a string if NET_SFTP_LOGGING == self::LOG_COMPLEX, an array if NET_SFTP_LOGGING == self::LOG_SIMPLE and false if !defined('NET_SFTP_LOGGING')
3444       *
3445       * @access public
3446       * @return array|string
3447       */
3448      public function getSFTPLog()
3449      {
3450          if (!defined('NET_SFTP_LOGGING')) {
3451              return false;
3452          }
3453  
3454          switch (NET_SFTP_LOGGING) {
3455              case self::LOG_COMPLEX:
3456                  return $this->format_log($this->packet_log, $this->packet_type_log);
3457                  break;
3458              //case self::LOG_SIMPLE:
3459              default:
3460                  return $this->packet_type_log;
3461          }
3462      }
3463  
3464      /**
3465       * Returns all errors
3466       *
3467       * @return array
3468       * @access public
3469       */
3470      public function getSFTPErrors()
3471      {
3472          return $this->sftp_errors;
3473      }
3474  
3475      /**
3476       * Returns the last error
3477       *
3478       * @return string
3479       * @access public
3480       */
3481      public function getLastSFTPError()
3482      {
3483          return count($this->sftp_errors) ? $this->sftp_errors[count($this->sftp_errors) - 1] : '';
3484      }
3485  
3486      /**
3487       * Get supported SFTP versions
3488       *
3489       * @return array
3490       * @access public
3491       */
3492      public function getSupportedVersions()
3493      {
3494          if (!($this->bitmap & SSH2::MASK_LOGIN)) {
3495              return false;
3496          }
3497  
3498          if (!$this->partial_init) {
3499              $this->partial_init_sftp_connection();
3500          }
3501  
3502          $temp = ['version' => $this->defaultVersion];
3503          if (isset($this->extensions['versions'])) {
3504              $temp['extensions'] = $this->extensions['versions'];
3505          }
3506          return $temp;
3507      }
3508  
3509      /**
3510       * Get supported SFTP versions
3511       *
3512       * @return int|false
3513       * @access public
3514       */
3515      public function getNegotiatedVersion()
3516      {
3517          if (!$this->precheck()) {
3518              return false;
3519          }
3520  
3521          return $this->version;
3522      }
3523  
3524      /**
3525       * Set preferred version
3526       *
3527       * If you're preferred version isn't supported then the highest supported
3528       * version of SFTP will be utilized. Set to null or false or int(0) to
3529       * unset the preferred version
3530       *
3531       * @param int $version
3532       * @access public
3533       */
3534      public function setPreferredVersion($version)
3535      {
3536          $this->preferredVersion = $version;
3537      }
3538  
3539      /**
3540       * Disconnect
3541       *
3542       * @param int $reason
3543       * @return false
3544       * @access protected
3545       */
3546      protected function disconnect_helper($reason)
3547      {
3548          $this->pwd = false;
3549          return parent::disconnect_helper($reason);
3550      }
3551  
3552      /**
3553       * Enable Date Preservation
3554       *
3555       * @access public
3556       */
3557      public function enableDatePreservation()
3558      {
3559          $this->preserveTime = true;
3560      }
3561  
3562      /**
3563       * Disable Date Preservation
3564       *
3565       * @access public
3566       */
3567      public function disableDatePreservation()
3568      {
3569          $this->preserveTime = false;
3570      }
3571  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer