Completed
Pull Request — release-2.1 (#5077)
by Mathias
06:33
created

Subs-Compat.php ➔ random_int()   C

Complexity

Conditions 13
Paths 10

Size

Total Lines 163

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
nc 10
nop 2
dl 0
loc 163
rs 5.2933
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file provides compatibility functions and code for older versions of
5
 * PHP, such as the sha1() function, missing extensions, or 64-bit vs 32-bit
6
 * systems. It is only included for those older versions or when the respective
7
 * extension or function cannot be found.
8
 *
9
 * Simple Machines Forum (SMF)
10
 *
11
 * @package SMF
12
 * @author Simple Machines http://www.simplemachines.org
13
 * @copyright 2018 Simple Machines and individual contributors
14
 * @license http://www.simplemachines.org/about/smf/license.php BSD
15
 *
16
 * @version 2.1 Beta 4
17
 */
18
19
if (!defined('SMF'))
20
	die('No direct access...');
21
22
23
/**
24
 * Define the old SMF sha1 function. Uses mhash if available
25
 * @param string $str The string
26
 * @return string The sha1 hashed version of $str
27
 */
28
function sha1_smf($str)
29
{
30
	// If we have mhash loaded in, use it instead!
31
	if (function_exists('mhash') && defined('MHASH_SHA1'))
32
		return bin2hex(mhash(MHASH_SHA1, $str));
33
34
	$nblk = (strlen($str) + 8 >> 6) + 1;
35
	$blks = array_pad(array(), $nblk * 16, 0);
36
37
	for ($i = 0; $i < strlen($str); $i++)
38
		$blks[$i >> 2] |= ord($str{$i}) << (24 - ($i % 4) * 8);
39
40
	$blks[$i >> 2] |= 0x80 << (24 - ($i % 4) * 8);
41
42
	return sha1_core($blks, strlen($str) * 8);
0 ignored issues
show
Documentation introduced by
$blks is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
43
}
44
45
/**
46
 * This is the core SHA-1 calculation routine, used by sha1().
47
 * @param string $x
48
 * @param int $len
49
 * @return string
50
 */
51
function sha1_core($x, $len)
52
{
53
	@$x[$len >> 5] |= 0x80 << (24 - $len % 32);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
54
	$x[(($len + 64 >> 9) << 4) + 15] = $len;
55
56
	$w = array();
57
	$a = 1732584193;
58
	$b = -271733879;
59
	$c = -1732584194;
60
	$d = 271733878;
61
	$e = -1009589776;
62
63
	for ($i = 0, $n = count($x); $i < $n; $i += 16)
64
	{
65
		$olda = $a;
66
		$oldb = $b;
67
		$oldc = $c;
68
		$oldd = $d;
69
		$olde = $e;
70
71
		for ($j = 0; $j < 80; $j++)
72
		{
73
			if ($j < 16)
74
				$w[$j] = isset($x[$i + $j]) ? $x[$i + $j] : 0;
75
			else
76
				$w[$j] = sha1_rol($w[$j - 3] ^ $w[$j - 8] ^ $w[$j - 14] ^ $w[$j - 16], 1);
77
78
			$t = sha1_rol($a, 5) + sha1_ft($j, $b, $c, $d) + $e + $w[$j] + sha1_kt($j);
79
			$e = $d;
80
			$d = $c;
81
			$c = sha1_rol($b, 30);
82
			$b = $a;
83
			$a = $t;
84
		}
85
86
		$a += $olda;
87
		$b += $oldb;
88
		$c += $oldc;
89
		$d += $oldd;
90
		$e += $olde;
91
	}
92
93
	return sprintf('%08x%08x%08x%08x%08x', $a, $b, $c, $d, $e);
94
}
95
96
/**
97
 * Helper function for the core SHA-1 calculation
98
 * @param int $t
99
 * @param int $b
100
 * @param int $c
101
 * @param int $d
102
 * @return int
103
 */
104
function sha1_ft($t, $b, $c, $d)
105
{
106
	if ($t < 20)
107
		return ($b & $c) | ((~$b) & $d);
108
	if ($t < 40)
109
		return $b ^ $c ^ $d;
110
	if ($t < 60)
111
		return ($b & $c) | ($b & $d) | ($c & $d);
112
113
	return $b ^ $c ^ $d;
114
}
115
116
/**
117
 * Helper function for the core SHA-1 calculation
118
 * @param int $t
119
 * @return int 1518500249, 1859775393, -1894007588 or -899497514 depending on the value of $t
120
 */
121
function sha1_kt($t)
122
{
123
	return $t < 20 ? 1518500249 : ($t < 40 ? 1859775393 : ($t < 60 ? -1894007588 : -899497514));
124
}
125
126
/**
127
 * Helper function for the core SHA-1 calculation
128
 * @param int $num
129
 * @param int $cnt
130
 * @return int
131
 */
132
function sha1_rol($num, $cnt)
133
{
134
	// Unfortunately, PHP uses unsigned 32-bit longs only.  So we have to kludge it a bit.
135
	if ($num & 0x80000000)
136
		$a = ($num >> 1 & 0x7fffffff) >> (31 - $cnt);
137
	else
138
		$a = $num >> (32 - $cnt);
139
140
	return ($num << $cnt) | $a;
141
}
142
143
/**
144
 * Available since: (PHP 5)
145
 * If the optional raw_output is set to TRUE, then the sha1 digest is instead returned in raw binary format with a length of 20,
146
 * otherwise the returned value is a 40-character hexadecimal number.
147
 * @param string $text The text to hash
148
 * @return string The sha1 hash of $text
149
 */
150
function sha1_raw($text)
151
{
152
	return sha1($text, true);
153
}
154
155
/**
156
 * Compatibility function.
157
 * crc32 doesn't work as expected on 64-bit functions - make our own.
158
 * https://php.net/crc32#79567
159
 * @param string $number
160
 * @return string The crc32 polynomial of $number
161
 */
162 View Code Duplication
if (!function_exists('smf_crc32'))
163
{
164
	function smf_crc32($number)
165
	{
166
		$crc = crc32($number);
167
168
		if ($crc & 0x80000000)
169
		{
170
			$crc ^= 0xffffffff;
171
			$crc += 1;
172
			$crc = -$crc;
173
		}
174
175
		return $crc;
176
	}
177
}
178
179
if (!is_callable('random_int')) {
180
    /**
181
     * Random_* Compatibility Library
182
     * for using the new PHP 7 random_* API in PHP 5 projects
183
     *
184
     * The MIT License (MIT)
185
     *
186
     * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
187
     *
188
     * Permission is hereby granted, free of charge, to any person obtaining a copy
189
     * of this software and associated documentation files (the "Software"), to deal
190
     * in the Software without restriction, including without limitation the rights
191
     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
192
     * copies of the Software, and to permit persons to whom the Software is
193
     * furnished to do so, subject to the following conditions:
194
     *
195
     * The above copyright notice and this permission notice shall be included in
196
     * all copies or substantial portions of the Software.
197
     *
198
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
199
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
200
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
201
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
202
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
203
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
204
     * SOFTWARE.
205
     */
206
207
    /**
208
     * Fetch a random integer between $min and $max inclusive
209
     *
210
     * @param int $min
211
     * @param int $max
212
     *
213
     * @throws Exception
214
     *
215
     * @return int
216
     */
217
    function random_int($min, $max)
218
    {
219
        /**
220
         * Type and input logic checks
221
         *
222
         * If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
223
         * (non-inclusive), it will sanely cast it to an int. If you it's equal to
224
         * ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats
225
         * lose precision, so the <= and => operators might accidentally let a float
226
         * through.
227
         */
228
229
        try {
230
            /** @var int $min */
231
            $min = RandomCompat_intval($min);
232
        } catch (TypeError $ex) {
0 ignored issues
show
Bug introduced by
The class TypeError does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
233
            throw new TypeError(
234
                'random_int(): $min must be an integer'
235
            );
236
        }
237
238
        try {
239
            /** @var int $max */
240
            $max = RandomCompat_intval($max);
241
        } catch (TypeError $ex) {
0 ignored issues
show
Bug introduced by
The class TypeError does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
242
            throw new TypeError(
243
                'random_int(): $max must be an integer'
244
            );
245
        }
246
247
        /**
248
         * Now that we've verified our weak typing system has given us an integer,
249
         * let's validate the logic then we can move forward with generating random
250
         * integers along a given range.
251
         */
252
        if ($min > $max) {
253
            throw new Error(
254
                'Minimum value must be less than or equal to the maximum value'
255
            );
256
        }
257
258
        if ($max === $min) {
259
            return (int) $min;
260
        }
261
262
        /**
263
         * Initialize variables to 0
264
         *
265
         * We want to store:
266
         * $bytes => the number of random bytes we need
267
         * $mask => an integer bitmask (for use with the &) operator
268
         *          so we can minimize the number of discards
269
         */
270
        $attempts = $bits = $bytes = $mask = $valueShift = 0;
271
        /** @var int $attempts */
272
        /** @var int $bits */
273
        /** @var int $bytes */
274
        /** @var int $mask */
275
        /** @var int $valueShift */
276
277
        /**
278
         * At this point, $range is a positive number greater than 0. It might
279
         * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
280
         * a float and we will lose some precision.
281
         *
282
         * @var int|float $range
283
         */
284
        $range = $max - $min;
285
286
        /**
287
         * Test for integer overflow:
288
         */
289
        if (!is_int($range)) {
290
291
            /**
292
             * Still safely calculate wider ranges.
293
             * Provided by @CodesInChaos, @oittaa
294
             *
295
             * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435
296
             *
297
             * We use ~0 as a mask in this case because it generates all 1s
298
             *
299
             * @ref https://eval.in/400356 (32-bit)
300
             * @ref http://3v4l.org/XX9r5  (64-bit)
301
             */
302
            $bytes = PHP_INT_SIZE;
303
            /** @var int $mask */
304
            $mask = ~0;
305
306
        } else {
307
308
            /**
309
             * $bits is effectively ceil(log($range, 2)) without dealing with
310
             * type juggling
311
             */
312
            while ($range > 0) {
313
                if ($bits % 8 === 0) {
314
                    ++$bytes;
315
                }
316
                ++$bits;
317
                $range >>= 1;
318
                /** @var int $mask */
319
                $mask = $mask << 1 | 1;
320
            }
321
            $valueShift = $min;
322
        }
323
324
        /** @var int $val */
325
        $val = 0;
326
        /**
327
         * Now that we have our parameters set up, let's begin generating
328
         * random integers until one falls between $min and $max
329
         */
330
        /** @psalm-suppress RedundantCondition */
331
        do {
332
            /**
333
             * The rejection probability is at most 0.5, so this corresponds
334
             * to a failure probability of 2^-128 for a working RNG
335
             */
336
            if ($attempts > 128) {
337
                throw new Exception(
338
                    'random_int: RNG is broken - too many rejections'
339
                );
340
            }
341
342
            /**
343
             * Let's grab the necessary number of random bytes
344
             */
345
            $randomByteString = random_bytes($bytes);
346
347
            /**
348
             * Let's turn $randomByteString into an integer
349
             *
350
             * This uses bitwise operators (<< and |) to build an integer
351
             * out of the values extracted from ord()
352
             *
353
             * Example: [9F] | [6D] | [32] | [0C] =>
354
             *   159 + 27904 + 3276800 + 201326592 =>
355
             *   204631455
356
             */
357
            $val &= 0;
358
            for ($i = 0; $i < $bytes; ++$i) {
359
                $val |= ord($randomByteString[$i]) << ($i * 8);
360
            }
361
            /** @var int $val */
362
363
            /**
364
             * Apply mask
365
             */
366
            $val &= $mask;
367
            $val += $valueShift;
368
369
            ++$attempts;
370
            /**
371
             * If $val overflows to a floating point number,
372
             * ... or is larger than $max,
373
             * ... or smaller than $min,
374
             * then try again.
375
             */
376
        } while (!is_int($val) || $val > $max || $val < $min);
377
378
        return (int) $val;
379
    }
380
}
381
382
383
?>