Subversion Repositories oidplus

Rev

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