Make WordPress Core

Changeset 42130


Ignore:
Timestamp:
11/08/2017 11:47:04 AM (6 years ago)
Author:
dd32
Message:

External Libraries: Update Random_Compat from 1.2.1 to 2.0.11.

Notably this fixes PHP7 parse errors of the files and removes the OpenSSL functionality.
Full Changes: https://github.com/paragonie/random_compat/compare/v1.2.1...v2.0.11

Props jrdelarosa.
See #42439.

Location:
trunk/src/wp-includes/random_compat
Files:
1 deleted
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/random_compat/byte_safe_strings.php

    r36886 r42130  
    66 * The MIT License (MIT)
    77 *
    8  * Copyright (c) 2015 Paragon Initiative Enterprises
     8 * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
    99 *
    1010 * Permission is hereby granted, free of charge, to any person obtaining a copy
     
    2727 */
    2828
    29 if (!function_exists('RandomCompat_strlen')) {
     29if (!is_callable('RandomCompat_strlen')) {
    3030    if (
    3131        defined('MB_OVERLOAD_STRING') &&
     
    5252            }
    5353
    54             return mb_strlen($binary_string, '8bit');
     54            return (int) mb_strlen($binary_string, '8bit');
    5555        }
    5656
     
    7474                );
    7575            }
    76             return strlen($binary_string);
     76            return (int) strlen($binary_string);
    7777        }
    7878    }
    7979}
    8080
    81 if (!function_exists('RandomCompat_substr')) {
     81if (!is_callable('RandomCompat_substr')) {
    8282
    8383    if (
     
    119119                 * PHP 5.3, so we have to find the length ourselves.
    120120                 */
    121                 $length = RandomCompat_strlen($length) - $start;
     121                $length = RandomCompat_strlen($binary_string) - $start;
    122122            } elseif (!is_int($length)) {
    123123                throw new TypeError(
     
    126126            }
    127127
    128             return mb_substr($binary_string, $start, $length, '8bit');
     128            // Consistency with PHP's behavior
     129            if ($start === RandomCompat_strlen($binary_string) && $length === 0) {
     130                return '';
     131            }
     132            if ($start > RandomCompat_strlen($binary_string)) {
     133                return '';
     134            }
     135
     136            return (string) mb_substr($binary_string, $start, $length, '8bit');
    129137        }
    130138
     
    165173                }
    166174
    167                 return substr($binary_string, $start, $length);
     175                return (string) substr($binary_string, $start, $length);
    168176            }
    169177
    170             return substr($binary_string, $start);
     178            return (string) substr($binary_string, $start);
    171179        }
    172180    }
  • trunk/src/wp-includes/random_compat/cast_to_int.php

    r36886 r42130  
    66 * The MIT License (MIT)
    77 *
    8  * Copyright (c) 2015 Paragon Initiative Enterprises
     8 * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
    99 *
    1010 * Permission is hereby granted, free of charge, to any person obtaining a copy
     
    2727 */
    2828
    29 if (!function_exists('RandomCompat_intval')) {
     29if (!is_callable('RandomCompat_intval')) {
    3030   
    3131    /**
     
    3939     *
    4040     * @param int|float $number    The number we want to convert to an int
    41      * @param boolean   $fail_open Set to true to not throw an exception
     41     * @param bool      $fail_open Set to true to not throw an exception
    4242     *
    43      * @return int (or float if $fail_open)
     43     * @return float|int
     44     * @psalm-suppress InvalidReturnType
    4445     *
    4546     * @throws TypeError
     
    4748    function RandomCompat_intval($number, $fail_open = false)
    4849    {
    49         if (is_numeric($number)) {
     50        if (is_int($number) || is_float($number)) {
     51            $number += 0;
     52        } elseif (is_numeric($number)) {
    5053            $number += 0;
    5154        }
     
    6164        }
    6265
    63         if (is_int($number) || $fail_open) {
    64             return $number;
     66        if (is_int($number)) {
     67            return (int) $number;
     68        } elseif (!$fail_open) {
     69            throw new TypeError(
     70                'Expected an integer.'
     71            );
    6572        }
    66 
    67         throw new TypeError(
    68             'Expected an integer.'
    69         );
     73        return $number;
    7074    }
    7175}
  • trunk/src/wp-includes/random_compat/error_polyfill.php

    r35365 r42130  
    55 *
    66 * The MIT License (MIT)
    7  * 
    8  * Copyright (c) 2015 Paragon Initiative Enterprises
     7 *
     8 * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
    99 *
    1010 * Permission is hereby granted, free of charge, to any person obtaining a copy
     
    3636
    3737if (!class_exists('TypeError', false)) {
    38     class TypeError extends Error
    39     {
    40        
     38    if (is_subclass_of('Error', 'Exception')) {
     39        class TypeError extends Error
     40        {
     41           
     42        }
     43    } else {
     44        class TypeError extends Exception
     45        {
     46           
     47        }
    4148    }
    4249}
  • trunk/src/wp-includes/random_compat/random.php

    r36886 r42130  
    44 * for using the new PHP 7 random_* API in PHP 5 projects
    55 *
    6  * @version 1.2.1
    7  * @released 2016-02-29
     6 * @version 2.0.10
     7 * @released 2017-03-13
    88 *
    99 * The MIT License (MIT)
    1010 *
    11  * Copyright (c) 2015 Paragon Initiative Enterprises
     11 * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
    1212 *
    1313 * Permission is hereby granted, free of charge, to any person obtaining a copy
     
    3232if (!defined('PHP_VERSION_ID')) {
    3333    // This constant was introduced in PHP 5.2.7
    34     $RandomCompatversion = explode('.', PHP_VERSION);
     34    $RandomCompatversion = array_map('intval', explode('.', PHP_VERSION));
    3535    define(
    3636        'PHP_VERSION_ID',
     
    4242}
    4343
    44 if (PHP_VERSION_ID < 70000) {
    45 
    46     if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
    47         define('RANDOM_COMPAT_READ_BUFFER', 8);
    48     }
    49 
    50     $RandomCompatDIR = dirname(__FILE__);
    51 
    52     require_once $RandomCompatDIR.'/byte_safe_strings.php';
    53     require_once $RandomCompatDIR.'/cast_to_int.php';
    54     require_once $RandomCompatDIR.'/error_polyfill.php';
    55 
    56     if (!function_exists('random_bytes')) {
     44/**
     45 * PHP 7.0.0 and newer have these functions natively.
     46 */
     47if (PHP_VERSION_ID >= 70000) {
     48    return;
     49}
     50
     51if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
     52    define('RANDOM_COMPAT_READ_BUFFER', 8);
     53}
     54
     55$RandomCompatDIR = dirname(__FILE__);
     56
     57require_once $RandomCompatDIR . '/byte_safe_strings.php';
     58require_once $RandomCompatDIR . '/cast_to_int.php';
     59require_once $RandomCompatDIR . '/error_polyfill.php';
     60
     61if (!is_callable('random_bytes')) {
     62    /**
     63     * PHP 5.2.0 - 5.6.x way to implement random_bytes()
     64     *
     65     * We use conditional statements here to define the function in accordance
     66     * to the operating environment. It's a micro-optimization.
     67     *
     68     * In order of preference:
     69     *   1. Use libsodium if available.
     70     *   2. fread() /dev/urandom if available (never on Windows)
     71     *   3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM)
     72     *   4. COM('CAPICOM.Utilities.1')->GetRandom()
     73     *
     74     * See RATIONALE.md for our reasoning behind this particular order
     75     */
     76    if (extension_loaded('libsodium')) {
     77        // See random_bytes_libsodium.php
     78        if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) {
     79            require_once $RandomCompatDIR . '/random_bytes_libsodium.php';
     80        } elseif (method_exists('Sodium', 'randombytes_buf')) {
     81            require_once $RandomCompatDIR . '/random_bytes_libsodium_legacy.php';
     82        }
     83    }
     84
     85    /**
     86     * Reading directly from /dev/urandom:
     87     */
     88    if (DIRECTORY_SEPARATOR === '/') {
     89        // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast
     90        // way to exclude Windows.
     91        $RandomCompatUrandom = true;
     92        $RandomCompat_basedir = ini_get('open_basedir');
     93
     94        if (!empty($RandomCompat_basedir)) {
     95            $RandomCompat_open_basedir = explode(
     96                PATH_SEPARATOR,
     97                strtolower($RandomCompat_basedir)
     98            );
     99            $RandomCompatUrandom = (array() !== array_intersect(
     100                array('/dev', '/dev/', '/dev/urandom'),
     101                $RandomCompat_open_basedir
     102            ));
     103            $RandomCompat_open_basedir = null;
     104        }
     105
     106        if (
     107            !is_callable('random_bytes')
     108            &&
     109            $RandomCompatUrandom
     110            &&
     111            @is_readable('/dev/urandom')
     112        ) {
     113            // Error suppression on is_readable() in case of an open_basedir
     114            // or safe_mode failure. All we care about is whether or not we
     115            // can read it at this point. If the PHP environment is going to
     116            // panic over trying to see if the file can be read in the first
     117            // place, that is not helpful to us here.
     118
     119            // See random_bytes_dev_urandom.php
     120            require_once $RandomCompatDIR . '/random_bytes_dev_urandom.php';
     121        }
     122        // Unset variables after use
     123        $RandomCompat_basedir = null;
     124    } else {
     125        $RandomCompatUrandom = false;
     126    }
     127
     128    /**
     129     * mcrypt_create_iv()
     130     *
     131     * We only want to use mcypt_create_iv() if:
     132     *
     133     * - random_bytes() hasn't already been defined
     134     * - the mcrypt extensions is loaded
     135     * - One of these two conditions is true:
     136     *   - We're on Windows (DIRECTORY_SEPARATOR !== '/')
     137     *   - We're not on Windows and /dev/urandom is readabale
     138     *     (i.e. we're not in a chroot jail)
     139     * - Special case:
     140     *   - If we're not on Windows, but the PHP version is between
     141     *     5.6.10 and 5.6.12, we don't want to use mcrypt. It will
     142     *     hang indefinitely. This is bad.
     143     *   - If we're on Windows, we want to use PHP >= 5.3.7 or else
     144     *     we get insufficient entropy errors.
     145     */
     146    if (
     147        !is_callable('random_bytes')
     148        &&
     149        // Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be.
     150        (DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307)
     151        &&
     152        // Prevent this code from hanging indefinitely on non-Windows;
     153        // see https://bugs.php.net/bug.php?id=69833
     154        (
     155            DIRECTORY_SEPARATOR !== '/' ||
     156            (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613)
     157        )
     158        &&
     159        extension_loaded('mcrypt')
     160    ) {
     161        // See random_bytes_mcrypt.php
     162        require_once $RandomCompatDIR . '/random_bytes_mcrypt.php';
     163    }
     164    $RandomCompatUrandom = null;
     165
     166    /**
     167     * This is a Windows-specific fallback, for when the mcrypt extension
     168     * isn't loaded.
     169     */
     170    if (
     171        !is_callable('random_bytes')
     172        &&
     173        extension_loaded('com_dotnet')
     174        &&
     175        class_exists('COM')
     176    ) {
     177        $RandomCompat_disabled_classes = preg_split(
     178            '#\s*,\s*#',
     179            strtolower(ini_get('disable_classes'))
     180        );
     181
     182        if (!in_array('com', $RandomCompat_disabled_classes)) {
     183            try {
     184                $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1');
     185                if (method_exists($RandomCompatCOMtest, 'GetRandom')) {
     186                    // See random_bytes_com_dotnet.php
     187                    require_once $RandomCompatDIR . '/random_bytes_com_dotnet.php';
     188                }
     189            } catch (com_exception $e) {
     190                // Don't try to use it.
     191            }
     192        }
     193        $RandomCompat_disabled_classes = null;
     194        $RandomCompatCOMtest = null;
     195    }
     196
     197    /**
     198     * throw new Exception
     199     */
     200    if (!is_callable('random_bytes')) {
    57201        /**
    58          * PHP 5.2.0 - 5.6.x way to implement random_bytes()
     202         * We don't have any more options, so let's throw an exception right now
     203         * and hope the developer won't let it fail silently.
    59204         *
    60          * We use conditional statements here to define the function in accordance
    61          * to the operating environment. It's a micro-optimization.
    62          *
    63          * In order of preference:
    64          *   1. Use libsodium if available.
    65          *   2. fread() /dev/urandom if available (never on Windows)
    66          *   3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM)
    67          *   4. COM('CAPICOM.Utilities.1')->GetRandom()
    68          *   5. openssl_random_pseudo_bytes() (absolute last resort)
    69          *
    70          * See ERRATA.md for our reasoning behind this particular order
     205         * @param mixed $length
     206         * @return void
     207         * @throws Exception
    71208         */
    72         if (extension_loaded('libsodium')) {
    73             // See random_bytes_libsodium.php
    74             if (PHP_VERSION_ID >= 50300 && function_exists('\\Sodium\\randombytes_buf')) {
    75                 require_once $RandomCompatDIR.'/random_bytes_libsodium.php';
    76             } elseif (method_exists('Sodium', 'randombytes_buf')) {
    77                 require_once $RandomCompatDIR.'/random_bytes_libsodium_legacy.php';
    78             }
    79         }
    80 
    81         /**
    82          * Reading directly from /dev/urandom:
    83          */
    84         if (DIRECTORY_SEPARATOR === '/') {
    85             // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast
    86             // way to exclude Windows.
    87             $RandomCompatUrandom = true;
    88             $RandomCompat_basedir = ini_get('open_basedir');
    89 
    90             if (!empty($RandomCompat_basedir)) {
    91                 $RandomCompat_open_basedir = explode(
    92                     PATH_SEPARATOR,
    93                     strtolower($RandomCompat_basedir)
    94                 );
    95                 $RandomCompatUrandom = in_array(
    96                     '/dev',
    97                     $RandomCompat_open_basedir
    98                 );
    99                 $RandomCompat_open_basedir = null;
    100             }
    101 
    102             if (
    103                 !function_exists('random_bytes')
    104                 &&
    105                 $RandomCompatUrandom
    106                 &&
    107                 @is_readable('/dev/urandom')
    108             ) {
    109                 // Error suppression on is_readable() in case of an open_basedir
    110                 // or safe_mode failure. All we care about is whether or not we
    111                 // can read it at this point. If the PHP environment is going to
    112                 // panic over trying to see if the file can be read in the first
    113                 // place, that is not helpful to us here.
    114 
    115                 // See random_bytes_dev_urandom.php
    116                 require_once $RandomCompatDIR.'/random_bytes_dev_urandom.php';
    117             }
    118             // Unset variables after use
    119             $RandomCompat_basedir = null;
    120             $RandomCompatUrandom = null;
    121         }
    122 
    123         /**
    124          * mcrypt_create_iv()
    125          */
    126         if (
    127             !function_exists('random_bytes')
    128             &&
    129             PHP_VERSION_ID >= 50307
    130             &&
    131             extension_loaded('mcrypt')
    132         ) {
    133             // Prevent this code from hanging indefinitely on non-Windows;
    134             // see https://bugs.php.net/bug.php?id=69833
    135             if (
    136                 DIRECTORY_SEPARATOR !== '/' ||
    137                 (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613)
    138             ) {
    139                 // See random_bytes_mcrypt.php
    140                 require_once $RandomCompatDIR.'/random_bytes_mcrypt.php';
    141             }
    142         }
    143 
    144         if (
    145             !function_exists('random_bytes')
    146             &&
    147             extension_loaded('com_dotnet')
    148             &&
    149             class_exists('COM')
    150         ) {
    151             $RandomCompat_disabled_classes = preg_split(
    152                 '#\s*,\s*#',
    153                 strtolower(ini_get('disable_classes'))
     209        function random_bytes($length)
     210        {
     211            unset($length); // Suppress "variable not used" warnings.
     212            throw new Exception(
     213                'There is no suitable CSPRNG installed on your system'
    154214            );
    155 
    156             if (!in_array('com', $RandomCompat_disabled_classes)) {
    157                 try {
    158                     $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1');
    159                     if (method_exists($RandomCompatCOMtest, 'GetRandom')) {
    160                         // See random_bytes_com_dotnet.php
    161                         require_once $RandomCompatDIR.'/random_bytes_com_dotnet.php';
    162                     }
    163                 } catch (com_exception $e) {
    164                     // Don't try to use it.
    165                 }
    166             }
    167             $RandomCompat_disabled_classes = null;
    168             $RandomCompatCOMtest = null;
    169         }
    170 
    171         /**
    172          * openssl_random_pseudo_bytes()
    173          */
    174         if (
    175             (
    176                 // Unix-like with PHP >= 5.3.0 or
    177                 (
    178                     DIRECTORY_SEPARATOR === '/'
    179                     &&
    180                     PHP_VERSION_ID >= 50300
    181                 )
    182                 ||
    183                 // Windows with PHP >= 5.4.1
    184                 PHP_VERSION_ID >= 50401
    185             )
    186             &&
    187             !function_exists('random_bytes')
    188             &&
    189             extension_loaded('openssl')
    190         ) {
    191             // See random_bytes_openssl.php
    192             require_once $RandomCompatDIR.'/random_bytes_openssl.php';
    193         }
    194 
    195         /**
    196          * throw new Exception
    197          */
    198         if (!function_exists('random_bytes')) {
    199             /**
    200              * We don't have any more options, so let's throw an exception right now
    201              * and hope the developer won't let it fail silently.
    202              */
    203             function random_bytes($length)
    204             {
    205                 throw new Exception(
    206                     'There is no suitable CSPRNG installed on your system'
    207                 );
    208             }
    209         }
    210     }
    211 
    212     if (!function_exists('random_int')) {
    213         require_once $RandomCompatDIR.'/random_int.php';
    214     }
    215 
    216     $RandomCompatDIR = null;
    217 }
     215        }
     216    }
     217}
     218
     219if (!is_callable('random_int')) {
     220    require_once $RandomCompatDIR . '/random_int.php';
     221}
     222
     223$RandomCompatDIR = null;
  • trunk/src/wp-includes/random_compat/random_bytes_com_dotnet.php

    r36886 r42130  
    66 * The MIT License (MIT)
    77 *
    8  * Copyright (c) 2015 Paragon Initiative Enterprises
     8 * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
    99 *
    1010 * Permission is hereby granted, free of charge, to any person obtaining a copy
     
    2727 */
    2828
    29 /**
    30  * Windows with PHP < 5.3.0 will not have the function
    31  * openssl_random_pseudo_bytes() available, so let's use
    32  * CAPICOM to work around this deficiency.
    33  *
    34  * @param int $bytes
    35  *
    36  * @throws Exception
    37  *
    38  * @return string
    39  */
    40 function random_bytes($bytes)
    41 {
    42     try {
    43         $bytes = RandomCompat_intval($bytes);
    44     } catch (TypeError $ex) {
    45         throw new TypeError(
    46             'random_bytes(): $bytes must be an integer'
     29if (!is_callable('random_bytes')) {
     30    /**
     31     * Windows with PHP < 5.3.0 will not have the function
     32     * openssl_random_pseudo_bytes() available, so let's use
     33     * CAPICOM to work around this deficiency.
     34     *
     35     * @param int $bytes
     36     *
     37     * @throws Exception
     38     *
     39     * @return string
     40     */
     41    function random_bytes($bytes)
     42    {
     43        try {
     44            $bytes = RandomCompat_intval($bytes);
     45        } catch (TypeError $ex) {
     46            throw new TypeError(
     47                'random_bytes(): $bytes must be an integer'
     48            );
     49        }
     50
     51        if ($bytes < 1) {
     52            throw new Error(
     53                'Length must be greater than 0'
     54            );
     55        }
     56
     57        $buf = '';
     58        if (!class_exists('COM')) {
     59            throw new Error(
     60                'COM does not exist'
     61            );
     62        }
     63        $util = new COM('CAPICOM.Utilities.1');
     64        $execCount = 0;
     65
     66        /**
     67         * Let's not let it loop forever. If we run N times and fail to
     68         * get N bytes of random data, then CAPICOM has failed us.
     69         */
     70        do {
     71            $buf .= base64_decode($util->GetRandom($bytes, 0));
     72            if (RandomCompat_strlen($buf) >= $bytes) {
     73                /**
     74                 * Return our random entropy buffer here:
     75                 */
     76                return RandomCompat_substr($buf, 0, $bytes);
     77            }
     78            ++$execCount;
     79        } while ($execCount < $bytes);
     80
     81        /**
     82         * If we reach here, PHP has failed us.
     83         */
     84        throw new Exception(
     85            'Could not gather sufficient random data'
    4786        );
    4887    }
    49 
    50     if ($bytes < 1) {
    51         throw new Error(
    52             'Length must be greater than 0'
    53         );
    54     }
    55 
    56     $buf = '';
    57     $util = new COM('CAPICOM.Utilities.1');
    58     $execCount = 0;
    59 
    60     /**
    61      * Let's not let it loop forever. If we run N times and fail to
    62      * get N bytes of random data, then CAPICOM has failed us.
    63      */
    64     do {
    65         $buf .= base64_decode($util->GetRandom($bytes, 0));
    66         if (RandomCompat_strlen($buf) >= $bytes) {
    67             /**
    68              * Return our random entropy buffer here:
    69              */
    70             return RandomCompat_substr($buf, 0, $bytes);
    71         }
    72         ++$execCount;
    73     } while ($execCount < $bytes);
    74 
    75     /**
    76      * If we reach here, PHP has failed us.
    77      */
    78     throw new Exception(
    79         'Could not gather sufficient random data'
    80     );
    8188}
  • trunk/src/wp-includes/random_compat/random_bytes_dev_urandom.php

    r36886 r42130  
    55 *
    66 * The MIT License (MIT)
    7  * 
    8  * Copyright (c) 2015 Paragon Initiative Enterprises
     7 *
     8 * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
    99 *
    1010 * Permission is hereby granted, free of charge, to any person obtaining a copy
     
    3131}
    3232
    33 /**
    34  * Unless open_basedir is enabled, use /dev/urandom for
    35  * random numbers in accordance with best practices
    36  *
    37  * Why we use /dev/urandom and not /dev/random
    38  * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers
    39  *
    40  * @param int $bytes
    41  *
    42  * @throws Exception
    43  *
    44  * @return string
    45  */
    46 function random_bytes($bytes)
    47 {
    48     static $fp = null;
     33if (!is_callable('random_bytes')) {
    4934    /**
    50      * This block should only be run once
     35     * Unless open_basedir is enabled, use /dev/urandom for
     36     * random numbers in accordance with best practices
     37     *
     38     * Why we use /dev/urandom and not /dev/random
     39     * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers
     40     *
     41     * @param int $bytes
     42     *
     43     * @throws Exception
     44     *
     45     * @return string
    5146     */
    52     if (empty($fp)) {
     47    function random_bytes($bytes)
     48    {
     49        static $fp = null;
    5350        /**
    54          * We use /dev/urandom if it is a char device.
    55          * We never fall back to /dev/random
     51         * This block should only be run once
    5652         */
    57         $fp = fopen('/dev/urandom', 'rb');
    58         if (!empty($fp)) {
    59             $st = fstat($fp);
    60             if (($st['mode'] & 0170000) !== 020000) {
    61                 fclose($fp);
    62                 $fp = false;
     53        if (empty($fp)) {
     54            /**
     55             * We use /dev/urandom if it is a char device.
     56             * We never fall back to /dev/random
     57             */
     58            $fp = fopen('/dev/urandom', 'rb');
     59            if (!empty($fp)) {
     60                $st = fstat($fp);
     61                if (($st['mode'] & 0170000) !== 020000) {
     62                    fclose($fp);
     63                    $fp = false;
     64                }
     65            }
     66
     67            if (!empty($fp)) {
     68                /**
     69                 * stream_set_read_buffer() does not exist in HHVM
     70                 *
     71                 * If we don't set the stream's read buffer to 0, PHP will
     72                 * internally buffer 8192 bytes, which can waste entropy
     73                 *
     74                 * stream_set_read_buffer returns 0 on success
     75                 */
     76                if (is_callable('stream_set_read_buffer')) {
     77                    stream_set_read_buffer($fp, RANDOM_COMPAT_READ_BUFFER);
     78                }
     79                if (is_callable('stream_set_chunk_size')) {
     80                    stream_set_chunk_size($fp, RANDOM_COMPAT_READ_BUFFER);
     81                }
    6382            }
    6483        }
    6584
     85        try {
     86            $bytes = RandomCompat_intval($bytes);
     87        } catch (TypeError $ex) {
     88            throw new TypeError(
     89                'random_bytes(): $bytes must be an integer'
     90            );
     91        }
     92
     93        if ($bytes < 1) {
     94            throw new Error(
     95                'Length must be greater than 0'
     96            );
     97        }
     98
     99        /**
     100         * This if() block only runs if we managed to open a file handle
     101         *
     102         * It does not belong in an else {} block, because the above
     103         * if (empty($fp)) line is logic that should only be run once per
     104         * page load.
     105         */
    66106        if (!empty($fp)) {
    67107            /**
    68              * stream_set_read_buffer() does not exist in HHVM
    69              *
    70              * If we don't set the stream's read buffer to 0, PHP will
    71              * internally buffer 8192 bytes, which can waste entropy
    72              *
    73              * stream_set_read_buffer returns 0 on success
     108             * @var int
    74109             */
    75             if (function_exists('stream_set_read_buffer')) {
    76                 stream_set_read_buffer($fp, RANDOM_COMPAT_READ_BUFFER);
    77             }
    78             if (function_exists('stream_set_chunk_size')) {
    79                 stream_set_chunk_size($fp, RANDOM_COMPAT_READ_BUFFER);
     110            $remaining = $bytes;
     111
     112            /**
     113             * @var string|bool
     114             */
     115            $buf = '';
     116
     117            /**
     118             * We use fread() in a loop to protect against partial reads
     119             */
     120            do {
     121                /**
     122                 * @var string|bool
     123                 */
     124                $read = fread($fp, $remaining);
     125                if (!is_string($read)) {
     126                    if ($read === false) {
     127                        /**
     128                         * We cannot safely read from the file. Exit the
     129                         * do-while loop and trigger the exception condition
     130                         *
     131                         * @var string|bool
     132                         */
     133                        $buf = false;
     134                        break;
     135                    }
     136                }
     137                /**
     138                 * Decrease the number of bytes returned from remaining
     139                 */
     140                $remaining -= RandomCompat_strlen($read);
     141                /**
     142                 * @var string|bool
     143                 */
     144                $buf = $buf . $read;
     145            } while ($remaining > 0);
     146
     147            /**
     148             * Is our result valid?
     149             */
     150            if (is_string($buf)) {
     151                if (RandomCompat_strlen($buf) === $bytes) {
     152                    /**
     153                     * Return our random entropy buffer here:
     154                     */
     155                    return $buf;
     156                }
    80157            }
    81158        }
    82     }
    83159
    84     try {
    85         $bytes = RandomCompat_intval($bytes);
    86     } catch (TypeError $ex) {
    87         throw new TypeError(
    88             'random_bytes(): $bytes must be an integer'
     160        /**
     161         * If we reach here, PHP has failed us.
     162         */
     163        throw new Exception(
     164            'Error reading from source device'
    89165        );
    90166    }
    91 
    92     if ($bytes < 1) {
    93         throw new Error(
    94             'Length must be greater than 0'
    95         );
    96     }
    97 
    98     /**
    99      * This if() block only runs if we managed to open a file handle
    100      *
    101      * It does not belong in an else {} block, because the above
    102      * if (empty($fp)) line is logic that should only be run once per
    103      * page load.
    104      */
    105     if (!empty($fp)) {
    106         $remaining = $bytes;
    107         $buf = '';
    108 
    109         /**
    110          * We use fread() in a loop to protect against partial reads
    111          */
    112         do {
    113             $read = fread($fp, $remaining);
    114             if ($read === false) {
    115                 /**
    116                  * We cannot safely read from the file. Exit the
    117                  * do-while loop and trigger the exception condition
    118                  */
    119                 $buf = false;
    120                 break;
    121             }
    122             /**
    123              * Decrease the number of bytes returned from remaining
    124              */
    125             $remaining -= RandomCompat_strlen($read);
    126             $buf .= $read;
    127         } while ($remaining > 0);
    128        
    129         /**
    130          * Is our result valid?
    131          */
    132         if ($buf !== false) {
    133             if (RandomCompat_strlen($buf) === $bytes) {
    134                 /**
    135                  * Return our random entropy buffer here:
    136                  */
    137                 return $buf;
    138             }
    139         }
    140     }
    141 
    142     /**
    143      * If we reach here, PHP has failed us.
    144      */
    145     throw new Exception(
    146         'Error reading from source device'
    147     );
    148167}
  • trunk/src/wp-includes/random_compat/random_bytes_libsodium.php

    r36886 r42130  
    55 *
    66 * The MIT License (MIT)
    7  * 
    8  * Copyright (c) 2015 Paragon Initiative Enterprises
     7 *
     8 * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
    99 *
    1010 * Permission is hereby granted, free of charge, to any person obtaining a copy
     
    2727 */
    2828
    29 /**
    30  * If the libsodium PHP extension is loaded, we'll use it above any other
    31  * solution.
    32  *
    33  * libsodium-php project:
    34  * @ref https://github.com/jedisct1/libsodium-php
    35  *
    36  * @param int $bytes
    37  *
    38  * @throws Exception
    39  *
    40  * @return string
    41  */
    42 function random_bytes($bytes)
    43 {
    44     try {
    45         $bytes = RandomCompat_intval($bytes);
    46     } catch (TypeError $ex) {
    47         throw new TypeError(
    48             'random_bytes(): $bytes must be an integer'
     29if (!is_callable('random_bytes')) {
     30    /**
     31     * If the libsodium PHP extension is loaded, we'll use it above any other
     32     * solution.
     33     *
     34     * libsodium-php project:
     35     * @ref https://github.com/jedisct1/libsodium-php
     36     *
     37     * @param int $bytes
     38     *
     39     * @throws Exception
     40     *
     41     * @return string
     42     */
     43    function random_bytes($bytes)
     44    {
     45        try {
     46            $bytes = RandomCompat_intval($bytes);
     47        } catch (TypeError $ex) {
     48            throw new TypeError(
     49                'random_bytes(): $bytes must be an integer'
     50            );
     51        }
     52
     53        if ($bytes < 1) {
     54            throw new Error(
     55                'Length must be greater than 0'
     56            );
     57        }
     58
     59        /**
     60         * \Sodium\randombytes_buf() doesn't allow more than 2147483647 bytes to be
     61         * generated in one invocation.
     62         */
     63        if ($bytes > 2147483647) {
     64            $buf = '';
     65            for ($i = 0; $i < $bytes; $i += 1073741824) {
     66                $n = ($bytes - $i) > 1073741824
     67                    ? 1073741824
     68                    : $bytes - $i;
     69                $buf .= \Sodium\randombytes_buf($n);
     70            }
     71        } else {
     72            $buf = \Sodium\randombytes_buf($bytes);
     73        }
     74
     75        if ($buf !== false) {
     76            if (RandomCompat_strlen($buf) === $bytes) {
     77                return $buf;
     78            }
     79        }
     80
     81        /**
     82         * If we reach here, PHP has failed us.
     83         */
     84        throw new Exception(
     85            'Could not gather sufficient random data'
    4986        );
    5087    }
    51 
    52     if ($bytes < 1) {
    53         throw new Error(
    54             'Length must be greater than 0'
    55         );
    56     }
    57 
    58     /**
    59      * \Sodium\randombytes_buf() doesn't allow more than 2147483647 bytes to be
    60      * generated in one invocation.
    61      */
    62     if ($bytes > 2147483647) {
    63         $buf = '';
    64         for ($i = 0; $i < $bytes; $i += 1073741824) {
    65             $n = ($bytes - $i) > 1073741824
    66                 ? 1073741824
    67                 : $bytes - $i;
    68             $buf .= \Sodium\randombytes_buf($n);
    69         }
    70     } else {
    71         $buf = \Sodium\randombytes_buf($bytes);
    72     }
    73 
    74     if ($buf !== false) {
    75         if (RandomCompat_strlen($buf) === $bytes) {
    76             return $buf;
    77         }
    78     }
    79 
    80     /**
    81      * If we reach here, PHP has failed us.
    82      */
    83     throw new Exception(
    84         'Could not gather sufficient random data'
    85     );
    8688}
  • trunk/src/wp-includes/random_compat/random_bytes_libsodium_legacy.php

    r36886 r42130  
    55 *
    66 * The MIT License (MIT)
    7  * 
    8  * Copyright (c) 2015 Paragon Initiative Enterprises
     7 *
     8 * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
    99 *
    1010 * Permission is hereby granted, free of charge, to any person obtaining a copy
     
    2727 */
    2828
    29 /**
    30  * If the libsodium PHP extension is loaded, we'll use it above any other
    31  * solution.
    32  *
    33  * libsodium-php project:
    34  * @ref https://github.com/jedisct1/libsodium-php
    35  *
    36  * @param int $bytes
    37  *
    38  * @throws Exception
    39  *
    40  * @return string
    41  */
    42 function random_bytes($bytes)
    43 {
    44     try {
    45         $bytes = RandomCompat_intval($bytes);
    46     } catch (TypeError $ex) {
    47         throw new TypeError(
    48             'random_bytes(): $bytes must be an integer'
     29if (!is_callable('random_bytes')) {
     30    /**
     31     * If the libsodium PHP extension is loaded, we'll use it above any other
     32     * solution.
     33     *
     34     * libsodium-php project:
     35     * @ref https://github.com/jedisct1/libsodium-php
     36     *
     37     * @param int $bytes
     38     *
     39     * @throws Exception
     40     *
     41     * @return string
     42     */
     43    function random_bytes($bytes)
     44    {
     45        try {
     46            $bytes = RandomCompat_intval($bytes);
     47        } catch (TypeError $ex) {
     48            throw new TypeError(
     49                'random_bytes(): $bytes must be an integer'
     50            );
     51        }
     52
     53        if ($bytes < 1) {
     54            throw new Error(
     55                'Length must be greater than 0'
     56            );
     57        }
     58
     59        /**
     60         * @var string
     61         */
     62        $buf = '';
     63
     64        /**
     65         * \Sodium\randombytes_buf() doesn't allow more than 2147483647 bytes to be
     66         * generated in one invocation.
     67         */
     68        if ($bytes > 2147483647) {
     69            for ($i = 0; $i < $bytes; $i += 1073741824) {
     70                $n = ($bytes - $i) > 1073741824
     71                    ? 1073741824
     72                    : $bytes - $i;
     73                $buf .= Sodium::randombytes_buf((int) $n);
     74            }
     75        } else {
     76            $buf .= Sodium::randombytes_buf((int) $bytes);
     77        }
     78
     79        if (is_string($buf)) {
     80            if (RandomCompat_strlen($buf) === $bytes) {
     81                return $buf;
     82            }
     83        }
     84
     85        /**
     86         * If we reach here, PHP has failed us.
     87         */
     88        throw new Exception(
     89            'Could not gather sufficient random data'
    4990        );
    5091    }
    51 
    52     if ($bytes < 1) {
    53         throw new Error(
    54             'Length must be greater than 0'
    55         );
    56     }
    57 
    58     /**
    59      * \Sodium\randombytes_buf() doesn't allow more than 2147483647 bytes to be
    60      * generated in one invocation.
    61      */
    62     if ($bytes > 2147483647) {
    63         $buf = '';
    64         for ($i = 0; $i < $bytes; $i += 1073741824) {
    65             $n = ($bytes - $i) > 1073741824
    66                 ? 1073741824
    67                 : $bytes - $i;
    68             $buf .= Sodium::randombytes_buf($n);
    69         }
    70     } else {
    71         $buf = Sodium::randombytes_buf($bytes);
    72     }
    73 
    74     if ($buf !== false) {
    75         if (RandomCompat_strlen($buf) === $bytes) {
    76             return $buf;
    77         }
    78     }
    79 
    80     /**
    81      * If we reach here, PHP has failed us.
    82      */
    83     throw new Exception(
    84         'Could not gather sufficient random data'
    85     );
    8692}
  • trunk/src/wp-includes/random_compat/random_bytes_mcrypt.php

    r36886 r42130  
    55 *
    66 * The MIT License (MIT)
    7  * 
    8  * Copyright (c) 2015 Paragon Initiative Enterprises
     7 *
     8 * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
    99 *
    1010 * Permission is hereby granted, free of charge, to any person obtaining a copy
     
    2727 */
    2828
     29if (!is_callable('random_bytes')) {
     30    /**
     31     * Powered by ext/mcrypt (and thankfully NOT libmcrypt)
     32     *
     33     * @ref https://bugs.php.net/bug.php?id=55169
     34     * @ref https://github.com/php/php-src/blob/c568ffe5171d942161fc8dda066bce844bdef676/ext/mcrypt/mcrypt.c#L1321-L1386
     35     *
     36     * @param int $bytes
     37     *
     38     * @throws Exception
     39     *
     40     * @return string
     41     */
     42    function random_bytes($bytes)
     43    {
     44        try {
     45            $bytes = RandomCompat_intval($bytes);
     46        } catch (TypeError $ex) {
     47            throw new TypeError(
     48                'random_bytes(): $bytes must be an integer'
     49            );
     50        }
    2951
    30 /**
    31  * Powered by ext/mcrypt (and thankfully NOT libmcrypt)
    32  *
    33  * @ref https://bugs.php.net/bug.php?id=55169
    34  * @ref https://github.com/php/php-src/blob/c568ffe5171d942161fc8dda066bce844bdef676/ext/mcrypt/mcrypt.c#L1321-L1386
    35  *
    36  * @param int $bytes
    37  *
    38  * @throws Exception
    39  *
    40  * @return string
    41  */
    42 function random_bytes($bytes)
    43 {
    44     try {
    45         $bytes = RandomCompat_intval($bytes);
    46     } catch (TypeError $ex) {
    47         throw new TypeError(
    48             'random_bytes(): $bytes must be an integer'
     52        if ($bytes < 1) {
     53            throw new Error(
     54                'Length must be greater than 0'
     55            );
     56        }
     57
     58        $buf = @mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
     59        if (
     60            $buf !== false
     61            &&
     62            RandomCompat_strlen($buf) === $bytes
     63        ) {
     64            /**
     65             * Return our random entropy buffer here:
     66             */
     67            return $buf;
     68        }
     69
     70        /**
     71         * If we reach here, PHP has failed us.
     72         */
     73        throw new Exception(
     74            'Could not gather sufficient random data'
    4975        );
    5076    }
    51 
    52     if ($bytes < 1) {
    53         throw new Error(
    54             'Length must be greater than 0'
    55         );
    56     }
    57 
    58     $buf = @mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
    59     if (
    60         $buf !== false
    61         &&
    62         RandomCompat_strlen($buf) === $bytes
    63     ) {
    64         /**
    65          * Return our random entropy buffer here:
    66          */
    67         return $buf;
    68     }
    69 
    70     /**
    71      * If we reach here, PHP has failed us.
    72      */
    73     throw new Exception(
    74         'Could not gather sufficient random data'
    75     );
    7677}
  • trunk/src/wp-includes/random_compat/random_int.php

    r36886 r42130  
    11<?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  */
    282
    29 /**
    30  * Fetch a random integer between $min and $max inclusive
    31  *
    32  * @param int $min
    33  * @param int $max
    34  *
    35  * @throws Exception
    36  *
    37  * @return int
    38  */
    39 function random_int($min, $max)
    40 {
     3if (!is_callable('random_int')) {
    414    /**
    42      * Type and input logic checks
    43      *
    44      * If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
    45      * (non-inclusive), it will sanely cast it to an int. If you it's equal to
    46      * ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats
    47      * lose precision, so the <= and => operators might accidentally let a float
    48      * through.
     5     * Random_* Compatibility Library
     6     * for using the new PHP 7 random_* API in PHP 5 projects
     7     *
     8     * The MIT License (MIT)
     9     *
     10     * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
     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.
    4929     */
    50    
    51     try {
    52         $min = RandomCompat_intval($min);
    53     } catch (TypeError $ex) {
    54         throw new TypeError(
    55             'random_int(): $min must be an integer'
    56         );
    57     }
    58 
    59     try {
    60         $max = RandomCompat_intval($max);
    61     } catch (TypeError $ex) {
    62         throw new TypeError(
    63             'random_int(): $max must be an integer'
    64         );
    65     }
    66    
    67     /**
    68      * Now that we've verified our weak typing system has given us an integer,
    69      * let's validate the logic then we can move forward with generating random
    70      * integers along a given range.
    71      */
    72     if ($min > $max) {
    73         throw new Error(
    74             'Minimum value must be less than or equal to the maximum value'
    75         );
    76     }
    77 
    78     if ($max === $min) {
    79         return $min;
    80     }
    8130
    8231    /**
    83      * Initialize variables to 0
    84      *
    85      * We want to store:
    86      * $bytes => the number of random bytes we need
    87      * $mask => an integer bitmask (for use with the &) operator
    88      *          so we can minimize the number of discards
     32     * Fetch a random integer between $min and $max inclusive
     33     *
     34     * @param int $min
     35     * @param int $max
     36     *
     37     * @throws Exception
     38     *
     39     * @return int
    8940     */
    90     $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         */
    9152
    92     /**
    93      * At this point, $range is a positive number greater than 0. It might
    94      * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
    95      * a float and we will lose some precision.
    96      */
    97     $range = $max - $min;
     53        try {
     54            $min = RandomCompat_intval($min);
     55        } catch (TypeError $ex) {
     56            throw new TypeError(
     57                'random_int(): $min must be an integer'
     58            );
     59        }
    9860
    99     /**
    100      * Test for integer overflow:
    101      */
    102     if (!is_int($range)) {
    103 
    104         /**
    105          * Still safely calculate wider ranges.
    106          * Provided by @CodesInChaos, @oittaa
    107          *
    108          * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435
    109          *
    110          * We use ~0 as a mask in this case because it generates all 1s
    111          *
    112          * @ref https://eval.in/400356 (32-bit)
    113          * @ref http://3v4l.org/XX9r5  (64-bit)
    114          */
    115         $bytes = PHP_INT_SIZE;
    116         $mask = ~0;
    117 
    118     } else {
    119 
    120         /**
    121          * $bits is effectively ceil(log($range, 2)) without dealing with
    122          * type juggling
    123          */
    124         while ($range > 0) {
    125             if ($bits % 8 === 0) {
    126                ++$bytes;
    127             }
    128             ++$bits;
    129             $range >>= 1;
    130             $mask = $mask << 1 | 1;
    131         }
    132         $valueShift = $min;
    133     }
    134 
    135     /**
    136      * Now that we have our parameters set up, let's begin generating
    137      * random integers until one falls between $min and $max
    138      */
    139     do {
    140         /**
    141          * The rejection probability is at most 0.5, so this corresponds
    142          * to a failure probability of 2^-128 for a working RNG
    143          */
    144         if ($attempts > 128) {
    145             throw new Exception(
    146                 'random_int: RNG is broken - too many rejections'
     61        try {
     62            $max = RandomCompat_intval($max);
     63        } catch (TypeError $ex) {
     64            throw new TypeError(
     65                'random_int(): $max must be an integer'
    14766            );
    14867        }
    14968
    15069        /**
    151          * 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.
    15273         */
    153         $randomByteString = random_bytes($bytes);
    154         if ($randomByteString === false) {
    155             throw new Exception(
    156                 'Random number generator failure'
     74        if ($min > $max) {
     75            throw new Error(
     76                'Minimum value must be less than or equal to the maximum value'
    15777            );
    15878        }
    15979
    160         /**
    161          * Let's turn $randomByteString into an integer
    162          *
    163          * This uses bitwise operators (<< and |) to build an integer
    164          * out of the values extracted from ord()
    165          *
    166          * Example: [9F] | [6D] | [32] | [0C] =>
    167          *   159 + 27904 + 3276800 + 201326592 =>
    168          *   204631455
    169          */
    170         $val = 0;
    171         for ($i = 0; $i < $bytes; ++$i) {
    172             $val |= ord($randomByteString[$i]) << ($i * 8);
     80        if ($max === $min) {
     81            return (int) $min;
    17382        }
    17483
    17584        /**
    176          * 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
    17791         */
    178         $val &= $mask;
    179         $val += $valueShift;
     92        $attempts = $bits = $bytes = $mask = $valueShift = 0;
    18093
    181         ++$attempts;
    18294        /**
    183          * If $val overflows to a floating point number,
    184          * ... or is larger than $max,
    185          * ... or smaller than $min,
    186          * then try again.
     95         * At this point, $range is a positive number greater than 0. It might
     96         * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
     97         * a float and we will lose some precision.
    18798         */
    188     } while (!is_int($val) || $val > $max || $val < $min);
     99        $range = $max - $min;
    189100
    190     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    }
    191190}
Note: See TracChangeset for help on using the changeset viewer.