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