wp/wp-includes/sodium_compat/src/Core/Util.php
changeset 9 177826044cd9
child 18 be944660c56a
equal deleted inserted replaced
8:c7c34916027a 9:177826044cd9
       
     1 <?php
       
     2 
       
     3 if (class_exists('ParagonIE_Sodium_Core_Util', false)) {
       
     4     return;
       
     5 }
       
     6 
       
     7 /**
       
     8  * Class ParagonIE_Sodium_Core_Util
       
     9  */
       
    10 abstract class ParagonIE_Sodium_Core_Util
       
    11 {
       
    12     /**
       
    13      * @param int $integer
       
    14      * @param int $size (16, 32, 64)
       
    15      * @return int
       
    16      */
       
    17     public static function abs($integer, $size = 0)
       
    18     {
       
    19         /** @var int $realSize */
       
    20         $realSize = (PHP_INT_SIZE << 3) - 1;
       
    21         if ($size) {
       
    22             --$size;
       
    23         } else {
       
    24             /** @var int $size */
       
    25             $size = $realSize;
       
    26         }
       
    27 
       
    28         $negative = -(($integer >> $size) & 1);
       
    29         return (int) (
       
    30             ($integer ^ $negative)
       
    31                 +
       
    32             (($negative >> $realSize) & 1)
       
    33         );
       
    34     }
       
    35 
       
    36     /**
       
    37      * Convert a binary string into a hexadecimal string without cache-timing
       
    38      * leaks
       
    39      *
       
    40      * @internal You should not use this directly from another application
       
    41      *
       
    42      * @param string $binaryString (raw binary)
       
    43      * @return string
       
    44      * @throws TypeError
       
    45      */
       
    46     public static function bin2hex($binaryString)
       
    47     {
       
    48         /* Type checks: */
       
    49         if (!is_string($binaryString)) {
       
    50             throw new TypeError('Argument 1 must be a string, ' . gettype($binaryString) . ' given.');
       
    51         }
       
    52 
       
    53         $hex = '';
       
    54         $len = self::strlen($binaryString);
       
    55         for ($i = 0; $i < $len; ++$i) {
       
    56             /** @var array<int, int> $chunk */
       
    57             $chunk = unpack('C', $binaryString[$i]);
       
    58             /** @var int $c */
       
    59             $c = $chunk[1] & 0xf;
       
    60             /** @var int $b */
       
    61             $b = $chunk[1] >> 4;
       
    62             $hex .= pack(
       
    63                 'CC',
       
    64                 (87 + $b + ((($b - 10) >> 8) & ~38)),
       
    65                 (87 + $c + ((($c - 10) >> 8) & ~38))
       
    66             );
       
    67         }
       
    68         return $hex;
       
    69     }
       
    70 
       
    71     /**
       
    72      * Convert a binary string into a hexadecimal string without cache-timing
       
    73      * leaks, returning uppercase letters (as per RFC 4648)
       
    74      *
       
    75      * @internal You should not use this directly from another application
       
    76      *
       
    77      * @param string $bin_string (raw binary)
       
    78      * @return string
       
    79      * @throws TypeError
       
    80      */
       
    81     public static function bin2hexUpper($bin_string)
       
    82     {
       
    83         $hex = '';
       
    84         $len = self::strlen($bin_string);
       
    85         for ($i = 0; $i < $len; ++$i) {
       
    86             /** @var array<int, int> $chunk */
       
    87             $chunk = unpack('C', $bin_string[$i]);
       
    88             /**
       
    89              * Lower 16 bits
       
    90              *
       
    91              * @var int $c
       
    92              */
       
    93             $c = $chunk[1] & 0xf;
       
    94 
       
    95             /**
       
    96              * Upper 16 bits
       
    97              * @var int $b
       
    98              */
       
    99             $b = $chunk[1] >> 4;
       
   100 
       
   101             /**
       
   102              * Use pack() and binary operators to turn the two integers
       
   103              * into hexadecimal characters. We don't use chr() here, because
       
   104              * it uses a lookup table internally and we want to avoid
       
   105              * cache-timing side-channels.
       
   106              */
       
   107             $hex .= pack(
       
   108                 'CC',
       
   109                 (55 + $b + ((($b - 10) >> 8) & ~6)),
       
   110                 (55 + $c + ((($c - 10) >> 8) & ~6))
       
   111             );
       
   112         }
       
   113         return $hex;
       
   114     }
       
   115 
       
   116     /**
       
   117      * Cache-timing-safe variant of ord()
       
   118      *
       
   119      * @internal You should not use this directly from another application
       
   120      *
       
   121      * @param string $chr
       
   122      * @return int
       
   123      * @throws SodiumException
       
   124      * @throws TypeError
       
   125      */
       
   126     public static function chrToInt($chr)
       
   127     {
       
   128         /* Type checks: */
       
   129         if (!is_string($chr)) {
       
   130             throw new TypeError('Argument 1 must be a string, ' . gettype($chr) . ' given.');
       
   131         }
       
   132         if (self::strlen($chr) !== 1) {
       
   133             throw new SodiumException('chrToInt() expects a string that is exactly 1 character long');
       
   134         }
       
   135         /** @var array<int, int> $chunk */
       
   136         $chunk = unpack('C', $chr);
       
   137         return (int) ($chunk[1]);
       
   138     }
       
   139 
       
   140     /**
       
   141      * Compares two strings.
       
   142      *
       
   143      * @internal You should not use this directly from another application
       
   144      *
       
   145      * @param string $left
       
   146      * @param string $right
       
   147      * @param int $len
       
   148      * @return int
       
   149      * @throws SodiumException
       
   150      * @throws TypeError
       
   151      */
       
   152     public static function compare($left, $right, $len = null)
       
   153     {
       
   154         $leftLen = self::strlen($left);
       
   155         $rightLen = self::strlen($right);
       
   156         if ($len === null) {
       
   157             $len = max($leftLen, $rightLen);
       
   158             $left = str_pad($left, $len, "\x00", STR_PAD_RIGHT);
       
   159             $right = str_pad($right, $len, "\x00", STR_PAD_RIGHT);
       
   160         }
       
   161 
       
   162         $gt = 0;
       
   163         $eq = 1;
       
   164         $i = $len;
       
   165         while ($i !== 0) {
       
   166             --$i;
       
   167             $gt |= ((self::chrToInt($right[$i]) - self::chrToInt($left[$i])) >> 8) & $eq;
       
   168             $eq &= ((self::chrToInt($right[$i]) ^ self::chrToInt($left[$i])) - 1) >> 8;
       
   169         }
       
   170         return ($gt + $gt + $eq) - 1;
       
   171     }
       
   172 
       
   173     /**
       
   174      * If a variable does not match a given type, throw a TypeError.
       
   175      *
       
   176      * @param mixed $mixedVar
       
   177      * @param string $type
       
   178      * @param int $argumentIndex
       
   179      * @throws TypeError
       
   180      * @throws SodiumException
       
   181      * @return void
       
   182      */
       
   183     public static function declareScalarType(&$mixedVar = null, $type = 'void', $argumentIndex = 0)
       
   184     {
       
   185         if (func_num_args() === 0) {
       
   186             /* Tautology, by default */
       
   187             return;
       
   188         }
       
   189         if (func_num_args() === 1) {
       
   190             throw new TypeError('Declared void, but passed a variable');
       
   191         }
       
   192         $realType = strtolower(gettype($mixedVar));
       
   193         $type = strtolower($type);
       
   194         switch ($type) {
       
   195             case 'null':
       
   196                 if ($mixedVar !== null) {
       
   197                     throw new TypeError('Argument ' . $argumentIndex . ' must be null, ' . $realType . ' given.');
       
   198                 }
       
   199                 break;
       
   200             case 'integer':
       
   201             case 'int':
       
   202                 $allow = array('int', 'integer');
       
   203                 if (!in_array($type, $allow)) {
       
   204                     throw new TypeError('Argument ' . $argumentIndex . ' must be an integer, ' . $realType . ' given.');
       
   205                 }
       
   206                 $mixedVar = (int) $mixedVar;
       
   207                 break;
       
   208             case 'boolean':
       
   209             case 'bool':
       
   210                 $allow = array('bool', 'boolean');
       
   211                 if (!in_array($type, $allow)) {
       
   212                     throw new TypeError('Argument ' . $argumentIndex . ' must be a boolean, ' . $realType . ' given.');
       
   213                 }
       
   214                 $mixedVar = (bool) $mixedVar;
       
   215                 break;
       
   216             case 'string':
       
   217                 if (!is_string($mixedVar)) {
       
   218                     throw new TypeError('Argument ' . $argumentIndex . ' must be a string, ' . $realType . ' given.');
       
   219                 }
       
   220                 $mixedVar = (string) $mixedVar;
       
   221                 break;
       
   222             case 'decimal':
       
   223             case 'double':
       
   224             case 'float':
       
   225                 $allow = array('decimal', 'double', 'float');
       
   226                 if (!in_array($type, $allow)) {
       
   227                     throw new TypeError('Argument ' . $argumentIndex . ' must be a float, ' . $realType . ' given.');
       
   228                 }
       
   229                 $mixedVar = (float) $mixedVar;
       
   230                 break;
       
   231             case 'object':
       
   232                 if (!is_object($mixedVar)) {
       
   233                     throw new TypeError('Argument ' . $argumentIndex . ' must be an object, ' . $realType . ' given.');
       
   234                 }
       
   235                 break;
       
   236             case 'array':
       
   237                 if (!is_array($mixedVar)) {
       
   238                     if (is_object($mixedVar)) {
       
   239                         if ($mixedVar instanceof ArrayAccess) {
       
   240                             return;
       
   241                         }
       
   242                     }
       
   243                     throw new TypeError('Argument ' . $argumentIndex . ' must be an array, ' . $realType . ' given.');
       
   244                 }
       
   245                 break;
       
   246             default:
       
   247                 throw new SodiumException('Unknown type (' . $realType .') does not match expect type (' . $type . ')');
       
   248         }
       
   249     }
       
   250 
       
   251     /**
       
   252      * Evaluate whether or not two strings are equal (in constant-time)
       
   253      *
       
   254      * @param string $left
       
   255      * @param string $right
       
   256      * @return bool
       
   257      * @throws SodiumException
       
   258      * @throws TypeError
       
   259      */
       
   260     public static function hashEquals($left, $right)
       
   261     {
       
   262         /* Type checks: */
       
   263         if (!is_string($left)) {
       
   264             throw new TypeError('Argument 1 must be a string, ' . gettype($left) . ' given.');
       
   265         }
       
   266         if (!is_string($right)) {
       
   267             throw new TypeError('Argument 2 must be a string, ' . gettype($right) . ' given.');
       
   268         }
       
   269 
       
   270         if (is_callable('hash_equals')) {
       
   271             return hash_equals($left, $right);
       
   272         }
       
   273         $d = 0;
       
   274         /** @var int $len */
       
   275         $len = self::strlen($left);
       
   276         if ($len !== self::strlen($right)) {
       
   277             return false;
       
   278         }
       
   279         for ($i = 0; $i < $len; ++$i) {
       
   280             $d |= self::chrToInt($left[$i]) ^ self::chrToInt($right[$i]);
       
   281         }
       
   282 
       
   283         if ($d !== 0) {
       
   284             return false;
       
   285         }
       
   286         return $left === $right;
       
   287     }
       
   288 
       
   289     /**
       
   290      * Convert a hexadecimal string into a binary string without cache-timing
       
   291      * leaks
       
   292      *
       
   293      * @internal You should not use this directly from another application
       
   294      *
       
   295      * @param string $hexString
       
   296      * @param bool $strictPadding
       
   297      * @return string (raw binary)
       
   298      * @throws RangeException
       
   299      * @throws TypeError
       
   300      */
       
   301     public static function hex2bin($hexString, $strictPadding = false)
       
   302     {
       
   303         /* Type checks: */
       
   304         if (!is_string($hexString)) {
       
   305             throw new TypeError('Argument 1 must be a string, ' . gettype($hexString) . ' given.');
       
   306         }
       
   307 
       
   308         /** @var int $hex_pos */
       
   309         $hex_pos = 0;
       
   310         /** @var string $bin */
       
   311         $bin = '';
       
   312         /** @var int $c_acc */
       
   313         $c_acc = 0;
       
   314         /** @var int $hex_len */
       
   315         $hex_len = self::strlen($hexString);
       
   316         /** @var int $state */
       
   317         $state = 0;
       
   318         if (($hex_len & 1) !== 0) {
       
   319             if ($strictPadding) {
       
   320                 throw new RangeException(
       
   321                     'Expected an even number of hexadecimal characters'
       
   322                 );
       
   323             } else {
       
   324                 $hexString = '0' . $hexString;
       
   325                 ++$hex_len;
       
   326             }
       
   327         }
       
   328 
       
   329         $chunk = unpack('C*', $hexString);
       
   330         while ($hex_pos < $hex_len) {
       
   331             ++$hex_pos;
       
   332             /** @var int $c */
       
   333             $c = $chunk[$hex_pos];
       
   334             /** @var int $c_num */
       
   335             $c_num = $c ^ 48;
       
   336             /** @var int $c_num0 */
       
   337             $c_num0 = ($c_num - 10) >> 8;
       
   338             /** @var int $c_alpha */
       
   339             $c_alpha = ($c & ~32) - 55;
       
   340             /** @var int $c_alpha0 */
       
   341             $c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8;
       
   342             if (($c_num0 | $c_alpha0) === 0) {
       
   343                 throw new RangeException(
       
   344                     'hex2bin() only expects hexadecimal characters'
       
   345                 );
       
   346             }
       
   347             /** @var int $c_val */
       
   348             $c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0);
       
   349             if ($state === 0) {
       
   350                 $c_acc = $c_val * 16;
       
   351             } else {
       
   352                 $bin .= pack('C', $c_acc | $c_val);
       
   353             }
       
   354             $state ^= 1;
       
   355         }
       
   356         return $bin;
       
   357     }
       
   358 
       
   359     /**
       
   360      * Turn an array of integers into a string
       
   361      *
       
   362      * @internal You should not use this directly from another application
       
   363      *
       
   364      * @param array<int, int> $ints
       
   365      * @return string
       
   366      */
       
   367     public static function intArrayToString(array $ints)
       
   368     {
       
   369         /** @var array<int, int> $args */
       
   370         $args = $ints;
       
   371         foreach ($args as $i => $v) {
       
   372             $args[$i] = (int) ($v & 0xff);
       
   373         }
       
   374         array_unshift($args, str_repeat('C', count($ints)));
       
   375         return (string) (call_user_func_array('pack', $args));
       
   376     }
       
   377 
       
   378     /**
       
   379      * Cache-timing-safe variant of ord()
       
   380      *
       
   381      * @internal You should not use this directly from another application
       
   382      *
       
   383      * @param int $int
       
   384      * @return string
       
   385      * @throws TypeError
       
   386      */
       
   387     public static function intToChr($int)
       
   388     {
       
   389         return pack('C', $int);
       
   390     }
       
   391 
       
   392     /**
       
   393      * Load a 3 character substring into an integer
       
   394      *
       
   395      * @internal You should not use this directly from another application
       
   396      *
       
   397      * @param string $string
       
   398      * @return int
       
   399      * @throws RangeException
       
   400      * @throws TypeError
       
   401      */
       
   402     public static function load_3($string)
       
   403     {
       
   404         /* Type checks: */
       
   405         if (!is_string($string)) {
       
   406             throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');
       
   407         }
       
   408 
       
   409         /* Input validation: */
       
   410         if (self::strlen($string) < 3) {
       
   411             throw new RangeException(
       
   412                 'String must be 3 bytes or more; ' . self::strlen($string) . ' given.'
       
   413             );
       
   414         }
       
   415         /** @var array<int, int> $unpacked */
       
   416         $unpacked = unpack('V', $string . "\0");
       
   417         return (int) ($unpacked[1] & 0xffffff);
       
   418     }
       
   419 
       
   420     /**
       
   421      * Load a 4 character substring into an integer
       
   422      *
       
   423      * @internal You should not use this directly from another application
       
   424      *
       
   425      * @param string $string
       
   426      * @return int
       
   427      * @throws RangeException
       
   428      * @throws TypeError
       
   429      */
       
   430     public static function load_4($string)
       
   431     {
       
   432         /* Type checks: */
       
   433         if (!is_string($string)) {
       
   434             throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');
       
   435         }
       
   436 
       
   437         /* Input validation: */
       
   438         if (self::strlen($string) < 4) {
       
   439             throw new RangeException(
       
   440                 'String must be 4 bytes or more; ' . self::strlen($string) . ' given.'
       
   441             );
       
   442         }
       
   443         /** @var array<int, int> $unpacked */
       
   444         $unpacked = unpack('V', $string);
       
   445         return (int) ($unpacked[1] & 0xffffffff);
       
   446     }
       
   447 
       
   448     /**
       
   449      * Load a 8 character substring into an integer
       
   450      *
       
   451      * @internal You should not use this directly from another application
       
   452      *
       
   453      * @param string $string
       
   454      * @return int
       
   455      * @throws RangeException
       
   456      * @throws SodiumException
       
   457      * @throws TypeError
       
   458      */
       
   459     public static function load64_le($string)
       
   460     {
       
   461         /* Type checks: */
       
   462         if (!is_string($string)) {
       
   463             throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');
       
   464         }
       
   465 
       
   466         /* Input validation: */
       
   467         if (self::strlen($string) < 4) {
       
   468             throw new RangeException(
       
   469                 'String must be 4 bytes or more; ' . self::strlen($string) . ' given.'
       
   470             );
       
   471         }
       
   472         if (PHP_VERSION_ID >= 50603 && PHP_INT_SIZE === 8) {
       
   473             /** @var array<int, int> $unpacked */
       
   474             $unpacked = unpack('P', $string);
       
   475             return (int) $unpacked[1];
       
   476         }
       
   477 
       
   478         /** @var int $result */
       
   479         $result  = (self::chrToInt($string[0]) & 0xff);
       
   480         $result |= (self::chrToInt($string[1]) & 0xff) <<  8;
       
   481         $result |= (self::chrToInt($string[2]) & 0xff) << 16;
       
   482         $result |= (self::chrToInt($string[3]) & 0xff) << 24;
       
   483         $result |= (self::chrToInt($string[4]) & 0xff) << 32;
       
   484         $result |= (self::chrToInt($string[5]) & 0xff) << 40;
       
   485         $result |= (self::chrToInt($string[6]) & 0xff) << 48;
       
   486         $result |= (self::chrToInt($string[7]) & 0xff) << 56;
       
   487         return (int) $result;
       
   488     }
       
   489 
       
   490     /**
       
   491      * @internal You should not use this directly from another application
       
   492      *
       
   493      * @param string $left
       
   494      * @param string $right
       
   495      * @return int
       
   496      * @throws SodiumException
       
   497      * @throws TypeError
       
   498      */
       
   499     public static function memcmp($left, $right)
       
   500     {
       
   501         if (self::hashEquals($left, $right)) {
       
   502             return 0;
       
   503         }
       
   504         return -1;
       
   505     }
       
   506 
       
   507     /**
       
   508      * Multiply two integers in constant-time
       
   509      *
       
   510      * Micro-architecture timing side-channels caused by how your CPU
       
   511      * implements multiplication are best prevented by never using the
       
   512      * multiplication operators and ensuring that our code always takes
       
   513      * the same number of operations to complete, regardless of the values
       
   514      * of $a and $b.
       
   515      *
       
   516      * @internal You should not use this directly from another application
       
   517      *
       
   518      * @param int $a
       
   519      * @param int $b
       
   520      * @param int $size Limits the number of operations (useful for small,
       
   521      *                  constant operands)
       
   522      * @return int
       
   523      */
       
   524     public static function mul($a, $b, $size = 0)
       
   525     {
       
   526         if (ParagonIE_Sodium_Compat::$fastMult) {
       
   527             return (int) ($a * $b);
       
   528         }
       
   529 
       
   530         static $defaultSize = null;
       
   531         /** @var int $defaultSize */
       
   532         if (!$defaultSize) {
       
   533             /** @var int $defaultSize */
       
   534             $defaultSize = (PHP_INT_SIZE << 3) - 1;
       
   535         }
       
   536         if ($size < 1) {
       
   537             /** @var int $size */
       
   538             $size = $defaultSize;
       
   539         }
       
   540         /** @var int $size */
       
   541 
       
   542         $c = 0;
       
   543 
       
   544         /**
       
   545          * Mask is either -1 or 0.
       
   546          *
       
   547          * -1 in binary looks like 0x1111 ... 1111
       
   548          *  0 in binary looks like 0x0000 ... 0000
       
   549          *
       
   550          * @var int
       
   551          */
       
   552         $mask = -(($b >> ((int) $defaultSize)) & 1);
       
   553 
       
   554         /**
       
   555          * Ensure $b is a positive integer, without creating
       
   556          * a branching side-channel
       
   557          *
       
   558          * @var int $b
       
   559          */
       
   560         $b = ($b & ~$mask) | ($mask & -$b);
       
   561 
       
   562         /**
       
   563          * Unless $size is provided:
       
   564          *
       
   565          * This loop always runs 32 times when PHP_INT_SIZE is 4.
       
   566          * This loop always runs 64 times when PHP_INT_SIZE is 8.
       
   567          */
       
   568         for ($i = $size; $i >= 0; --$i) {
       
   569             $c += (int) ($a & -($b & 1));
       
   570             $a <<= 1;
       
   571             $b >>= 1;
       
   572         }
       
   573 
       
   574         /**
       
   575          * If $b was negative, we then apply the same value to $c here.
       
   576          * It doesn't matter much if $a was negative; the $c += above would
       
   577          * have produced a negative integer to begin with. But a negative $b
       
   578          * makes $b >>= 1 never return 0, so we would end up with incorrect
       
   579          * results.
       
   580          *
       
   581          * The end result is what we'd expect from integer multiplication.
       
   582          */
       
   583         return (int) (($c & ~$mask) | ($mask & -$c));
       
   584     }
       
   585 
       
   586     /**
       
   587      * Convert any arbitrary numbers into two 32-bit integers that represent
       
   588      * a 64-bit integer.
       
   589      *
       
   590      * @internal You should not use this directly from another application
       
   591      *
       
   592      * @param int|float $num
       
   593      * @return array<int, int>
       
   594      */
       
   595     public static function numericTo64BitInteger($num)
       
   596     {
       
   597         $high = 0;
       
   598         /** @var int $low */
       
   599         $low = $num & 0xffffffff;
       
   600 
       
   601         if ((+(abs($num))) >= 1) {
       
   602             if ($num > 0) {
       
   603                 /** @var int $high */
       
   604                 $high = min((+(floor($num/4294967296))), 4294967295);
       
   605             } else {
       
   606                 /** @var int $high */
       
   607                 $high = ~~((+(ceil(($num - (+((~~($num)))))/4294967296))));
       
   608             }
       
   609         }
       
   610         return array((int) $high, (int) $low);
       
   611     }
       
   612 
       
   613     /**
       
   614      * Store a 24-bit integer into a string, treating it as big-endian.
       
   615      *
       
   616      * @internal You should not use this directly from another application
       
   617      *
       
   618      * @param int $int
       
   619      * @return string
       
   620      * @throws TypeError
       
   621      */
       
   622     public static function store_3($int)
       
   623     {
       
   624         /* Type checks: */
       
   625         if (!is_int($int)) {
       
   626             if (is_numeric($int)) {
       
   627                 $int = (int) $int;
       
   628             } else {
       
   629                 throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
       
   630             }
       
   631         }
       
   632         /** @var string $packed */
       
   633         $packed = pack('N', $int);
       
   634         return self::substr($packed, 1, 3);
       
   635     }
       
   636 
       
   637     /**
       
   638      * Store a 32-bit integer into a string, treating it as little-endian.
       
   639      *
       
   640      * @internal You should not use this directly from another application
       
   641      *
       
   642      * @param int $int
       
   643      * @return string
       
   644      * @throws TypeError
       
   645      */
       
   646     public static function store32_le($int)
       
   647     {
       
   648         /* Type checks: */
       
   649         if (!is_int($int)) {
       
   650             if (is_numeric($int)) {
       
   651                 $int = (int) $int;
       
   652             } else {
       
   653                 throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
       
   654             }
       
   655         }
       
   656 
       
   657         /** @var string $packed */
       
   658         $packed = pack('V', $int);
       
   659         return $packed;
       
   660     }
       
   661 
       
   662     /**
       
   663      * Store a 32-bit integer into a string, treating it as big-endian.
       
   664      *
       
   665      * @internal You should not use this directly from another application
       
   666      *
       
   667      * @param int $int
       
   668      * @return string
       
   669      * @throws TypeError
       
   670      */
       
   671     public static function store_4($int)
       
   672     {
       
   673         /* Type checks: */
       
   674         if (!is_int($int)) {
       
   675             if (is_numeric($int)) {
       
   676                 $int = (int) $int;
       
   677             } else {
       
   678                 throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
       
   679             }
       
   680         }
       
   681 
       
   682         /** @var string $packed */
       
   683         $packed = pack('N', $int);
       
   684         return $packed;
       
   685     }
       
   686 
       
   687     /**
       
   688      * Stores a 64-bit integer as an string, treating it as little-endian.
       
   689      *
       
   690      * @internal You should not use this directly from another application
       
   691      *
       
   692      * @param int $int
       
   693      * @return string
       
   694      * @throws TypeError
       
   695      */
       
   696     public static function store64_le($int)
       
   697     {
       
   698         /* Type checks: */
       
   699         if (!is_int($int)) {
       
   700             if (is_numeric($int)) {
       
   701                 $int = (int) $int;
       
   702             } else {
       
   703                 throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
       
   704             }
       
   705         }
       
   706 
       
   707         if (PHP_INT_SIZE === 8) {
       
   708             if (PHP_VERSION_ID >= 50603) {
       
   709                 /** @var string $packed */
       
   710                 $packed = pack('P', $int);
       
   711                 return $packed;
       
   712             }
       
   713             return self::intToChr($int & 0xff) .
       
   714                 self::intToChr(($int >>  8) & 0xff) .
       
   715                 self::intToChr(($int >> 16) & 0xff) .
       
   716                 self::intToChr(($int >> 24) & 0xff) .
       
   717                 self::intToChr(($int >> 32) & 0xff) .
       
   718                 self::intToChr(($int >> 40) & 0xff) .
       
   719                 self::intToChr(($int >> 48) & 0xff) .
       
   720                 self::intToChr(($int >> 56) & 0xff);
       
   721         }
       
   722         if ($int > PHP_INT_MAX) {
       
   723             list($hiB, $int) = self::numericTo64BitInteger($int);
       
   724         } else {
       
   725             $hiB = 0;
       
   726         }
       
   727         return
       
   728             self::intToChr(($int      ) & 0xff) .
       
   729             self::intToChr(($int >>  8) & 0xff) .
       
   730             self::intToChr(($int >> 16) & 0xff) .
       
   731             self::intToChr(($int >> 24) & 0xff) .
       
   732             self::intToChr($hiB & 0xff) .
       
   733             self::intToChr(($hiB >>  8) & 0xff) .
       
   734             self::intToChr(($hiB >> 16) & 0xff) .
       
   735             self::intToChr(($hiB >> 24) & 0xff);
       
   736     }
       
   737 
       
   738     /**
       
   739      * Safe string length
       
   740      *
       
   741      * @internal You should not use this directly from another application
       
   742      *
       
   743      * @ref mbstring.func_overload
       
   744      *
       
   745      * @param string $str
       
   746      * @return int
       
   747      * @throws TypeError
       
   748      */
       
   749     public static function strlen($str)
       
   750     {
       
   751         /* Type checks: */
       
   752         if (!is_string($str)) {
       
   753             throw new TypeError('String expected');
       
   754         }
       
   755 
       
   756         return (int) (
       
   757         self::isMbStringOverride()
       
   758             ? mb_strlen($str, '8bit')
       
   759             : strlen($str)
       
   760         );
       
   761     }
       
   762 
       
   763     /**
       
   764      * Turn a string into an array of integers
       
   765      *
       
   766      * @internal You should not use this directly from another application
       
   767      *
       
   768      * @param string $string
       
   769      * @return array<int, int>
       
   770      * @throws TypeError
       
   771      */
       
   772     public static function stringToIntArray($string)
       
   773     {
       
   774         if (!is_string($string)) {
       
   775             throw new TypeError('String expected');
       
   776         }
       
   777         /**
       
   778          * @var array<int, int>
       
   779          */
       
   780         $values = array_values(
       
   781             unpack('C*', $string)
       
   782         );
       
   783         return $values;
       
   784     }
       
   785 
       
   786     /**
       
   787      * Safe substring
       
   788      *
       
   789      * @internal You should not use this directly from another application
       
   790      *
       
   791      * @ref mbstring.func_overload
       
   792      *
       
   793      * @param string $str
       
   794      * @param int $start
       
   795      * @param int $length
       
   796      * @return string
       
   797      * @throws TypeError
       
   798      */
       
   799     public static function substr($str, $start = 0, $length = null)
       
   800     {
       
   801         /* Type checks: */
       
   802         if (!is_string($str)) {
       
   803             throw new TypeError('String expected');
       
   804         }
       
   805 
       
   806         if ($length === 0) {
       
   807             return '';
       
   808         }
       
   809 
       
   810         if (self::isMbStringOverride()) {
       
   811             if (PHP_VERSION_ID < 50400 && $length === null) {
       
   812                 $length = self::strlen($str);
       
   813             }
       
   814             $sub = (string) mb_substr($str, $start, $length, '8bit');
       
   815         } elseif ($length === null) {
       
   816             $sub = (string) substr($str, $start);
       
   817         } else {
       
   818             $sub = (string) substr($str, $start, $length);
       
   819         }
       
   820         if ($sub !== '') {
       
   821             return $sub;
       
   822         }
       
   823         return '';
       
   824     }
       
   825 
       
   826     /**
       
   827      * Compare a 16-character byte string in constant time.
       
   828      *
       
   829      * @internal You should not use this directly from another application
       
   830      *
       
   831      * @param string $a
       
   832      * @param string $b
       
   833      * @return bool
       
   834      * @throws SodiumException
       
   835      * @throws TypeError
       
   836      */
       
   837     public static function verify_16($a, $b)
       
   838     {
       
   839         /* Type checks: */
       
   840         if (!is_string($a)) {
       
   841             throw new TypeError('String expected');
       
   842         }
       
   843         if (!is_string($b)) {
       
   844             throw new TypeError('String expected');
       
   845         }
       
   846         return self::hashEquals(
       
   847             self::substr($a, 0, 16),
       
   848             self::substr($b, 0, 16)
       
   849         );
       
   850     }
       
   851 
       
   852     /**
       
   853      * Compare a 32-character byte string in constant time.
       
   854      *
       
   855      * @internal You should not use this directly from another application
       
   856      *
       
   857      * @param string $a
       
   858      * @param string $b
       
   859      * @return bool
       
   860      * @throws SodiumException
       
   861      * @throws TypeError
       
   862      */
       
   863     public static function verify_32($a, $b)
       
   864     {
       
   865         /* Type checks: */
       
   866         if (!is_string($a)) {
       
   867             throw new TypeError('String expected');
       
   868         }
       
   869         if (!is_string($b)) {
       
   870             throw new TypeError('String expected');
       
   871         }
       
   872         return self::hashEquals(
       
   873             self::substr($a, 0, 32),
       
   874             self::substr($b, 0, 32)
       
   875         );
       
   876     }
       
   877 
       
   878     /**
       
   879      * Calculate $a ^ $b for two strings.
       
   880      *
       
   881      * @internal You should not use this directly from another application
       
   882      *
       
   883      * @param string $a
       
   884      * @param string $b
       
   885      * @return string
       
   886      * @throws TypeError
       
   887      */
       
   888     public static function xorStrings($a, $b)
       
   889     {
       
   890         /* Type checks: */
       
   891         if (!is_string($a)) {
       
   892             throw new TypeError('Argument 1 must be a string');
       
   893         }
       
   894         if (!is_string($b)) {
       
   895             throw new TypeError('Argument 2 must be a string');
       
   896         }
       
   897 
       
   898         return (string) ($a ^ $b);
       
   899     }
       
   900 
       
   901     /**
       
   902      * Returns whether or not mbstring.func_overload is in effect.
       
   903      *
       
   904      * @internal You should not use this directly from another application
       
   905      *
       
   906      * @return bool
       
   907      */
       
   908     protected static function isMbStringOverride()
       
   909     {
       
   910         static $mbstring = null;
       
   911 
       
   912         if ($mbstring === null) {
       
   913             $mbstring = extension_loaded('mbstring')
       
   914                 &&
       
   915             ((int) (ini_get('mbstring.func_overload')) & MB_OVERLOAD_STRING);
       
   916         }
       
   917         /** @var bool $mbstring */
       
   918 
       
   919         return $mbstring;
       
   920     }
       
   921 }