PluginProbe
ManageWP Worker / 4.9.25
ManageWP Worker v4.9.25
4.9.38 4.9.37 4.9.36 4.9.35 4.9.34 3.8.7 3.8.8 3.9.0 3.9.1 3.9.10 3.9.11 3.9.12 3.9.13 3.9.14 3.9.15 3.9.16 3.9.17 3.9.18 3.9.19 3.9.2 3.9.20 3.9.21 3.9.22 3.9.23 3.9.24 All 73 releases
worker / src / PHPSecLib / Net / SFTP.php

SFTP.php in ManageWP Worker 4.9.25, at src/PHPSecLib/Net/SFTP.php

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