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