Subversion Repositories oidplus

Rev

Rev 1249 | Rev 1308 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

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