wp/wp-includes/random_compat/random_int.php
changeset 9 177826044cd9
parent 7 cf61fcea0001
child 19 3d72ae0968f4
equal deleted inserted replaced
8:c7c34916027a 9:177826044cd9
     1 <?php
     1 <?php
     2 /**
       
     3  * Random_* Compatibility Library 
       
     4  * for using the new PHP 7 random_* API in PHP 5 projects
       
     5  * 
       
     6  * The MIT License (MIT)
       
     7  * 
       
     8  * Copyright (c) 2015 Paragon Initiative Enterprises
       
     9  * 
       
    10  * Permission is hereby granted, free of charge, to any person obtaining a copy
       
    11  * of this software and associated documentation files (the "Software"), to deal
       
    12  * in the Software without restriction, including without limitation the rights
       
    13  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       
    14  * copies of the Software, and to permit persons to whom the Software is
       
    15  * furnished to do so, subject to the following conditions:
       
    16  * 
       
    17  * The above copyright notice and this permission notice shall be included in
       
    18  * all copies or substantial portions of the Software.
       
    19  * 
       
    20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       
    21  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       
    22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       
    23  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       
    24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       
    25  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       
    26  * SOFTWARE.
       
    27  */
       
    28 
     2 
    29 if ( ! is_callable( 'random_int' ) ):
     3 if (!is_callable('random_int')) {
    30 /**
       
    31  * Fetch a random integer between $min and $max inclusive
       
    32  * 
       
    33  * @param int $min
       
    34  * @param int $max
       
    35  * 
       
    36  * @throws Exception
       
    37  * 
       
    38  * @return int
       
    39  */
       
    40 function random_int($min, $max)
       
    41 {
       
    42     /**
     4     /**
    43      * Type and input logic checks
     5      * Random_* Compatibility Library
    44      * 
     6      * for using the new PHP 7 random_* API in PHP 5 projects
    45      * If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
     7      *
    46      * (non-inclusive), it will sanely cast it to an int. If you it's equal to
     8      * The MIT License (MIT)
    47      * ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats 
     9      *
    48      * lose precision, so the <= and => operators might accidentally let a float
    10      * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
    49      * through.
    11      *
       
    12      * Permission is hereby granted, free of charge, to any person obtaining a copy
       
    13      * of this software and associated documentation files (the "Software"), to deal
       
    14      * in the Software without restriction, including without limitation the rights
       
    15      * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       
    16      * copies of the Software, and to permit persons to whom the Software is
       
    17      * furnished to do so, subject to the following conditions:
       
    18      *
       
    19      * The above copyright notice and this permission notice shall be included in
       
    20      * all copies or substantial portions of the Software.
       
    21      *
       
    22      * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       
    23      * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       
    24      * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       
    25      * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       
    26      * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       
    27      * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       
    28      * SOFTWARE.
    50      */
    29      */
    51     
       
    52     try {
       
    53         $min = RandomCompat_intval($min);
       
    54     } catch (TypeError $ex) {
       
    55         throw new TypeError(
       
    56             'random_int(): $min must be an integer'
       
    57         );
       
    58     }
       
    59 
       
    60     try {
       
    61         $max = RandomCompat_intval($max);
       
    62     } catch (TypeError $ex) {
       
    63         throw new TypeError(
       
    64             'random_int(): $max must be an integer'
       
    65         );
       
    66     }
       
    67     
       
    68     /**
       
    69      * Now that we've verified our weak typing system has given us an integer,
       
    70      * let's validate the logic then we can move forward with generating random
       
    71      * integers along a given range.
       
    72      */
       
    73     if ($min > $max) {
       
    74         throw new Error(
       
    75             'Minimum value must be less than or equal to the maximum value'
       
    76         );
       
    77     }
       
    78 
       
    79     if ($max === $min) {
       
    80         return $min;
       
    81     }
       
    82 
    30 
    83     /**
    31     /**
    84      * Initialize variables to 0
    32      * Fetch a random integer between $min and $max inclusive
    85      * 
    33      *
    86      * We want to store:
    34      * @param int $min
    87      * $bytes => the number of random bytes we need
    35      * @param int $max
    88      * $mask => an integer bitmask (for use with the &) operator
    36      *
    89      *          so we can minimize the number of discards
    37      * @throws Exception
       
    38      *
       
    39      * @return int
    90      */
    40      */
    91     $attempts = $bits = $bytes = $mask = $valueShift = 0;
    41     function random_int($min, $max)
       
    42     {
       
    43         /**
       
    44          * Type and input logic checks
       
    45          *
       
    46          * If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
       
    47          * (non-inclusive), it will sanely cast it to an int. If you it's equal to
       
    48          * ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats
       
    49          * lose precision, so the <= and => operators might accidentally let a float
       
    50          * through.
       
    51          */
    92 
    52 
    93     /**
    53         try {
    94      * At this point, $range is a positive number greater than 0. It might
    54             $min = RandomCompat_intval($min);
    95      * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
    55         } catch (TypeError $ex) {
    96      * a float and we will lose some precision.
    56             throw new TypeError(
    97      */
    57                 'random_int(): $min must be an integer'
    98     $range = $max - $min;
    58             );
       
    59         }
    99 
    60 
   100     /**
    61         try {
   101      * Test for integer overflow:
    62             $max = RandomCompat_intval($max);
   102      */
    63         } catch (TypeError $ex) {
   103     if (!is_int($range)) {
    64             throw new TypeError(
   104 
    65                 'random_int(): $max must be an integer'
   105         /**
       
   106          * Still safely calculate wider ranges.
       
   107          * Provided by @CodesInChaos, @oittaa
       
   108          * 
       
   109          * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435
       
   110          * 
       
   111          * We use ~0 as a mask in this case because it generates all 1s
       
   112          * 
       
   113          * @ref https://eval.in/400356 (32-bit)
       
   114          * @ref http://3v4l.org/XX9r5  (64-bit)
       
   115          */
       
   116         $bytes = PHP_INT_SIZE;
       
   117         $mask = ~0;
       
   118 
       
   119     } else {
       
   120 
       
   121         /**
       
   122          * $bits is effectively ceil(log($range, 2)) without dealing with 
       
   123          * type juggling
       
   124          */
       
   125         while ($range > 0) {
       
   126             if ($bits % 8 === 0) {
       
   127                ++$bytes;
       
   128             }
       
   129             ++$bits;
       
   130             $range >>= 1;
       
   131             $mask = $mask << 1 | 1;
       
   132         }
       
   133         $valueShift = $min;
       
   134     }
       
   135 
       
   136     /**
       
   137      * Now that we have our parameters set up, let's begin generating
       
   138      * random integers until one falls between $min and $max
       
   139      */
       
   140     do {
       
   141         /**
       
   142          * The rejection probability is at most 0.5, so this corresponds
       
   143          * to a failure probability of 2^-128 for a working RNG
       
   144          */
       
   145         if ($attempts > 128) {
       
   146             throw new Exception(
       
   147                 'random_int: RNG is broken - too many rejections'
       
   148             );
    66             );
   149         }
    67         }
   150 
    68 
   151         /**
    69         /**
   152          * Let's grab the necessary number of random bytes
    70          * Now that we've verified our weak typing system has given us an integer,
       
    71          * let's validate the logic then we can move forward with generating random
       
    72          * integers along a given range.
   153          */
    73          */
   154         $randomByteString = random_bytes($bytes);
    74         if ($min > $max) {
   155         if ($randomByteString === false) {
    75             throw new Error(
   156             throw new Exception(
    76                 'Minimum value must be less than or equal to the maximum value'
   157                 'Random number generator failure'
       
   158             );
    77             );
   159         }
    78         }
   160 
    79 
   161         /**
    80         if ($max === $min) {
   162          * Let's turn $randomByteString into an integer
    81             return (int) $min;
   163          * 
       
   164          * This uses bitwise operators (<< and |) to build an integer
       
   165          * out of the values extracted from ord()
       
   166          * 
       
   167          * Example: [9F] | [6D] | [32] | [0C] =>
       
   168          *   159 + 27904 + 3276800 + 201326592 =>
       
   169          *   204631455
       
   170          */
       
   171         $val = 0;
       
   172         for ($i = 0; $i < $bytes; ++$i) {
       
   173             $val |= ord($randomByteString[$i]) << ($i * 8);
       
   174         }
    82         }
   175 
    83 
   176         /**
    84         /**
   177          * Apply mask
    85          * Initialize variables to 0
       
    86          *
       
    87          * We want to store:
       
    88          * $bytes => the number of random bytes we need
       
    89          * $mask => an integer bitmask (for use with the &) operator
       
    90          *          so we can minimize the number of discards
   178          */
    91          */
   179         $val &= $mask;
    92         $attempts = $bits = $bytes = $mask = $valueShift = 0;
   180         $val += $valueShift;
       
   181 
    93 
   182         ++$attempts;
       
   183         /**
    94         /**
   184          * If $val overflows to a floating point number,
    95          * At this point, $range is a positive number greater than 0. It might
   185          * ... or is larger than $max,
    96          * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
   186          * ... or smaller than $min,
    97          * a float and we will lose some precision.
   187          * then try again.
       
   188          */
    98          */
   189     } while (!is_int($val) || $val > $max || $val < $min);
    99         $range = $max - $min;
   190 
   100 
   191     return (int) $val;
   101         /**
       
   102          * Test for integer overflow:
       
   103          */
       
   104         if (!is_int($range)) {
       
   105 
       
   106             /**
       
   107              * Still safely calculate wider ranges.
       
   108              * Provided by @CodesInChaos, @oittaa
       
   109              *
       
   110              * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435
       
   111              *
       
   112              * We use ~0 as a mask in this case because it generates all 1s
       
   113              *
       
   114              * @ref https://eval.in/400356 (32-bit)
       
   115              * @ref http://3v4l.org/XX9r5  (64-bit)
       
   116              */
       
   117             $bytes = PHP_INT_SIZE;
       
   118             $mask = ~0;
       
   119 
       
   120         } else {
       
   121 
       
   122             /**
       
   123              * $bits is effectively ceil(log($range, 2)) without dealing with
       
   124              * type juggling
       
   125              */
       
   126             while ($range > 0) {
       
   127                 if ($bits % 8 === 0) {
       
   128                     ++$bytes;
       
   129                 }
       
   130                 ++$bits;
       
   131                 $range >>= 1;
       
   132                 $mask = $mask << 1 | 1;
       
   133             }
       
   134             $valueShift = $min;
       
   135         }
       
   136 
       
   137         $val = 0;
       
   138         /**
       
   139          * Now that we have our parameters set up, let's begin generating
       
   140          * random integers until one falls between $min and $max
       
   141          */
       
   142         do {
       
   143             /**
       
   144              * The rejection probability is at most 0.5, so this corresponds
       
   145              * to a failure probability of 2^-128 for a working RNG
       
   146              */
       
   147             if ($attempts > 128) {
       
   148                 throw new Exception(
       
   149                     'random_int: RNG is broken - too many rejections'
       
   150                 );
       
   151             }
       
   152 
       
   153             /**
       
   154              * Let's grab the necessary number of random bytes
       
   155              */
       
   156             $randomByteString = random_bytes($bytes);
       
   157 
       
   158             /**
       
   159              * Let's turn $randomByteString into an integer
       
   160              *
       
   161              * This uses bitwise operators (<< and |) to build an integer
       
   162              * out of the values extracted from ord()
       
   163              *
       
   164              * Example: [9F] | [6D] | [32] | [0C] =>
       
   165              *   159 + 27904 + 3276800 + 201326592 =>
       
   166              *   204631455
       
   167              */
       
   168             $val &= 0;
       
   169             for ($i = 0; $i < $bytes; ++$i) {
       
   170                 $val |= ord($randomByteString[$i]) << ($i * 8);
       
   171             }
       
   172 
       
   173             /**
       
   174              * Apply mask
       
   175              */
       
   176             $val &= $mask;
       
   177             $val += $valueShift;
       
   178 
       
   179             ++$attempts;
       
   180             /**
       
   181              * If $val overflows to a floating point number,
       
   182              * ... or is larger than $max,
       
   183              * ... or smaller than $min,
       
   184              * then try again.
       
   185              */
       
   186         } while (!is_int($val) || $val > $max || $val < $min);
       
   187 
       
   188         return (int) $val;
       
   189     }
   192 }
   190 }
   193 endif;