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