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