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