2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
\r
5 * Pure-PHP implementation of SFTP.
\r
7 * PHP versions 4 and 5
\r
9 * Currently only supports SFTPv3, which, according to wikipedia.org, "is the most widely used version,
\r
10 * implemented by the popular OpenSSH SFTP server". If you want SFTPv4/5/6 support, provide me with access
\r
11 * to an SFTPv4/5/6 server.
\r
13 * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
\r
15 * Here's a short example of how to use this library:
\r
18 * include('Net/SFTP.php');
\r
20 * $sftp = new Net_SFTP('www.domain.tld');
\r
21 * if (!$sftp->login('username', 'password')) {
\r
22 * exit('Login Failed');
\r
25 * echo $sftp->pwd() . "\r\n";
\r
26 * $sftp->put('filename.ext', 'hello, world!');
\r
27 * print_r($sftp->nlist());
\r
31 * LICENSE: This library is free software; you can redistribute it and/or
\r
32 * modify it under the terms of the GNU Lesser General Public
\r
33 * License as published by the Free Software Foundation; either
\r
34 * version 2.1 of the License, or (at your option) any later version.
\r
36 * This library is distributed in the hope that it will be useful,
\r
37 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
38 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
\r
39 * Lesser General Public License for more details.
\r
41 * You should have received a copy of the GNU Lesser General Public
\r
42 * License along with this library; if not, write to the Free Software
\r
43 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
\r
48 * @author Jim Wigginton <terrafrost@php.net>
\r
49 * @copyright MMIX Jim Wigginton
\r
50 * @license http://www.gnu.org/licenses/lgpl.txt
\r
51 * @version $Id: SFTP.php,v 1.21 2010/04/09 02:31:34 terrafrost Exp $
\r
52 * @link http://phpseclib.sourceforge.net
\r
58 require_once('Net/SSH2.php');
\r
62 * @see Net_SFTP::getLog()
\r
65 * Returns the message numbers
\r
67 define('NET_SFTP_LOG_SIMPLE', NET_SSH2_LOG_SIMPLE);
\r
69 * Returns the message content
\r
71 define('NET_SFTP_LOG_COMPLEX', NET_SSH2_LOG_COMPLEX);
\r
75 * SFTP channel constant
\r
77 * Net_SSH2::exec() uses 0 and Net_SSH2::interactiveRead() / Net_SSH2::interactiveWrite() use 1.
\r
79 * @see Net_SSH2::_send_channel_packet()
\r
80 * @see Net_SSH2::_get_channel_packet()
\r
83 define('NET_SFTP_CHANNEL', 2);
\r
87 * @see Net_SFTP::put()
\r
90 * Reads data from a local file.
\r
92 define('NET_SFTP_LOCAL_FILE', 1);
\r
94 * Reads data from a string.
\r
96 define('NET_SFTP_STRING', 2);
\r
100 * Pure-PHP implementations of SFTP.
\r
102 * @author Jim Wigginton <terrafrost@php.net>
\r
105 * @package Net_SFTP
\r
107 class Net_SFTP extends Net_SSH2 {
\r
111 * @see Net_SFTP::Net_SFTP()
\r
115 var $packet_types = array();
\r
120 * @see Net_SFTP::Net_SFTP()
\r
124 var $status_codes = array();
\r
129 * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
\r
130 * concurrent actions, so it's somewhat academic, here.
\r
133 * @see Net_SFTP::_send_sftp_packet()
\r
136 var $request_id = false;
\r
141 * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
\r
142 * concurrent actions, so it's somewhat academic, here.
\r
145 * @see Net_SFTP::_get_sftp_packet()
\r
148 var $packet_type = -1;
\r
154 * @see Net_SFTP::_get_sftp_packet()
\r
157 var $packet_buffer = '';
\r
160 * Extensions supported by the server
\r
163 * @see Net_SFTP::_initChannel()
\r
166 var $extensions = array();
\r
169 * Server SFTP version
\r
172 * @see Net_SFTP::_initChannel()
\r
178 * Current working directory
\r
181 * @see Net_SFTP::_realpath()
\r
182 * @see Net_SFTP::chdir()
\r
190 * @see Net_SFTP::getLog()
\r
194 var $packet_type_log = array();
\r
199 * @see Net_SFTP::getLog()
\r
203 var $packet_log = array();
\r
206 * Error information
\r
208 * @see Net_SFTP::getSFTPErrors()
\r
209 * @see Net_SFTP::getLastSFTPError()
\r
213 var $errors = array();
\r
216 * Default Constructor.
\r
218 * Connects to an SFTP server
\r
220 * @param String $host
\r
221 * @param optional Integer $port
\r
222 * @param optional Integer $timeout
\r
226 function Net_SFTP($host, $port = 22, $timeout = 10)
\r
228 parent::Net_SSH2($host, $port, $timeout);
\r
229 $this->packet_types = array(
\r
230 1 => 'NET_SFTP_INIT',
\r
231 2 => 'NET_SFTP_VERSION',
\r
232 /* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+:
\r
233 SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1
\r
234 pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */
\r
235 3 => 'NET_SFTP_OPEN',
\r
236 4 => 'NET_SFTP_CLOSE',
\r
237 5 => 'NET_SFTP_READ',
\r
238 6 => 'NET_SFTP_WRITE',
\r
239 8 => 'NET_SFTP_FSTAT',
\r
240 9 => 'NET_SFTP_SETSTAT',
\r
241 11 => 'NET_SFTP_OPENDIR',
\r
242 12 => 'NET_SFTP_READDIR',
\r
243 13 => 'NET_SFTP_REMOVE',
\r
244 14 => 'NET_SFTP_MKDIR',
\r
245 15 => 'NET_SFTP_RMDIR',
\r
246 16 => 'NET_SFTP_REALPATH',
\r
247 17 => 'NET_SFTP_STAT',
\r
248 /* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+:
\r
249 SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
\r
250 pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */
\r
251 18 => 'NET_SFTP_RENAME',
\r
253 101=> 'NET_SFTP_STATUS',
\r
254 102=> 'NET_SFTP_HANDLE',
\r
255 /* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+:
\r
256 SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4
\r
257 pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */
\r
258 103=> 'NET_SFTP_DATA',
\r
259 104=> 'NET_SFTP_NAME',
\r
260 105=> 'NET_SFTP_ATTRS',
\r
262 200=> 'NET_SFTP_EXTENDED'
\r
264 $this->status_codes = array(
\r
265 0 => 'NET_SFTP_STATUS_OK',
\r
266 1 => 'NET_SFTP_STATUS_EOF',
\r
267 2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
\r
268 3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
\r
269 4 => 'NET_SFTP_STATUS_FAILURE',
\r
270 5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
\r
271 6 => 'NET_SFTP_STATUS_NO_CONNECTION',
\r
272 7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
\r
273 8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED'
\r
275 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
\r
276 // the order, in this case, matters quite a lot - see Net_SFTP::_parseAttributes() to understand why
\r
277 $this->attributes = array(
\r
278 0x00000001 => 'NET_SFTP_ATTR_SIZE',
\r
279 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
\r
280 0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
\r
281 0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
\r
282 -1 => 'NET_SFTP_ATTR_EXTENDED' // unpack('N', "\xFF\xFF\xFF\xFF") == array(1 => int(-1))
\r
284 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
\r
285 // the flag definitions change somewhat in SFTPv5+. if SFTPv5+ support is added to this library, maybe name
\r
286 // the array for that $this->open5_flags and similarily alter the constant names.
\r
287 $this->open_flags = array(
\r
288 0x00000001 => 'NET_SFTP_OPEN_READ',
\r
289 0x00000002 => 'NET_SFTP_OPEN_WRITE',
\r
290 0x00000008 => 'NET_SFTP_OPEN_CREATE',
\r
291 0x00000010 => 'NET_SFTP_OPEN_TRUNCATE'
\r
293 $this->_define_array(
\r
294 $this->packet_types,
\r
295 $this->status_codes,
\r
304 * @param String $username
\r
305 * @param optional String $password
\r
309 function login($username, $password = '')
\r
311 if (!parent::login($username, $password)) {
\r
315 $this->window_size_client_to_server[NET_SFTP_CHANNEL] = $this->window_size;
\r
317 $packet = pack('CNa*N3',
\r
318 NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', NET_SFTP_CHANNEL, $this->window_size, 0x4000);
\r
320 if (!$this->_send_binary_packet($packet)) {
\r
324 $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;
\r
326 $response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
\r
327 if ($response === false) {
\r
331 $packet = pack('CNNa*CNa*',
\r
332 NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SFTP_CHANNEL], strlen('subsystem'), 'subsystem', 1, strlen('sftp'), 'sftp');
\r
333 if (!$this->_send_binary_packet($packet)) {
\r
337 $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
\r
339 $response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
\r
340 if ($response === false) {
\r
344 $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;
\r
346 if (!$this->_send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) {
\r
350 $response = $this->_get_sftp_packet();
\r
351 if ($this->packet_type != NET_SFTP_VERSION) {
\r
352 user_error('Expected SSH_FXP_VERSION', E_USER_NOTICE);
\r
356 extract(unpack('Nversion', $this->_string_shift($response, 4)));
\r
357 $this->version = $version;
\r
358 while (!empty($response)) {
\r
359 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
360 $key = $this->_string_shift($response, $length);
\r
361 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
362 $value = $this->_string_shift($response, $length);
\r
363 $this->extensions[$key] = $value;
\r
367 SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com',
\r
368 however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's
\r
369 not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for
\r
370 one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that
\r
371 'newline@vandyke.com' would.
\r
374 if (isset($this->extensions['newline@vandyke.com'])) {
\r
375 $this->extensions['newline'] = $this->extensions['newline@vandyke.com'];
\r
376 unset($this->extensions['newline@vandyke.com']);
\r
380 $this->request_id = 1;
\r
383 A Note on SFTPv4/5/6 support:
\r
384 <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.1> states the following:
\r
386 "If the client wishes to interoperate with servers that support noncontiguous version
\r
387 numbers it SHOULD send '3'"
\r
389 Given that the server only sends its version number after the client has already done so, the above
\r
390 seems to be suggesting that v3 should be the default version. This makes sense given that v3 is the
\r
393 <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.5> states the following;
\r
395 "If the server did not send the "versions" extension, or the version-from-list was not included, the
\r
396 server MAY send a status response describing the failure, but MUST then close the channel without
\r
397 processing any further requests."
\r
399 So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and
\r
400 a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4? If it only implements
\r
401 v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed
\r
402 in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what Net_SFTP would do is close the
\r
403 channel and reopen it with a new and updated SSH_FXP_INIT packet.
\r
405 if ($this->version != 3) {
\r
409 $this->pwd = $this->_realpath('.');
\r
415 * Returns the current directory name
\r
426 * Canonicalize the Server-Side Path Name
\r
428 * SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it. Returns
\r
429 * the absolute (canonicalized) path. If $mode is set to NET_SFTP_CONFIRM_DIR (as opposed to NET_SFTP_CONFIRM_NONE,
\r
430 * which is what it is set to by default), false is returned if $dir is not a valid directory.
\r
432 * @see Net_SFTP::chdir()
\r
433 * @param String $dir
\r
434 * @param optional Integer $mode
\r
438 function _realpath($dir)
\r
441 "This protocol represents file names as strings. File names are
\r
442 assumed to use the slash ('/') character as a directory separator.
\r
444 File names starting with a slash are "absolute", and are relative to
\r
445 the root of the file system. Names starting with any other character
\r
446 are relative to the user's default directory (home directory). Note
\r
447 that identifying the user is assumed to take place outside of this
\r
450 -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-6
\r
453 if ($this->pwd !== false) {
\r
454 // if the SFTP server returned the canonicalized path even for non-existant files this wouldn't be necessary
\r
455 // on OpenSSH it isn't necessary but on other SFTP servers it is. that and since the specs say nothing on
\r
456 // the subject, we'll go ahead and work around it with the following.
\r
457 if ($dir[strlen($dir) - 1] != '/') {
\r
458 $file = basename($dir);
\r
459 $dir = dirname($dir);
\r
462 if ($dir == '.' || $dir == $this->pwd) {
\r
463 return $this->pwd . $file;
\r
466 if ($dir[0] != '/') {
\r
467 $dir = $this->pwd . '/' . $dir;
\r
469 // on the surface it seems like maybe resolving a path beginning with / is unnecessary, but such paths
\r
470 // can contain .'s and ..'s just like any other. we could parse those out as appropriate or we can let
\r
471 // the server do it. we'll do the latter.
\r
475 that SSH_FXP_REALPATH returns SSH_FXP_NAME does not necessarily mean that anything actually exists at the
\r
476 specified path. generally speaking, no attributes are returned with this particular SSH_FXP_NAME packet
\r
477 regardless of whether or not a file actually exists. and in SFTPv3, the longname field and the filename
\r
478 field match for this particular SSH_FXP_NAME packet. for other SSH_FXP_NAME packets, this will likely
\r
479 not be the case, but for this one, it is.
\r
481 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
\r
482 if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($dir), $dir))) {
\r
486 $response = $this->_get_sftp_packet();
\r
487 switch ($this->packet_type) {
\r
488 case NET_SFTP_NAME:
\r
489 // although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following
\r
490 // should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks
\r
491 // at is the first part and that part is defined the same in SFTP versions 3 through 6.
\r
492 $this->_string_shift($response, 4); // skip over the count - it should be 1, anyway
\r
493 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
494 $realpath = $this->_string_shift($response, $length);
\r
496 case NET_SFTP_STATUS:
\r
497 extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
\r
498 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
501 user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS', E_USER_NOTICE);
\r
505 // if $this->pwd isn't set than the only thing $realpath could be is for '.', which is pretty much guaranteed to
\r
506 // be a bonafide directory
\r
507 return $realpath . '/' . $file;
\r
511 * Changes the current directory
\r
513 * @param String $dir
\r
517 function chdir($dir)
\r
519 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
\r
523 if ($dir[strlen($dir) - 1] != '/') {
\r
526 $dir = $this->_realpath($dir);
\r
528 // confirm that $dir is, in fact, a valid directory
\r
529 if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
\r
533 // see Net_SFTP::nlist() for a more thorough explanation of the following
\r
534 $response = $this->_get_sftp_packet();
\r
535 switch ($this->packet_type) {
\r
536 case NET_SFTP_HANDLE:
\r
537 $handle = substr($response, 4);
\r
539 case NET_SFTP_STATUS:
\r
540 extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
\r
541 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
544 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE);
\r
548 if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
\r
552 $response = $this->_get_sftp_packet();
\r
553 if ($this->packet_type != NET_SFTP_STATUS) {
\r
554 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
\r
558 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
\r
559 if ($status != NET_SFTP_STATUS_OK) {
\r
560 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
561 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
570 * Returns a list of files in the given directory
\r
572 * @param optional String $dir
\r
576 function nlist($dir = '.')
\r
578 return $this->_list($dir, false);
\r
582 * Returns a list of files in the given directory
\r
584 * @param optional String $dir
\r
588 function rawlist($dir = '.')
\r
590 return $this->_list($dir, true);
\r
593 function _list($dir, $raw = true)
\r
595 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
\r
599 $dir = $this->_realpath($dir);
\r
600 if ($dir === false) {
\r
604 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2
\r
605 if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
\r
609 $response = $this->_get_sftp_packet();
\r
610 switch ($this->packet_type) {
\r
611 case NET_SFTP_HANDLE:
\r
612 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2
\r
613 // since 'handle' is the last field in the SSH_FXP_HANDLE packet, we'll just remove the first four bytes that
\r
614 // represent the length of the string and leave it at that
\r
615 $handle = substr($response, 4);
\r
617 case NET_SFTP_STATUS:
\r
618 // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
\r
619 extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
\r
620 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
623 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE);
\r
627 $contents = array();
\r
629 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2
\r
630 // why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many
\r
631 // SSH_MSG_CHANNEL_DATA messages is not known to me.
\r
632 if (!$this->_send_sftp_packet(NET_SFTP_READDIR, pack('Na*', strlen($handle), $handle))) {
\r
636 $response = $this->_get_sftp_packet();
\r
637 switch ($this->packet_type) {
\r
638 case NET_SFTP_NAME:
\r
639 extract(unpack('Ncount', $this->_string_shift($response, 4)));
\r
640 for ($i = 0; $i < $count; $i++) {
\r
641 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
642 $shortname = $this->_string_shift($response, $length);
\r
643 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
644 $this->_string_shift($response, $length); // SFTPv4+ drop this field - the "longname" field
\r
645 $attributes = $this->_parseAttributes($response); // we also don't care about the attributes
\r
647 $contents[] = $shortname;
\r
649 $contents[$shortname] = $attributes;
\r
651 // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the
\r
652 // final SSH_FXP_STATUS packet should tell us that, already.
\r
655 case NET_SFTP_STATUS:
\r
656 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
\r
657 if ($status != NET_SFTP_STATUS_EOF) {
\r
658 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
659 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
664 user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS', E_USER_NOTICE);
\r
669 if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
\r
673 // "The client MUST release all resources associated with the handle regardless of the status."
\r
674 // -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3
\r
675 $response = $this->_get_sftp_packet();
\r
676 if ($this->packet_type != NET_SFTP_STATUS) {
\r
677 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
\r
681 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
\r
682 if ($status != NET_SFTP_STATUS_OK) {
\r
683 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
684 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
692 * Returns the file size, in bytes, or false, on failure
\r
694 * Files larger than 4GB will show up as being exactly 4GB.
\r
696 * @param optional String $dir
\r
700 function size($filename)
\r
702 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
\r
706 $filename = $this->_realpath($filename);
\r
707 if ($filename === false) {
\r
711 // SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
\r
712 $packet = pack('Na*', strlen($filename), $filename);
\r
713 if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) {
\r
717 $response = $this->_get_sftp_packet();
\r
718 switch ($this->packet_type) {
\r
719 case NET_SFTP_ATTRS:
\r
720 $attrs = $this->_parseAttributes($response);
\r
721 return $attrs['size'];
\r
722 case NET_SFTP_STATUS:
\r
723 extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
\r
724 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
728 user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS', E_USER_NOTICE);
\r
733 * Set permissions on a file.
\r
735 * Returns the new file permissions on success or FALSE on error.
\r
737 * @param Integer $mode
\r
738 * @param String $filename
\r
742 function chmod($mode, $filename)
\r
744 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
\r
748 $filename = $this->_realpath($filename);
\r
749 if ($filename === false) {
\r
753 // SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to
\r
754 // SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT.
\r
755 $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
\r
756 if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($filename), $filename, $attr))) {
\r
761 "Because some systems must use separate system calls to set various attributes, it is possible that a failure
\r
762 response will be returned, but yet some of the attributes may be have been successfully modified. If possible,
\r
763 servers SHOULD avoid this situation; however, clients MUST be aware that this is possible."
\r
765 -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6
\r
767 $response = $this->_get_sftp_packet();
\r
768 if ($this->packet_type != NET_SFTP_STATUS) {
\r
769 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
\r
773 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
\r
774 if ($status != NET_SFTP_STATUS_EOF) {
\r
775 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
776 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
779 // rather than return what the permissions *should* be, we'll return what they actually are. this will also
\r
780 // tell us if the file actually exists.
\r
781 // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
\r
782 $packet = pack('Na*', strlen($filename), $filename);
\r
783 if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) {
\r
787 $response = $this->_get_sftp_packet();
\r
788 switch ($this->packet_type) {
\r
789 case NET_SFTP_ATTRS:
\r
790 $attrs = $this->_parseAttributes($response);
\r
791 return $attrs['permissions'];
\r
792 case NET_SFTP_STATUS:
\r
793 extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
\r
794 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
798 user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS', E_USER_NOTICE);
\r
803 * Creates a directory.
\r
805 * @param String $dir
\r
809 function mkdir($dir)
\r
811 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
\r
815 $dir = $this->_realpath(rtrim($dir, '/'));
\r
816 if ($dir === false) {
\r
820 // by not providing any permissions, hopefully the server will use the logged in users umask - their
\r
821 // default permissions.
\r
822 if (!$this->_send_sftp_packet(NET_SFTP_MKDIR, pack('Na*N', strlen($dir), $dir, 0))) {
\r
826 $response = $this->_get_sftp_packet();
\r
827 if ($this->packet_type != NET_SFTP_STATUS) {
\r
828 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
\r
832 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
\r
833 if ($status != NET_SFTP_STATUS_OK) {
\r
834 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
835 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
843 * Removes a directory.
\r
845 * @param String $dir
\r
849 function rmdir($dir)
\r
851 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
\r
855 $dir = $this->_realpath($dir);
\r
856 if ($dir === false) {
\r
860 if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($dir), $dir))) {
\r
864 $response = $this->_get_sftp_packet();
\r
865 if ($this->packet_type != NET_SFTP_STATUS) {
\r
866 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
\r
870 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
\r
871 if ($status != NET_SFTP_STATUS_OK) {
\r
872 // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED?
\r
873 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
874 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
882 * Uploads a file to the SFTP server.
\r
884 * By default, Net_SFTP::put() does not read from the local filesystem. $data is dumped directly into $remote_file.
\r
885 * So, for example, if you set $data to 'filename.ext' and then do Net_SFTP::get(), you will get a file, twelve bytes
\r
886 * long, containing 'filename.ext' as its contents.
\r
888 * Setting $mode to NET_SFTP_LOCAL_FILE will change the above behavior. With NET_SFTP_LOCAL_FILE, $remote_file will
\r
889 * contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how
\r
890 * large $remote_file will be, as well.
\r
892 * Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take
\r
893 * care of that, yourself.
\r
895 * @param String $remote_file
\r
896 * @param String $data
\r
897 * @param optional Integer $flags
\r
900 * @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - Net_SFTP::setMode().
\r
902 function put($remote_file, $data, $mode = NET_SFTP_STRING)
\r
904 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
\r
908 $remote_file = $this->_realpath($remote_file);
\r
909 if ($remote_file === false) {
\r
913 $packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_TRUNCATE, 0);
\r
914 if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
\r
918 $response = $this->_get_sftp_packet();
\r
919 switch ($this->packet_type) {
\r
920 case NET_SFTP_HANDLE:
\r
921 $handle = substr($response, 4);
\r
923 case NET_SFTP_STATUS:
\r
924 extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
\r
925 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
928 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE);
\r
932 $initialize = true;
\r
934 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3
\r
935 if ($mode == NET_SFTP_LOCAL_FILE) {
\r
936 if (!is_file($data)) {
\r
937 user_error("$data is not a valid file", E_USER_NOTICE);
\r
940 $fp = fopen($data, 'rb');
\r
945 $size = filesize($data);
\r
948 $size = strlen($data);
\r
951 $size = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
\r
953 $sftp_packet_size = 34000; // PuTTY uses 4096
\r
955 while ($sent < $size) {
\r
956 $temp = $mode == NET_SFTP_LOCAL_FILE ? fread($fp, $sftp_packet_size) : $this->_string_shift($data, $sftp_packet_size);
\r
957 $packet = pack('Na*N3a*', strlen($handle), $handle, 0, $sent, strlen($temp), $temp);
\r
958 if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet)) {
\r
962 $sent+= strlen($temp);
\r
967 if (!$this->_read_put_responses($i)) {
\r
975 $this->_read_put_responses($i);
\r
977 if ($mode == NET_SFTP_LOCAL_FILE) {
\r
981 if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
\r
985 $response = $this->_get_sftp_packet();
\r
986 if ($this->packet_type != NET_SFTP_STATUS) {
\r
987 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
\r
991 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
\r
992 if ($status != NET_SFTP_STATUS_OK) {
\r
993 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
994 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
1002 * Reads multiple successive SSH_FXP_WRITE responses
\r
1004 * Sending an SSH_FXP_WRITE packet and immediately reading its response isn't as efficient as blindly sending out $i
\r
1005 * SSH_FXP_WRITEs, in succession, and then reading $i responses.
\r
1007 * @param Integer $i
\r
1011 function _read_put_responses($i)
\r
1014 $response = $this->_get_sftp_packet();
\r
1015 if ($this->packet_type != NET_SFTP_STATUS) {
\r
1016 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
\r
1020 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
\r
1021 if ($status != NET_SFTP_STATUS_OK) {
\r
1022 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
1023 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
1032 * Downloads a file from the SFTP server.
\r
1034 * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
\r
1035 * the operation was unsuccessful. If $local_file is defined, returns true or false depending on the success of the
\r
1038 * @param String $remote_file
\r
1039 * @param optional String $local_file
\r
1043 function get($remote_file, $local_file = false)
\r
1045 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
\r
1049 $remote_file = $this->_realpath($remote_file);
\r
1050 if ($remote_file === false) {
\r
1054 $packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_READ, 0);
\r
1055 if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
\r
1059 $response = $this->_get_sftp_packet();
\r
1060 switch ($this->packet_type) {
\r
1061 case NET_SFTP_HANDLE:
\r
1062 $handle = substr($response, 4);
\r
1064 case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
\r
1065 extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
\r
1066 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
1069 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE);
\r
1073 $packet = pack('Na*', strlen($handle), $handle);
\r
1074 if (!$this->_send_sftp_packet(NET_SFTP_FSTAT, $packet)) {
\r
1078 $response = $this->_get_sftp_packet();
\r
1079 switch ($this->packet_type) {
\r
1080 case NET_SFTP_ATTRS:
\r
1081 $attrs = $this->_parseAttributes($response);
\r
1083 case NET_SFTP_STATUS:
\r
1084 extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
\r
1085 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
1088 user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS', E_USER_NOTICE);
\r
1092 if ($local_file !== false) {
\r
1093 $fp = fopen($local_file, 'wb');
\r
1102 while ($read < $attrs['size']) {
\r
1103 $packet = pack('Na*N3', strlen($handle), $handle, 0, $read, 1 << 20);
\r
1104 if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet)) {
\r
1108 $response = $this->_get_sftp_packet();
\r
1109 switch ($this->packet_type) {
\r
1110 case NET_SFTP_DATA:
\r
1111 $temp = substr($response, 4);
\r
1112 $read+= strlen($temp);
\r
1113 if ($local_file === false) {
\r
1116 fputs($fp, $temp);
\r
1119 case NET_SFTP_STATUS:
\r
1120 extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
\r
1121 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
1124 user_error('Expected SSH_FXP_DATA or SSH_FXP_STATUS', E_USER_NOTICE);
\r
1129 if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
\r
1133 $response = $this->_get_sftp_packet();
\r
1134 if ($this->packet_type != NET_SFTP_STATUS) {
\r
1135 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
\r
1139 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
\r
1140 if ($status != NET_SFTP_STATUS_OK) {
\r
1141 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
1142 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
1146 if (isset($content)) {
\r
1155 * Deletes a file on the SFTP server.
\r
1157 * @param String $path
\r
1161 function delete($path)
\r
1163 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
\r
1167 $remote_file = $this->_realpath($path);
\r
1168 if ($path === false) {
\r
1172 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
\r
1173 if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path))) {
\r
1177 $response = $this->_get_sftp_packet();
\r
1178 if ($this->packet_type != NET_SFTP_STATUS) {
\r
1179 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
\r
1183 // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
\r
1184 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
\r
1185 if ($status != NET_SFTP_STATUS_OK) {
\r
1186 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
1187 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
1195 * Renames a file or a directory on the SFTP server
\r
1197 * @param String $oldname
\r
1198 * @param String $newname
\r
1202 function rename($oldname, $newname)
\r
1204 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
\r
1208 $oldname = $this->_realpath($oldname);
\r
1209 $newname = $this->_realpath($newname);
\r
1210 if ($oldname === false || $newname === false) {
\r
1214 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
\r
1215 $packet = pack('Na*Na*', strlen($oldname), $oldname, strlen($newname), $newname);
\r
1216 if (!$this->_send_sftp_packet(NET_SFTP_RENAME, $packet)) {
\r
1220 $response = $this->_get_sftp_packet();
\r
1221 if ($this->packet_type != NET_SFTP_STATUS) {
\r
1222 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
\r
1226 // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
\r
1227 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
\r
1228 if ($status != NET_SFTP_STATUS_OK) {
\r
1229 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
1230 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
\r
1238 * Parse Attributes
\r
1240 * See '7. File Attributes' of draft-ietf-secsh-filexfer-13 for more info.
\r
1242 * @param String $response
\r
1246 function _parseAttributes(&$response)
\r
1249 extract(unpack('Nflags', $this->_string_shift($response, 4)));
\r
1250 // SFTPv4+ have a type field (a byte) that follows the above flag field
\r
1251 foreach ($this->attributes as $key => $value) {
\r
1252 switch ($flags & $key) {
\r
1253 case NET_SFTP_ATTR_SIZE: // 0x00000001
\r
1254 // size is represented by a 64-bit integer, so we perhaps ought to be doing the following:
\r
1255 // $attr['size'] = new Math_BigInteger($this->_string_shift($response, 8), 256);
\r
1256 // of course, you shouldn't be using Net_SFTP to transfer files that are in excess of 4GB
\r
1257 // (0xFFFFFFFF bytes), anyway. as such, we'll just represent all file sizes that are bigger than
\r
1258 // 4GB as being 4GB.
\r
1259 extract(unpack('Nupper/Nsize', $this->_string_shift($response, 8)));
\r
1261 $attr['size'] = 0xFFFFFFFF;
\r
1263 $attr['size'] = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
\r
1266 case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
\r
1267 $attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8));
\r
1269 case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
\r
1270 $attr+= unpack('Npermissions', $this->_string_shift($response, 4));
\r
1272 case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
\r
1273 $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8));
\r
1275 case NET_SFTP_ATTR_EXTENDED: // 0x80000000
\r
1276 extract(unpack('Ncount', $this->_string_shift($response, 4)));
\r
1277 for ($i = 0; $i < $count; $i++) {
\r
1278 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
1279 $key = $this->_string_shift($response, $length);
\r
1280 extract(unpack('Nlength', $this->_string_shift($response, 4)));
\r
1281 $attr[$key] = $this->_string_shift($response, $length);
\r
1289 * Sends SFTP Packets
\r
1291 * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
\r
1293 * @param Integer $type
\r
1294 * @param String $data
\r
1295 * @see Net_SFTP::_get_sftp_packet()
\r
1296 * @see Net_SSH2::_send_channel_packet()
\r
1300 function _send_sftp_packet($type, $data)
\r
1302 $packet = $this->request_id !== false ?
\r
1303 pack('NCNa*', strlen($data) + 5, $type, $this->request_id, $data) :
\r
1304 pack('NCa*', strlen($data) + 1, $type, $data);
\r
1306 $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
\r
1307 $result = $this->_send_channel_packet(NET_SFTP_CHANNEL, $packet);
\r
1308 $stop = strtok(microtime(), ' ') + strtok('');
\r
1310 if (defined('NET_SFTP_LOGGING')) {
\r
1311 $this->packet_type_log[] = '-> ' . $this->packet_types[$type] .
\r
1312 ' (' . round($stop - $start, 4) . 's)';
\r
1313 if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
\r
1314 $this->packet_log[] = $data;
\r
1322 * Receives SFTP Packets
\r
1324 * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
\r
1326 * Incidentally, the number of SSH_MSG_CHANNEL_DATA messages has no bearing on the number of SFTP packets present.
\r
1327 * There can be one SSH_MSG_CHANNEL_DATA messages containing two SFTP packets or there can be two SSH_MSG_CHANNEL_DATA
\r
1328 * messages containing one SFTP packet.
\r
1330 * @see Net_SFTP::_send_sftp_packet()
\r
1334 function _get_sftp_packet()
\r
1336 $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
\r
1338 // SFTP packet length
\r
1339 while (strlen($this->packet_buffer) < 4) {
\r
1340 $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL);
\r
1341 if (is_bool($temp)) {
\r
1342 $this->packet_type = false;
\r
1343 $this->packet_buffer = '';
\r
1346 $this->packet_buffer.= $temp;
\r
1348 extract(unpack('Nlength', $this->_string_shift($this->packet_buffer, 4)));
\r
1349 $tempLength = $length;
\r
1350 $tempLength-= strlen($this->packet_buffer);
\r
1352 // SFTP packet type and data payload
\r
1353 while ($tempLength > 0) {
\r
1354 $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL);
\r
1355 if (is_bool($temp)) {
\r
1356 $this->packet_type = false;
\r
1357 $this->packet_buffer = '';
\r
1360 $this->packet_buffer.= $temp;
\r
1361 $tempLength-= strlen($temp);
\r
1364 $stop = strtok(microtime(), ' ') + strtok('');
\r
1366 $this->packet_type = ord($this->_string_shift($this->packet_buffer));
\r
1368 if ($this->request_id !== false) {
\r
1369 $this->_string_shift($this->packet_buffer, 4); // remove the request id
\r
1370 $length-= 5; // account for the request id and the packet type
\r
1372 $length-= 1; // account for the packet type
\r
1375 $packet = $this->_string_shift($this->packet_buffer, $length);
\r
1377 if (defined('NET_SFTP_LOGGING')) {
\r
1378 $this->packet_type_log[] = '<- ' . $this->packet_types[$this->packet_type] .
\r
1379 ' (' . round($stop - $start, 4) . 's)';
\r
1380 if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
\r
1381 $this->packet_log[] = $packet;
\r
1389 * Returns a log of the packets that have been sent and received.
\r
1391 * 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')
\r
1394 * @return String or Array
\r
1396 function getSFTPLog()
\r
1398 if (!defined('NET_SFTP_LOGGING')) {
\r
1402 switch (NET_SFTP_LOGGING) {
\r
1403 case NET_SFTP_LOG_COMPLEX:
\r
1404 return $this->_format_log($this->packet_log, $this->packet_type_log);
\r
1406 //case NET_SFTP_LOG_SIMPLE:
\r
1408 return $this->packet_type_log;
\r
1413 * Returns all errors
\r
1418 function getSFTPErrors()
\r
1420 return $this->sftp_errors;
\r
1424 * Returns the last error
\r
1429 function getLastSFTPError()
\r
1431 return $this->sftp_errors[count($this->sftp_errors) - 1];
\r
1435 * Get supported SFTP versions
\r
1440 function getSupportedVersions()
\r
1442 $temp = array('version' => $this->version);
\r
1443 if (isset($this->extensions['versions'])) {
\r
1444 $temp['extensions'] = $this->extensions['versions'];
\r
1452 * @param Integer $reason
\r
1456 function _disconnect($reason)
\r
1458 $this->pwd = false;
\r
1459 parent::_disconnect($reason);
\r