[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

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