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