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