Subversion Repositories oidplus

Rev

Rev 1249 | Rev 1308 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

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