PluginProbe
ManageWP Worker / 3.9.28
ManageWP Worker v3.9.28
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 / lib / PHPSecLib / Net / SFTP.php

SFTP.php in ManageWP Worker 3.9.28, at lib/PHPSecLib/Net/SFTP.php

2,223 lines 73.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
4 /**
5 * Pure-PHP implementation of SFTP.
6 *
7 * PHP versions 4 and 5
8 *
9 * Currently only supports SFTPv2 and v3, which, according to wikipedia.org, "is the most widely used version,
10 * implemented by the popular OpenSSH SFTP server". If you want SFTPv4/5/6 support, provide me with access
11 * to an SFTPv4/5/6 server.
12 *
13 * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
14 *
15 * Here's a short example of how to use this library:
16 * <code>
17 * <?php
18 * include('Net/SFTP.php');
19 *
20 * $sftp = new Net_SFTP('www.domain.tld');
21 * if (!$sftp->login('username', 'password')) {
22 * exit('Login Failed');
23 * }
24 *
25 * echo $sftp->pwd() . "\r\n";
26 * $sftp->put('filename.ext', 'hello, world!');
27 * print_r($sftp->nlist());
28 * ?>
29 * </code>
30 *
31 * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
32 * of this software and associated documentation files (the "Software"), to deal
33 * in the Software without restriction, including without limitation the rights
34 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
35 * copies of the Software, and to permit persons to whom the Software is
36 * furnished to do so, subject to the following conditions:
37 *
38 * The above copyright notice and this permission notice shall be included in
39 * all copies or substantial portions of the Software.
40 *
41 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
42 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
43 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
44 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
45 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
46 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
47 * THE SOFTWARE.
48 *
49 * @category Net
50 * @package Net_SFTP
51 * @author Jim Wigginton <terrafrost@php.net>
52 * @copyright MMIX Jim Wigginton
53 * @license http://www.opensource.org/licenses/mit-license.html MIT License
54 * @link http://phpseclib.sourceforge.net
55 */
56
57 /**
58 * Include Net_SSH2
59 */
60 if (!class_exists('Net_SSH2')) {
61 require_once('SSH2.php');
62 }
63
64 /**#@+
65 * @access public
66 * @see Net_SFTP::getLog()
67 */
68 /**
69 * Returns the message numbers
70 */
71 define('NET_SFTP_LOG_SIMPLE', NET_SSH2_LOG_SIMPLE);
72 /**
73 * Returns the message content
74 */
75 define('NET_SFTP_LOG_COMPLEX', NET_SSH2_LOG_COMPLEX);
76 /**
77 * Outputs the message content in real-time.
78 */
79 define('NET_SFTP_LOG_REALTIME', 3);
80 /**#@-*/
81
82 /**
83 * SFTP channel constant
84 *
85 * Net_SSH2::exec() uses 0 and Net_SSH2::read() / Net_SSH2::write() use 1.
86 *
87 * @see Net_SSH2::_send_channel_packet()
88 * @see Net_SSH2::_get_channel_packet()
89 * @access private
90 */
91 define('NET_SFTP_CHANNEL', 2);
92
93 /**#@+
94 * @access public
95 * @see Net_SFTP::put()
96 */
97 /**
98 * Reads data from a local file.
99 */
100 define('NET_SFTP_LOCAL_FILE', 1);
101 /**
102 * Reads data from a string.
103 */
104 // this value isn't really used anymore but i'm keeping it reserved for historical reasons
105 define('NET_SFTP_STRING', 2);
106 /**
107 * Resumes an upload
108 */
109 define('NET_SFTP_RESUME', 4);
110 /**#@-*/
111
112 /**
113 * Pure-PHP implementations of SFTP.
114 *
115 * @author Jim Wigginton <terrafrost@php.net>
116 * @version 0.1.0
117 * @access public
118 * @package Net_SFTP
119 */
120 class Net_SFTP extends Net_SSH2 {
121 /**
122 * Packet Types
123 *
124 * @see Net_SFTP::Net_SFTP()
125 * @var Array
126 * @access private
127 */
128 var $packet_types = array();
129
130 /**
131 * Status Codes
132 *
133 * @see Net_SFTP::Net_SFTP()
134 * @var Array
135 * @access private
136 */
137 var $status_codes = array();
138
139 /**
140 * The Request ID
141 *
142 * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
143 * concurrent actions, so it's somewhat academic, here.
144 *
145 * @var Integer
146 * @see Net_SFTP::_send_sftp_packet()
147 * @access private
148 */
149 var $request_id = false;
150
151 /**
152 * The Packet Type
153 *
154 * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
155 * concurrent actions, so it's somewhat academic, here.
156 *
157 * @var Integer
158 * @see Net_SFTP::_get_sftp_packet()
159 * @access private
160 */
161 var $packet_type = -1;
162
163 /**
164 * Packet Buffer
165 *
166 * @var String
167 * @see Net_SFTP::_get_sftp_packet()
168 * @access private
169 */
170 var $packet_buffer = '';
171
172 /**
173 * Extensions supported by the server
174 *
175 * @var Array
176 * @see Net_SFTP::_initChannel()
177 * @access private
178 */
179 var $extensions = array();
180
181 /**
182 * Server SFTP version
183 *
184 * @var Integer
185 * @see Net_SFTP::_initChannel()
186 * @access private
187 */
188 var $version;
189
190 /**
191 * Current working directory
192 *
193 * @var String
194 * @see Net_SFTP::_realpath()
195 * @see Net_SFTP::chdir()
196 * @access private
197 */
198 var $pwd = false;
199
200 /**
201 * Packet Type Log
202 *
203 * @see Net_SFTP::getLog()
204 * @var Array
205 * @access private
206 */
207 var $packet_type_log = array();
208
209 /**
210 * Packet Log
211 *
212 * @see Net_SFTP::getLog()
213 * @var Array
214 * @access private
215 */
216 var $packet_log = array();
217
218 /**
219 * Error information
220 *
221 * @see Net_SFTP::getSFTPErrors()
222 * @see Net_SFTP::getLastSFTPError()
223 * @var String
224 * @access private
225 */
226 var $sftp_errors = array();
227
228 /**
229 * Directory Cache
230 *
231 * Rather than always having to open a directory and close it immediately there after to see if a file is a directory or
232 * rather than always
233 *
234 * @see Net_SFTP::_save_dir()
235 * @see Net_SFTP::_remove_dir()
236 * @see Net_SFTP::_is_dir()
237 * @var Array
238 * @access private
239 */
240 var $dirs = array();
241
242 /**
243 * Default Constructor.
244 *
245 * Connects to an SFTP server
246 *
247 * @param String $host
248 * @param optional Integer $port
249 * @param optional Integer $timeout
250 * @return Net_SFTP
251 * @access public
252 */
253 function Net_SFTP($host, $port = 22, $timeout = 10)
254 {
255 parent::Net_SSH2($host, $port, $timeout);
256 $this->packet_types = array(
257 1 => 'NET_SFTP_INIT',
258 2 => 'NET_SFTP_VERSION',
259 /* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+:
260 SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1
261 pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */
262 3 => 'NET_SFTP_OPEN',
263 4 => 'NET_SFTP_CLOSE',
264 5 => 'NET_SFTP_READ',
265 6 => 'NET_SFTP_WRITE',
266 7 => 'NET_SFTP_LSTAT',
267 9 => 'NET_SFTP_SETSTAT',
268 11 => 'NET_SFTP_OPENDIR',
269 12 => 'NET_SFTP_READDIR',
270 13 => 'NET_SFTP_REMOVE',
271 14 => 'NET_SFTP_MKDIR',
272 15 => 'NET_SFTP_RMDIR',
273 16 => 'NET_SFTP_REALPATH',
274 17 => 'NET_SFTP_STAT',
275 /* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+:
276 SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
277 pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */
278 18 => 'NET_SFTP_RENAME',
279
280 101=> 'NET_SFTP_STATUS',
281 102=> 'NET_SFTP_HANDLE',
282 /* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+:
283 SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4
284 pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */
285 103=> 'NET_SFTP_DATA',
286 104=> 'NET_SFTP_NAME',
287 105=> 'NET_SFTP_ATTRS',
288
289 200=> 'NET_SFTP_EXTENDED'
290 );
291 $this->status_codes = array(
292 0 => 'NET_SFTP_STATUS_OK',
293 1 => 'NET_SFTP_STATUS_EOF',
294 2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
295 3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
296 4 => 'NET_SFTP_STATUS_FAILURE',
297 5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
298 6 => 'NET_SFTP_STATUS_NO_CONNECTION',
299 7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
300 8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
301 9 => 'NET_SFTP_STATUS_INVALID_HANDLE',
302 10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
303 11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
304 12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
305 13 => 'NET_SFTP_STATUS_NO_MEDIA',
306 14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
307 15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
308 16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
309 17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
310 18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
311 19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
312 20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
313 21 => 'NET_SFTP_STATUS_LINK_LOOP',
314 22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
315 23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
316 24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
317 25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
318 26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
319 27 => 'NET_SFTP_STATUS_DELETE_PENDING',
320 28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
321 29 => 'NET_SFTP_STATUS_OWNER_INVALID',
322 30 => 'NET_SFTP_STATUS_GROUP_INVALID',
323 31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK'
324 );
325 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
326 // the order, in this case, matters quite a lot - see Net_SFTP::_parseAttributes() to understand why
327 $this->attributes = array(
328 0x00000001 => 'NET_SFTP_ATTR_SIZE',
329 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
330 0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
331 0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
332 // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
333 // yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in
334 // two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000.
335 // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
336 -1 << 31 => 'NET_SFTP_ATTR_EXTENDED'
337 );
338 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
339 // the flag definitions change somewhat in SFTPv5+. if SFTPv5+ support is added to this library, maybe name
340 // the array for that $this->open5_flags and similarily alter the constant names.
341 $this->open_flags = array(
342 0x00000001 => 'NET_SFTP_OPEN_READ',
343 0x00000002 => 'NET_SFTP_OPEN_WRITE',
344 0x00000004 => 'NET_SFTP_OPEN_APPEND',
345 0x00000008 => 'NET_SFTP_OPEN_CREATE',
346 0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
347 0x00000020 => 'NET_SFTP_OPEN_EXCL'
348 );
349 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
350 // see Net_SFTP::_parseLongname() for an explanation
351 $this->file_types = array(
352 1 => 'NET_SFTP_TYPE_REGULAR',
353 2 => 'NET_SFTP_TYPE_DIRECTORY',
354 3 => 'NET_SFTP_TYPE_SYMLINK',
355 4 => 'NET_SFTP_TYPE_SPECIAL',
356 5 => 'NET_SFTP_TYPE_UNKNOWN',
357 // the followin types were first defined for use in SFTPv5+
358 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
359 6 => 'NET_SFTP_TYPE_SOCKET',
360 7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
361 8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
362 9 => 'NET_SFTP_TYPE_FIFO'
363 );
364 $this->_define_array(
365 $this->packet_types,
366 $this->status_codes,
367 $this->attributes,
368 $this->open_flags,
369 $this->file_types
370 );
371 }
372
373 /**
374 * Login
375 *
376 * @param String $username
377 * @param optional String $password
378 * @return Boolean
379 * @access public
380 */
381 function login($username)
382 {
383 $args = func_get_args();
384 if (!call_user_func_array(array('Net_SSH2', 'login'), $args)) {
385 return false;
386 }
387
388 $this->window_size_server_to_client[NET_SFTP_CHANNEL] = $this->window_size;
389
390 $packet = pack('CNa*N3',
391 NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', NET_SFTP_CHANNEL, $this->window_size, 0x4000);
392
393 if (!$this->_send_binary_packet($packet)) {
394 return false;
395 }
396
397 $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;
398
399 $response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
400 if ($response === false) {
401 return false;
402 }
403
404 $packet = pack('CNNa*CNa*',
405 NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SFTP_CHANNEL], strlen('subsystem'), 'subsystem', 1, strlen('sftp'), 'sftp');
406 if (!$this->_send_binary_packet($packet)) {
407 return false;
408 }
409
410 $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
411
412 $response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
413 if ($response === false) {
414 // from PuTTY's psftp.exe
415 $command = "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" .
416 "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" .
417 "exec sftp-server";
418 // 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
419 // is redundant
420 $packet = pack('CNNa*CNa*',
421 NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SFTP_CHANNEL], strlen('exec'), 'exec', 1, strlen($command), $command);
422 if (!$this->_send_binary_packet($packet)) {
423 return false;
424 }
425
426 $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
427
428 $response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
429 if ($response === false) {
430 return false;
431 }
432 }
433
434 $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;
435
436 if (!$this->_send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) {
437 return false;
438 }
439
440 $response = $this->_get_sftp_packet();
441 if ($this->packet_type != NET_SFTP_VERSION) {
442 user_error('Expected SSH_FXP_VERSION');
443 return false;
444 }
445
446 extract(unpack('Nversion', $this->_string_shift($response, 4)));
447 $this->version = $version;
448 while (!empty($response)) {
449 extract(unpack('Nlength', $this->_string_shift($response, 4)));
450 $key = $this->_string_shift($response, $length);
451 extract(unpack('Nlength', $this->_string_shift($response, 4)));
452 $value = $this->_string_shift($response, $length);
453 $this->extensions[$key] = $value;
454 }
455
456 /*
457 SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com',
458 however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's
459 not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for
460 one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that
461 'newline@vandyke.com' would.
462 */
463 /*
464 if (isset($this->extensions['newline@vandyke.com'])) {
465 $this->extensions['newline'] = $this->extensions['newline@vandyke.com'];
466 unset($this->extensions['newline@vandyke.com']);
467 }
468 */
469
470 $this->request_id = 1;
471
472 /*
473 A Note on SFTPv4/5/6 support:
474 <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.1> states the following:
475
476 "If the client wishes to interoperate with servers that support noncontiguous version
477 numbers it SHOULD send '3'"
478
479 Given that the server only sends its version number after the client has already done so, the above
480 seems to be suggesting that v3 should be the default version. This makes sense given that v3 is the
481 most popular.
482
483 <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.5> states the following;
484
485 "If the server did not send the "versions" extension, or the version-from-list was not included, the
486 server MAY send a status response describing the failure, but MUST then close the channel without
487 processing any further requests."
488
489 So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and
490 a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4? If it only implements
491 v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed
492 in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what Net_SFTP would do is close the
493 channel and reopen it with a new and updated SSH_FXP_INIT packet.
494 */
495 switch ($this->version) {
496 case 2:
497 case 3:
498 break;
499 default:
500 return false;
501 }
502
503 $this->pwd = $this->_realpath('.');
504
505 $this->_save_dir($this->pwd);
506
507 return true;
508 }
509
510 /**
511 * Returns the current directory name
512 *
513 * @return Mixed
514 * @access public
515 */
516 function pwd()
517 {
518 return $this->pwd;
519 }
520
521 /**
522 * Logs errors
523 *
524 * @param String $response
525 * @param optional Integer $status
526 * @access public
527 */
528 function _logError($response, $status = -1)
529 {
530 if ($status == -1) {
531 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
532 }
533
534 $error = $this->status_codes[$status];
535
536 if ($this->version > 2) {
537 extract(unpack('Nlength', $this->_string_shift($response, 4)));
538 $this->sftp_errors[] = $error . ': ' . $this->_string_shift($response, $length);
539 } else {
540 $this->sftp_errors[] = $error;
541 }
542 }
543
544 /**
545 * Canonicalize the Server-Side Path Name
546 *
547 * SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it. Returns
548 * the absolute (canonicalized) path.
549 *
550 * @see Net_SFTP::chdir()
551 * @param String $path
552 * @return Mixed
553 * @access private
554 */
555 function _realpath($path)
556 {
557 if ($this->pwd === false) {
558 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
559 if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($path), $path))) {
560 return false;
561 }
562
563 $response = $this->_get_sftp_packet();
564 switch ($this->packet_type) {
565 case NET_SFTP_NAME:
566 // although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following
567 // should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks
568 // at is the first part and that part is defined the same in SFTP versions 3 through 6.
569 $this->_string_shift($response, 4); // skip over the count - it should be 1, anyway
570 extract(unpack('Nlength', $this->_string_shift($response, 4)));
571 return $this->_string_shift($response, $length);
572 case NET_SFTP_STATUS:
573 $this->_logError($response);
574 return false;
575 default:
576 user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
577 return false;
578 }
579 }
580
581 if ($path[0] != '/') {
582 $path = $this->pwd . '/' . $path;
583 }
584
585 $path = explode('/', $path);
586 $new = array();
587 foreach ($path as $dir) {
588 if (!strlen($dir)) {
589 continue;
590 }
591 switch ($dir) {
592 case '..':
593 array_pop($new);
594 case '.':
595 break;
596 default:
597 $new[] = $dir;
598 }
599 }
600
601 return '/' . implode('/', $new);
602 }
603
604 /**
605 * Changes the current directory
606 *
607 * @param String $dir
608 * @return Boolean
609 * @access public
610 */
611 function chdir($dir)
612 {
613 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
614 return false;
615 }
616
617 if ($dir[strlen($dir) - 1] != '/') {
618 $dir.= '/';
619 }
620
621 $dir = $this->_realpath($dir);
622
623 // confirm that $dir is, in fact, a valid directory
624 if ($this->_is_dir($dir)) {
625 $this->pwd = $dir;
626 return true;
627 }
628
629 // we could do a stat on the alleged $dir to see if it's a directory but that doesn't tell us
630 // the currently logged in user has the appropriate permissions or not. maybe you could see if
631 // the file's uid / gid match the currently logged in user's uid / gid but how there's no easy
632 // way to get those with SFTP
633
634 if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
635 return false;
636 }
637
638 // see Net_SFTP::nlist() for a more thorough explanation of the following
639 $response = $this->_get_sftp_packet();
640 switch ($this->packet_type) {
641 case NET_SFTP_HANDLE:
642 $handle = substr($response, 4);
643 break;
644 case NET_SFTP_STATUS:
645 $this->_logError($response);
646 return false;
647 default:
648 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
649 return false;
650 }
651
652 if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
653 return false;
654 }
655
656 $response = $this->_get_sftp_packet();
657 if ($this->packet_type != NET_SFTP_STATUS) {
658 user_error('Expected SSH_FXP_STATUS');
659 return false;
660 }
661
662 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
663 if ($status != NET_SFTP_STATUS_OK) {
664 $this->_logError($response, $status);
665 return false;
666 }
667
668 $this->_save_dir($dir);
669
670 $this->pwd = $dir;
671 return true;
672 }
673
674 /**
675 * Returns a list of files in the given directory
676 *
677 * @param optional String $dir
678 * @return Mixed
679 * @access public
680 */
681 function nlist($dir = '.')
682 {
683 return $this->_list($dir, false);
684 }
685
686 /**
687 * Returns a detailed list of files in the given directory
688 *
689 * @param optional String $dir
690 * @return Mixed
691 * @access public
692 */
693 function rawlist($dir = '.')
694 {
695 return $this->_list($dir, true);
696 }
697
698 /**
699 * Reads a list, be it detailed or not, of files in the given directory
700 *
701 * $realpath exists because, in the case of the recursive deletes and recursive chmod's $realpath has already
702 * been calculated.
703 *
704 * @param String $dir
705 * @param optional Boolean $raw
706 * @param optional Boolean $realpath
707 * @return Mixed
708 * @access private
709 */
710 function _list($dir, $raw = true, $realpath = true)
711 {
712 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
713 return false;
714 }
715
716 $dir = $this->_realpath($dir . '/');
717 if ($dir === false) {
718 return false;
719 }
720
721 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2
722 if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
723 return false;
724 }
725
726 $response = $this->_get_sftp_packet();
727 switch ($this->packet_type) {
728 case NET_SFTP_HANDLE:
729 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2
730 // since 'handle' is the last field in the SSH_FXP_HANDLE packet, we'll just remove the first four bytes that
731 // represent the length of the string and leave it at that
732 $handle = substr($response, 4);
733 break;
734 case NET_SFTP_STATUS:
735 // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
736 $this->_logError($response);
737 return false;
738 default:
739 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
740 return false;
741 }
742
743 $this->_save_dir($dir);
744
745 $contents = array();
746 while (true) {
747 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2
748 // why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many
749 // SSH_MSG_CHANNEL_DATA messages is not known to me.
750 if (!$this->_send_sftp_packet(NET_SFTP_READDIR, pack('Na*', strlen($handle), $handle))) {
751 return false;
752 }
753
754 $response = $this->_get_sftp_packet();
755 switch ($this->packet_type) {
756 case NET_SFTP_NAME:
757 extract(unpack('Ncount', $this->_string_shift($response, 4)));
758 for ($i = 0; $i < $count; $i++) {
759 extract(unpack('Nlength', $this->_string_shift($response, 4)));
760 $shortname = $this->_string_shift($response, $length);
761 extract(unpack('Nlength', $this->_string_shift($response, 4)));
762 $longname = $this->_string_shift($response, $length);
763 $attributes = $this->_parseAttributes($response);
764 if (!isset($attributes['type'])) {
765 $fileType = $this->_parseLongname($longname);
766 if ($fileType) {
767 $attributes['type'] = $fileType;
768 }
769 }
770 if (!$raw) {
771 $contents[] = $shortname;
772 } else {
773 $contents[$shortname] = $attributes;
774 }
775
776 if (isset($attributes['type']) && $attributes['type'] == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) {
777 $this->_save_dir($dir . '/' . $shortname);
778 }
779 // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the
780 // final SSH_FXP_STATUS packet should tell us that, already.
781 }
782 break;
783 case NET_SFTP_STATUS:
784 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
785 if ($status != NET_SFTP_STATUS_EOF) {
786 $this->_logError($response, $status);
787 return false;
788 }
789 break 2;
790 default:
791 user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
792 return false;
793 }
794 }
795
796 if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
797 return false;
798 }
799
800 // "The client MUST release all resources associated with the handle regardless of the status."
801 // -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3
802 $response = $this->_get_sftp_packet();
803 if ($this->packet_type != NET_SFTP_STATUS) {
804 user_error('Expected SSH_FXP_STATUS');
805 return false;
806 }
807
808 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
809 if ($status != NET_SFTP_STATUS_OK) {
810 $this->_logError($response, $status);
811 return false;
812 }
813
814 return $contents;
815 }
816
817 /**
818 * Returns the file size, in bytes, or false, on failure
819 *
820 * Files larger than 4GB will show up as being exactly 4GB.
821 *
822 * @param String $filename
823 * @return Mixed
824 * @access public
825 */
826 function size($filename)
827 {
828 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
829 return false;
830 }
831
832 $filename = $this->_realpath($filename);
833 if ($filename === false) {
834 return false;
835 }
836
837 return $this->_size($filename);
838 }
839
840 /**
841 * Save directories to cache
842 *
843 * @param String $dir
844 * @access private
845 */
846 function _save_dir($dir)
847 {
848 // preg_replace('#^/|/(?=/)|/$#', '', $dir) == str_replace('//', '/', trim($dir, '/'))
849 $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir));
850
851 $temp = &$this->dirs;
852 foreach ($dirs as $dir) {
853 if (!isset($temp[$dir])) {
854 $temp[$dir] = array();
855 }
856 $temp = &$temp[$dir];
857 }
858 }
859
860 /**
861 * Remove directories from cache
862 *
863 * @param String $dir
864 * @access private
865 */
866 function _remove_dir($dir)
867 {
868 $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir));
869
870 $temp = &$this->dirs;
871 foreach ($dirs as $dir) {
872 if ($dir == end($dirs)) {
873 unset($temp[$dir]);
874 return true;
875 }
876 if (!isset($temp[$dir])) {
877 return false;
878 }
879 $temp = &$temp[$dir];
880 }
881 }
882
883 /**
884 * Checks cache for directory
885 *
886 * Mainly used by chdir, which is, in turn, also used for determining whether or not an individual
887 * file is a directory or not by stat() and lstat()
888 *
889 * @param String $dir
890 * @access private
891 */
892 function _is_dir($dir)
893 {
894 $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir));
895
896 $temp = &$this->dirs;
897 foreach ($dirs as $dir) {
898 if (!isset($temp[$dir])) {
899 return false;
900 }
901 $temp = &$temp[$dir];
902 }
903 return true;
904 }
905
906 /**
907 * Returns general information about a file.
908 *
909 * Returns an array on success and false otherwise.
910 *
911 * @param String $filename
912 * @return Mixed
913 * @access public
914 */
915 function stat($filename)
916 {
917 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
918 return false;
919 }
920
921 $filename = $this->_realpath($filename);
922 if ($filename === false) {
923 return false;
924 }
925
926 $stat = $this->_stat($filename, NET_SFTP_STAT);
927 if ($stat === false) {
928 return false;
929 }
930 if (isset($stat['type'])) {
931 return $stat;
932 }
933
934 $pwd = $this->pwd;
935 $stat['type'] = $this->chdir($filename) ?
936 NET_SFTP_TYPE_DIRECTORY :
937 NET_SFTP_TYPE_REGULAR;
938 $this->pwd = $pwd;
939
940 return $stat;
941 }
942
943 /**
944 * Returns general information about a file or symbolic link.
945 *
946 * Returns an array on success and false otherwise.
947 *
948 * @param String $filename
949 * @return Mixed
950 * @access public
951 */
952 function lstat($filename)
953 {
954 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
955 return false;
956 }
957
958 $filename = $this->_realpath($filename);
959 if ($filename === false) {
960 return false;
961 }
962
963 $lstat = $this->_stat($filename, NET_SFTP_LSTAT);
964 if ($lstat === false) {
965 return false;
966 }
967 if (isset($lstat['type'])) {
968 return $lstat;
969 }
970
971 $stat = $this->_stat($filename, NET_SFTP_STAT);
972
973 if ($lstat != $stat) {
974 return array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK));
975 }
976
977 $pwd = $this->pwd;
978 $lstat['type'] = $this->chdir($filename) ?
979 NET_SFTP_TYPE_DIRECTORY :
980 NET_SFTP_TYPE_REGULAR;
981 $this->pwd = $pwd;
982
983 return $lstat;
984 }
985
986 /**
987 * Returns general information about a file or symbolic link
988 *
989 * Determines information without calling Net_SFTP::_realpath().
990 * The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT.
991 *
992 * @param String $filename
993 * @param Integer $type
994 * @return Mixed
995 * @access private
996 */
997 function _stat($filename, $type)
998 {
999 // SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
1000 $packet = pack('Na*', strlen($filename), $filename);
1001 if (!$this->_send_sftp_packet($type, $packet)) {
1002 return false;
1003 }
1004
1005 $response = $this->_get_sftp_packet();
1006 switch ($this->packet_type) {
1007 case NET_SFTP_ATTRS:
1008 return $this->_parseAttributes($response);
1009 case NET_SFTP_STATUS:
1010 $this->_logError($response);
1011 return false;
1012 }
1013
1014 user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
1015 return false;
1016 }
1017
1018 /**
1019 * Returns the file size, in bytes, or false, on failure
1020 *
1021 * Determines the size without calling Net_SFTP::_realpath()
1022 *
1023 * @param String $filename
1024 * @return Mixed
1025 * @access private
1026 */
1027 function _size($filename)
1028 {
1029 $result = $this->_stat($filename, NET_SFTP_STAT);
1030 if ($result === false) {
1031 return false;
1032 }
1033 return isset($result['size']) ? $result['size'] : -1;
1034 }
1035
1036 /**
1037 * Truncates a file to a given length
1038 *
1039 * @param String $filename
1040 * @param Integer $new_size
1041 * @return Boolean
1042 * @access public
1043 */
1044 function truncate($filename, $new_size)
1045 {
1046 $attr = pack('N3', NET_SFTP_ATTR_SIZE, $new_size / 0x100000000, $new_size);
1047
1048 return $this->_setstat($filename, $attr, false);
1049 }
1050
1051 /**
1052 * Sets access and modification time of file.
1053 *
1054 * If the file does not exist, it will be created.
1055 *
1056 * @param String $filename
1057 * @param optional Integer $time
1058 * @param optional Integer $atime
1059 * @return Boolean
1060 * @access public
1061 */
1062 function touch($filename, $time = NULL, $atime = NULL)
1063 {
1064 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1065 return false;
1066 }
1067
1068 $filename = $this->_realpath($filename);
1069 if ($filename === false) {
1070 return false;
1071 }
1072
1073 if (!isset($time)) {
1074 $time = time();
1075 }
1076 if (!isset($atime)) {
1077 $atime = $time;
1078 }
1079
1080 $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL;
1081 $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime);
1082 $packet = pack('Na*Na*', strlen($filename), $filename, $flags, $attr);
1083 if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
1084 return false;
1085 }
1086
1087 $response = $this->_get_sftp_packet();
1088 switch ($this->packet_type) {
1089 case NET_SFTP_HANDLE:
1090 $handle = substr($response, 4);
1091
1092 if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
1093 return false;
1094 }
1095
1096 $response = $this->_get_sftp_packet();
1097 if ($this->packet_type != NET_SFTP_STATUS) {
1098 user_error('Expected SSH_FXP_STATUS');
1099 return false;
1100 }
1101
1102 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1103 if ($status != NET_SFTP_STATUS_OK) {
1104 $this->_logError($response, $status);
1105 return false;
1106 }
1107
1108 return true;
1109 case NET_SFTP_STATUS:
1110 $this->_logError($response);
1111 break;
1112 default:
1113 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
1114 return false;
1115 }
1116
1117 return $this->_setstat($filename, $attr, false);
1118 }
1119
1120 /**
1121 * Changes file or directory owner
1122 *
1123 * Returns TRUE on success or FALSE on error.
1124 *
1125 * @param String $filename
1126 * @param Integer $uid
1127 * @param optional Boolean $recursive
1128 * @return Boolean
1129 * @access public
1130 */
1131 function chown($filename, $uid, $recursive = false)
1132 {
1133 // quoting from <http://www.kernel.org/doc/man-pages/online/pages/man2/chown.2.html>,
1134 // "if the owner or group is specified as -1, then that ID is not changed"
1135 $attr = pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1);
1136
1137 return $this->_setstat($filename, $attr, $recursive);
1138 }
1139
1140 /**
1141 * Changes file or directory group
1142 *
1143 * Returns TRUE on success or FALSE on error.
1144 *
1145 * @param String $filename
1146 * @param Integer $gid
1147 * @param optional Boolean $recursive
1148 * @return Boolean
1149 * @access public
1150 */
1151 function chgrp($filename, $gid, $recursive = false)
1152 {
1153 $attr = pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid);
1154
1155 return $this->_setstat($filename, $attr, $recursive);
1156 }
1157
1158 /**
1159 * Set permissions on a file.
1160 *
1161 * Returns the new file permissions on success or FALSE on error.
1162 * If $recursive is true than this just returns TRUE or FALSE.
1163 *
1164 * @param Integer $mode
1165 * @param String $filename
1166 * @param optional Boolean $recursive
1167 * @return Mixed
1168 * @access public
1169 */
1170 function chmod($mode, $filename, $recursive = false)
1171 {
1172 if (is_string($mode) && is_int($filename)) {
1173 $temp = $mode;
1174 $mode = $filename;
1175 $filename = $temp;
1176 }
1177
1178 $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
1179 if (!$this->_setstat($filename, $attr, $recursive)) {
1180 return false;
1181 }
1182 if ($recursive) {
1183 return true;
1184 }
1185
1186 // rather than return what the permissions *should* be, we'll return what they actually are. this will also
1187 // tell us if the file actually exists.
1188 // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
1189 $packet = pack('Na*', strlen($filename), $filename);
1190 if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) {
1191 return false;
1192 }
1193
1194 $response = $this->_get_sftp_packet();
1195 switch ($this->packet_type) {
1196 case NET_SFTP_ATTRS:
1197 $attrs = $this->_parseAttributes($response);
1198 return $attrs['permissions'];
1199 case NET_SFTP_STATUS:
1200 $this->_logError($response);
1201 return false;
1202 }
1203
1204 user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
1205 return false;
1206 }
1207
1208 /**
1209 * Sets information about a file
1210 *
1211 * @param String $filename
1212 * @param String $attr
1213 * @param Boolean $recursive
1214 * @return Boolean
1215 * @access private
1216 */
1217 function _setstat($filename, $attr, $recursive)
1218 {
1219 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1220 return false;
1221 }
1222
1223 $filename = $this->_realpath($filename);
1224 if ($filename === false) {
1225 return false;
1226 }
1227
1228 if ($recursive) {
1229 $i = 0;
1230 $result = $this->_setstat_recursive($filename, $attr, $i);
1231 $this->_read_put_responses($i);
1232 return $result;
1233 }
1234
1235 // SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to
1236 // SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT.
1237 if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($filename), $filename, $attr))) {
1238 return false;
1239 }
1240
1241 /*
1242 "Because some systems must use separate system calls to set various attributes, it is possible that a failure
1243 response will be returned, but yet some of the attributes may be have been successfully modified. If possible,
1244 servers SHOULD avoid this situation; however, clients MUST be aware that this is possible."
1245
1246 -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6
1247 */
1248 $response = $this->_get_sftp_packet();
1249 if ($this->packet_type != NET_SFTP_STATUS) {
1250 user_error('Expected SSH_FXP_STATUS');
1251 return false;
1252 }
1253
1254 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1255 if ($status != NET_SFTP_STATUS_OK) {
1256 $this->_logError($response, $status);
1257 return false;
1258 }
1259
1260 return true;
1261 }
1262
1263 /**
1264 * Recursively sets information on directories on the SFTP server
1265 *
1266 * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
1267 *
1268 * @param String $path
1269 * @param String $attr
1270 * @param Integer $i
1271 * @return Boolean
1272 * @access private
1273 */
1274 function _setstat_recursive($path, $attr, &$i)
1275 {
1276 if (!$this->_read_put_responses($i)) {
1277 return false;
1278 }
1279 $i = 0;
1280 $entries = $this->_list($path, true, false);
1281
1282 if ($entries === false) {
1283 return $this->_setstat($path, $attr, false);
1284 }
1285
1286 // normally $entries would have at least . and .. but it might not if the directories
1287 // permissions didn't allow reading
1288 if (empty($entries)) {
1289 return false;
1290 }
1291
1292 foreach ($entries as $filename=>$props) {
1293 if ($filename == '.' || $filename == '..') {
1294 continue;
1295 }
1296
1297 if (!isset($props['type'])) {
1298 return false;
1299 }
1300
1301 $temp = $path . '/' . $filename;
1302 if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
1303 if (!$this->_setstat_recursive($temp, $attr, $i)) {
1304 return false;
1305 }
1306 } else {
1307 if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($temp), $temp, $attr))) {
1308 return false;
1309 }
1310
1311 $i++;
1312
1313 if ($i >= 50) {
1314 if (!$this->_read_put_responses($i)) {
1315 return false;
1316 }
1317 $i = 0;
1318 }
1319 }
1320 }
1321
1322 if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($path), $path, $attr))) {
1323 return false;
1324 }
1325
1326 $i++;
1327
1328 if ($i >= 50) {
1329 if (!$this->_read_put_responses($i)) {
1330 return false;
1331 }
1332 $i = 0;
1333 }
1334
1335 return true;
1336 }
1337
1338 /**
1339 * Creates a directory.
1340 *
1341 * @param String $dir
1342 * @return Boolean
1343 * @access public
1344 */
1345 function mkdir($dir, $mode = -1, $recursive = false)
1346 {
1347 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1348 return false;
1349 }
1350
1351 $dir = $this->_realpath($dir);
1352 // by not providing any permissions, hopefully the server will use the logged in users umask - their
1353 // default permissions.
1354 $attr = $mode == -1 ? "\0\0\0\0" : pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
1355
1356 if ($recursive) {
1357 $dirs = explode('/', preg_replace('#/(?=/)|/$#', '', $dir));
1358 if (empty($dirs[0])) {
1359 array_shift($dirs);
1360 $dirs[0] = '/' . $dirs[0];
1361 }
1362 for ($i = 0; $i < count($dirs); $i++) {
1363 $temp = array_slice($dirs, 0, $i + 1);
1364 $temp = implode('/', $temp);
1365 $result = $this->_mkdir_helper($temp, $attr);
1366 }
1367 return $result;
1368 }
1369
1370 return $this->_mkdir_helper($dir, $attr);
1371 }
1372
1373 /**
1374 * Helper function for directory creation
1375 *
1376 * @param String $dir
1377 * @return Boolean
1378 * @access private
1379 */
1380 function _mkdir_helper($dir, $attr)
1381 {
1382 if (!$this->_send_sftp_packet(NET_SFTP_MKDIR, pack('Na*a*', strlen($dir), $dir, $attr))) {
1383 return false;
1384 }
1385
1386 $response = $this->_get_sftp_packet();
1387 if ($this->packet_type != NET_SFTP_STATUS) {
1388 user_error('Expected SSH_FXP_STATUS');
1389 return false;
1390 }
1391
1392 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1393 if ($status != NET_SFTP_STATUS_OK) {
1394 $this->_logError($response, $status);
1395 return false;
1396 }
1397
1398 $this->_save_dir($dir);
1399
1400 return true;
1401 }
1402
1403 /**
1404 * Removes a directory.
1405 *
1406 * @param String $dir
1407 * @return Boolean
1408 * @access public
1409 */
1410 function rmdir($dir)
1411 {
1412 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1413 return false;
1414 }
1415
1416 $dir = $this->_realpath($dir);
1417 if ($dir === false) {
1418 return false;
1419 }
1420
1421 if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($dir), $dir))) {
1422 return false;
1423 }
1424
1425 $response = $this->_get_sftp_packet();
1426 if ($this->packet_type != NET_SFTP_STATUS) {
1427 user_error('Expected SSH_FXP_STATUS');
1428 return false;
1429 }
1430
1431 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1432 if ($status != NET_SFTP_STATUS_OK) {
1433 // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED?
1434 $this->_logError($response, $status);
1435 return false;
1436 }
1437
1438 $this->_remove_dir($dir);
1439
1440 return true;
1441 }
1442
1443 /**
1444 * Uploads a file to the SFTP server.
1445 *
1446 * By default, Net_SFTP::put() does not read from the local filesystem. $data is dumped directly into $remote_file.
1447 * So, for example, if you set $data to 'filename.ext' and then do Net_SFTP::get(), you will get a file, twelve bytes
1448 * long, containing 'filename.ext' as its contents.
1449 *
1450 * Setting $mode to NET_SFTP_LOCAL_FILE will change the above behavior. With NET_SFTP_LOCAL_FILE, $remote_file will
1451 * contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how
1452 * large $remote_file will be, as well.
1453 *
1454 * Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take
1455 * care of that, yourself.
1456 *
1457 * As for $start... if it's negative (which it is by default) a new file will be created or an existing
1458 * file truncated depending on $mode | NET_SFTP_RESUME. If it's zero or positive it'll be updated at that
1459 * spot.
1460 *
1461 * @param String $remote_file
1462 * @param String $data
1463 * @param optional Integer $mode
1464 * @param optional Integer $start
1465 * @return Boolean
1466 * @access public
1467 * @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - Net_SFTP::setMode().
1468 */
1469 function put($remote_file, $data, $mode = NET_SFTP_STRING, $start = -1)
1470 {
1471 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1472 return false;
1473 }
1474
1475 $remote_file = $this->_realpath($remote_file);
1476 if ($remote_file === false) {
1477 return false;
1478 }
1479
1480 $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE;
1481 // according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file."
1482 // in practice, it doesn't seem to do that.
1483 //$flags|= ($mode & NET_SFTP_RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE;
1484
1485 // if NET_SFTP_OPEN_APPEND worked as it should the following (up until the -----------) wouldn't be necessary
1486 $offset = 0;
1487 if (($mode & NET_SFTP_RESUME) || $start >= 0) {
1488 if ($start >= 0) {
1489 $offset = $start;
1490 } else {
1491 $size = $this->_size($remote_file);
1492 $offset = $size !== false ? $size : 0;
1493 }
1494 } else {
1495 $flags|= NET_SFTP_OPEN_TRUNCATE;
1496 }
1497 // --------------
1498
1499 $packet = pack('Na*N2', strlen($remote_file), $remote_file, $flags, 0);
1500 if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
1501 return false;
1502 }
1503
1504 $response = $this->_get_sftp_packet();
1505 switch ($this->packet_type) {
1506 case NET_SFTP_HANDLE:
1507 $handle = substr($response, 4);
1508 break;
1509 case NET_SFTP_STATUS:
1510 $this->_logError($response);
1511 return false;
1512 default:
1513 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
1514 return false;
1515 }
1516
1517 $initialize = true;
1518
1519 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3
1520 if ($mode & NET_SFTP_LOCAL_FILE) {
1521 if (!is_file($data)) {
1522 user_error("$data is not a valid file");
1523 return false;
1524 }
1525 $fp = @fopen($data, 'rb');
1526 if (!$fp) {
1527 return false;
1528 }
1529 $size = filesize($data);
1530 } else {
1531 $size = strlen($data);
1532 }
1533
1534 $sent = 0;
1535 $size = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
1536
1537 $sftp_packet_size = 4096; // PuTTY uses 4096
1538 $i = 0;
1539 while ($sent < $size) {
1540 $temp = $mode & NET_SFTP_LOCAL_FILE ? fread($fp, $sftp_packet_size) : $this->_string_shift($data, $sftp_packet_size);
1541 $subtemp = $offset + $sent;
1542 $packet = pack('Na*N3a*', strlen($handle), $handle, $subtemp / 0x100000000, $subtemp, strlen($temp), $temp);
1543 if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet)) {
1544 fclose($fp);
1545 return false;
1546 }
1547 $sent+= strlen($temp);
1548
1549 $i++;
1550
1551 if ($i == 50) {
1552 if (!$this->_read_put_responses($i)) {
1553 $i = 0;
1554 break;
1555 }
1556 $i = 0;
1557 }
1558 }
1559
1560 if (!$this->_read_put_responses($i)) {
1561 return false;
1562 }
1563
1564 if ($mode & NET_SFTP_LOCAL_FILE) {
1565 fclose($fp);
1566 }
1567
1568 if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
1569 return false;
1570 }
1571
1572 $response = $this->_get_sftp_packet();
1573 if ($this->packet_type != NET_SFTP_STATUS) {
1574 user_error('Expected SSH_FXP_STATUS');
1575 return false;
1576 }
1577
1578 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1579 if ($status != NET_SFTP_STATUS_OK) {
1580 $this->_logError($response, $status);
1581 return false;
1582 }
1583
1584 return true;
1585 }
1586
1587 /**
1588 * Reads multiple successive SSH_FXP_WRITE responses
1589 *
1590 * Sending an SSH_FXP_WRITE packet and immediately reading its response isn't as efficient as blindly sending out $i
1591 * SSH_FXP_WRITEs, in succession, and then reading $i responses.
1592 *
1593 * @param Integer $i
1594 * @return Boolean
1595 * @access private
1596 */
1597 function _read_put_responses($i)
1598 {
1599 while ($i--) {
1600 $response = $this->_get_sftp_packet();
1601 if ($this->packet_type != NET_SFTP_STATUS) {
1602 user_error('Expected SSH_FXP_STATUS');
1603 return false;
1604 }
1605
1606 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1607 if ($status != NET_SFTP_STATUS_OK) {
1608 $this->_logError($response, $status);
1609 break;
1610 }
1611 }
1612
1613 return $i < 0;
1614 }
1615
1616 /**
1617 * Downloads a file from the SFTP server.
1618 *
1619 * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
1620 * the operation was unsuccessful. If $local_file is defined, returns true or false depending on the success of the
1621 * operation.
1622 *
1623 * $offset and $length can be used to download files in chunks.
1624 *
1625 * @param String $remote_file
1626 * @param optional String $local_file
1627 * @param optional Integer $offset
1628 * @param optional Integer $length
1629 * @return Mixed
1630 * @access public
1631 */
1632 function get($remote_file, $local_file = false, $offset = 0, $length = -1)
1633 {
1634 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1635 return false;
1636 }
1637
1638 $remote_file = $this->_realpath($remote_file);
1639 if ($remote_file === false) {
1640 return false;
1641 }
1642
1643 $packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_READ, 0);
1644 if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
1645 return false;
1646 }
1647
1648 $response = $this->_get_sftp_packet();
1649 switch ($this->packet_type) {
1650 case NET_SFTP_HANDLE:
1651 $handle = substr($response, 4);
1652 break;
1653 case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
1654 $this->_logError($response);
1655 return false;
1656 default:
1657 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
1658 return false;
1659 }
1660
1661 if ($local_file !== false) {
1662 $fp = fopen($local_file, 'wb');
1663 if (!$fp) {
1664 return false;
1665 }
1666 } else {
1667 $content = '';
1668 }
1669
1670 $size = (1 << 20) < $length || $length < 0 ? 1 << 20 : $length;
1671 while (true) {
1672 $packet = pack('Na*N3', strlen($handle), $handle, $offset / 0x100000000, $offset, $size);
1673 if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet)) {
1674 if ($local_file !== false) {
1675 fclose($fp);
1676 }
1677 return false;
1678 }
1679
1680 $response = $this->_get_sftp_packet();
1681 switch ($this->packet_type) {
1682 case NET_SFTP_DATA:
1683 $temp = substr($response, 4);
1684 $offset+= strlen($temp);
1685 if ($local_file === false) {
1686 $content.= $temp;
1687 } else {
1688 fputs($fp, $temp);
1689 }
1690 break;
1691 case NET_SFTP_STATUS:
1692 // could, in theory, return false if !strlen($content) but we'll hold off for the time being
1693 $this->_logError($response);
1694 break 2;
1695 default:
1696 user_error('Expected SSH_FXP_DATA or SSH_FXP_STATUS');
1697 if ($local_file !== false) {
1698 fclose($fp);
1699 }
1700 return false;
1701 }
1702
1703 if ($length > 0 && $length <= $offset - $size) {
1704 break;
1705 }
1706 }
1707
1708 if ($length > 0 && $length <= $offset - $size) {
1709 if ($local_file === false) {
1710 $content = substr($content, 0, $length);
1711 } else {
1712 ftruncate($fp, $length);
1713 }
1714 }
1715
1716 if ($local_file !== false) {
1717 fclose($fp);
1718 }
1719
1720 if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
1721 return false;
1722 }
1723
1724 $response = $this->_get_sftp_packet();
1725 if ($this->packet_type != NET_SFTP_STATUS) {
1726 user_error('Expected SSH_FXP_STATUS');
1727 return false;
1728 }
1729
1730 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1731 if ($status != NET_SFTP_STATUS_OK) {
1732 $this->_logError($response, $status);
1733 return false;
1734 }
1735
1736 // if $content isn't set that means a file was written to
1737 if (isset($content)) {
1738 return $content;
1739 }
1740
1741 return true;
1742 }
1743
1744 /**
1745 * Deletes a file on the SFTP server.
1746 *
1747 * @param String $path
1748 * @param Boolean $recursive
1749 * @return Boolean
1750 * @access public
1751 */
1752 function delete($path, $recursive = true)
1753 {
1754 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1755 return false;
1756 }
1757
1758 $path = $this->_realpath($path);
1759 if ($path === false) {
1760 return false;
1761 }
1762
1763 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
1764 if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path))) {
1765 return false;
1766 }
1767
1768 $response = $this->_get_sftp_packet();
1769 if ($this->packet_type != NET_SFTP_STATUS) {
1770 user_error('Expected SSH_FXP_STATUS');
1771 return false;
1772 }
1773
1774 // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
1775 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1776 if ($status != NET_SFTP_STATUS_OK) {
1777 $this->_logError($response, $status);
1778 if (!$recursive) {
1779 return false;
1780 }
1781 $i = 0;
1782 $result = $this->_delete_recursive($path, $i);
1783 $this->_read_put_responses($i);
1784 return $result;
1785 }
1786
1787 return true;
1788 }
1789
1790 /**
1791 * Recursively deletes directories on the SFTP server
1792 *
1793 * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
1794 *
1795 * @param String $path
1796 * @param Integer $i
1797 * @return Boolean
1798 * @access private
1799 */
1800 function _delete_recursive($path, &$i)
1801 {
1802 if (!$this->_read_put_responses($i)) {
1803 return false;
1804 }
1805 $i = 0;
1806 $entries = $this->_list($path, true, false);
1807
1808 // normally $entries would have at least . and .. but it might not if the directories
1809 // permissions didn't allow reading
1810 if (empty($entries)) {
1811 return false;
1812 }
1813
1814 foreach ($entries as $filename=>$props) {
1815 if ($filename == '.' || $filename == '..') {
1816 continue;
1817 }
1818
1819 if (!isset($props['type'])) {
1820 return false;
1821 }
1822
1823 $temp = $path . '/' . $filename;
1824 if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
1825 if (!$this->_delete_recursive($temp, $i)) {
1826 return false;
1827 }
1828 } else {
1829 if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($temp), $temp))) {
1830 return false;
1831 }
1832
1833 $i++;
1834
1835 if ($i >= 50) {
1836 if (!$this->_read_put_responses($i)) {
1837 return false;
1838 }
1839 $i = 0;
1840 }
1841 }
1842 }
1843
1844 if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($path), $path))) {
1845 return false;
1846 }
1847 $this->_remove_dir($path);
1848
1849 $i++;
1850
1851 if ($i >= 50) {
1852 if (!$this->_read_put_responses($i)) {
1853 return false;
1854 }
1855 $i = 0;
1856 }
1857
1858 return true;
1859 }
1860
1861 /**
1862 * Renames a file or a directory on the SFTP server
1863 *
1864 * @param String $oldname
1865 * @param String $newname
1866 * @return Boolean
1867 * @access public
1868 */
1869 function rename($oldname, $newname)
1870 {
1871 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1872 return false;
1873 }
1874
1875 $oldname = $this->_realpath($oldname);
1876 $newname = $this->_realpath($newname);
1877 if ($oldname === false || $newname === false) {
1878 return false;
1879 }
1880
1881 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
1882 $packet = pack('Na*Na*', strlen($oldname), $oldname, strlen($newname), $newname);
1883 if (!$this->_send_sftp_packet(NET_SFTP_RENAME, $packet)) {
1884 return false;
1885 }
1886
1887 $response = $this->_get_sftp_packet();
1888 if ($this->packet_type != NET_SFTP_STATUS) {
1889 user_error('Expected SSH_FXP_STATUS');
1890 return false;
1891 }
1892
1893 // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
1894 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1895 if ($status != NET_SFTP_STATUS_OK) {
1896 $this->_logError($response, $status);
1897 return false;
1898 }
1899
1900 return true;
1901 }
1902
1903 /**
1904 * Parse Attributes
1905 *
1906 * See '7. File Attributes' of draft-ietf-secsh-filexfer-13 for more info.
1907 *
1908 * @param String $response
1909 * @return Array
1910 * @access private
1911 */
1912 function _parseAttributes(&$response)
1913 {
1914 $attr = array();
1915 extract(unpack('Nflags', $this->_string_shift($response, 4)));
1916 // SFTPv4+ have a type field (a byte) that follows the above flag field
1917 foreach ($this->attributes as $key => $value) {
1918 switch ($flags & $key) {
1919 case NET_SFTP_ATTR_SIZE: // 0x00000001
1920 // size is represented by a 64-bit integer, so we perhaps ought to be doing the following:
1921 // $attr['size'] = new Math_BigInteger($this->_string_shift($response, 8), 256);
1922 // of course, you shouldn't be using Net_SFTP to transfer files that are in excess of 4GB
1923 // (0xFFFFFFFF bytes), anyway. as such, we'll just represent all file sizes that are bigger than
1924 // 4GB as being 4GB.
1925 extract(unpack('Nupper/Nsize', $this->_string_shift($response, 8)));
1926 $attr['size'] = $upper ? 0x100000000 * $upper : 0;
1927 $attr['size']+= $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
1928 break;
1929 case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
1930 $attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8));
1931 break;
1932 case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
1933 $attr+= unpack('Npermissions', $this->_string_shift($response, 4));
1934 // mode == permissions; permissions was the original array key and is retained for bc purposes.
1935 // mode was added because that's the more industry standard terminology
1936 $attr+= array('mode' => $attr['permissions']);
1937 $fileType = $this->_parseMode($attr['permissions']);
1938 if ($fileType !== false) {
1939 $attr+= array('type' => $fileType);
1940 }
1941 break;
1942 case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
1943 $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8));
1944 break;
1945 case NET_SFTP_ATTR_EXTENDED: // 0x80000000
1946 extract(unpack('Ncount', $this->_string_shift($response, 4)));
1947 for ($i = 0; $i < $count; $i++) {
1948 extract(unpack('Nlength', $this->_string_shift($response, 4)));
1949 $key = $this->_string_shift($response, $length);
1950 extract(unpack('Nlength', $this->_string_shift($response, 4)));
1951 $attr[$key] = $this->_string_shift($response, $length);
1952 }
1953 }
1954 }
1955 return $attr;
1956 }
1957
1958 /**
1959 * Attempt to identify the file type
1960 *
1961 * Quoting the SFTP RFC, "Implementations MUST NOT send bits that are not defined" but they seem to anyway
1962 *
1963 * @param Integer $mode
1964 * @return Integer
1965 * @access private
1966 */
1967 function _parseMode($mode)
1968 {
1969 // values come from http://lxr.free-electrons.com/source/include/uapi/linux/stat.h#L12
1970 // see, also, http://linux.die.net/man/2/stat
1971 switch ($mode & 0170000) {// ie. 1111 0000 0000 0000
1972 case 0000000: // no file type specified - figure out the file type using alternative means
1973 return false;
1974 case 0040000:
1975 return NET_SFTP_TYPE_DIRECTORY;
1976 case 0100000:
1977 return NET_SFTP_TYPE_REGULAR;
1978 case 0120000:
1979 return NET_SFTP_TYPE_SYMLINK;
1980 // new types introduced in SFTPv5+
1981 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
1982 case 0010000: // named pipe (fifo)
1983 return NET_SFTP_TYPE_FIFO;
1984 case 0020000: // character special
1985 return NET_SFTP_TYPE_CHAR_DEVICE;
1986 case 0060000: // block special
1987 return NET_SFTP_BLOCK_DEVICE;
1988 case 0140000: // socket
1989 return NET_SFTP_TYPE_SOCKET;
1990 case 0160000: // whiteout
1991 // "SPECIAL should be used for files that are of
1992 // a known type which cannot be expressed in the protocol"
1993 return NET_SFTP_TYPE_SPECIAL;
1994 default:
1995 return NET_SFTP_TYPE_UNKNOWN;
1996 }
1997 }
1998
1999 /**
2000 * Parse Longname
2001 *
2002 * SFTPv3 doesn't provide any easy way of identifying a file type. You could try to open
2003 * a file as a directory and see if an error is returned or you could try to parse the
2004 * SFTPv3-specific longname field of the SSH_FXP_NAME packet. That's what this function does.
2005 * The result is returned using the
2006 * {@link http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2 SFTPv4 type constants}.
2007 *
2008 * If the longname is in an unrecognized format bool(false) is returned.
2009 *
2010 * @param String $longname
2011 * @return Mixed
2012 * @access private
2013 */
2014 function _parseLongname($longname)
2015 {
2016 // http://en.wikipedia.org/wiki/Unix_file_types
2017 // http://en.wikipedia.org/wiki/Filesystem_permissions#Notation_of_traditional_Unix_permissions
2018 if (preg_match('#^[^/]([r-][w-][xstST-]){3}#', $longname)) {
2019 switch ($longname[0]) {
2020 case '-':
2021 return NET_SFTP_TYPE_REGULAR;
2022 case 'd':
2023 return NET_SFTP_TYPE_DIRECTORY;
2024 case 'l':
2025 return NET_SFTP_TYPE_SYMLINK;
2026 default:
2027 return NET_SFTP_TYPE_SPECIAL;
2028 }
2029 }
2030
2031 return false;
2032 }
2033
2034 /**
2035 * Sends SFTP Packets
2036 *
2037 * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
2038 *
2039 * @param Integer $type
2040 * @param String $data
2041 * @see Net_SFTP::_get_sftp_packet()
2042 * @see Net_SSH2::_send_channel_packet()
2043 * @return Boolean
2044 * @access private
2045 */
2046 function _send_sftp_packet($type, $data)
2047 {
2048 $packet = $this->request_id !== false ?
2049 pack('NCNa*', strlen($data) + 5, $type, $this->request_id, $data) :
2050 pack('NCa*', strlen($data) + 1, $type, $data);
2051
2052 $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
2053 $result = $this->_send_channel_packet(NET_SFTP_CHANNEL, $packet);
2054 $stop = strtok(microtime(), ' ') + strtok('');
2055
2056 if (defined('NET_SFTP_LOGGING')) {
2057 $packet_type = '-> ' . $this->packet_types[$type] .
2058 ' (' . round($stop - $start, 4) . 's)';
2059 if (NET_SFTP_LOGGING == NET_SFTP_LOG_REALTIME) {
2060 echo "<pre>\r\n" . $this->_format_log(array($data), array($packet_type)) . "\r\n</pre>\r\n";
2061 flush();
2062 ob_flush();
2063 } else {
2064 $this->packet_type_log[] = $packet_type;
2065 if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
2066 $this->packet_log[] = $data;
2067 }
2068 }
2069 }
2070
2071 return $result;
2072 }
2073
2074 /**
2075 * Receives SFTP Packets
2076 *
2077 * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
2078 *
2079 * Incidentally, the number of SSH_MSG_CHANNEL_DATA messages has no bearing on the number of SFTP packets present.
2080 * There can be one SSH_MSG_CHANNEL_DATA messages containing two SFTP packets or there can be two SSH_MSG_CHANNEL_DATA
2081 * messages containing one SFTP packet.
2082 *
2083 * @see Net_SFTP::_send_sftp_packet()
2084 * @return String
2085 * @access private
2086 */
2087 function _get_sftp_packet()
2088 {
2089 $this->curTimeout = false;
2090
2091 $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
2092
2093 // SFTP packet length
2094 while (strlen($this->packet_buffer) < 4) {
2095 $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL);
2096 if (is_bool($temp)) {
2097 $this->packet_type = false;
2098 $this->packet_buffer = '';
2099 return false;
2100 }
2101 $this->packet_buffer.= $temp;
2102 }
2103 extract(unpack('Nlength', $this->_string_shift($this->packet_buffer, 4)));
2104 $tempLength = $length;
2105 $tempLength-= strlen($this->packet_buffer);
2106
2107 // SFTP packet type and data payload
2108 while ($tempLength > 0) {
2109 $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL);
2110 if (is_bool($temp)) {
2111 $this->packet_type = false;
2112 $this->packet_buffer = '';
2113 return false;
2114 }
2115 $this->packet_buffer.= $temp;
2116 $tempLength-= strlen($temp);
2117 }
2118
2119 $stop = strtok(microtime(), ' ') + strtok('');
2120
2121 $this->packet_type = ord($this->_string_shift($this->packet_buffer));
2122
2123 if ($this->request_id !== false) {
2124 $this->_string_shift($this->packet_buffer, 4); // remove the request id
2125 $length-= 5; // account for the request id and the packet type
2126 } else {
2127 $length-= 1; // account for the packet type
2128 }
2129
2130 $packet = $this->_string_shift($this->packet_buffer, $length);
2131
2132 if (defined('NET_SFTP_LOGGING')) {
2133 $packet_type = '<- ' . $this->packet_types[$this->packet_type] .
2134 ' (' . round($stop - $start, 4) . 's)';
2135 if (NET_SFTP_LOGGING == NET_SFTP_LOG_REALTIME) {
2136 echo "<pre>\r\n" . $this->_format_log(array($packet), array($packet_type)) . "\r\n</pre>\r\n";
2137 flush();
2138 ob_flush();
2139 } else {
2140 $this->packet_type_log[] = $packet_type;
2141 if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
2142 $this->packet_log[] = $packet;
2143 }
2144 }
2145 }
2146
2147 return $packet;
2148 }
2149
2150 /**
2151 * Returns a log of the packets that have been sent and received.
2152 *
2153 * 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')
2154 *
2155 * @access public
2156 * @return String or Array
2157 */
2158 function getSFTPLog()
2159 {
2160 if (!defined('NET_SFTP_LOGGING')) {
2161 return false;
2162 }
2163
2164 switch (NET_SFTP_LOGGING) {
2165 case NET_SFTP_LOG_COMPLEX:
2166 return $this->_format_log($this->packet_log, $this->packet_type_log);
2167 break;
2168 //case NET_SFTP_LOG_SIMPLE:
2169 default:
2170 return $this->packet_type_log;
2171 }
2172 }
2173
2174 /**
2175 * Returns all errors
2176 *
2177 * @return String
2178 * @access public
2179 */
2180 function getSFTPErrors()
2181 {
2182 return $this->sftp_errors;
2183 }
2184
2185 /**
2186 * Returns the last error
2187 *
2188 * @return String
2189 * @access public
2190 */
2191 function getLastSFTPError()
2192 {
2193 return count($this->sftp_errors) ? $this->sftp_errors[count($this->sftp_errors) - 1] : '';
2194 }
2195
2196 /**
2197 * Get supported SFTP versions
2198 *
2199 * @return Array
2200 * @access public
2201 */
2202 function getSupportedVersions()
2203 {
2204 $temp = array('version' => $this->version);
2205 if (isset($this->extensions['versions'])) {
2206 $temp['extensions'] = $this->extensions['versions'];
2207 }
2208 return $temp;
2209 }
2210
2211 /**
2212 * Disconnect
2213 *
2214 * @param Integer $reason
2215 * @return Boolean
2216 * @access private
2217 */
2218 function _disconnect($reason)
2219 {
2220 $this->pwd = false;
2221 parent::_disconnect($reason);
2222 }
2223 }