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