2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
5 * Pure-PHP implementation of SFTP.
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.
13 * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
15 * Here's a short example of how to use this library:
18 * include('Net/SFTP.php');
20 * $sftp = new Net_SFTP('www.domain.tld');
21 * if (!$sftp->login('username', 'password')) {
22 * exit('Login Failed');
25 * echo $sftp->pwd() . "\r\n";
26 * $sftp->put('filename.ext', 'hello, world!');
27 * print_r($sftp->nlist());
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:
38 * The above copyright notice and this permission notice shall be included in
39 * all copies or substantial portions of the Software.
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
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
60 if (!class_exists('Net_SSH2')) {
61 require_once('Net/SSH2.php');
66 * @see Net_SFTP::getLog()
69 * Returns the message numbers
71 define('NET_SFTP_LOG_SIMPLE', NET_SSH2_LOG_SIMPLE
);
73 * Returns the message content
75 define('NET_SFTP_LOG_COMPLEX', NET_SSH2_LOG_COMPLEX
);
77 * Outputs the message content in real-time.
79 define('NET_SFTP_LOG_REALTIME', 3);
83 * SFTP channel constant
85 * Net_SSH2::exec() uses 0 and Net_SSH2::read() / Net_SSH2::write() use 1.
87 * @see Net_SSH2::_send_channel_packet()
88 * @see Net_SSH2::_get_channel_packet()
91 define('NET_SFTP_CHANNEL', 2);
95 * @see Net_SFTP::put()
98 * Reads data from a local file.
100 define('NET_SFTP_LOCAL_FILE', 1);
102 * Reads data from a string.
104 // this value isn't really used anymore but i'm keeping it reserved for historical reasons
105 define('NET_SFTP_STRING', 2);
109 define('NET_SFTP_RESUME', 4);
113 * Pure-PHP implementations of SFTP.
115 * @author Jim Wigginton <terrafrost@php.net>
120 class Net_SFTP
extends Net_SSH2
{
124 * @see Net_SFTP::Net_SFTP()
128 var $packet_types = array();
133 * @see Net_SFTP::Net_SFTP()
137 var $status_codes = array();
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.
146 * @see Net_SFTP::_send_sftp_packet()
149 var $request_id = false;
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.
158 * @see Net_SFTP::_get_sftp_packet()
161 var $packet_type = -1;
167 * @see Net_SFTP::_get_sftp_packet()
170 var $packet_buffer = '';
173 * Extensions supported by the server
176 * @see Net_SFTP::_initChannel()
179 var $extensions = array();
182 * Server SFTP version
185 * @see Net_SFTP::_initChannel()
191 * Current working directory
194 * @see Net_SFTP::_realpath()
195 * @see Net_SFTP::chdir()
203 * @see Net_SFTP::getLog()
207 var $packet_type_log = array();
212 * @see Net_SFTP::getLog()
216 var $packet_log = array();
221 * @see Net_SFTP::getSFTPErrors()
222 * @see Net_SFTP::getLastSFTPError()
226 var $sftp_errors = array();
231 * @see Net_SFTP::_parseLongname()
240 * Rather than always having to open a directory and close it immediately there after to see if a file is a directory or
243 * @see Net_SFTP::_save_dir()
244 * @see Net_SFTP::_remove_dir()
245 * @see Net_SFTP::_is_dir()
252 * Default Constructor.
254 * Connects to an SFTP server
256 * @param String $host
257 * @param optional Integer $port
258 * @param optional Integer $timeout
262 function Net_SFTP($host, $port = 22, $timeout = 10)
264 parent
::Net_SSH2($host, $port, $timeout);
265 $this->packet_types
= array(
266 1 => 'NET_SFTP_INIT',
267 2 => 'NET_SFTP_VERSION',
268 /* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+:
269 SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1
270 pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */
271 3 => 'NET_SFTP_OPEN',
272 4 => 'NET_SFTP_CLOSE',
273 5 => 'NET_SFTP_READ',
274 6 => 'NET_SFTP_WRITE',
275 7 => 'NET_SFTP_LSTAT',
276 9 => 'NET_SFTP_SETSTAT',
277 11 => 'NET_SFTP_OPENDIR',
278 12 => 'NET_SFTP_READDIR',
279 13 => 'NET_SFTP_REMOVE',
280 14 => 'NET_SFTP_MKDIR',
281 15 => 'NET_SFTP_RMDIR',
282 16 => 'NET_SFTP_REALPATH',
283 17 => 'NET_SFTP_STAT',
284 /* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+:
285 SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
286 pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */
287 18 => 'NET_SFTP_RENAME',
289 101=> 'NET_SFTP_STATUS',
290 102=> 'NET_SFTP_HANDLE',
291 /* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+:
292 SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4
293 pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */
294 103=> 'NET_SFTP_DATA',
295 104=> 'NET_SFTP_NAME',
296 105=> 'NET_SFTP_ATTRS',
298 200=> 'NET_SFTP_EXTENDED'
300 $this->status_codes
= array(
301 0 => 'NET_SFTP_STATUS_OK',
302 1 => 'NET_SFTP_STATUS_EOF',
303 2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
304 3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
305 4 => 'NET_SFTP_STATUS_FAILURE',
306 5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
307 6 => 'NET_SFTP_STATUS_NO_CONNECTION',
308 7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
309 8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED'
311 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
312 // the order, in this case, matters quite a lot - see Net_SFTP::_parseAttributes() to understand why
313 $this->attributes
= array(
314 0x00000001 => 'NET_SFTP_ATTR_SIZE',
315 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
316 0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
317 0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
318 // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
319 // yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in
320 // two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000.
321 // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
322 -1 << 31 => 'NET_SFTP_ATTR_EXTENDED'
324 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
325 // the flag definitions change somewhat in SFTPv5+. if SFTPv5+ support is added to this library, maybe name
326 // the array for that $this->open5_flags and similarily alter the constant names.
327 $this->open_flags
= array(
328 0x00000001 => 'NET_SFTP_OPEN_READ',
329 0x00000002 => 'NET_SFTP_OPEN_WRITE',
330 0x00000004 => 'NET_SFTP_OPEN_APPEND',
331 0x00000008 => 'NET_SFTP_OPEN_CREATE',
332 0x00000010 => 'NET_SFTP_OPEN_TRUNCATE'
334 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
335 // see Net_SFTP::_parseLongname() for an explanation
336 $this->file_types
= array(
337 1 => 'NET_SFTP_TYPE_REGULAR',
338 2 => 'NET_SFTP_TYPE_DIRECTORY',
339 3 => 'NET_SFTP_TYPE_SYMLINK',
340 4 => 'NET_SFTP_TYPE_SPECIAL'
342 $this->_define_array(
354 * @param String $username
355 * @param optional String $password
359 function login($username, $password = '')
361 if (!parent
::login($username, $password)) {
365 $this->window_size_client_to_server
[NET_SFTP_CHANNEL
] = $this->window_size
;
367 $packet = pack('CNa*N3',
368 NET_SSH2_MSG_CHANNEL_OPEN
, strlen('session'), 'session', NET_SFTP_CHANNEL
, $this->window_size
, 0x4000);
370 if (!$this->_send_binary_packet($packet)) {
374 $this->channel_status
[NET_SFTP_CHANNEL
] = NET_SSH2_MSG_CHANNEL_OPEN
;
376 $response = $this->_get_channel_packet(NET_SFTP_CHANNEL
);
377 if ($response === false) {
381 $packet = pack('CNNa*CNa*',
382 NET_SSH2_MSG_CHANNEL_REQUEST
, $this->server_channels
[NET_SFTP_CHANNEL
], strlen('subsystem'), 'subsystem', 1, strlen('sftp'), 'sftp');
383 if (!$this->_send_binary_packet($packet)) {
387 $this->channel_status
[NET_SFTP_CHANNEL
] = NET_SSH2_MSG_CHANNEL_REQUEST
;
389 $response = $this->_get_channel_packet(NET_SFTP_CHANNEL
);
390 if ($response === false) {
394 $this->channel_status
[NET_SFTP_CHANNEL
] = NET_SSH2_MSG_CHANNEL_DATA
;
396 if (!$this->_send_sftp_packet(NET_SFTP_INIT
, "\0\0\0\3")) {
400 $response = $this->_get_sftp_packet();
401 if ($this->packet_type
!= NET_SFTP_VERSION
) {
402 user_error('Expected SSH_FXP_VERSION', E_USER_NOTICE
);
406 extract(unpack('Nversion', $this->_string_shift($response, 4)));
407 $this->version
= $version;
408 while (!empty($response)) {
409 extract(unpack('Nlength', $this->_string_shift($response, 4)));
410 $key = $this->_string_shift($response, $length);
411 extract(unpack('Nlength', $this->_string_shift($response, 4)));
412 $value = $this->_string_shift($response, $length);
413 $this->extensions
[$key] = $value;
417 SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com',
418 however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's
419 not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for
420 one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that
421 'newline@vandyke.com' would.
424 if (isset($this->extensions['newline@vandyke.com'])) {
425 $this->extensions['newline'] = $this->extensions['newline@vandyke.com'];
426 unset($this->extensions['newline@vandyke.com']);
430 $this->request_id
= 1;
433 A Note on SFTPv4/5/6 support:
434 <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.1> states the following:
436 "If the client wishes to interoperate with servers that support noncontiguous version
437 numbers it SHOULD send '3'"
439 Given that the server only sends its version number after the client has already done so, the above
440 seems to be suggesting that v3 should be the default version. This makes sense given that v3 is the
443 <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.5> states the following;
445 "If the server did not send the "versions" extension, or the version-from-list was not included, the
446 server MAY send a status response describing the failure, but MUST then close the channel without
447 processing any further requests."
449 So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and
450 a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4? If it only implements
451 v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed
452 in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what Net_SFTP would do is close the
453 channel and reopen it with a new and updated SSH_FXP_INIT packet.
455 switch ($this->version
) {
463 $this->pwd
= $this->_realpath('.', false);
465 $this->_save_dir($this->pwd
);
471 * Returns the current directory name
484 * @param String $response
485 * @param optional Integer $status
488 function _logError($response, $status = -1) {
490 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
493 $error = $this->status_codes
[$status];
495 if ($this->version
> 2) {
496 extract(unpack('Nlength', $this->_string_shift($response, 4)));
497 $this->sftp_errors
[] = $error . ': ' . $this->_string_shift($response, $length);
499 $this->sftp_errors
[] = $error;
504 * Canonicalize the Server-Side Path Name
506 * SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it. Returns
507 * the absolute (canonicalized) path.
509 * @see Net_SFTP::chdir()
514 function _realpath($dir, $check_dir = true)
516 if ($check_dir && $this->_is_dir($dir)) {
521 "This protocol represents file names as strings. File names are
522 assumed to use the slash ('/') character as a directory separator.
524 File names starting with a slash are "absolute", and are relative to
525 the root of the file system. Names starting with any other character
526 are relative to the user's default directory (home directory). Note
527 that identifying the user is assumed to take place outside of this
530 -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-6
533 if ($this->pwd
!== false) {
534 // if the SFTP server returned the canonicalized path even for non-existant files this wouldn't be necessary
535 // on OpenSSH it isn't necessary but on other SFTP servers it is. that and since the specs say nothing on
536 // the subject, we'll go ahead and work around it with the following.
537 if (empty($dir) ||
$dir[strlen($dir) - 1] != '/') {
538 $file = basename($dir);
539 $dir = dirname($dir);
542 $dir = $dir[0] == '/' ?
'/' . rtrim(substr($dir, 1), '/') : rtrim($dir, '/');
544 if ($dir == '.' ||
$dir == $this->pwd
) {
552 if ($dir[0] != '/') {
553 $dir = $this->pwd
. '/' . $dir;
555 // on the surface it seems like maybe resolving a path beginning with / is unnecessary, but such paths
556 // can contain .'s and ..'s just like any other. we could parse those out as appropriate or we can let
557 // the server do it. we'll do the latter.
561 that SSH_FXP_REALPATH returns SSH_FXP_NAME does not necessarily mean that anything actually exists at the
562 specified path. generally speaking, no attributes are returned with this particular SSH_FXP_NAME packet
563 regardless of whether or not a file actually exists. and in SFTPv3, the longname field and the filename
564 field match for this particular SSH_FXP_NAME packet. for other SSH_FXP_NAME packets, this will likely
565 not be the case, but for this one, it is.
567 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
568 if (!$this->_send_sftp_packet(NET_SFTP_REALPATH
, pack('Na*', strlen($dir), $dir))) {
572 $response = $this->_get_sftp_packet();
573 switch ($this->packet_type
) {
575 // although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following
576 // should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks
577 // at is the first part and that part is defined the same in SFTP versions 3 through 6.
578 $this->_string_shift($response, 4); // skip over the count - it should be 1, anyway
579 extract(unpack('Nlength', $this->_string_shift($response, 4)));
580 $realpath = $this->_string_shift($response, $length);
581 // the following is SFTPv3 only code. see Net_SFTP::_parseLongname() for more information.
582 // per the above comment, this is a shot in the dark that, on most servers, won't help us in determining
583 // the file type for Net_SFTP::stat() and Net_SFTP::lstat() but it's worth a shot.
584 extract(unpack('Nlength', $this->_string_shift($response, 4)));
585 $this->fileType
= $this->_parseLongname($this->_string_shift($response, $length));
587 case NET_SFTP_STATUS
:
588 $this->_logError($response);
591 user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS', E_USER_NOTICE
);
595 // if $this->pwd isn't set than the only thing $realpath could be is for '.', which is pretty much guaranteed to
596 // be a bonafide directory
598 $realpath.= '/' . $file;
605 * Changes the current directory
613 if (!($this->bitmap
& NET_SSH2_MASK_LOGIN
)) {
617 if ($dir[strlen($dir) - 1] != '/') {
621 // confirm that $dir is, in fact, a valid directory
622 if ($this->_is_dir($dir)) {
627 $dir = $this->_realpath($dir, false);
629 if ($this->_is_dir($dir)) {
634 if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR
, pack('Na*', strlen($dir), $dir))) {
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);
644 case NET_SFTP_STATUS
:
645 $this->_logError($response);
648 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE
);
652 if (!$this->_send_sftp_packet(NET_SFTP_CLOSE
, pack('Na*', strlen($handle), $handle))) {
656 $response = $this->_get_sftp_packet();
657 if ($this->packet_type
!= NET_SFTP_STATUS
) {
658 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE
);
662 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
663 if ($status != NET_SFTP_STATUS_OK
) {
664 $this->_logError($response, $status);
668 $this->_save_dir($dir);
675 * Returns a list of files in the given directory
677 * @param optional String $dir
681 function nlist($dir = '.')
683 return $this->_list($dir, false);
687 * Returns a detailed list of files in the given directory
689 * @param optional String $dir
693 function rawlist($dir = '.')
695 return $this->_list($dir, true);
699 * Reads a list, be it detailed or not, of files in the given directory
701 * @param optional String $dir
705 function _list($dir, $raw = true, $realpath = true)
707 if (!($this->bitmap
& NET_SSH2_MASK_LOGIN
)) {
711 $dir = $this->_realpath($dir . '/');
712 if ($dir === false) {
716 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2
717 if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR
, pack('Na*', strlen($dir), $dir))) {
721 $response = $this->_get_sftp_packet();
722 switch ($this->packet_type
) {
723 case NET_SFTP_HANDLE
:
724 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2
725 // since 'handle' is the last field in the SSH_FXP_HANDLE packet, we'll just remove the first four bytes that
726 // represent the length of the string and leave it at that
727 $handle = substr($response, 4);
729 case NET_SFTP_STATUS
:
730 // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
731 $this->_logError($response);
734 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE
);
738 $this->_save_dir($dir);
742 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2
743 // why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many
744 // SSH_MSG_CHANNEL_DATA messages is not known to me.
745 if (!$this->_send_sftp_packet(NET_SFTP_READDIR
, pack('Na*', strlen($handle), $handle))) {
749 $response = $this->_get_sftp_packet();
750 switch ($this->packet_type
) {
752 extract(unpack('Ncount', $this->_string_shift($response, 4)));
753 for ($i = 0; $i < $count; $i++
) {
754 extract(unpack('Nlength', $this->_string_shift($response, 4)));
755 $shortname = $this->_string_shift($response, $length);
756 extract(unpack('Nlength', $this->_string_shift($response, 4)));
757 $longname = $this->_string_shift($response, $length);
758 $attributes = $this->_parseAttributes($response); // we also don't care about the attributes
760 $contents[] = $shortname;
762 $contents[$shortname] = $attributes;
763 $fileType = $this->_parseLongname($longname);
765 if ($fileType == NET_SFTP_TYPE_DIRECTORY
&& ($shortname != '.' && $shortname != '..')) {
766 $this->_save_dir($dir . '/' . $shortname);
768 $contents[$shortname]['type'] = $fileType;
771 // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the
772 // final SSH_FXP_STATUS packet should tell us that, already.
775 case NET_SFTP_STATUS
:
776 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
777 if ($status != NET_SFTP_STATUS_EOF
) {
778 $this->_logError($response, $status);
783 user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS', E_USER_NOTICE
);
788 if (!$this->_send_sftp_packet(NET_SFTP_CLOSE
, pack('Na*', strlen($handle), $handle))) {
792 // "The client MUST release all resources associated with the handle regardless of the status."
793 // -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3
794 $response = $this->_get_sftp_packet();
795 if ($this->packet_type
!= NET_SFTP_STATUS
) {
796 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE
);
800 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
801 if ($status != NET_SFTP_STATUS_OK
) {
802 $this->_logError($response, $status);
810 * Returns the file size, in bytes, or false, on failure
812 * Files larger than 4GB will show up as being exactly 4GB.
814 * @param String $filename
818 function size($filename)
820 if (!($this->bitmap
& NET_SSH2_MASK_LOGIN
)) {
824 $filename = $this->_realpath($filename);
825 if ($filename === false) {
829 return $this->_size($filename);
833 * Save directories to cache
838 function _save_dir($dir)
840 // preg_replace('#^/|/(?=/)|/$#', '', $dir) == str_replace('//', '/', trim($dir, '/'))
841 $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir));
843 $temp = &$this->dirs
;
844 foreach ($dirs as $dir) {
845 if (!isset($temp[$dir])) {
846 $temp[$dir] = array();
848 $temp = &$temp[$dir];
853 * Remove directories from cache
858 function _remove_dir($dir)
860 $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir));
862 $temp = &$this->dirs
;
863 foreach ($dirs as $dir) {
864 if ($dir == end($dirs)) {
868 if (!isset($temp[$dir])) {
871 $temp = &$temp[$dir];
876 * Checks cache for directory
881 function _is_dir($dir)
883 $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir));
885 $temp = &$this->dirs
;
886 foreach ($dirs as $dir) {
887 if (!isset($temp[$dir])) {
890 $temp = &$temp[$dir];
895 * Returns general information about a file.
897 * Returns an array on success and false otherwise.
899 * @param String $filename
903 function stat($filename)
905 if (!($this->bitmap
& NET_SSH2_MASK_LOGIN
)) {
909 $filename = $this->_realpath($filename);
910 if ($filename === false) {
914 $stat = $this->_stat($filename, NET_SFTP_STAT
);
915 if ($stat === false) {
920 $stat['type'] = $this->chdir($filename) ?
921 NET_SFTP_TYPE_DIRECTORY
:
922 NET_SFTP_TYPE_REGULAR
;
929 * Returns general information about a file or symbolic link.
931 * Returns an array on success and false otherwise.
933 * @param String $filename
937 function lstat($filename)
939 if (!($this->bitmap
& NET_SSH2_MASK_LOGIN
)) {
943 $filename = $this->_realpath($filename);
944 if ($filename === false) {
948 $lstat = $this->_stat($filename, NET_SFTP_LSTAT
);
949 $stat = $this->_stat($filename, NET_SFTP_STAT
);
950 if ($stat === false) {
954 if ($lstat != $stat) {
955 return array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK
));
959 $lstat['type'] = $this->chdir($filename) ?
960 NET_SFTP_TYPE_DIRECTORY
:
961 NET_SFTP_TYPE_REGULAR
;
968 * Returns general information about a file or symbolic link
970 * Determines information without calling Net_SFTP::_realpath().
971 * The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT.
973 * @param String $filename
974 * @param Integer $type
978 function _stat($filename, $type)
980 // SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
981 $packet = pack('Na*', strlen($filename), $filename);
982 if (!$this->_send_sftp_packet($type, $packet)) {
986 $response = $this->_get_sftp_packet();
987 switch ($this->packet_type
) {
989 $attributes = $this->_parseAttributes($response);
990 if ($this->fileType
) {
991 $attributes['type'] = $this->fileType
;
994 case NET_SFTP_STATUS
:
995 $this->_logError($response);
999 user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS', E_USER_NOTICE
);
1004 * Attempt to identify the file type
1006 * @param String $path
1007 * @param Array $stat
1008 * @param Array $lstat
1012 function _identify_type($path, $stat1, $stat2)
1014 $stat1 = $this->_stat($path, $stat1);
1015 $stat2 = $this->_stat($path, $stat2);
1017 if ($stat1 != $stat2) {
1018 return array_merge($stat1, array('type' => NET_SFTP_TYPE_SYMLINK
));
1022 $stat1['type'] = $this->chdir($path) ?
1023 NET_SFTP_TYPE_DIRECTORY
:
1024 NET_SFTP_TYPE_REGULAR
;
1031 * Returns the file size, in bytes, or false, on failure
1033 * Determines the size without calling Net_SFTP::_realpath()
1035 * @param String $filename
1039 function _size($filename)
1041 $result = $this->_stat($filename, NET_SFTP_LSTAT
);
1042 if ($result === false) {
1045 return isset($result['size']) ?
$result['size'] : -1;
1049 * Set permissions on a file.
1051 * Returns the new file permissions on success or FALSE on error.
1053 * @param Integer $mode
1054 * @param String $filename
1058 function chmod($mode, $filename, $recursive = false)
1060 if (!($this->bitmap
& NET_SSH2_MASK_LOGIN
)) {
1064 $filename = $this->_realpath($filename);
1065 if ($filename === false) {
1071 $result = $this->_chmod_recursive($mode, $filename, $i);
1072 $this->_read_put_responses($i);
1076 // SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to
1077 // SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT.
1078 $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS
, $mode & 07777);
1079 if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT
, pack('Na*a*', strlen($filename), $filename, $attr))) {
1084 "Because some systems must use separate system calls to set various attributes, it is possible that a failure
1085 response will be returned, but yet some of the attributes may be have been successfully modified. If possible,
1086 servers SHOULD avoid this situation; however, clients MUST be aware that this is possible."
1088 -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6
1090 $response = $this->_get_sftp_packet();
1091 if ($this->packet_type
!= NET_SFTP_STATUS
) {
1092 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE
);
1096 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1097 if ($status != NET_SFTP_STATUS_OK
) {
1098 $this->_logError($response, $status);
1101 // rather than return what the permissions *should* be, we'll return what they actually are. this will also
1102 // tell us if the file actually exists.
1103 // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
1104 $packet = pack('Na*', strlen($filename), $filename);
1105 if (!$this->_send_sftp_packet(NET_SFTP_STAT
, $packet)) {
1109 $response = $this->_get_sftp_packet();
1110 switch ($this->packet_type
) {
1111 case NET_SFTP_ATTRS
:
1112 $attrs = $this->_parseAttributes($response);
1113 return $attrs['permissions'];
1114 case NET_SFTP_STATUS
:
1115 $this->_logError($response);
1119 user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS', E_USER_NOTICE
);
1124 * Recursively chmods directories on the SFTP server
1126 * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
1128 * @param Integer $mode
1129 * @param String $filename
1133 function _chmod_recursive($mode, $path, &$i)
1135 if (!$this->_read_put_responses($i)) {
1139 $entries = $this->_list($path, true, false);
1141 if ($entries === false) {
1142 return $this->chmod($mode, $path);
1145 // normally $entries would have at least . and .. but it might not if the directories
1146 // permissions didn't allow reading
1147 if (empty($entries)) {
1151 foreach ($entries as $filename=>$props) {
1152 if ($filename == '.' ||
$filename == '..') {
1156 if (!isset($props['type'])) {
1160 $temp = $path . '/' . $filename;
1161 if ($props['type'] == NET_SFTP_TYPE_DIRECTORY
) {
1162 if (!$this->_chmod_recursive($mode, $temp, $i)) {
1166 $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS
, $mode & 07777);
1167 if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT
, pack('Na*a*', strlen($temp), $temp, $attr))) {
1174 if (!$this->_read_put_responses($i)) {
1182 $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS
, $mode & 07777);
1183 if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT
, pack('Na*a*', strlen($path), $path, $attr))) {
1190 if (!$this->_read_put_responses($i)) {
1200 * Creates a directory.
1202 * @param String $dir
1206 function mkdir($dir)
1208 if (!($this->bitmap
& NET_SSH2_MASK_LOGIN
)) {
1212 if ($dir[0] != '/') {
1213 $dir = $this->_realpath(rtrim($dir, '/'));
1214 if ($dir === false) {
1217 if (!$this->_mkdir_helper($dir)) {
1221 $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir));
1223 foreach ($dirs as $dir) {
1225 $result = $this->_mkdir_helper($temp);
1236 * Helper function for directory creation
1238 * @param String $dir
1242 function _mkdir_helper($dir)
1244 // by not providing any permissions, hopefully the server will use the logged in users umask - their
1245 // default permissions.
1246 if (!$this->_send_sftp_packet(NET_SFTP_MKDIR
, pack('Na*N', strlen($dir), $dir, 0))) {
1250 $response = $this->_get_sftp_packet();
1251 if ($this->packet_type
!= NET_SFTP_STATUS
) {
1252 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE
);
1256 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1257 if ($status != NET_SFTP_STATUS_OK
) {
1258 $this->_logError($response, $status);
1262 $this->_save_dir($dir);
1268 * Removes a directory.
1270 * @param String $dir
1274 function rmdir($dir)
1276 if (!($this->bitmap
& NET_SSH2_MASK_LOGIN
)) {
1280 $dir = $this->_realpath($dir);
1281 if ($dir === false) {
1285 if (!$this->_send_sftp_packet(NET_SFTP_RMDIR
, pack('Na*', strlen($dir), $dir))) {
1289 $response = $this->_get_sftp_packet();
1290 if ($this->packet_type
!= NET_SFTP_STATUS
) {
1291 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE
);
1295 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1296 if ($status != NET_SFTP_STATUS_OK
) {
1297 // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED?
1298 $this->_logError($response, $status);
1302 $this->_remove_dir($dir);
1308 * Uploads a file to the SFTP server.
1310 * By default, Net_SFTP::put() does not read from the local filesystem. $data is dumped directly into $remote_file.
1311 * So, for example, if you set $data to 'filename.ext' and then do Net_SFTP::get(), you will get a file, twelve bytes
1312 * long, containing 'filename.ext' as its contents.
1314 * Setting $mode to NET_SFTP_LOCAL_FILE will change the above behavior. With NET_SFTP_LOCAL_FILE, $remote_file will
1315 * contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how
1316 * large $remote_file will be, as well.
1318 * Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take
1319 * care of that, yourself.
1321 * @param String $remote_file
1322 * @param String $data
1323 * @param optional Integer $mode
1326 * @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - Net_SFTP::setMode().
1328 function put($remote_file, $data, $mode = NET_SFTP_STRING
)
1330 if (!($this->bitmap
& NET_SSH2_MASK_LOGIN
)) {
1334 $remote_file = $this->_realpath($remote_file);
1335 if ($remote_file === false) {
1339 $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE
;
1340 // according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file."
1341 // in practice, it doesn't seem to do that.
1342 //$flags|= ($mode & NET_SFTP_RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE;
1344 // if NET_SFTP_OPEN_APPEND worked as it should the following (up until the -----------) wouldn't be necessary
1346 if ($mode & NET_SFTP_RESUME
) {
1347 $size = $this->_size($remote_file);
1348 $offset = $size !== false ?
$size : 0;
1350 $flags|
= NET_SFTP_OPEN_TRUNCATE
;
1354 $packet = pack('Na*N2', strlen($remote_file), $remote_file, $flags, 0);
1355 if (!$this->_send_sftp_packet(NET_SFTP_OPEN
, $packet)) {
1359 $response = $this->_get_sftp_packet();
1360 switch ($this->packet_type
) {
1361 case NET_SFTP_HANDLE
:
1362 $handle = substr($response, 4);
1364 case NET_SFTP_STATUS
:
1365 $this->_logError($response);
1368 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE
);
1374 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3
1375 if ($mode & NET_SFTP_LOCAL_FILE
) {
1376 if (!is_file($data)) {
1377 user_error("$data is not a valid file", E_USER_NOTICE
);
1380 $fp = @fopen
($data, 'rb');
1384 $size = filesize($data);
1386 $size = strlen($data);
1390 $size = $size < 0 ?
($size & 0x7FFFFFFF) +
0x80000000 : $size;
1392 $sftp_packet_size = 4096; // PuTTY uses 4096
1394 while ($sent < $size) {
1395 $temp = $mode & NET_SFTP_LOCAL_FILE ?
fread($fp, $sftp_packet_size) : $this->_string_shift($data, $sftp_packet_size);
1396 $packet = pack('Na*N3a*', strlen($handle), $handle, 0, $offset +
$sent, strlen($temp), $temp);
1397 if (!$this->_send_sftp_packet(NET_SFTP_WRITE
, $packet)) {
1401 $sent+
= strlen($temp);
1406 if (!$this->_read_put_responses($i)) {
1414 if (!$this->_read_put_responses($i)) {
1418 if ($mode & NET_SFTP_LOCAL_FILE
) {
1422 if (!$this->_send_sftp_packet(NET_SFTP_CLOSE
, pack('Na*', strlen($handle), $handle))) {
1426 $response = $this->_get_sftp_packet();
1427 if ($this->packet_type
!= NET_SFTP_STATUS
) {
1428 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE
);
1432 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1433 if ($status != NET_SFTP_STATUS_OK
) {
1434 $this->_logError($response, $status);
1442 * Reads multiple successive SSH_FXP_WRITE responses
1444 * Sending an SSH_FXP_WRITE packet and immediately reading its response isn't as efficient as blindly sending out $i
1445 * SSH_FXP_WRITEs, in succession, and then reading $i responses.
1451 function _read_put_responses($i)
1454 $response = $this->_get_sftp_packet();
1455 if ($this->packet_type
!= NET_SFTP_STATUS
) {
1456 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE
);
1460 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1461 if ($status != NET_SFTP_STATUS_OK
) {
1462 $this->_logError($response, $status);
1471 * Downloads a file from the SFTP server.
1473 * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
1474 * the operation was unsuccessful. If $local_file is defined, returns true or false depending on the success of the
1477 * @param String $remote_file
1478 * @param optional String $local_file
1482 function get($remote_file, $local_file = false)
1484 if (!($this->bitmap
& NET_SSH2_MASK_LOGIN
)) {
1488 $remote_file = $this->_realpath($remote_file);
1489 if ($remote_file === false) {
1493 $packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_READ
, 0);
1494 if (!$this->_send_sftp_packet(NET_SFTP_OPEN
, $packet)) {
1498 $response = $this->_get_sftp_packet();
1499 switch ($this->packet_type
) {
1500 case NET_SFTP_HANDLE
:
1501 $handle = substr($response, 4);
1503 case NET_SFTP_STATUS
: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
1504 $this->_logError($response);
1507 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE
);
1511 if ($local_file !== false) {
1512 $fp = fopen($local_file, 'wb');
1522 $packet = pack('Na*N3', strlen($handle), $handle, 0, $read, 1 << 20);
1523 if (!$this->_send_sftp_packet(NET_SFTP_READ
, $packet)) {
1524 if ($local_file !== false) {
1530 $response = $this->_get_sftp_packet();
1531 switch ($this->packet_type
) {
1533 $temp = substr($response, 4);
1534 $read+
= strlen($temp);
1535 if ($local_file === false) {
1541 case NET_SFTP_STATUS
:
1542 $this->_logError($response);
1545 user_error('Expected SSH_FXP_DATA or SSH_FXP_STATUS', E_USER_NOTICE
);
1546 if ($local_file !== false) {
1553 if ($local_file !== false) {
1557 if (!$this->_send_sftp_packet(NET_SFTP_CLOSE
, pack('Na*', strlen($handle), $handle))) {
1561 $response = $this->_get_sftp_packet();
1562 if ($this->packet_type
!= NET_SFTP_STATUS
) {
1563 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE
);
1567 $this->_logError($response);
1569 // check the status from the NET_SFTP_STATUS case in the above switch after the file has been closed
1570 if ($status != NET_SFTP_STATUS_OK
) {
1574 if (isset($content)) {
1582 * Deletes a file on the SFTP server.
1584 * @param String $path
1585 * @param Boolean $recursive
1589 function delete($path, $recursive = true)
1591 if (!($this->bitmap
& NET_SSH2_MASK_LOGIN
)) {
1595 $path = $this->_realpath($path);
1596 if ($path === false) {
1600 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
1601 if (!$this->_send_sftp_packet(NET_SFTP_REMOVE
, pack('Na*', strlen($path), $path))) {
1605 $response = $this->_get_sftp_packet();
1606 if ($this->packet_type
!= NET_SFTP_STATUS
) {
1607 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE
);
1611 // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
1612 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1613 if ($status != NET_SFTP_STATUS_OK
) {
1614 $this->_logError($response, $status);
1619 $result = $this->_delete_recursive($path, $i);
1620 $this->_read_put_responses($i);
1628 * Recursively deletes directories on the SFTP server
1630 * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
1632 * @param String $path
1637 function _delete_recursive($path, &$i)
1639 if (!$this->_read_put_responses($i)) {
1643 $entries = $this->_list($path, true, false);
1645 // normally $entries would have at least . and .. but it might not if the directories
1646 // permissions didn't allow reading
1647 if (empty($entries)) {
1651 foreach ($entries as $filename=>$props) {
1652 if ($filename == '.' ||
$filename == '..') {
1656 if (!isset($props['type'])) {
1660 $temp = $path . '/' . $filename;
1661 if ($props['type'] == NET_SFTP_TYPE_DIRECTORY
) {
1662 if (!$this->_delete_recursive($temp, $i)) {
1666 if (!$this->_send_sftp_packet(NET_SFTP_REMOVE
, pack('Na*', strlen($temp), $temp))) {
1673 if (!$this->_read_put_responses($i)) {
1681 if (!$this->_send_sftp_packet(NET_SFTP_RMDIR
, pack('Na*', strlen($path), $path))) {
1684 $this->_remove_dir($path);
1689 if (!$this->_read_put_responses($i)) {
1699 * Renames a file or a directory on the SFTP server
1701 * @param String $oldname
1702 * @param String $newname
1706 function rename($oldname, $newname)
1708 if (!($this->bitmap
& NET_SSH2_MASK_LOGIN
)) {
1712 $oldname = $this->_realpath($oldname);
1713 $newname = $this->_realpath($newname);
1714 if ($oldname === false ||
$newname === false) {
1718 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
1719 $packet = pack('Na*Na*', strlen($oldname), $oldname, strlen($newname), $newname);
1720 if (!$this->_send_sftp_packet(NET_SFTP_RENAME
, $packet)) {
1724 $response = $this->_get_sftp_packet();
1725 if ($this->packet_type
!= NET_SFTP_STATUS
) {
1726 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE
);
1730 // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
1731 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1732 if ($status != NET_SFTP_STATUS_OK
) {
1733 $this->_logError($response, $status);
1743 * See '7. File Attributes' of draft-ietf-secsh-filexfer-13 for more info.
1745 * @param String $response
1749 function _parseAttributes(&$response)
1752 extract(unpack('Nflags', $this->_string_shift($response, 4)));
1753 // SFTPv4+ have a type field (a byte) that follows the above flag field
1754 foreach ($this->attributes
as $key => $value) {
1755 switch ($flags & $key) {
1756 case NET_SFTP_ATTR_SIZE
: // 0x00000001
1757 // size is represented by a 64-bit integer, so we perhaps ought to be doing the following:
1758 // $attr['size'] = new Math_BigInteger($this->_string_shift($response, 8), 256);
1759 // of course, you shouldn't be using Net_SFTP to transfer files that are in excess of 4GB
1760 // (0xFFFFFFFF bytes), anyway. as such, we'll just represent all file sizes that are bigger than
1761 // 4GB as being 4GB.
1762 extract(unpack('Nupper/Nsize', $this->_string_shift($response, 8)));
1764 $attr['size'] = 0xFFFFFFFF;
1766 $attr['size'] = $size < 0 ?
($size & 0x7FFFFFFF) +
0x80000000 : $size;
1769 case NET_SFTP_ATTR_UIDGID
: // 0x00000002 (SFTPv3 only)
1770 $attr+
= unpack('Nuid/Ngid', $this->_string_shift($response, 8));
1772 case NET_SFTP_ATTR_PERMISSIONS
: // 0x00000004
1773 $attr+
= unpack('Npermissions', $this->_string_shift($response, 4));
1775 case NET_SFTP_ATTR_ACCESSTIME
: // 0x00000008
1776 $attr+
= unpack('Natime/Nmtime', $this->_string_shift($response, 8));
1778 case NET_SFTP_ATTR_EXTENDED
: // 0x80000000
1779 extract(unpack('Ncount', $this->_string_shift($response, 4)));
1780 for ($i = 0; $i < $count; $i++
) {
1781 extract(unpack('Nlength', $this->_string_shift($response, 4)));
1782 $key = $this->_string_shift($response, $length);
1783 extract(unpack('Nlength', $this->_string_shift($response, 4)));
1784 $attr[$key] = $this->_string_shift($response, $length);
1794 * SFTPv3 doesn't provide any easy way of identifying a file type. You could try to open
1795 * a file as a directory and see if an error is returned or you could try to parse the
1796 * SFTPv3-specific longname field of the SSH_FXP_NAME packet. That's what this function does.
1797 * The result is returned using the
1798 * {@link http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2 SFTPv4 type constants}.
1800 * If the longname is in an unrecognized format bool(false) is returned.
1802 * @param String $longname
1806 function _parseLongname($longname)
1808 // http://en.wikipedia.org/wiki/Unix_file_types
1809 // http://en.wikipedia.org/wiki/Filesystem_permissions#Notation_of_traditional_Unix_permissions
1810 if (preg_match('#^[^/]([r-][w-][xstST-]){3}#', $longname)) {
1811 switch ($longname[0]) {
1813 return NET_SFTP_TYPE_REGULAR
;
1815 return NET_SFTP_TYPE_DIRECTORY
;
1817 return NET_SFTP_TYPE_SYMLINK
;
1819 return NET_SFTP_TYPE_SPECIAL
;
1827 * Sends SFTP Packets
1829 * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
1831 * @param Integer $type
1832 * @param String $data
1833 * @see Net_SFTP::_get_sftp_packet()
1834 * @see Net_SSH2::_send_channel_packet()
1838 function _send_sftp_packet($type, $data)
1840 $packet = $this->request_id
!== false ?
1841 pack('NCNa*', strlen($data) +
5, $type, $this->request_id
, $data) :
1842 pack('NCa*', strlen($data) +
1, $type, $data);
1844 $start = strtok(microtime(), ' ') +
strtok(''); // http://php.net/microtime#61838
1845 $result = $this->_send_channel_packet(NET_SFTP_CHANNEL
, $packet);
1846 $stop = strtok(microtime(), ' ') +
strtok('');
1848 if (defined('NET_SFTP_LOGGING')) {
1849 $packet_type = '-> ' . $this->packet_types
[$type] .
1850 ' (' . round($stop - $start, 4) . 's)';
1851 if (NET_SFTP_LOGGING
== NET_SFTP_LOG_REALTIME
) {
1852 echo "<pre>\r\n" . $this->_format_log(array($data), array($packet_type)) . "\r\n</pre>\r\n";
1856 $this->packet_type_log
[] = $packet_type;
1857 if (NET_SFTP_LOGGING
== NET_SFTP_LOG_COMPLEX
) {
1858 $this->packet_log
[] = $data;
1867 * Receives SFTP Packets
1869 * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
1871 * Incidentally, the number of SSH_MSG_CHANNEL_DATA messages has no bearing on the number of SFTP packets present.
1872 * There can be one SSH_MSG_CHANNEL_DATA messages containing two SFTP packets or there can be two SSH_MSG_CHANNEL_DATA
1873 * messages containing one SFTP packet.
1875 * @see Net_SFTP::_send_sftp_packet()
1879 function _get_sftp_packet()
1881 $this->curTimeout
= false;
1883 $start = strtok(microtime(), ' ') +
strtok(''); // http://php.net/microtime#61838
1885 // SFTP packet length
1886 while (strlen($this->packet_buffer
) < 4) {
1887 $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL
);
1888 if (is_bool($temp)) {
1889 $this->packet_type
= false;
1890 $this->packet_buffer
= '';
1893 $this->packet_buffer
.= $temp;
1895 extract(unpack('Nlength', $this->_string_shift($this->packet_buffer
, 4)));
1896 $tempLength = $length;
1897 $tempLength-= strlen($this->packet_buffer
);
1899 // SFTP packet type and data payload
1900 while ($tempLength > 0) {
1901 $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL
);
1902 if (is_bool($temp)) {
1903 $this->packet_type
= false;
1904 $this->packet_buffer
= '';
1907 $this->packet_buffer
.= $temp;
1908 $tempLength-= strlen($temp);
1911 $stop = strtok(microtime(), ' ') +
strtok('');
1913 $this->packet_type
= ord($this->_string_shift($this->packet_buffer
));
1915 if ($this->request_id
!== false) {
1916 $this->_string_shift($this->packet_buffer
, 4); // remove the request id
1917 $length-= 5; // account for the request id and the packet type
1919 $length-= 1; // account for the packet type
1922 $packet = $this->_string_shift($this->packet_buffer
, $length);
1924 if (defined('NET_SFTP_LOGGING')) {
1925 $packet_type = '<- ' . $this->packet_types
[$this->packet_type
] .
1926 ' (' . round($stop - $start, 4) . 's)';
1927 if (NET_SFTP_LOGGING
== NET_SFTP_LOG_REALTIME
) {
1928 echo "<pre>\r\n" . $this->_format_log(array($packet), array($packet_type)) . "\r\n</pre>\r\n";
1932 $this->packet_type_log
[] = $packet_type;
1933 if (NET_SFTP_LOGGING
== NET_SFTP_LOG_COMPLEX
) {
1934 $this->packet_log
[] = $packet;
1943 * Returns a log of the packets that have been sent and received.
1945 * 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')
1948 * @return String or Array
1950 function getSFTPLog()
1952 if (!defined('NET_SFTP_LOGGING')) {
1956 switch (NET_SFTP_LOGGING
) {
1957 case NET_SFTP_LOG_COMPLEX
:
1958 return $this->_format_log($this->packet_log
, $this->packet_type_log
);
1960 //case NET_SFTP_LOG_SIMPLE:
1962 return $this->packet_type_log
;
1967 * Returns all errors
1972 function getSFTPErrors()
1974 return $this->sftp_errors
;
1978 * Returns the last error
1983 function getLastSFTPError()
1985 return count($this->sftp_errors
) ?
$this->sftp_errors
[count($this->sftp_errors
) - 1] : '';
1989 * Get supported SFTP versions
1994 function getSupportedVersions()
1996 $temp = array('version' => $this->version
);
1997 if (isset($this->extensions
['versions'])) {
1998 $temp['extensions'] = $this->extensions
['versions'];
2006 * @param Integer $reason
2010 function _disconnect($reason)
2013 parent
::_disconnect($reason);