[ 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 (!strlen($path) || $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 (!is_array($temp)) {
1202                  return false;
1203              }
1204              if ($i === $max) {
1205                  unset($temp[$dir]);
1206                  return true;
1207              }
1208              if (!isset($temp[$dir])) {
1209                  return false;
1210              }
1211              $temp = &$temp[$dir];
1212          }
1213      }
1214  
1215      /**
1216       * Checks cache for path
1217       *
1218       * Mainly used by file_exists
1219       *
1220       * @param string $dir
1221       * @return mixed
1222       * @access private
1223       */
1224      function _query_stat_cache($path)
1225      {
1226          $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
1227  
1228          $temp = &$this->stat_cache;
1229          foreach ($dirs as $dir) {
1230              if (!is_array($temp)) {
1231                  return null;
1232              }
1233              if (!isset($temp[$dir])) {
1234                  return null;
1235              }
1236              $temp = &$temp[$dir];
1237          }
1238          return $temp;
1239      }
1240  
1241      /**
1242       * Returns general information about a file.
1243       *
1244       * Returns an array on success and false otherwise.
1245       *
1246       * @param string $filename
1247       * @return mixed
1248       * @access public
1249       */
1250      function stat($filename)
1251      {
1252          if (!($this->bitmap & SSH2::MASK_LOGIN)) {
1253              return false;
1254          }
1255  
1256          $filename = $this->_realpath($filename);
1257          if ($filename === false) {
1258              return false;
1259          }
1260  
1261          if ($this->use_stat_cache) {
1262              $result = $this->_query_stat_cache($filename);
1263              if (is_array($result) && isset($result['.']) && isset($result['.']->stat)) {
1264                  return $result['.']->stat;
1265              }
1266              if (is_object($result) && isset($result->stat)) {
1267                  return $result->stat;
1268              }
1269          }
1270  
1271          $stat = $this->_stat($filename, NET_SFTP_STAT);
1272          if ($stat === false) {
1273              $this->_remove_from_stat_cache($filename);
1274              return false;
1275          }
1276          if (isset($stat['type'])) {
1277              if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1278                  $filename.= '/.';
1279              }
1280              $this->_update_stat_cache($filename, (object) array('stat' => $stat));
1281              return $stat;
1282          }
1283  
1284          $pwd = $this->pwd;
1285          $stat['type'] = $this->chdir($filename) ?
1286              NET_SFTP_TYPE_DIRECTORY :
1287              NET_SFTP_TYPE_REGULAR;
1288          $this->pwd = $pwd;
1289  
1290          if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1291              $filename.= '/.';
1292          }
1293          $this->_update_stat_cache($filename, (object) array('stat' => $stat));
1294  
1295          return $stat;
1296      }
1297  
1298      /**
1299       * Returns general information about a file or symbolic link.
1300       *
1301       * Returns an array on success and false otherwise.
1302       *
1303       * @param string $filename
1304       * @return mixed
1305       * @access public
1306       */
1307      function lstat($filename)
1308      {
1309          if (!($this->bitmap & SSH2::MASK_LOGIN)) {
1310              return false;
1311          }
1312  
1313          $filename = $this->_realpath($filename);
1314          if ($filename === false) {
1315              return false;
1316          }
1317  
1318          if ($this->use_stat_cache) {
1319              $result = $this->_query_stat_cache($filename);
1320              if (is_array($result) && isset($result['.']) && isset($result['.']->lstat)) {
1321                  return $result['.']->lstat;
1322              }
1323              if (is_object($result) && isset($result->lstat)) {
1324                  return $result->lstat;
1325              }
1326          }
1327  
1328          $lstat = $this->_stat($filename, NET_SFTP_LSTAT);
1329          if ($lstat === false) {
1330              $this->_remove_from_stat_cache($filename);
1331              return false;
1332          }
1333          if (isset($lstat['type'])) {
1334              if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1335                  $filename.= '/.';
1336              }
1337              $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
1338              return $lstat;
1339          }
1340  
1341          $stat = $this->_stat($filename, NET_SFTP_STAT);
1342  
1343          if ($lstat != $stat) {
1344              $lstat = array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK));
1345              $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
1346              return $stat;
1347          }
1348  
1349          $pwd = $this->pwd;
1350          $lstat['type'] = $this->chdir($filename) ?
1351              NET_SFTP_TYPE_DIRECTORY :
1352              NET_SFTP_TYPE_REGULAR;
1353          $this->pwd = $pwd;
1354  
1355          if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1356              $filename.= '/.';
1357          }
1358          $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
1359  
1360          return $lstat;
1361      }
1362  
1363      /**
1364       * Returns general information about a file or symbolic link
1365       *
1366       * Determines information without calling \phpseclib\Net\SFTP::realpath().
1367       * The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT.
1368       *
1369       * @param string $filename
1370       * @param int $type
1371       * @return mixed
1372       * @access private
1373       */
1374      function _stat($filename, $type)
1375      {
1376          // SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
1377          $packet = pack('Na*', strlen($filename), $filename);
1378          if (!$this->_send_sftp_packet($type, $packet)) {
1379              return false;
1380          }
1381  
1382          $response = $this->_get_sftp_packet();
1383          switch ($this->packet_type) {
1384              case NET_SFTP_ATTRS:
1385                  return $this->_parseAttributes($response);
1386              case NET_SFTP_STATUS:
1387                  $this->_logError($response);
1388                  return false;
1389          }
1390  
1391          user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
1392          return false;
1393      }
1394  
1395      /**
1396       * Truncates a file to a given length
1397       *
1398       * @param string $filename
1399       * @param int $new_size
1400       * @return bool
1401       * @access public
1402       */
1403      function truncate($filename, $new_size)
1404      {
1405          $attr = pack('N3', NET_SFTP_ATTR_SIZE, $new_size / 4294967296, $new_size); // 4294967296 == 0x100000000 == 1<<32
1406  
1407          return $this->_setstat($filename, $attr, false);
1408      }
1409  
1410      /**
1411       * Sets access and modification time of file.
1412       *
1413       * If the file does not exist, it will be created.
1414       *
1415       * @param string $filename
1416       * @param int $time
1417       * @param int $atime
1418       * @return bool
1419       * @access public
1420       */
1421      function touch($filename, $time = null, $atime = null)
1422      {
1423          if (!($this->bitmap & SSH2::MASK_LOGIN)) {
1424              return false;
1425          }
1426  
1427          $filename = $this->_realpath($filename);
1428          if ($filename === false) {
1429              return false;
1430          }
1431  
1432          if (!isset($time)) {
1433              $time = time();
1434          }
1435          if (!isset($atime)) {
1436              $atime = $time;
1437          }
1438  
1439          $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL;
1440          $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime);
1441          $packet = pack('Na*Na*', strlen($filename), $filename, $flags, $attr);
1442          if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
1443              return false;
1444          }
1445  
1446          $response = $this->_get_sftp_packet();
1447          switch ($this->packet_type) {
1448              case NET_SFTP_HANDLE:
1449                  return $this->_close_handle(substr($response, 4));
1450              case NET_SFTP_STATUS:
1451                  $this->_logError($response);
1452                  break;
1453              default:
1454                  user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
1455                  return false;
1456          }
1457  
1458          return $this->_setstat($filename, $attr, false);
1459      }
1460  
1461      /**
1462       * Changes file or directory owner
1463       *
1464       * Returns true on success or false on error.
1465       *
1466       * @param string $filename
1467       * @param int $uid
1468       * @param bool $recursive
1469       * @return bool
1470       * @access public
1471       */
1472      function chown($filename, $uid, $recursive = false)
1473      {
1474          // quoting from <http://www.kernel.org/doc/man-pages/online/pages/man2/chown.2.html>,
1475          // "if the owner or group is specified as -1, then that ID is not changed"
1476          $attr = pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1);
1477  
1478          return $this->_setstat($filename, $attr, $recursive);
1479      }
1480  
1481      /**
1482       * Changes file or directory group
1483       *
1484       * Returns true on success or false on error.
1485       *
1486       * @param string $filename
1487       * @param int $gid
1488       * @param bool $recursive
1489       * @return bool
1490       * @access public
1491       */
1492      function chgrp($filename, $gid, $recursive = false)
1493      {
1494          $attr = pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid);
1495  
1496          return $this->_setstat($filename, $attr, $recursive);
1497      }
1498  
1499      /**
1500       * Set permissions on a file.
1501       *
1502       * Returns the new file permissions on success or false on error.
1503       * If $recursive is true than this just returns true or false.
1504       *
1505       * @param int $mode
1506       * @param string $filename
1507       * @param bool $recursive
1508       * @return mixed
1509       * @access public
1510       */
1511      function chmod($mode, $filename, $recursive = false)
1512      {
1513          if (is_string($mode) && is_int($filename)) {
1514              $temp = $mode;
1515              $mode = $filename;
1516              $filename = $temp;
1517          }
1518  
1519          $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
1520          if (!$this->_setstat($filename, $attr, $recursive)) {
1521              return false;
1522          }
1523          if ($recursive) {
1524              return true;
1525          }
1526  
1527          $filename = $this->realpath($filename);
1528          // rather than return what the permissions *should* be, we'll return what they actually are.  this will also
1529          // tell us if the file actually exists.
1530          // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
1531          $packet = pack('Na*', strlen($filename), $filename);
1532          if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) {
1533              return false;
1534          }
1535  
1536          $response = $this->_get_sftp_packet();
1537          switch ($this->packet_type) {
1538              case NET_SFTP_ATTRS:
1539                  $attrs = $this->_parseAttributes($response);
1540                  return $attrs['permissions'];
1541              case NET_SFTP_STATUS:
1542                  $this->_logError($response);
1543                  return false;
1544          }
1545  
1546          user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
1547          return false;
1548      }
1549  
1550      /**
1551       * Sets information about a file
1552       *
1553       * @param string $filename
1554       * @param string $attr
1555       * @param bool $recursive
1556       * @return bool
1557       * @access private
1558       */
1559      function _setstat($filename, $attr, $recursive)
1560      {
1561          if (!($this->bitmap & SSH2::MASK_LOGIN)) {
1562              return false;
1563          }
1564  
1565          $filename = $this->_realpath($filename);
1566          if ($filename === false) {
1567              return false;
1568          }
1569  
1570          $this->_remove_from_stat_cache($filename);
1571  
1572          if ($recursive) {
1573              $i = 0;
1574              $result = $this->_setstat_recursive($filename, $attr, $i);
1575              $this->_read_put_responses($i);
1576              return $result;
1577          }
1578  
1579          // SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to
1580          // SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT.
1581          if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($filename), $filename, $attr))) {
1582              return false;
1583          }
1584  
1585          /*
1586           "Because some systems must use separate system calls to set various attributes, it is possible that a failure
1587            response will be returned, but yet some of the attributes may be have been successfully modified.  If possible,
1588            servers SHOULD avoid this situation; however, clients MUST be aware that this is possible."
1589  
1590            -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6
1591          */
1592          $response = $this->_get_sftp_packet();
1593          if ($this->packet_type != NET_SFTP_STATUS) {
1594              user_error('Expected SSH_FXP_STATUS');
1595              return false;
1596          }
1597  
1598          if (strlen($response) < 4) {
1599              return false;
1600          }
1601          extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1602          if ($status != NET_SFTP_STATUS_OK) {
1603              $this->_logError($response, $status);
1604              return false;
1605          }
1606  
1607          return true;
1608      }
1609  
1610      /**
1611       * Recursively sets information on directories on the SFTP server
1612       *
1613       * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
1614       *
1615       * @param string $path
1616       * @param string $attr
1617       * @param int $i
1618       * @return bool
1619       * @access private
1620       */
1621      function _setstat_recursive($path, $attr, &$i)
1622      {
1623          if (!$this->_read_put_responses($i)) {
1624              return false;
1625          }
1626          $i = 0;
1627          $entries = $this->_list($path, true);
1628  
1629          if ($entries === false) {
1630              return $this->_setstat($path, $attr, false);
1631          }
1632  
1633          // normally $entries would have at least . and .. but it might not if the directories
1634          // permissions didn't allow reading
1635          if (empty($entries)) {
1636              return false;
1637          }
1638  
1639          unset($entries['.'], $entries['..']);
1640          foreach ($entries as $filename => $props) {
1641              if (!isset($props['type'])) {
1642                  return false;
1643              }
1644  
1645              $temp = $path . '/' . $filename;
1646              if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
1647                  if (!$this->_setstat_recursive($temp, $attr, $i)) {
1648                      return false;
1649                  }
1650              } else {
1651                  if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($temp), $temp, $attr))) {
1652                      return false;
1653                  }
1654  
1655                  $i++;
1656  
1657                  if ($i >= NET_SFTP_QUEUE_SIZE) {
1658                      if (!$this->_read_put_responses($i)) {
1659                          return false;
1660                      }
1661                      $i = 0;
1662                  }
1663              }
1664          }
1665  
1666          if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($path), $path, $attr))) {
1667              return false;
1668          }
1669  
1670          $i++;
1671  
1672          if ($i >= NET_SFTP_QUEUE_SIZE) {
1673              if (!$this->_read_put_responses($i)) {
1674                  return false;
1675              }
1676              $i = 0;
1677          }
1678  
1679          return true;
1680      }
1681  
1682      /**
1683       * Return the target of a symbolic link
1684       *
1685       * @param string $link
1686       * @return mixed
1687       * @access public
1688       */
1689      function readlink($link)
1690      {
1691          if (!($this->bitmap & SSH2::MASK_LOGIN)) {
1692              return false;
1693          }
1694  
1695          $link = $this->_realpath($link);
1696  
1697          if (!$this->_send_sftp_packet(NET_SFTP_READLINK, pack('Na*', strlen($link), $link))) {
1698              return false;
1699          }
1700  
1701          $response = $this->_get_sftp_packet();
1702          switch ($this->packet_type) {
1703              case NET_SFTP_NAME:
1704                  break;
1705              case NET_SFTP_STATUS:
1706                  $this->_logError($response);
1707                  return false;
1708              default:
1709                  user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
1710                  return false;
1711          }
1712  
1713          if (strlen($response) < 4) {
1714              return false;
1715          }
1716          extract(unpack('Ncount', $this->_string_shift($response, 4)));
1717          // the file isn't a symlink
1718          if (!$count) {
1719              return false;
1720          }
1721  
1722          if (strlen($response) < 4) {
1723              return false;
1724          }
1725          extract(unpack('Nlength', $this->_string_shift($response, 4)));
1726          return $this->_string_shift($response, $length);
1727      }
1728  
1729      /**
1730       * Create a symlink
1731       *
1732       * symlink() creates a symbolic link to the existing target with the specified name link.
1733       *
1734       * @param string $target
1735       * @param string $link
1736       * @return bool
1737       * @access public
1738       */
1739      function symlink($target, $link)
1740      {
1741          if (!($this->bitmap & SSH2::MASK_LOGIN)) {
1742              return false;
1743          }
1744  
1745          //$target = $this->_realpath($target);
1746          $link = $this->_realpath($link);
1747  
1748          $packet = pack('Na*Na*', strlen($target), $target, strlen($link), $link);
1749          if (!$this->_send_sftp_packet(NET_SFTP_SYMLINK, $packet)) {
1750              return false;
1751          }
1752  
1753          $response = $this->_get_sftp_packet();
1754          if ($this->packet_type != NET_SFTP_STATUS) {
1755              user_error('Expected SSH_FXP_STATUS');
1756              return false;
1757          }
1758  
1759          if (strlen($response) < 4) {
1760              return false;
1761          }
1762          extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1763          if ($status != NET_SFTP_STATUS_OK) {
1764              $this->_logError($response, $status);
1765              return false;
1766          }
1767  
1768          return true;
1769      }
1770  
1771      /**
1772       * Creates a directory.
1773       *
1774       * @param string $dir
1775       * @return bool
1776       * @access public
1777       */
1778      function mkdir($dir, $mode = -1, $recursive = false)
1779      {
1780          if (!($this->bitmap & SSH2::MASK_LOGIN)) {
1781              return false;
1782          }
1783  
1784          $dir = $this->_realpath($dir);
1785  
1786          if ($recursive) {
1787              $dirs = explode('/', preg_replace('#/(?=/)|/$#', '', $dir));
1788              if (empty($dirs[0])) {
1789                  array_shift($dirs);
1790                  $dirs[0] = '/' . $dirs[0];
1791              }
1792              for ($i = 0; $i < count($dirs); $i++) {
1793                  $temp = array_slice($dirs, 0, $i + 1);
1794                  $temp = implode('/', $temp);
1795                  $result = $this->_mkdir_helper($temp, $mode);
1796              }
1797              return $result;
1798          }
1799  
1800          return $this->_mkdir_helper($dir, $mode);
1801      }
1802  
1803      /**
1804       * Helper function for directory creation
1805       *
1806       * @param string $dir
1807       * @return bool
1808       * @access private
1809       */
1810      function _mkdir_helper($dir, $mode)
1811      {
1812          // send SSH_FXP_MKDIR without any attributes (that's what the \0\0\0\0 is doing)
1813          if (!$this->_send_sftp_packet(NET_SFTP_MKDIR, pack('Na*a*', strlen($dir), $dir, "\0\0\0\0"))) {
1814              return false;
1815          }
1816  
1817          $response = $this->_get_sftp_packet();
1818          if ($this->packet_type != NET_SFTP_STATUS) {
1819              user_error('Expected SSH_FXP_STATUS');
1820              return false;
1821          }
1822  
1823          if (strlen($response) < 4) {
1824              return false;
1825          }
1826          extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1827          if ($status != NET_SFTP_STATUS_OK) {
1828              $this->_logError($response, $status);
1829              return false;
1830          }
1831  
1832          if ($mode !== -1) {
1833              $this->chmod($mode, $dir);
1834          }
1835  
1836          return true;
1837      }
1838  
1839      /**
1840       * Removes a directory.
1841       *
1842       * @param string $dir
1843       * @return bool
1844       * @access public
1845       */
1846      function rmdir($dir)
1847      {
1848          if (!($this->bitmap & SSH2::MASK_LOGIN)) {
1849              return false;
1850          }
1851  
1852          $dir = $this->_realpath($dir);
1853          if ($dir === false) {
1854              return false;
1855          }
1856  
1857          if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($dir), $dir))) {
1858              return false;
1859          }
1860  
1861          $response = $this->_get_sftp_packet();
1862          if ($this->packet_type != NET_SFTP_STATUS) {
1863              user_error('Expected SSH_FXP_STATUS');
1864              return false;
1865          }
1866  
1867          if (strlen($response) < 4) {
1868              return false;
1869          }
1870          extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1871          if ($status != NET_SFTP_STATUS_OK) {
1872              // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED?
1873              $this->_logError($response, $status);
1874              return false;
1875          }
1876  
1877          $this->_remove_from_stat_cache($dir);
1878          // the following will do a soft delete, which would be useful if you deleted a file
1879          // and then tried to do a stat on the deleted file. the above, in contrast, does
1880          // a hard delete
1881          //$this->_update_stat_cache($dir, false);
1882  
1883          return true;
1884      }
1885  
1886      /**
1887       * Uploads a file to the SFTP server.
1888       *
1889       * By default, \phpseclib\Net\SFTP::put() does not read from the local filesystem.  $data is dumped directly into $remote_file.
1890       * So, for example, if you set $data to 'filename.ext' and then do \phpseclib\Net\SFTP::get(), you will get a file, twelve bytes
1891       * long, containing 'filename.ext' as its contents.
1892       *
1893       * Setting $mode to self::SOURCE_LOCAL_FILE will change the above behavior.  With self::SOURCE_LOCAL_FILE, $remote_file will
1894       * contain as many bytes as filename.ext does on your local filesystem.  If your filename.ext is 1MB then that is how
1895       * large $remote_file will be, as well.
1896       *
1897       * 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
1898       *
1899       * If $data is a resource then it'll be used as a resource instead.
1900       *
1901       * Currently, only binary mode is supported.  As such, if the line endings need to be adjusted, you will need to take
1902       * care of that, yourself.
1903       *
1904       * $mode can take an additional two parameters - self::RESUME and self::RESUME_START. These are bitwise AND'd with
1905       * $mode. So if you want to resume upload of a 300mb file on the local file system you'd set $mode to the following:
1906       *
1907       * self::SOURCE_LOCAL_FILE | self::RESUME
1908       *
1909       * If you wanted to simply append the full contents of a local file to the full contents of a remote file you'd replace
1910       * self::RESUME with self::RESUME_START.
1911       *
1912       * If $mode & (self::RESUME | self::RESUME_START) then self::RESUME_START will be assumed.
1913       *
1914       * $start and $local_start give you more fine grained control over this process and take precident over self::RESUME
1915       * when they're non-negative. ie. $start could let you write at the end of a file (like self::RESUME) or in the middle
1916       * of one. $local_start could let you start your reading from the end of a file (like self::RESUME_START) or in the
1917       * middle of one.
1918       *
1919       * Setting $local_start to > 0 or $mode | self::RESUME_START doesn't do anything unless $mode | self::SOURCE_LOCAL_FILE.
1920       *
1921       * @param string $remote_file
1922       * @param string|resource $data
1923       * @param int $mode
1924       * @param int $start
1925       * @param int $local_start
1926       * @param callable|null $progressCallback
1927       * @return bool
1928       * @access public
1929       * @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - \phpseclib\Net\SFTP::setMode().
1930       */
1931      function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $local_start = -1, $progressCallback = null)
1932      {
1933          if (!($this->bitmap & SSH2::MASK_LOGIN)) {
1934              return false;
1935          }
1936  
1937          $remote_file = $this->_realpath($remote_file);
1938          if ($remote_file === false) {
1939              return false;
1940          }
1941  
1942          $this->_remove_from_stat_cache($remote_file);
1943  
1944          $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE;
1945          // according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file."
1946          // in practice, it doesn't seem to do that.
1947          //$flags|= ($mode & self::RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE;
1948  
1949          if ($start >= 0) {
1950              $offset = $start;
1951          } elseif ($mode & self::RESUME) {
1952              // if NET_SFTP_OPEN_APPEND worked as it should _size() wouldn't need to be called
1953              $size = $this->size($remote_file);
1954              $offset = $size !== false ? $size : 0;
1955          } else {
1956              $offset = 0;
1957              $flags|= NET_SFTP_OPEN_TRUNCATE;
1958          }
1959  
1960          $packet = pack('Na*N2', strlen($remote_file), $remote_file, $flags, 0);
1961          if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
1962              return false;
1963          }
1964  
1965          $response = $this->_get_sftp_packet();
1966          switch ($this->packet_type) {
1967              case NET_SFTP_HANDLE:
1968                  $handle = substr($response, 4);
1969                  break;
1970              case NET_SFTP_STATUS:
1971                  $this->_logError($response);
1972                  return false;
1973              default:
1974                  user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
1975                  return false;
1976          }
1977  
1978          // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3
1979          $dataCallback = false;
1980          switch (true) {
1981              case $mode & self::SOURCE_CALLBACK:
1982                  if (!is_callable($data)) {
1983                      user_error("\$data should be is_callable() if you specify SOURCE_CALLBACK flag");
1984                  }
1985                  $dataCallback = $data;
1986                  // do nothing
1987                  break;
1988              case is_resource($data):
1989                  $mode = $mode & ~self::SOURCE_LOCAL_FILE;
1990                  $info = stream_get_meta_data($data);
1991                  if ($info['wrapper_type'] == 'PHP' && $info['stream_type'] == 'Input') {
1992                      $fp = fopen('php://memory', 'w+');
1993                      stream_copy_to_stream($data, $fp);
1994                      rewind($fp);
1995                  } else {
1996                      $fp = $data;
1997                  }
1998                  break;
1999              case $mode & self::SOURCE_LOCAL_FILE:
2000                  if (!is_file($data)) {
2001                      user_error("$data is not a valid file");
2002                      return false;
2003                  }
2004                  $fp = @fopen($data, 'rb');
2005                  if (!$fp) {
2006                      return false;
2007                  }
2008          }
2009  
2010          if (isset($fp)) {
2011              $stat = fstat($fp);
2012              $size = !empty($stat) ? $stat['size'] : 0;
2013  
2014              if ($local_start >= 0) {
2015                  fseek($fp, $local_start);
2016                  $size-= $local_start;
2017              }
2018          } elseif ($dataCallback) {
2019              $size = 0;
2020          } else {
2021              $size = strlen($data);
2022          }
2023  
2024          $sent = 0;
2025          $size = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
2026  
2027          $sftp_packet_size = $this->max_sftp_packet;
2028          // make the SFTP packet be exactly the SFTP packet size by including the bytes in the NET_SFTP_WRITE packets "header"
2029          $sftp_packet_size-= strlen($handle) + 25;
2030          $i = $j = 0;
2031          while ($dataCallback || ($size === 0 || $sent < $size)) {
2032              if ($dataCallback) {
2033                  $temp = call_user_func($dataCallback, $sftp_packet_size);
2034                  if (is_null($temp)) {
2035                      break;
2036                  }
2037              } else {
2038                  $temp = isset($fp) ? fread($fp, $sftp_packet_size) : substr($data, $sent, $sftp_packet_size);
2039                  if ($temp === false || $temp === '') {
2040                      break;
2041                  }
2042              }
2043  
2044              $subtemp = $offset + $sent;
2045              $packet = pack('Na*N3a*', strlen($handle), $handle, $subtemp / 4294967296, $subtemp, strlen($temp), $temp);
2046              if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet, $j)) {
2047                  if ($mode & self::SOURCE_LOCAL_FILE) {
2048                      fclose($fp);
2049                  }
2050                  return false;
2051              }
2052              $sent+= strlen($temp);
2053              if (is_callable($progressCallback)) {
2054                  call_user_func($progressCallback, $sent);
2055              }
2056  
2057              $i++;
2058              $j++;
2059  
2060              if ($i == NET_SFTP_UPLOAD_QUEUE_SIZE) {
2061                  if (!$this->_read_put_responses($i)) {
2062                      $i = 0;
2063                      break;
2064                  }
2065                  $i = 0;
2066              }
2067          }
2068  
2069          if (!$this->_read_put_responses($i)) {
2070              if ($mode & self::SOURCE_LOCAL_FILE) {
2071                  fclose($fp);
2072              }
2073              $this->_close_handle($handle);
2074              return false;
2075          }
2076  
2077          if ($mode & self::SOURCE_LOCAL_FILE) {
2078              fclose($fp);
2079          }
2080  
2081          return $this->_close_handle($handle);
2082      }
2083  
2084      /**
2085       * Reads multiple successive SSH_FXP_WRITE responses
2086       *
2087       * Sending an SSH_FXP_WRITE packet and immediately reading its response isn't as efficient as blindly sending out $i
2088       * SSH_FXP_WRITEs, in succession, and then reading $i responses.
2089       *
2090       * @param int $i
2091       * @return bool
2092       * @access private
2093       */
2094      function _read_put_responses($i)
2095      {
2096          while ($i--) {
2097              $response = $this->_get_sftp_packet();
2098              if ($this->packet_type != NET_SFTP_STATUS) {
2099                  user_error('Expected SSH_FXP_STATUS');
2100                  return false;
2101              }
2102  
2103              if (strlen($response) < 4) {
2104                  return false;
2105              }
2106              extract(unpack('Nstatus', $this->_string_shift($response, 4)));
2107              if ($status != NET_SFTP_STATUS_OK) {
2108                  $this->_logError($response, $status);
2109                  break;
2110              }
2111          }
2112  
2113          return $i < 0;
2114      }
2115  
2116      /**
2117       * Close handle
2118       *
2119       * @param string $handle
2120       * @return bool
2121       * @access private
2122       */
2123      function _close_handle($handle)
2124      {
2125          if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
2126              return false;
2127          }
2128  
2129          // "The client MUST release all resources associated with the handle regardless of the status."
2130          //  -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3
2131          $response = $this->_get_sftp_packet();
2132          if ($this->packet_type != NET_SFTP_STATUS) {
2133              user_error('Expected SSH_FXP_STATUS');
2134              return false;
2135          }
2136  
2137          if (strlen($response) < 4) {
2138              return false;
2139          }
2140          extract(unpack('Nstatus', $this->_string_shift($response, 4)));
2141          if ($status != NET_SFTP_STATUS_OK) {
2142              $this->_logError($response, $status);
2143              return false;
2144          }
2145  
2146          return true;
2147      }
2148  
2149      /**
2150       * Downloads a file from the SFTP server.
2151       *
2152       * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
2153       * the operation was unsuccessful.  If $local_file is defined, returns true or false depending on the success of the
2154       * operation.
2155       *
2156       * $offset and $length can be used to download files in chunks.
2157       *
2158       * @param string $remote_file
2159       * @param string $local_file
2160       * @param int $offset
2161       * @param int $length
2162       * @param callable|null $progressCallback
2163       * @return mixed
2164       * @access public
2165       */
2166      function get($remote_file, $local_file = false, $offset = 0, $length = -1, $progressCallback = null)
2167      {
2168          if (!($this->bitmap & SSH2::MASK_LOGIN)) {
2169              return false;
2170          }
2171  
2172          $remote_file = $this->_realpath($remote_file);
2173          if ($remote_file === false) {
2174              return false;
2175          }
2176  
2177          $packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_READ, 0);
2178          if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
2179              return false;
2180          }
2181  
2182          $response = $this->_get_sftp_packet();
2183          switch ($this->packet_type) {
2184              case NET_SFTP_HANDLE:
2185                  $handle = substr($response, 4);
2186                  break;
2187              case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2188                  $this->_logError($response);
2189                  return false;
2190              default:
2191                  user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
2192                  return false;
2193          }
2194  
2195          if (is_resource($local_file)) {
2196              $fp = $local_file;
2197              $stat = fstat($fp);
2198              $res_offset = $stat['size'];
2199          } else {
2200              $res_offset = 0;
2201              if ($local_file !== false) {
2202                  $fp = fopen($local_file, 'wb');
2203                  if (!$fp) {
2204                      return false;
2205                  }
2206              } else {
2207                  $content = '';
2208              }
2209          }
2210  
2211          $fclose_check = $local_file !== false && !is_resource($local_file);
2212  
2213          $start = $offset;
2214          $read = 0;
2215          while (true) {
2216              $i = 0;
2217  
2218              while ($i < NET_SFTP_QUEUE_SIZE && ($length < 0 || $read < $length)) {
2219                  $tempoffset = $start + $read;
2220  
2221                  $packet_size = $length > 0 ? min($this->max_sftp_packet, $length - $read) : $this->max_sftp_packet;
2222  
2223                  $packet = pack('Na*N3', strlen($handle), $handle, $tempoffset / 4294967296, $tempoffset, $packet_size);
2224                  if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet, $i)) {
2225                      if ($fclose_check) {
2226                          fclose($fp);
2227                      }
2228                      return false;
2229                  }
2230                  $packet = null;
2231                  $read+= $packet_size;
2232                  if (is_callable($progressCallback)) {
2233                      call_user_func($progressCallback, $read);
2234                  }
2235                  $i++;
2236              }
2237  
2238              if (!$i) {
2239                  break;
2240              }
2241  
2242              $packets_sent = $i - 1;
2243  
2244              $clear_responses = false;
2245              while ($i > 0) {
2246                  $i--;
2247  
2248                  if ($clear_responses) {
2249                      $this->_get_sftp_packet($packets_sent - $i);
2250                      continue;
2251                  } else {
2252                      $response = $this->_get_sftp_packet($packets_sent - $i);
2253                  }
2254  
2255                  switch ($this->packet_type) {
2256                      case NET_SFTP_DATA:
2257                          $temp = substr($response, 4);
2258                          $offset+= strlen($temp);
2259                          if ($local_file === false) {
2260                              $content.= $temp;
2261                          } else {
2262                              fputs($fp, $temp);
2263                          }
2264                          $temp = null;
2265                          break;
2266                      case NET_SFTP_STATUS:
2267                          // could, in theory, return false if !strlen($content) but we'll hold off for the time being
2268                          $this->_logError($response);
2269                          $clear_responses = true; // don't break out of the loop yet, so we can read the remaining responses
2270                          break;
2271                      default:
2272                          if ($fclose_check) {
2273                              fclose($fp);
2274                          }
2275                          user_error('Expected SSH_FX_DATA or SSH_FXP_STATUS');
2276                  }
2277                  $response = null;
2278              }
2279  
2280              if ($clear_responses) {
2281                  break;
2282              }
2283          }
2284  
2285          if ($length > 0 && $length <= $offset - $start) {
2286              if ($local_file === false) {
2287                  $content = substr($content, 0, $length);
2288              } else {
2289                  ftruncate($fp, $length + $res_offset);
2290              }
2291          }
2292  
2293          if ($fclose_check) {
2294              fclose($fp);
2295          }
2296  
2297          if (!$this->_close_handle($handle)) {
2298              return false;
2299          }
2300  
2301          // if $content isn't set that means a file was written to
2302          return isset($content) ? $content : true;
2303      }
2304  
2305      /**
2306       * Deletes a file on the SFTP server.
2307       *
2308       * @param string $path
2309       * @param bool $recursive
2310       * @return bool
2311       * @access public
2312       */
2313      function delete($path, $recursive = true)
2314      {
2315          if (!($this->bitmap & SSH2::MASK_LOGIN)) {
2316              return false;
2317          }
2318  
2319          if (is_object($path)) {
2320              // It's an object. Cast it as string before we check anything else.
2321              $path = (string) $path;
2322          }
2323  
2324          if (!is_string($path) || $path == '') {
2325              return false;
2326          }
2327  
2328          $path = $this->_realpath($path);
2329          if ($path === false) {
2330              return false;
2331          }
2332  
2333          // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
2334          if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path))) {
2335              return false;
2336          }
2337  
2338          $response = $this->_get_sftp_packet();
2339          if ($this->packet_type != NET_SFTP_STATUS) {
2340              user_error('Expected SSH_FXP_STATUS');
2341              return false;
2342          }
2343  
2344          // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2345          if (strlen($response) < 4) {
2346              return false;
2347          }
2348          extract(unpack('Nstatus', $this->_string_shift($response, 4)));
2349          if ($status != NET_SFTP_STATUS_OK) {
2350              $this->_logError($response, $status);
2351              if (!$recursive) {
2352                  return false;
2353              }
2354              $i = 0;
2355              $result = $this->_delete_recursive($path, $i);
2356              $this->_read_put_responses($i);
2357              return $result;
2358          }
2359  
2360          $this->_remove_from_stat_cache($path);
2361  
2362          return true;
2363      }
2364  
2365      /**
2366       * Recursively deletes directories on the SFTP server
2367       *
2368       * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
2369       *
2370       * @param string $path
2371       * @param int $i
2372       * @return bool
2373       * @access private
2374       */
2375      function _delete_recursive($path, &$i)
2376      {
2377          if (!$this->_read_put_responses($i)) {
2378              return false;
2379          }
2380          $i = 0;
2381          $entries = $this->_list($path, true);
2382  
2383          // normally $entries would have at least . and .. but it might not if the directories
2384          // permissions didn't allow reading
2385          if (empty($entries)) {
2386              return false;
2387          }
2388  
2389          unset($entries['.'], $entries['..']);
2390          foreach ($entries as $filename => $props) {
2391              if (!isset($props['type'])) {
2392                  return false;
2393              }
2394  
2395              $temp = $path . '/' . $filename;
2396              if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
2397                  if (!$this->_delete_recursive($temp, $i)) {
2398                      return false;
2399                  }
2400              } else {
2401                  if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($temp), $temp))) {
2402                      return false;
2403                  }
2404                  $this->_remove_from_stat_cache($temp);
2405  
2406                  $i++;
2407  
2408                  if ($i >= NET_SFTP_QUEUE_SIZE) {
2409                      if (!$this->_read_put_responses($i)) {
2410                          return false;
2411                      }
2412                      $i = 0;
2413                  }
2414              }
2415          }
2416  
2417          if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($path), $path))) {
2418              return false;
2419          }
2420          $this->_remove_from_stat_cache($path);
2421  
2422          $i++;
2423  
2424          if ($i >= NET_SFTP_QUEUE_SIZE) {
2425              if (!$this->_read_put_responses($i)) {
2426                  return false;
2427              }
2428              $i = 0;
2429          }
2430  
2431          return true;
2432      }
2433  
2434      /**
2435       * Checks whether a file or directory exists
2436       *
2437       * @param string $path
2438       * @return bool
2439       * @access public
2440       */
2441      function file_exists($path)
2442      {
2443          if ($this->use_stat_cache) {
2444              $path = $this->_realpath($path);
2445  
2446              $result = $this->_query_stat_cache($path);
2447  
2448              if (isset($result)) {
2449                  // return true if $result is an array or if it's an stdClass object
2450                  return $result !== false;
2451              }
2452          }
2453  
2454          return $this->stat($path) !== false;
2455      }
2456  
2457      /**
2458       * Tells whether the filename is a directory
2459       *
2460       * @param string $path
2461       * @return bool
2462       * @access public
2463       */
2464      function is_dir($path)
2465      {
2466          $result = $this->_get_stat_cache_prop($path, 'type');
2467          if ($result === false) {
2468              return false;
2469          }
2470          return $result === NET_SFTP_TYPE_DIRECTORY;
2471      }
2472  
2473      /**
2474       * Tells whether the filename is a regular file
2475       *
2476       * @param string $path
2477       * @return bool
2478       * @access public
2479       */
2480      function is_file($path)
2481      {
2482          $result = $this->_get_stat_cache_prop($path, 'type');
2483          if ($result === false) {
2484              return false;
2485          }
2486          return $result === NET_SFTP_TYPE_REGULAR;
2487      }
2488  
2489      /**
2490       * Tells whether the filename is a symbolic link
2491       *
2492       * @param string $path
2493       * @return bool
2494       * @access public
2495       */
2496      function is_link($path)
2497      {
2498          $result = $this->_get_lstat_cache_prop($path, 'type');
2499          if ($result === false) {
2500              return false;
2501          }
2502          return $result === NET_SFTP_TYPE_SYMLINK;
2503      }
2504  
2505      /**
2506       * Tells whether a file exists and is readable
2507       *
2508       * @param string $path
2509       * @return bool
2510       * @access public
2511       */
2512      function is_readable($path)
2513      {
2514          $path = $this->_realpath($path);
2515  
2516          $packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_READ, 0);
2517          if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
2518              return false;
2519          }
2520  
2521          $response = $this->_get_sftp_packet();
2522          switch ($this->packet_type) {
2523              case NET_SFTP_HANDLE:
2524                  return true;
2525              case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2526                  return false;
2527              default:
2528                  user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
2529                  return false;
2530          }
2531      }
2532  
2533      /**
2534       * Tells whether the filename is writable
2535       *
2536       * @param string $path
2537       * @return bool
2538       * @access public
2539       */
2540      function is_writable($path)
2541      {
2542          $path = $this->_realpath($path);
2543  
2544          $packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_WRITE, 0);
2545          if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
2546              return false;
2547          }
2548  
2549          $response = $this->_get_sftp_packet();
2550          switch ($this->packet_type) {
2551              case NET_SFTP_HANDLE:
2552                  return true;
2553              case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2554                  return false;
2555              default:
2556                  user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
2557                  return false;
2558          }
2559      }
2560  
2561      /**
2562       * Tells whether the filename is writeable
2563       *
2564       * Alias of is_writable
2565       *
2566       * @param string $path
2567       * @return bool
2568       * @access public
2569       */
2570      function is_writeable($path)
2571      {
2572          return $this->is_writable($path);
2573      }
2574  
2575      /**
2576       * Gets last access time of file
2577       *
2578       * @param string $path
2579       * @return mixed
2580       * @access public
2581       */
2582      function fileatime($path)
2583      {
2584          return $this->_get_stat_cache_prop($path, 'atime');
2585      }
2586  
2587      /**
2588       * Gets file modification time
2589       *
2590       * @param string $path
2591       * @return mixed
2592       * @access public
2593       */
2594      function filemtime($path)
2595      {
2596          return $this->_get_stat_cache_prop($path, 'mtime');
2597      }
2598  
2599      /**
2600       * Gets file permissions
2601       *
2602       * @param string $path
2603       * @return mixed
2604       * @access public
2605       */
2606      function fileperms($path)
2607      {
2608          return $this->_get_stat_cache_prop($path, 'permissions');
2609      }
2610  
2611      /**
2612       * Gets file owner
2613       *
2614       * @param string $path
2615       * @return mixed
2616       * @access public
2617       */
2618      function fileowner($path)
2619      {
2620          return $this->_get_stat_cache_prop($path, 'uid');
2621      }
2622  
2623      /**
2624       * Gets file group
2625       *
2626       * @param string $path
2627       * @return mixed
2628       * @access public
2629       */
2630      function filegroup($path)
2631      {
2632          return $this->_get_stat_cache_prop($path, 'gid');
2633      }
2634  
2635      /**
2636       * Gets file size
2637       *
2638       * @param string $path
2639       * @return mixed
2640       * @access public
2641       */
2642      function filesize($path)
2643      {
2644          return $this->_get_stat_cache_prop($path, 'size');
2645      }
2646  
2647      /**
2648       * Gets file type
2649       *
2650       * @param string $path
2651       * @return mixed
2652       * @access public
2653       */
2654      function filetype($path)
2655      {
2656          $type = $this->_get_stat_cache_prop($path, 'type');
2657          if ($type === false) {
2658              return false;
2659          }
2660  
2661          switch ($type) {
2662              case NET_SFTP_TYPE_BLOCK_DEVICE:
2663                  return 'block';
2664              case NET_SFTP_TYPE_CHAR_DEVICE:
2665                  return 'char';
2666              case NET_SFTP_TYPE_DIRECTORY:
2667                  return 'dir';
2668              case NET_SFTP_TYPE_FIFO:
2669                  return 'fifo';
2670              case NET_SFTP_TYPE_REGULAR:
2671                  return 'file';
2672              case NET_SFTP_TYPE_SYMLINK:
2673                  return 'link';
2674              default:
2675                  return false;
2676          }
2677      }
2678  
2679      /**
2680       * Return a stat properity
2681       *
2682       * Uses cache if appropriate.
2683       *
2684       * @param string $path
2685       * @param string $prop
2686       * @return mixed
2687       * @access private
2688       */
2689      function _get_stat_cache_prop($path, $prop)
2690      {
2691          return $this->_get_xstat_cache_prop($path, $prop, 'stat');
2692      }
2693  
2694      /**
2695       * Return an lstat properity
2696       *
2697       * Uses cache if appropriate.
2698       *
2699       * @param string $path
2700       * @param string $prop
2701       * @return mixed
2702       * @access private
2703       */
2704      function _get_lstat_cache_prop($path, $prop)
2705      {
2706          return $this->_get_xstat_cache_prop($path, $prop, 'lstat');
2707      }
2708  
2709      /**
2710       * Return a stat or lstat properity
2711       *
2712       * Uses cache if appropriate.
2713       *
2714       * @param string $path
2715       * @param string $prop
2716       * @return mixed
2717       * @access private
2718       */
2719      function _get_xstat_cache_prop($path, $prop, $type)
2720      {
2721          if ($this->use_stat_cache) {
2722              $path = $this->_realpath($path);
2723  
2724              $result = $this->_query_stat_cache($path);
2725  
2726              if (is_object($result) && isset($result->$type)) {
2727                  return $result->{$type}[$prop];
2728              }
2729          }
2730  
2731          $result = $this->$type($path);
2732  
2733          if ($result === false || !isset($result[$prop])) {
2734              return false;
2735          }
2736  
2737          return $result[$prop];
2738      }
2739  
2740      /**
2741       * Renames a file or a directory on the SFTP server
2742       *
2743       * @param string $oldname
2744       * @param string $newname
2745       * @return bool
2746       * @access public
2747       */
2748      function rename($oldname, $newname)
2749      {
2750          if (!($this->bitmap & SSH2::MASK_LOGIN)) {
2751              return false;
2752          }
2753  
2754          $oldname = $this->_realpath($oldname);
2755          $newname = $this->_realpath($newname);
2756          if ($oldname === false || $newname === false) {
2757              return false;
2758          }
2759  
2760          // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
2761          $packet = pack('Na*Na*', strlen($oldname), $oldname, strlen($newname), $newname);
2762          if (!$this->_send_sftp_packet(NET_SFTP_RENAME, $packet)) {
2763              return false;
2764          }
2765  
2766          $response = $this->_get_sftp_packet();
2767          if ($this->packet_type != NET_SFTP_STATUS) {
2768              user_error('Expected SSH_FXP_STATUS');
2769              return false;
2770          }
2771  
2772          // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2773          if (strlen($response) < 4) {
2774              return false;
2775          }
2776          extract(unpack('Nstatus', $this->_string_shift($response, 4)));
2777          if ($status != NET_SFTP_STATUS_OK) {
2778              $this->_logError($response, $status);
2779              return false;
2780          }
2781  
2782          // don't move the stat cache entry over since this operation could very well change the
2783          // atime and mtime attributes
2784          //$this->_update_stat_cache($newname, $this->_query_stat_cache($oldname));
2785          $this->_remove_from_stat_cache($oldname);
2786          $this->_remove_from_stat_cache($newname);
2787  
2788          return true;
2789      }
2790  
2791      /**
2792       * Parse Attributes
2793       *
2794       * See '7.  File Attributes' of draft-ietf-secsh-filexfer-13 for more info.
2795       *
2796       * @param string $response
2797       * @return array
2798       * @access private
2799       */
2800      function _parseAttributes(&$response)
2801      {
2802          $attr = array();
2803          if (strlen($response) < 4) {
2804              user_error('Malformed file attributes');
2805              return array();
2806          }
2807          extract(unpack('Nflags', $this->_string_shift($response, 4)));
2808          // SFTPv4+ have a type field (a byte) that follows the above flag field
2809          foreach ($this->attributes as $key => $value) {
2810              switch ($flags & $key) {
2811                  case NET_SFTP_ATTR_SIZE: // 0x00000001
2812                      // The size attribute is defined as an unsigned 64-bit integer.
2813                      // The following will use floats on 32-bit platforms, if necessary.
2814                      // As can be seen in the BigInteger class, floats are generally
2815                      // IEEE 754 binary64 "double precision" on such platforms and
2816                      // as such can represent integers of at least 2^50 without loss
2817                      // of precision. Interpreted in filesize, 2^50 bytes = 1024 TiB.
2818                      $attr['size'] = hexdec(bin2hex($this->_string_shift($response, 8)));
2819                      break;
2820                  case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
2821                      if (strlen($response) < 8) {
2822                          user_error('Malformed file attributes');
2823                          return $attr;
2824                      }
2825                      $attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8));
2826                      break;
2827                  case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
2828                      if (strlen($response) < 4) {
2829                          user_error('Malformed file attributes');
2830                          return $attr;
2831                      }
2832                      $attr+= unpack('Npermissions', $this->_string_shift($response, 4));
2833                      // mode == permissions; permissions was the original array key and is retained for bc purposes.
2834                      // mode was added because that's the more industry standard terminology
2835                      $attr+= array('mode' => $attr['permissions']);
2836                      $fileType = $this->_parseMode($attr['permissions']);
2837                      if ($fileType !== false) {
2838                          $attr+= array('type' => $fileType);
2839                      }
2840                      break;
2841                  case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
2842                      if (strlen($response) < 8) {
2843                          user_error('Malformed file attributes');
2844                          return $attr;
2845                      }
2846                      $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8));
2847                      break;
2848                  case NET_SFTP_ATTR_EXTENDED: // 0x80000000
2849                      if (strlen($response) < 4) {
2850                          user_error('Malformed file attributes');
2851                          return $attr;
2852                      }
2853                      extract(unpack('Ncount', $this->_string_shift($response, 4)));
2854                      for ($i = 0; $i < $count; $i++) {
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                          $key = $this->_string_shift($response, $length);
2861                          if (strlen($response) < 4) {
2862                              user_error('Malformed file attributes');
2863                              return $attr;
2864                          }
2865                          extract(unpack('Nlength', $this->_string_shift($response, 4)));
2866                          $attr[$key] = $this->_string_shift($response, $length);
2867                      }
2868              }
2869          }
2870          return $attr;
2871      }
2872  
2873      /**
2874       * Attempt to identify the file type
2875       *
2876       * Quoting the SFTP RFC, "Implementations MUST NOT send bits that are not defined" but they seem to anyway
2877       *
2878       * @param int $mode
2879       * @return int
2880       * @access private
2881       */
2882      function _parseMode($mode)
2883      {
2884          // values come from http://lxr.free-electrons.com/source/include/uapi/linux/stat.h#L12
2885          // see, also, http://linux.die.net/man/2/stat
2886          switch ($mode & 0170000) {// ie. 1111 0000 0000 0000
2887              case 0000000: // no file type specified - figure out the file type using alternative means
2888                  return false;
2889              case 0040000:
2890                  return NET_SFTP_TYPE_DIRECTORY;
2891              case 0100000:
2892                  return NET_SFTP_TYPE_REGULAR;
2893              case 0120000:
2894                  return NET_SFTP_TYPE_SYMLINK;
2895              // new types introduced in SFTPv5+
2896              // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
2897              case 0010000: // named pipe (fifo)
2898                  return NET_SFTP_TYPE_FIFO;
2899              case 0020000: // character special
2900                  return NET_SFTP_TYPE_CHAR_DEVICE;
2901              case 0060000: // block special
2902                  return NET_SFTP_TYPE_BLOCK_DEVICE;
2903              case 0140000: // socket
2904                  return NET_SFTP_TYPE_SOCKET;
2905              case 0160000: // whiteout
2906                  // "SPECIAL should be used for files that are of
2907                  //  a known type which cannot be expressed in the protocol"
2908                  return NET_SFTP_TYPE_SPECIAL;
2909              default:
2910                  return NET_SFTP_TYPE_UNKNOWN;
2911          }
2912      }
2913  
2914      /**
2915       * Parse Longname
2916       *
2917       * SFTPv3 doesn't provide any easy way of identifying a file type.  You could try to open
2918       * a file as a directory and see if an error is returned or you could try to parse the
2919       * SFTPv3-specific longname field of the SSH_FXP_NAME packet.  That's what this function does.
2920       * The result is returned using the
2921       * {@link http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2 SFTPv4 type constants}.
2922       *
2923       * If the longname is in an unrecognized format bool(false) is returned.
2924       *
2925       * @param string $longname
2926       * @return mixed
2927       * @access private
2928       */
2929      function _parseLongname($longname)
2930      {
2931          // http://en.wikipedia.org/wiki/Unix_file_types
2932          // http://en.wikipedia.org/wiki/Filesystem_permissions#Notation_of_traditional_Unix_permissions
2933          if (preg_match('#^[^/]([r-][w-][xstST-]){3}#', $longname)) {
2934              switch ($longname[0]) {
2935                  case '-':
2936                      return NET_SFTP_TYPE_REGULAR;
2937                  case 'd':
2938                      return NET_SFTP_TYPE_DIRECTORY;
2939                  case 'l':
2940                      return NET_SFTP_TYPE_SYMLINK;
2941                  default:
2942                      return NET_SFTP_TYPE_SPECIAL;
2943              }
2944          }
2945  
2946          return false;
2947      }
2948  
2949      /**
2950       * Sends SFTP Packets
2951       *
2952       * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
2953       *
2954       * @param int $type
2955       * @param string $data
2956       * @see self::_get_sftp_packet()
2957       * @see self::_send_channel_packet()
2958       * @return bool
2959       * @access private
2960       */
2961      function _send_sftp_packet($type, $data, $request_id = 1)
2962      {
2963          $packet = $this->use_request_id ?
2964              pack('NCNa*', strlen($data) + 5, $type, $request_id, $data) :
2965              pack('NCa*',  strlen($data) + 1, $type, $data);
2966  
2967          $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
2968          $result = $this->_send_channel_packet(self::CHANNEL, $packet);
2969          $stop = strtok(microtime(), ' ') + strtok('');
2970  
2971          if (defined('NET_SFTP_LOGGING')) {
2972              $packet_type = '-> ' . $this->packet_types[$type] .
2973                             ' (' . round($stop - $start, 4) . 's)';
2974              if (NET_SFTP_LOGGING == self::LOG_REALTIME) {
2975                  echo "<pre>\r\n" . $this->_format_log(array($data), array($packet_type)) . "\r\n</pre>\r\n";
2976                  flush();
2977                  ob_flush();
2978              } else {
2979                  $this->packet_type_log[] = $packet_type;
2980                  if (NET_SFTP_LOGGING == self::LOG_COMPLEX) {
2981                      $this->packet_log[] = $data;
2982                  }
2983              }
2984          }
2985  
2986          return $result;
2987      }
2988  
2989      /**
2990       * Resets a connection for re-use
2991       *
2992       * @param int $reason
2993       * @access private
2994       */
2995      function _reset_connection($reason)
2996      {
2997          parent::_reset_connection($reason);
2998          $this->use_request_id = false;
2999          $this->pwd = false;
3000          $this->requestBuffer = array();
3001      }
3002  
3003      /**
3004       * Receives SFTP Packets
3005       *
3006       * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
3007       *
3008       * Incidentally, the number of SSH_MSG_CHANNEL_DATA messages has no bearing on the number of SFTP packets present.
3009       * There can be one SSH_MSG_CHANNEL_DATA messages containing two SFTP packets or there can be two SSH_MSG_CHANNEL_DATA
3010       * messages containing one SFTP packet.
3011       *
3012       * @see self::_send_sftp_packet()
3013       * @return string
3014       * @access private
3015       */
3016      function _get_sftp_packet($request_id = null)
3017      {
3018          if (isset($request_id) && isset($this->requestBuffer[$request_id])) {
3019              $this->packet_type = $this->requestBuffer[$request_id]['packet_type'];
3020              $temp = $this->requestBuffer[$request_id]['packet'];
3021              unset($this->requestBuffer[$request_id]);
3022              return $temp;
3023          }
3024  
3025          // in SSH2.php the timeout is cumulative per function call. eg. exec() will
3026          // timeout after 10s. but for SFTP.php it's cumulative per packet
3027          $this->curTimeout = $this->timeout;
3028  
3029          $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
3030  
3031          // SFTP packet length
3032          while (strlen($this->packet_buffer) < 4) {
3033              $temp = $this->_get_channel_packet(self::CHANNEL, true);
3034              if (is_bool($temp)) {
3035                  $this->packet_type = false;
3036                  $this->packet_buffer = '';
3037                  return false;
3038              }
3039              $this->packet_buffer.= $temp;
3040          }
3041          if (strlen($this->packet_buffer) < 4) {
3042              return false;
3043          }
3044          extract(unpack('Nlength', $this->_string_shift($this->packet_buffer, 4)));
3045          $tempLength = $length;
3046          $tempLength-= strlen($this->packet_buffer);
3047  
3048  
3049          // 256 * 1024 is what SFTP_MAX_MSG_LENGTH is set to in OpenSSH's sftp-common.h
3050          if ($tempLength > 256 * 1024) {
3051              user_error('Invalid SFTP packet size');
3052              return false;
3053          }
3054  
3055          // SFTP packet type and data payload
3056          while ($tempLength > 0) {
3057              $temp = $this->_get_channel_packet(self::CHANNEL, true);
3058              if (is_bool($temp)) {
3059                  $this->packet_type = false;
3060                  $this->packet_buffer = '';
3061                  return false;
3062              }
3063              $this->packet_buffer.= $temp;
3064              $tempLength-= strlen($temp);
3065          }
3066  
3067          $stop = strtok(microtime(), ' ') + strtok('');
3068  
3069          $this->packet_type = ord($this->_string_shift($this->packet_buffer));
3070  
3071          if ($this->use_request_id) {
3072              extract(unpack('Npacket_id', $this->_string_shift($this->packet_buffer, 4))); // remove the request id
3073              $length-= 5; // account for the request id and the packet type
3074          } else {
3075              $length-= 1; // account for the packet type
3076          }
3077  
3078          $packet = $this->_string_shift($this->packet_buffer, $length);
3079  
3080          if (defined('NET_SFTP_LOGGING')) {
3081              $packet_type = '<- ' . $this->packet_types[$this->packet_type] .
3082                             ' (' . round($stop - $start, 4) . 's)';
3083              if (NET_SFTP_LOGGING == self::LOG_REALTIME) {
3084                  echo "<pre>\r\n" . $this->_format_log(array($packet), array($packet_type)) . "\r\n</pre>\r\n";
3085                  flush();
3086                  ob_flush();
3087              } else {
3088                  $this->packet_type_log[] = $packet_type;
3089                  if (NET_SFTP_LOGGING == self::LOG_COMPLEX) {
3090                      $this->packet_log[] = $packet;
3091                  }
3092              }
3093          }
3094  
3095          if (isset($request_id) && $this->use_request_id && $packet_id != $request_id) {
3096              $this->requestBuffer[$packet_id] = array(
3097                  'packet_type' => $this->packet_type,
3098                  'packet' => $packet
3099              );
3100              return $this->_get_sftp_packet($request_id);
3101          }
3102  
3103          return $packet;
3104      }
3105  
3106      /**
3107       * Returns a log of the packets that have been sent and received.
3108       *
3109       * 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')
3110       *
3111       * @access public
3112       * @return string or Array
3113       */
3114      function getSFTPLog()
3115      {
3116          if (!defined('NET_SFTP_LOGGING')) {
3117              return false;
3118          }
3119  
3120          switch (NET_SFTP_LOGGING) {
3121              case self::LOG_COMPLEX:
3122                  return $this->_format_log($this->packet_log, $this->packet_type_log);
3123                  break;
3124              //case self::LOG_SIMPLE:
3125              default:
3126                  return $this->packet_type_log;
3127          }
3128      }
3129  
3130      /**
3131       * Returns all errors
3132       *
3133       * @return array
3134       * @access public
3135       */
3136      function getSFTPErrors()
3137      {
3138          return $this->sftp_errors;
3139      }
3140  
3141      /**
3142       * Returns the last error
3143       *
3144       * @return string
3145       * @access public
3146       */
3147      function getLastSFTPError()
3148      {
3149          return count($this->sftp_errors) ? $this->sftp_errors[count($this->sftp_errors) - 1] : '';
3150      }
3151  
3152      /**
3153       * Get supported SFTP versions
3154       *
3155       * @return array
3156       * @access public
3157       */
3158      function getSupportedVersions()
3159      {
3160          $temp = array('version' => $this->version);
3161          if (isset($this->extensions['versions'])) {
3162              $temp['extensions'] = $this->extensions['versions'];
3163          }
3164          return $temp;
3165      }
3166  
3167      /**
3168       * Disconnect
3169       *
3170       * @param int $reason
3171       * @return bool
3172       * @access private
3173       */
3174      function _disconnect($reason)
3175      {
3176          $this->pwd = false;
3177          parent::_disconnect($reason);
3178      }
3179  }