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