[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/vendor/defuse/php-encryption/src/ -> File.php (source)

   1  <?php
   2  
   3  namespace Defuse\Crypto;
   4  
   5  use Defuse\Crypto\Exception as Ex;
   6  
   7  final class File
   8  {
   9      /**
  10       * Encrypts the input file, saving the ciphertext to the output file.
  11       *
  12       * @param string $inputFilename
  13       * @param string $outputFilename
  14       * @param Key    $key
  15       * @return void
  16       *
  17       * @throws Ex\EnvironmentIsBrokenException
  18       * @throws Ex\IOException
  19       */
  20      public static function encryptFile($inputFilename, $outputFilename, Key $key)
  21      {
  22          self::encryptFileInternal(
  23              $inputFilename,
  24              $outputFilename,
  25              KeyOrPassword::createFromKey($key)
  26          );
  27      }
  28  
  29      /**
  30       * Encrypts a file with a password, using a slow key derivation function to
  31       * make password cracking more expensive.
  32       *
  33       * @param string $inputFilename
  34       * @param string $outputFilename
  35       * @param string $password
  36       * @return void
  37       *
  38       * @throws Ex\EnvironmentIsBrokenException
  39       * @throws Ex\IOException
  40       */
  41      public static function encryptFileWithPassword($inputFilename, $outputFilename, $password)
  42      {
  43          self::encryptFileInternal(
  44              $inputFilename,
  45              $outputFilename,
  46              KeyOrPassword::createFromPassword($password)
  47          );
  48      }
  49  
  50      /**
  51       * Decrypts the input file, saving the plaintext to the output file.
  52       *
  53       * @param string $inputFilename
  54       * @param string $outputFilename
  55       * @param Key    $key
  56       * @return void
  57       *
  58       * @throws Ex\EnvironmentIsBrokenException
  59       * @throws Ex\IOException
  60       * @throws Ex\WrongKeyOrModifiedCiphertextException
  61       */
  62      public static function decryptFile($inputFilename, $outputFilename, Key $key)
  63      {
  64          self::decryptFileInternal(
  65              $inputFilename,
  66              $outputFilename,
  67              KeyOrPassword::createFromKey($key)
  68          );
  69      }
  70  
  71      /**
  72       * Decrypts a file with a password, using a slow key derivation function to
  73       * make password cracking more expensive.
  74       *
  75       * @param string $inputFilename
  76       * @param string $outputFilename
  77       * @param string $password
  78       * @return void
  79       *
  80       * @throws Ex\EnvironmentIsBrokenException
  81       * @throws Ex\IOException
  82       * @throws Ex\WrongKeyOrModifiedCiphertextException
  83       */
  84      public static function decryptFileWithPassword($inputFilename, $outputFilename, $password)
  85      {
  86          self::decryptFileInternal(
  87              $inputFilename,
  88              $outputFilename,
  89              KeyOrPassword::createFromPassword($password)
  90          );
  91      }
  92  
  93      /**
  94       * Takes two resource handles and encrypts the contents of the first,
  95       * writing the ciphertext into the second.
  96       *
  97       * @param resource $inputHandle
  98       * @param resource $outputHandle
  99       * @param Key      $key
 100       * @return void
 101       *
 102       * @throws Ex\EnvironmentIsBrokenException
 103       * @throws Ex\WrongKeyOrModifiedCiphertextException
 104       */
 105      public static function encryptResource($inputHandle, $outputHandle, Key $key)
 106      {
 107          self::encryptResourceInternal(
 108              $inputHandle,
 109              $outputHandle,
 110              KeyOrPassword::createFromKey($key)
 111          );
 112      }
 113  
 114      /**
 115       * Encrypts the contents of one resource handle into another with a
 116       * password, using a slow key derivation function to make password cracking
 117       * more expensive.
 118       *
 119       * @param resource $inputHandle
 120       * @param resource $outputHandle
 121       * @param string   $password
 122       * @return void
 123       *
 124       * @throws Ex\EnvironmentIsBrokenException
 125       * @throws Ex\IOException
 126       * @throws Ex\WrongKeyOrModifiedCiphertextException
 127       */
 128      public static function encryptResourceWithPassword($inputHandle, $outputHandle, $password)
 129      {
 130          self::encryptResourceInternal(
 131              $inputHandle,
 132              $outputHandle,
 133              KeyOrPassword::createFromPassword($password)
 134          );
 135      }
 136  
 137      /**
 138       * Takes two resource handles and decrypts the contents of the first,
 139       * writing the plaintext into the second.
 140       *
 141       * @param resource $inputHandle
 142       * @param resource $outputHandle
 143       * @param Key      $key
 144       * @return void
 145       *
 146       * @throws Ex\EnvironmentIsBrokenException
 147       * @throws Ex\IOException
 148       * @throws Ex\WrongKeyOrModifiedCiphertextException
 149       */
 150      public static function decryptResource($inputHandle, $outputHandle, Key $key)
 151      {
 152          self::decryptResourceInternal(
 153              $inputHandle,
 154              $outputHandle,
 155              KeyOrPassword::createFromKey($key)
 156          );
 157      }
 158  
 159      /**
 160       * Decrypts the contents of one resource into another with a password, using
 161       * a slow key derivation function to make password cracking more expensive.
 162       *
 163       * @param resource $inputHandle
 164       * @param resource $outputHandle
 165       * @param string   $password
 166       * @return void
 167       *
 168       * @throws Ex\EnvironmentIsBrokenException
 169       * @throws Ex\IOException
 170       * @throws Ex\WrongKeyOrModifiedCiphertextException
 171       */
 172      public static function decryptResourceWithPassword($inputHandle, $outputHandle, $password)
 173      {
 174          self::decryptResourceInternal(
 175              $inputHandle,
 176              $outputHandle,
 177              KeyOrPassword::createFromPassword($password)
 178          );
 179      }
 180  
 181      /**
 182       * Encrypts a file with either a key or a password.
 183       *
 184       * @param string        $inputFilename
 185       * @param string        $outputFilename
 186       * @param KeyOrPassword $secret
 187       * @return void
 188       *
 189       * @throws Ex\CryptoException
 190       * @throws Ex\IOException
 191       */
 192      private static function encryptFileInternal($inputFilename, $outputFilename, KeyOrPassword $secret)
 193      {
 194          if (file_exists($inputFilename) && file_exists($outputFilename) && realpath($inputFilename) === realpath($outputFilename)) {
 195              throw new Ex\IOException('Input and output filenames must be different.');
 196          }
 197  
 198          /* Open the input file. */
 199          $if = @\fopen($inputFilename, 'rb');
 200          if ($if === false) {
 201              throw new Ex\IOException(
 202                  'Cannot open input file for encrypting: ' .
 203                  self::getLastErrorMessage()
 204              );
 205          }
 206          if (\is_callable('\\stream_set_read_buffer')) {
 207              /* This call can fail, but the only consequence is performance. */
 208              \stream_set_read_buffer($if, 0);
 209          }
 210  
 211          /* Open the output file. */
 212          $of = @\fopen($outputFilename, 'wb');
 213          if ($of === false) {
 214              \fclose($if);
 215              throw new Ex\IOException(
 216                  'Cannot open output file for encrypting: ' .
 217                  self::getLastErrorMessage()
 218              );
 219          }
 220          if (\is_callable('\\stream_set_write_buffer')) {
 221              /* This call can fail, but the only consequence is performance. */
 222              \stream_set_write_buffer($of, 0);
 223          }
 224  
 225          /* Perform the encryption. */
 226          try {
 227              self::encryptResourceInternal($if, $of, $secret);
 228          } catch (Ex\CryptoException $ex) {
 229              \fclose($if);
 230              \fclose($of);
 231              throw $ex;
 232          }
 233  
 234          /* Close the input file. */
 235          if (\fclose($if) === false) {
 236              \fclose($of);
 237              throw new Ex\IOException(
 238                  'Cannot close input file after encrypting'
 239              );
 240          }
 241  
 242          /* Close the output file. */
 243          if (\fclose($of) === false) {
 244              throw new Ex\IOException(
 245                  'Cannot close output file after encrypting'
 246              );
 247          }
 248      }
 249  
 250      /**
 251       * Decrypts a file with either a key or a password.
 252       *
 253       * @param string        $inputFilename
 254       * @param string        $outputFilename
 255       * @param KeyOrPassword $secret
 256       * @return void
 257       *
 258       * @throws Ex\CryptoException
 259       * @throws Ex\IOException
 260       */
 261      private static function decryptFileInternal($inputFilename, $outputFilename, KeyOrPassword $secret)
 262      {
 263          if (file_exists($inputFilename) && file_exists($outputFilename) && realpath($inputFilename) === realpath($outputFilename)) {
 264              throw new Ex\IOException('Input and output filenames must be different.');
 265          }
 266  
 267          /* Open the input file. */
 268          $if = @\fopen($inputFilename, 'rb');
 269          if ($if === false) {
 270              throw new Ex\IOException(
 271                  'Cannot open input file for decrypting: ' .
 272                  self::getLastErrorMessage()
 273              );
 274          }
 275  
 276          if (\is_callable('\\stream_set_read_buffer')) {
 277              /* This call can fail, but the only consequence is performance. */
 278              \stream_set_read_buffer($if, 0);
 279          }
 280  
 281          /* Open the output file. */
 282          $of = @\fopen($outputFilename, 'wb');
 283          if ($of === false) {
 284              \fclose($if);
 285              throw new Ex\IOException(
 286                  'Cannot open output file for decrypting: ' .
 287                  self::getLastErrorMessage()
 288              );
 289          }
 290  
 291          if (\is_callable('\\stream_set_write_buffer')) {
 292              /* This call can fail, but the only consequence is performance. */
 293              \stream_set_write_buffer($of, 0);
 294          }
 295  
 296          /* Perform the decryption. */
 297          try {
 298              self::decryptResourceInternal($if, $of, $secret);
 299          } catch (Ex\CryptoException $ex) {
 300              \fclose($if);
 301              \fclose($of);
 302              throw $ex;
 303          }
 304  
 305          /* Close the input file. */
 306          if (\fclose($if) === false) {
 307              \fclose($of);
 308              throw new Ex\IOException(
 309                  'Cannot close input file after decrypting'
 310              );
 311          }
 312  
 313          /* Close the output file. */
 314          if (\fclose($of) === false) {
 315              throw new Ex\IOException(
 316                  'Cannot close output file after decrypting'
 317              );
 318          }
 319      }
 320  
 321      /**
 322       * Encrypts a resource with either a key or a password.
 323       *
 324       * @param resource      $inputHandle
 325       * @param resource      $outputHandle
 326       * @param KeyOrPassword $secret
 327       * @return void
 328       *
 329       * @throws Ex\EnvironmentIsBrokenException
 330       * @throws Ex\IOException
 331       * @psalm-suppress PossiblyInvalidArgument
 332       *      Fixes erroneous errors caused by PHP 7.2 switching the return value
 333       *      of hash_init from a resource to a HashContext.
 334       */
 335      private static function encryptResourceInternal($inputHandle, $outputHandle, KeyOrPassword $secret)
 336      {
 337          if (! \is_resource($inputHandle)) {
 338              throw new Ex\IOException(
 339                  'Input handle must be a resource!'
 340              );
 341          }
 342          if (! \is_resource($outputHandle)) {
 343              throw new Ex\IOException(
 344                  'Output handle must be a resource!'
 345              );
 346          }
 347  
 348          $inputStat = \fstat($inputHandle);
 349          $inputSize = $inputStat['size'];
 350  
 351          $file_salt = Core::secureRandom(Core::SALT_BYTE_SIZE);
 352          $keys = $secret->deriveKeys($file_salt);
 353          $ekey = $keys->getEncryptionKey();
 354          $akey = $keys->getAuthenticationKey();
 355  
 356          $ivsize = Core::BLOCK_BYTE_SIZE;
 357          $iv     = Core::secureRandom($ivsize);
 358  
 359          /* Initialize a streaming HMAC state. */
 360          /** @var mixed $hmac */
 361          $hmac = \hash_init(Core::HASH_FUNCTION_NAME, HASH_HMAC, $akey);
 362          Core::ensureTrue(
 363              \is_resource($hmac) || \is_object($hmac),
 364              'Cannot initialize a hash context'
 365          );
 366  
 367          /* Write the header, salt, and IV. */
 368          self::writeBytes(
 369              $outputHandle,
 370              Core::CURRENT_VERSION . $file_salt . $iv,
 371              Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + $ivsize
 372          );
 373  
 374          /* Add the header, salt, and IV to the HMAC. */
 375          \hash_update($hmac, Core::CURRENT_VERSION);
 376          \hash_update($hmac, $file_salt);
 377          \hash_update($hmac, $iv);
 378  
 379          /* $thisIv will be incremented after each call to the encryption. */
 380          $thisIv = $iv;
 381  
 382          /* How many blocks do we encrypt at a time? We increment by this value. */
 383          /**
 384           * @psalm-suppress RedundantCast
 385           */
 386          $inc = (int) (Core::BUFFER_BYTE_SIZE / Core::BLOCK_BYTE_SIZE);
 387  
 388          /* Loop until we reach the end of the input file. */
 389          $at_file_end = false;
 390          while (! (\feof($inputHandle) || $at_file_end)) {
 391              /* Find out if we can read a full buffer, or only a partial one. */
 392              /** @var int */
 393              $pos = \ftell($inputHandle);
 394              if (!\is_int($pos)) {
 395                  throw new Ex\IOException(
 396                      'Could not get current position in input file during encryption'
 397                  );
 398              }
 399              if ($pos + Core::BUFFER_BYTE_SIZE >= $inputSize) {
 400                  /* We're at the end of the file, so we need to break out of the loop. */
 401                  $at_file_end = true;
 402                  $read = self::readBytes(
 403                      $inputHandle,
 404                      $inputSize - $pos
 405                  );
 406              } else {
 407                  $read = self::readBytes(
 408                      $inputHandle,
 409                      Core::BUFFER_BYTE_SIZE
 410                  );
 411              }
 412  
 413              /* Encrypt this buffer. */
 414              /** @var string */
 415              $encrypted = \openssl_encrypt(
 416                  $read,
 417                  Core::CIPHER_METHOD,
 418                  $ekey,
 419                  OPENSSL_RAW_DATA,
 420                  $thisIv
 421              );
 422  
 423              Core::ensureTrue(\is_string($encrypted), 'OpenSSL encryption error');
 424  
 425              /* Write this buffer's ciphertext. */
 426              self::writeBytes($outputHandle, $encrypted, Core::ourStrlen($encrypted));
 427              /* Add this buffer's ciphertext to the HMAC. */
 428              \hash_update($hmac, $encrypted);
 429  
 430              /* Increment the counter by the number of blocks in a buffer. */
 431              $thisIv = Core::incrementCounter($thisIv, $inc);
 432              /* WARNING: Usually, unless the file is a multiple of the buffer
 433               * size, $thisIv will contain an incorrect value here on the last
 434               * iteration of this loop. */
 435          }
 436  
 437          /* Get the HMAC and append it to the ciphertext. */
 438          $final_mac = \hash_final($hmac, true);
 439          self::writeBytes($outputHandle, $final_mac, Core::MAC_BYTE_SIZE);
 440      }
 441  
 442      /**
 443       * Decrypts a file-backed resource with either a key or a password.
 444       *
 445       * @param resource      $inputHandle
 446       * @param resource      $outputHandle
 447       * @param KeyOrPassword $secret
 448       * @return void
 449       *
 450       * @throws Ex\EnvironmentIsBrokenException
 451       * @throws Ex\IOException
 452       * @throws Ex\WrongKeyOrModifiedCiphertextException
 453       * @psalm-suppress PossiblyInvalidArgument
 454       *      Fixes erroneous errors caused by PHP 7.2 switching the return value
 455       *      of hash_init from a resource to a HashContext.
 456       */
 457      public static function decryptResourceInternal($inputHandle, $outputHandle, KeyOrPassword $secret)
 458      {
 459          if (! \is_resource($inputHandle)) {
 460              throw new Ex\IOException(
 461                  'Input handle must be a resource!'
 462              );
 463          }
 464          if (! \is_resource($outputHandle)) {
 465              throw new Ex\IOException(
 466                  'Output handle must be a resource!'
 467              );
 468          }
 469  
 470          /* Make sure the file is big enough for all the reads we need to do. */
 471          $stat = \fstat($inputHandle);
 472          if ($stat['size'] < Core::MINIMUM_CIPHERTEXT_SIZE) {
 473              throw new Ex\WrongKeyOrModifiedCiphertextException(
 474                  'Input file is too small to have been created by this library.'
 475              );
 476          }
 477  
 478          /* Check the version header. */
 479          $header = self::readBytes($inputHandle, Core::HEADER_VERSION_SIZE);
 480          if ($header !== Core::CURRENT_VERSION) {
 481              throw new Ex\WrongKeyOrModifiedCiphertextException(
 482                  'Bad version header.'
 483              );
 484          }
 485  
 486          /* Get the salt. */
 487          $file_salt = self::readBytes($inputHandle, Core::SALT_BYTE_SIZE);
 488  
 489          /* Get the IV. */
 490          $ivsize = Core::BLOCK_BYTE_SIZE;
 491          $iv     = self::readBytes($inputHandle, $ivsize);
 492  
 493          /* Derive the authentication and encryption keys. */
 494          $keys = $secret->deriveKeys($file_salt);
 495          $ekey = $keys->getEncryptionKey();
 496          $akey = $keys->getAuthenticationKey();
 497  
 498          /* We'll store the MAC of each buffer-sized chunk as we verify the
 499           * actual MAC, so that we can check them again when decrypting. */
 500          $macs = [];
 501  
 502          /* $thisIv will be incremented after each call to the decryption. */
 503          $thisIv = $iv;
 504  
 505          /* How many blocks do we encrypt at a time? We increment by this value. */
 506          /**
 507           * @psalm-suppress RedundantCast
 508           */
 509          $inc = (int) (Core::BUFFER_BYTE_SIZE / Core::BLOCK_BYTE_SIZE);
 510  
 511          /* Get the HMAC. */
 512          if (\fseek($inputHandle, (-1 * Core::MAC_BYTE_SIZE), SEEK_END) === -1) {
 513              throw new Ex\IOException(
 514                  'Cannot seek to beginning of MAC within input file'
 515              );
 516          }
 517  
 518          /* Get the position of the last byte in the actual ciphertext. */
 519          /** @var int $cipher_end */
 520          $cipher_end = \ftell($inputHandle);
 521          if (!\is_int($cipher_end)) {
 522              throw new Ex\IOException(
 523                  'Cannot read input file'
 524              );
 525          }
 526          /* We have the position of the first byte of the HMAC. Go back by one. */
 527          --$cipher_end;
 528  
 529          /* Read the HMAC. */
 530          /** @var string $stored_mac */
 531          $stored_mac = self::readBytes($inputHandle, Core::MAC_BYTE_SIZE);
 532  
 533          /* Initialize a streaming HMAC state. */
 534          /** @var mixed $hmac */
 535          $hmac = \hash_init(Core::HASH_FUNCTION_NAME, HASH_HMAC, $akey);
 536          Core::ensureTrue(\is_resource($hmac) || \is_object($hmac), 'Cannot initialize a hash context');
 537  
 538          /* Reset file pointer to the beginning of the file after the header */
 539          if (\fseek($inputHandle, Core::HEADER_VERSION_SIZE, SEEK_SET) === -1) {
 540              throw new Ex\IOException(
 541                  'Cannot read seek within input file'
 542              );
 543          }
 544  
 545          /* Seek to the start of the actual ciphertext. */
 546          if (\fseek($inputHandle, Core::SALT_BYTE_SIZE + $ivsize, SEEK_CUR) === -1) {
 547              throw new Ex\IOException(
 548                  'Cannot seek input file to beginning of ciphertext'
 549              );
 550          }
 551  
 552          /* PASS #1: Calculating the HMAC. */
 553  
 554          \hash_update($hmac, $header);
 555          \hash_update($hmac, $file_salt);
 556          \hash_update($hmac, $iv);
 557          /** @var mixed $hmac2 */
 558          $hmac2 = \hash_copy($hmac);
 559  
 560          $break = false;
 561          while (! $break) {
 562              /** @var int $pos */
 563              $pos = \ftell($inputHandle);
 564              if (!\is_int($pos)) {
 565                  throw new Ex\IOException(
 566                      'Could not get current position in input file during decryption'
 567                  );
 568              }
 569  
 570              /* Read the next buffer-sized chunk (or less). */
 571              if ($pos + Core::BUFFER_BYTE_SIZE >= $cipher_end) {
 572                  $break = true;
 573                  $read  = self::readBytes(
 574                      $inputHandle,
 575                      $cipher_end - $pos + 1
 576                  );
 577              } else {
 578                  $read = self::readBytes(
 579                      $inputHandle,
 580                      Core::BUFFER_BYTE_SIZE
 581                  );
 582              }
 583  
 584              /* Update the HMAC. */
 585              \hash_update($hmac, $read);
 586  
 587              /* Remember this buffer-sized chunk's HMAC. */
 588              /** @var mixed $chunk_mac */
 589              $chunk_mac = \hash_copy($hmac);
 590              Core::ensureTrue(\is_resource($chunk_mac) || \is_object($chunk_mac), 'Cannot duplicate a hash context');
 591              $macs []= \hash_final($chunk_mac);
 592          }
 593  
 594          /* Get the final HMAC, which should match the stored one. */
 595          /** @var string $final_mac */
 596          $final_mac = \hash_final($hmac, true);
 597  
 598          /* Verify the HMAC. */
 599          if (! Core::hashEquals($final_mac, $stored_mac)) {
 600              throw new Ex\WrongKeyOrModifiedCiphertextException(
 601                  'Integrity check failed.'
 602              );
 603          }
 604  
 605          /* PASS #2: Decrypt and write output. */
 606  
 607          /* Rewind to the start of the actual ciphertext. */
 608          if (\fseek($inputHandle, Core::SALT_BYTE_SIZE + $ivsize + Core::HEADER_VERSION_SIZE, SEEK_SET) === -1) {
 609              throw new Ex\IOException(
 610                  'Could not move the input file pointer during decryption'
 611              );
 612          }
 613  
 614          $at_file_end = false;
 615          while (! $at_file_end) {
 616              /** @var int $pos */
 617              $pos = \ftell($inputHandle);
 618              if (!\is_int($pos)) {
 619                  throw new Ex\IOException(
 620                      'Could not get current position in input file during decryption'
 621                  );
 622              }
 623  
 624              /* Read the next buffer-sized chunk (or less). */
 625              if ($pos + Core::BUFFER_BYTE_SIZE >= $cipher_end) {
 626                  $at_file_end = true;
 627                  $read   = self::readBytes(
 628                      $inputHandle,
 629                      $cipher_end - $pos + 1
 630                  );
 631              } else {
 632                  $read = self::readBytes(
 633                      $inputHandle,
 634                      Core::BUFFER_BYTE_SIZE
 635                  );
 636              }
 637  
 638              /* Recalculate the MAC (so far) and compare it with the one we
 639               * remembered from pass #1 to ensure attackers didn't change the
 640               * ciphertext after MAC verification. */
 641              \hash_update($hmac2, $read);
 642              /** @var mixed $calc_mac */
 643              $calc_mac = \hash_copy($hmac2);
 644              Core::ensureTrue(\is_resource($calc_mac) || \is_object($calc_mac), 'Cannot duplicate a hash context');
 645              $calc = \hash_final($calc_mac);
 646  
 647              if (empty($macs)) {
 648                  throw new Ex\WrongKeyOrModifiedCiphertextException(
 649                      'File was modified after MAC verification'
 650                  );
 651              } elseif (! Core::hashEquals(\array_shift($macs), $calc)) {
 652                  throw new Ex\WrongKeyOrModifiedCiphertextException(
 653                      'File was modified after MAC verification'
 654                  );
 655              }
 656  
 657              /* Decrypt this buffer-sized chunk. */
 658              /** @var string $decrypted */
 659              $decrypted = \openssl_decrypt(
 660                  $read,
 661                  Core::CIPHER_METHOD,
 662                  $ekey,
 663                  OPENSSL_RAW_DATA,
 664                  $thisIv
 665              );
 666              Core::ensureTrue(\is_string($decrypted), 'OpenSSL decryption error');
 667  
 668              /* Write the plaintext to the output file. */
 669              self::writeBytes(
 670                  $outputHandle,
 671                  $decrypted,
 672                  Core::ourStrlen($decrypted)
 673              );
 674  
 675              /* Increment the IV by the amount of blocks in a buffer. */
 676              /** @var string $thisIv */
 677              $thisIv = Core::incrementCounter($thisIv, $inc);
 678              /* WARNING: Usually, unless the file is a multiple of the buffer
 679               * size, $thisIv will contain an incorrect value here on the last
 680               * iteration of this loop. */
 681          }
 682      }
 683  
 684      /**
 685       * Read from a stream; prevent partial reads.
 686       *
 687       * @param resource $stream
 688       * @param int      $num_bytes
 689       * @return string
 690       *
 691       * @throws Ex\IOException
 692       * @throws Ex\EnvironmentIsBrokenException
 693       */
 694      public static function readBytes($stream, $num_bytes)
 695      {
 696          Core::ensureTrue($num_bytes >= 0, 'Tried to read less than 0 bytes');
 697  
 698          if ($num_bytes === 0) {
 699              return '';
 700          }
 701  
 702          $buf       = '';
 703          $remaining = $num_bytes;
 704          while ($remaining > 0 && ! \feof($stream)) {
 705              /** @var string $read */
 706              $read = \fread($stream, $remaining);
 707              if (!\is_string($read)) {
 708                  throw new Ex\IOException(
 709                      'Could not read from the file'
 710                  );
 711              }
 712              $buf .= $read;
 713              $remaining -= Core::ourStrlen($read);
 714          }
 715          if (Core::ourStrlen($buf) !== $num_bytes) {
 716              throw new Ex\IOException(
 717                  'Tried to read past the end of the file'
 718              );
 719          }
 720          return $buf;
 721      }
 722  
 723      /**
 724       * Write to a stream; prevents partial writes.
 725       *
 726       * @param resource $stream
 727       * @param string   $buf
 728       * @param int      $num_bytes
 729       * @return int
 730       *
 731       * @throws Ex\IOException
 732       */
 733      public static function writeBytes($stream, $buf, $num_bytes = null)
 734      {
 735          $bufSize = Core::ourStrlen($buf);
 736          if ($num_bytes === null) {
 737              $num_bytes = $bufSize;
 738          }
 739          if ($num_bytes > $bufSize) {
 740              throw new Ex\IOException(
 741                  'Trying to write more bytes than the buffer contains.'
 742              );
 743          }
 744          if ($num_bytes < 0) {
 745              throw new Ex\IOException(
 746                  'Tried to write less than 0 bytes'
 747              );
 748          }
 749          $remaining = $num_bytes;
 750          while ($remaining > 0) {
 751              /** @var int $written */
 752              $written = \fwrite($stream, $buf, $remaining);
 753              if (!\is_int($written)) {
 754                  throw new Ex\IOException(
 755                      'Could not write to the file'
 756                  );
 757              }
 758              $buf = (string) Core::ourSubstr($buf, $written, null);
 759              $remaining -= $written;
 760          }
 761          return $num_bytes;
 762      }
 763  
 764      /**
 765       * Returns the last PHP error's or warning's message string.
 766       *
 767       * @return string
 768       */
 769      private static function getLastErrorMessage()
 770      {
 771          $error = error_get_last();
 772          if ($error === null) {
 773              return '[no PHP error]';
 774          } else {
 775              return $error['message'];
 776          }
 777      }
 778  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer