PHPSECLIB 0.3.1 added to the project to support SFTP transfers of lab orders and...
[openemr.git] / library / phpseclib / Net / SFTP.php
blobfc2bf2e9b713f80df7aef448c77564de6f8054c1
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4 /**
5 * Pure-PHP implementation of SFTP.
7 * PHP versions 4 and 5
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:
16 * <code>
17 * <?php
18 * include('Net/SFTP.php');
20 * $sftp = new Net_SFTP('www.domain.tld');
21 * if (!$sftp->login('username', 'password')) {
22 * exit('Login Failed');
23 * }
25 * echo $sftp->pwd() . "\r\n";
26 * $sftp->put('filename.ext', 'hello, world!');
27 * print_r($sftp->nlist());
28 * ?>
29 * </code>
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
47 * THE SOFTWARE.
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
57 /**
58 * Include Net_SSH2
60 if (!class_exists('Net_SSH2')) {
61 require_once('Net/SSH2.php');
64 /**#@+
65 * @access public
66 * @see Net_SFTP::getLog()
68 /**
69 * Returns the message numbers
71 define('NET_SFTP_LOG_SIMPLE', NET_SSH2_LOG_SIMPLE);
72 /**
73 * Returns the message content
75 define('NET_SFTP_LOG_COMPLEX', NET_SSH2_LOG_COMPLEX);
76 /**
77 * Outputs the message content in real-time.
79 define('NET_SFTP_LOG_REALTIME', 3);
80 /**#@-*/
82 /**
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()
89 * @access private
91 define('NET_SFTP_CHANNEL', 2);
93 /**#@+
94 * @access public
95 * @see Net_SFTP::put()
97 /**
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);
107 * Resumes an upload
109 define('NET_SFTP_RESUME', 4);
110 /**#@-*/
113 * Pure-PHP implementations of SFTP.
115 * @author Jim Wigginton <terrafrost@php.net>
116 * @version 0.1.0
117 * @access public
118 * @package Net_SFTP
120 class Net_SFTP extends Net_SSH2 {
122 * Packet Types
124 * @see Net_SFTP::Net_SFTP()
125 * @var Array
126 * @access private
128 var $packet_types = array();
131 * Status Codes
133 * @see Net_SFTP::Net_SFTP()
134 * @var Array
135 * @access private
137 var $status_codes = array();
140 * The Request ID
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.
145 * @var Integer
146 * @see Net_SFTP::_send_sftp_packet()
147 * @access private
149 var $request_id = false;
152 * The Packet Type
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.
157 * @var Integer
158 * @see Net_SFTP::_get_sftp_packet()
159 * @access private
161 var $packet_type = -1;
164 * Packet Buffer
166 * @var String
167 * @see Net_SFTP::_get_sftp_packet()
168 * @access private
170 var $packet_buffer = '';
173 * Extensions supported by the server
175 * @var Array
176 * @see Net_SFTP::_initChannel()
177 * @access private
179 var $extensions = array();
182 * Server SFTP version
184 * @var Integer
185 * @see Net_SFTP::_initChannel()
186 * @access private
188 var $version;
191 * Current working directory
193 * @var String
194 * @see Net_SFTP::_realpath()
195 * @see Net_SFTP::chdir()
196 * @access private
198 var $pwd = false;
201 * Packet Type Log
203 * @see Net_SFTP::getLog()
204 * @var Array
205 * @access private
207 var $packet_type_log = array();
210 * Packet Log
212 * @see Net_SFTP::getLog()
213 * @var Array
214 * @access private
216 var $packet_log = array();
219 * Error information
221 * @see Net_SFTP::getSFTPErrors()
222 * @see Net_SFTP::getLastSFTPError()
223 * @var String
224 * @access private
226 var $sftp_errors = array();
229 * File Type
231 * @see Net_SFTP::_parseLongname()
232 * @var Integer
233 * @access private
235 var $fileType = 0;
238 * Directory Cache
240 * Rather than always having to open a directory and close it immediately there after to see if a file is a directory or
241 * rather than always
243 * @see Net_SFTP::_save_dir()
244 * @see Net_SFTP::_remove_dir()
245 * @see Net_SFTP::_is_dir()
246 * @var Array
247 * @access private
249 var $dirs = array();
252 * Default Constructor.
254 * Connects to an SFTP server
256 * @param String $host
257 * @param optional Integer $port
258 * @param optional Integer $timeout
259 * @return Net_SFTP
260 * @access public
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(
343 $this->packet_types,
344 $this->status_codes,
345 $this->attributes,
346 $this->open_flags,
347 $this->file_types
352 * Login
354 * @param String $username
355 * @param optional String $password
356 * @return Boolean
357 * @access public
359 function login($username, $password = '')
361 if (!parent::login($username, $password)) {
362 return false;
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)) {
371 return false;
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) {
378 return 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)) {
384 return false;
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) {
391 return 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")) {
397 return false;
400 $response = $this->_get_sftp_packet();
401 if ($this->packet_type != NET_SFTP_VERSION) {
402 user_error('Expected SSH_FXP_VERSION', E_USER_NOTICE);
403 return false;
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
441 most popular.
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) {
456 case 2:
457 case 3:
458 break;
459 default:
460 return false;
463 $this->pwd = $this->_realpath('.', false);
465 $this->_save_dir($this->pwd);
467 return true;
471 * Returns the current directory name
473 * @return Mixed
474 * @access public
476 function pwd()
478 return $this->pwd;
482 * Logs errors
484 * @param String $response
485 * @param optional Integer $status
486 * @access public
488 function _logError($response, $status = -1) {
489 if ($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);
498 } else {
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()
510 * @param String $dir
511 * @return Mixed
512 * @access private
514 function _realpath($dir, $check_dir = true)
516 if ($check_dir && $this->_is_dir($dir)) {
517 return true;
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
528 protocol."
530 -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-6
532 $file = '';
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) {
545 $temp = $this->pwd;
546 if (!empty($file)) {
547 $temp.= '/' . $file;
549 return $temp;
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))) {
569 return false;
572 $response = $this->_get_sftp_packet();
573 switch ($this->packet_type) {
574 case NET_SFTP_NAME:
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));
586 break;
587 case NET_SFTP_STATUS:
588 $this->_logError($response);
589 return false;
590 default:
591 user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS', E_USER_NOTICE);
592 return false;
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
597 if (!empty($file)) {
598 $realpath.= '/' . $file;
601 return $realpath;
605 * Changes the current directory
607 * @param String $dir
608 * @return Boolean
609 * @access public
611 function chdir($dir)
613 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
614 return false;
617 if ($dir[strlen($dir) - 1] != '/') {
618 $dir.= '/';
621 // confirm that $dir is, in fact, a valid directory
622 if ($this->_is_dir($dir)) {
623 $this->pwd = $dir;
624 return true;
627 $dir = $this->_realpath($dir, false);
629 if ($this->_is_dir($dir)) {
630 $this->pwd = $dir;
631 return true;
634 if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
635 return false;
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', E_USER_NOTICE);
649 return false;
652 if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
653 return false;
656 $response = $this->_get_sftp_packet();
657 if ($this->packet_type != NET_SFTP_STATUS) {
658 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
659 return false;
662 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
663 if ($status != NET_SFTP_STATUS_OK) {
664 $this->_logError($response, $status);
665 return false;
668 $this->_save_dir($dir);
670 $this->pwd = $dir;
671 return true;
675 * Returns a list of files in the given directory
677 * @param optional String $dir
678 * @return Mixed
679 * @access public
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
690 * @return Mixed
691 * @access public
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
702 * @return Mixed
703 * @access private
705 function _list($dir, $raw = true, $realpath = true)
707 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
708 return false;
711 $dir = $this->_realpath($dir . '/');
712 if ($dir === false) {
713 return 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))) {
718 return false;
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);
728 break;
729 case NET_SFTP_STATUS:
730 // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
731 $this->_logError($response);
732 return false;
733 default:
734 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE);
735 return false;
738 $this->_save_dir($dir);
740 $contents = array();
741 while (true) {
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))) {
746 return false;
749 $response = $this->_get_sftp_packet();
750 switch ($this->packet_type) {
751 case NET_SFTP_NAME:
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
759 if (!$raw) {
760 $contents[] = $shortname;
761 } else {
762 $contents[$shortname] = $attributes;
763 $fileType = $this->_parseLongname($longname);
764 if ($fileType) {
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.
774 break;
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);
779 return false;
781 break 2;
782 default:
783 user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS', E_USER_NOTICE);
784 return false;
788 if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
789 return false;
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);
797 return false;
800 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
801 if ($status != NET_SFTP_STATUS_OK) {
802 $this->_logError($response, $status);
803 return false;
806 return $contents;
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
815 * @return Mixed
816 * @access public
818 function size($filename)
820 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
821 return false;
824 $filename = $this->_realpath($filename);
825 if ($filename === false) {
826 return false;
829 return $this->_size($filename);
833 * Save directories to cache
835 * @param String $dir
836 * @access private
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
855 * @param String $dir
856 * @access private
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)) {
865 unset($temp[$dir]);
866 return true;
868 if (!isset($temp[$dir])) {
869 return false;
871 $temp = &$temp[$dir];
876 * Checks cache for directory
878 * @param String $dir
879 * @access private
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])) {
888 return false;
890 $temp = &$temp[$dir];
895 * Returns general information about a file.
897 * Returns an array on success and false otherwise.
899 * @param String $filename
900 * @return Mixed
901 * @access public
903 function stat($filename)
905 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
906 return false;
909 $filename = $this->_realpath($filename);
910 if ($filename === false) {
911 return false;
914 $stat = $this->_stat($filename, NET_SFTP_STAT);
915 if ($stat === false) {
916 return false;
919 $pwd = $this->pwd;
920 $stat['type'] = $this->chdir($filename) ?
921 NET_SFTP_TYPE_DIRECTORY :
922 NET_SFTP_TYPE_REGULAR;
923 $this->pwd = $pwd;
925 return $stat;
929 * Returns general information about a file or symbolic link.
931 * Returns an array on success and false otherwise.
933 * @param String $filename
934 * @return Mixed
935 * @access public
937 function lstat($filename)
939 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
940 return false;
943 $filename = $this->_realpath($filename);
944 if ($filename === false) {
945 return false;
948 $lstat = $this->_stat($filename, NET_SFTP_LSTAT);
949 $stat = $this->_stat($filename, NET_SFTP_STAT);
950 if ($stat === false) {
951 return false;
954 if ($lstat != $stat) {
955 return array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK));
958 $pwd = $this->pwd;
959 $lstat['type'] = $this->chdir($filename) ?
960 NET_SFTP_TYPE_DIRECTORY :
961 NET_SFTP_TYPE_REGULAR;
962 $this->pwd = $pwd;
964 return $lstat;
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
975 * @return Mixed
976 * @access private
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)) {
983 return false;
986 $response = $this->_get_sftp_packet();
987 switch ($this->packet_type) {
988 case NET_SFTP_ATTRS:
989 $attributes = $this->_parseAttributes($response);
990 if ($this->fileType) {
991 $attributes['type'] = $this->fileType;
993 return $attributes;
994 case NET_SFTP_STATUS:
995 $this->_logError($response);
996 return false;
999 user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS', E_USER_NOTICE);
1000 return false;
1004 * Attempt to identify the file type
1006 * @param String $path
1007 * @param Array $stat
1008 * @param Array $lstat
1009 * @return Integer
1010 * @access private
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));
1021 $pwd = $this->pwd;
1022 $stat1['type'] = $this->chdir($path) ?
1023 NET_SFTP_TYPE_DIRECTORY :
1024 NET_SFTP_TYPE_REGULAR;
1025 $this->pwd = $pwd;
1027 return $stat1;
1031 * Returns the file size, in bytes, or false, on failure
1033 * Determines the size without calling Net_SFTP::_realpath()
1035 * @param String $filename
1036 * @return Mixed
1037 * @access private
1039 function _size($filename)
1041 $result = $this->_stat($filename, NET_SFTP_LSTAT);
1042 if ($result === false) {
1043 return 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
1055 * @return Mixed
1056 * @access public
1058 function chmod($mode, $filename, $recursive = false)
1060 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1061 return false;
1064 $filename = $this->_realpath($filename);
1065 if ($filename === false) {
1066 return false;
1069 if ($recursive) {
1070 $i = 0;
1071 $result = $this->_chmod_recursive($mode, $filename, $i);
1072 $this->_read_put_responses($i);
1073 return $result;
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))) {
1080 return false;
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);
1093 return false;
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)) {
1106 return false;
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);
1116 return false;
1119 user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS', E_USER_NOTICE);
1120 return false;
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
1130 * @return Boolean
1131 * @access private
1133 function _chmod_recursive($mode, $path, &$i)
1135 if (!$this->_read_put_responses($i)) {
1136 return false;
1138 $i = 0;
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)) {
1148 return false;
1151 foreach ($entries as $filename=>$props) {
1152 if ($filename == '.' || $filename == '..') {
1153 continue;
1156 if (!isset($props['type'])) {
1157 return false;
1160 $temp = $path . '/' . $filename;
1161 if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
1162 if (!$this->_chmod_recursive($mode, $temp, $i)) {
1163 return false;
1165 } else {
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))) {
1168 return false;
1171 $i++;
1173 if ($i >= 50) {
1174 if (!$this->_read_put_responses($i)) {
1175 return false;
1177 $i = 0;
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))) {
1184 return false;
1187 $i++;
1189 if ($i >= 50) {
1190 if (!$this->_read_put_responses($i)) {
1191 return false;
1193 $i = 0;
1196 return true;
1200 * Creates a directory.
1202 * @param String $dir
1203 * @return Boolean
1204 * @access public
1206 function mkdir($dir)
1208 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1209 return false;
1212 if ($dir[0] != '/') {
1213 $dir = $this->_realpath(rtrim($dir, '/'));
1214 if ($dir === false) {
1215 return false;
1217 if (!$this->_mkdir_helper($dir)) {
1218 return false;
1220 } else {
1221 $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir));
1222 $temp = '';
1223 foreach ($dirs as $dir) {
1224 $temp.= '/' . $dir;
1225 $result = $this->_mkdir_helper($temp);
1227 if (!$result) {
1228 return false;
1232 return true;
1236 * Helper function for directory creation
1238 * @param String $dir
1239 * @return Boolean
1240 * @access private
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))) {
1247 return false;
1250 $response = $this->_get_sftp_packet();
1251 if ($this->packet_type != NET_SFTP_STATUS) {
1252 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
1253 return false;
1256 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1257 if ($status != NET_SFTP_STATUS_OK) {
1258 $this->_logError($response, $status);
1259 return false;
1262 $this->_save_dir($dir);
1264 return true;
1268 * Removes a directory.
1270 * @param String $dir
1271 * @return Boolean
1272 * @access public
1274 function rmdir($dir)
1276 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1277 return false;
1280 $dir = $this->_realpath($dir);
1281 if ($dir === false) {
1282 return false;
1285 if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($dir), $dir))) {
1286 return false;
1289 $response = $this->_get_sftp_packet();
1290 if ($this->packet_type != NET_SFTP_STATUS) {
1291 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
1292 return false;
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);
1299 return false;
1302 $this->_remove_dir($dir);
1304 return true;
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
1324 * @return Boolean
1325 * @access public
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)) {
1331 return false;
1334 $remote_file = $this->_realpath($remote_file);
1335 if ($remote_file === false) {
1336 return 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
1345 $offset = 0;
1346 if ($mode & NET_SFTP_RESUME) {
1347 $size = $this->_size($remote_file);
1348 $offset = $size !== false ? $size : 0;
1349 } else {
1350 $flags|= NET_SFTP_OPEN_TRUNCATE;
1352 // --------------
1354 $packet = pack('Na*N2', strlen($remote_file), $remote_file, $flags, 0);
1355 if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
1356 return false;
1359 $response = $this->_get_sftp_packet();
1360 switch ($this->packet_type) {
1361 case NET_SFTP_HANDLE:
1362 $handle = substr($response, 4);
1363 break;
1364 case NET_SFTP_STATUS:
1365 $this->_logError($response);
1366 return false;
1367 default:
1368 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE);
1369 return false;
1372 $initialize = true;
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);
1378 return false;
1380 $fp = @fopen($data, 'rb');
1381 if (!$fp) {
1382 return false;
1384 $size = filesize($data);
1385 } else {
1386 $size = strlen($data);
1389 $sent = 0;
1390 $size = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
1392 $sftp_packet_size = 4096; // PuTTY uses 4096
1393 $i = 0;
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)) {
1398 fclose($fp);
1399 return false;
1401 $sent+= strlen($temp);
1403 $i++;
1405 if ($i == 50) {
1406 if (!$this->_read_put_responses($i)) {
1407 $i = 0;
1408 break;
1410 $i = 0;
1414 if (!$this->_read_put_responses($i)) {
1415 return false;
1418 if ($mode & NET_SFTP_LOCAL_FILE) {
1419 fclose($fp);
1422 if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
1423 return false;
1426 $response = $this->_get_sftp_packet();
1427 if ($this->packet_type != NET_SFTP_STATUS) {
1428 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
1429 return false;
1432 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1433 if ($status != NET_SFTP_STATUS_OK) {
1434 $this->_logError($response, $status);
1435 return false;
1438 return true;
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.
1447 * @param Integer $i
1448 * @return Boolean
1449 * @access private
1451 function _read_put_responses($i)
1453 while ($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);
1457 return false;
1460 extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1461 if ($status != NET_SFTP_STATUS_OK) {
1462 $this->_logError($response, $status);
1463 break;
1467 return $i < 0;
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
1475 * operation
1477 * @param String $remote_file
1478 * @param optional String $local_file
1479 * @return Mixed
1480 * @access public
1482 function get($remote_file, $local_file = false)
1484 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1485 return false;
1488 $remote_file = $this->_realpath($remote_file);
1489 if ($remote_file === false) {
1490 return 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)) {
1495 return false;
1498 $response = $this->_get_sftp_packet();
1499 switch ($this->packet_type) {
1500 case NET_SFTP_HANDLE:
1501 $handle = substr($response, 4);
1502 break;
1503 case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
1504 $this->_logError($response);
1505 return false;
1506 default:
1507 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE);
1508 return false;
1511 if ($local_file !== false) {
1512 $fp = fopen($local_file, 'wb');
1513 if (!$fp) {
1514 return false;
1516 } else {
1517 $content = '';
1520 $read = 0;
1521 while (true) {
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) {
1525 fclose($fp);
1527 return false;
1530 $response = $this->_get_sftp_packet();
1531 switch ($this->packet_type) {
1532 case NET_SFTP_DATA:
1533 $temp = substr($response, 4);
1534 $read+= strlen($temp);
1535 if ($local_file === false) {
1536 $content.= $temp;
1537 } else {
1538 fputs($fp, $temp);
1540 break;
1541 case NET_SFTP_STATUS:
1542 $this->_logError($response);
1543 break 2;
1544 default:
1545 user_error('Expected SSH_FXP_DATA or SSH_FXP_STATUS', E_USER_NOTICE);
1546 if ($local_file !== false) {
1547 fclose($fp);
1549 return false;
1553 if ($local_file !== false) {
1554 fclose($fp);
1557 if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
1558 return false;
1561 $response = $this->_get_sftp_packet();
1562 if ($this->packet_type != NET_SFTP_STATUS) {
1563 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
1564 return false;
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) {
1571 return false;
1574 if (isset($content)) {
1575 return $content;
1578 return true;
1582 * Deletes a file on the SFTP server.
1584 * @param String $path
1585 * @param Boolean $recursive
1586 * @return Boolean
1587 * @access public
1589 function delete($path, $recursive = true)
1591 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1592 return false;
1595 $path = $this->_realpath($path);
1596 if ($path === false) {
1597 return 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))) {
1602 return false;
1605 $response = $this->_get_sftp_packet();
1606 if ($this->packet_type != NET_SFTP_STATUS) {
1607 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
1608 return false;
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);
1615 if (!$recursive) {
1616 return false;
1618 $i = 0;
1619 $result = $this->_delete_recursive($path, $i);
1620 $this->_read_put_responses($i);
1621 return $result;
1624 return true;
1628 * Recursively deletes directories on the SFTP server
1630 * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
1632 * @param String $path
1633 * @param Integer $i
1634 * @return Boolean
1635 * @access private
1637 function _delete_recursive($path, &$i)
1639 if (!$this->_read_put_responses($i)) {
1640 return false;
1642 $i = 0;
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)) {
1648 return false;
1651 foreach ($entries as $filename=>$props) {
1652 if ($filename == '.' || $filename == '..') {
1653 continue;
1656 if (!isset($props['type'])) {
1657 return false;
1660 $temp = $path . '/' . $filename;
1661 if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
1662 if (!$this->_delete_recursive($temp, $i)) {
1663 return false;
1665 } else {
1666 if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($temp), $temp))) {
1667 return false;
1670 $i++;
1672 if ($i >= 50) {
1673 if (!$this->_read_put_responses($i)) {
1674 return false;
1676 $i = 0;
1681 if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($path), $path))) {
1682 return false;
1684 $this->_remove_dir($path);
1686 $i++;
1688 if ($i >= 50) {
1689 if (!$this->_read_put_responses($i)) {
1690 return false;
1692 $i = 0;
1695 return true;
1699 * Renames a file or a directory on the SFTP server
1701 * @param String $oldname
1702 * @param String $newname
1703 * @return Boolean
1704 * @access public
1706 function rename($oldname, $newname)
1708 if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1709 return false;
1712 $oldname = $this->_realpath($oldname);
1713 $newname = $this->_realpath($newname);
1714 if ($oldname === false || $newname === false) {
1715 return 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)) {
1721 return false;
1724 $response = $this->_get_sftp_packet();
1725 if ($this->packet_type != NET_SFTP_STATUS) {
1726 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
1727 return false;
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);
1734 return false;
1737 return true;
1741 * Parse Attributes
1743 * See '7. File Attributes' of draft-ietf-secsh-filexfer-13 for more info.
1745 * @param String $response
1746 * @return Array
1747 * @access private
1749 function _parseAttributes(&$response)
1751 $attr = array();
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)));
1763 if ($upper) {
1764 $attr['size'] = 0xFFFFFFFF;
1765 } else {
1766 $attr['size'] = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
1768 break;
1769 case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
1770 $attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8));
1771 break;
1772 case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
1773 $attr+= unpack('Npermissions', $this->_string_shift($response, 4));
1774 break;
1775 case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
1776 $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8));
1777 break;
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);
1788 return $attr;
1792 * Parse Longname
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
1803 * @return Mixed
1804 * @access private
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]) {
1812 case '-':
1813 return NET_SFTP_TYPE_REGULAR;
1814 case 'd':
1815 return NET_SFTP_TYPE_DIRECTORY;
1816 case 'l':
1817 return NET_SFTP_TYPE_SYMLINK;
1818 default:
1819 return NET_SFTP_TYPE_SPECIAL;
1823 return false;
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()
1835 * @return Boolean
1836 * @access private
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";
1853 flush();
1854 ob_flush();
1855 } else {
1856 $this->packet_type_log[] = $packet_type;
1857 if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
1858 $this->packet_log[] = $data;
1863 return $result;
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()
1876 * @return String
1877 * @access private
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 = '';
1891 return false;
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 = '';
1905 return false;
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
1918 } else {
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";
1929 flush();
1930 ob_flush();
1931 } else {
1932 $this->packet_type_log[] = $packet_type;
1933 if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
1934 $this->packet_log[] = $packet;
1939 return $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')
1947 * @access public
1948 * @return String or Array
1950 function getSFTPLog()
1952 if (!defined('NET_SFTP_LOGGING')) {
1953 return false;
1956 switch (NET_SFTP_LOGGING) {
1957 case NET_SFTP_LOG_COMPLEX:
1958 return $this->_format_log($this->packet_log, $this->packet_type_log);
1959 break;
1960 //case NET_SFTP_LOG_SIMPLE:
1961 default:
1962 return $this->packet_type_log;
1967 * Returns all errors
1969 * @return String
1970 * @access public
1972 function getSFTPErrors()
1974 return $this->sftp_errors;
1978 * Returns the last error
1980 * @return String
1981 * @access public
1983 function getLastSFTPError()
1985 return count($this->sftp_errors) ? $this->sftp_errors[count($this->sftp_errors) - 1] : '';
1989 * Get supported SFTP versions
1991 * @return Array
1992 * @access public
1994 function getSupportedVersions()
1996 $temp = array('version' => $this->version);
1997 if (isset($this->extensions['versions'])) {
1998 $temp['extensions'] = $this->extensions['versions'];
2000 return $temp;
2004 * Disconnect
2006 * @param Integer $reason
2007 * @return Boolean
2008 * @access private
2010 function _disconnect($reason)
2012 $this->pwd = false;
2013 parent::_disconnect($reason);