SSH SFTP Updater Support - Version 0.6

Version Description

  • update phpseclib to latest version
  • make plugin work with 4.2's new modal dialog
Download this release

Release Info

Developer TerraFrost
Plugin Icon wp plugin SSH SFTP Updater Support
Version 0.6
Comparing to
See all releases

Code changes from version 0.6.1 to 0.6

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