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