Subversion Repositories cheapmusic

Rev

Details | Last modification | View Log | RSS feed

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