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