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