Make WordPress Core

Ticket #55635: 55635.diff

File 55635.diff, 6.6 KB (added by dmsnell, 4 years ago)

Refactors wp_convert_hr_to_bytes() to accurately report values parsed internally in PHP.

  • wp-includes/load.php

    diff --git a/wp-includes/load.php b/wp-includes/load.php
    index ae5ada388d..1a44906c11 100644
    a b function is_ssl() { 
    14251425}
    14261426
    14271427/**
    1428  * Converts a shorthand byte value to an integer byte value.
     1428 * Returns byte size represented in a numeric php.ini directive.
     1429 *
     1430 * php.ini directives may use a string representation of a number of bytes
     1431 * or a "shorthand" byte size to reference larger values. Multiple numeric
     1432 * php.ini directive use these shorthands even when they don't refer bytes.
     1433 *
     1434 * Example:
     1435 *
     1436 *     wp_convert_hr_to_bytes( "1m" ) == 1048576
     1437 *     wp_convert_hr_to_bytes( "2K" ) == 2048 // 2 * 1024
     1438 *     wp_convert_hr_to_bytes( "0.5g" ) == 0
     1439 *     wp_convert_hr_to_bytes( "14.6e-13g" ) == 15032385536 // 14 * 1024^3
     1440 *     wp_convert_hr_to_bytes( "-813k" ) == 0;
     1441 *     wp_convert_hr_to_bytes( "boat" ) == 0;
     1442 *
     1443 *     // This gives an answer, but it's _wrong_ because
     1444 *     // the underlying mechanism in PHP overflowed and
     1445 *     // the real return value depends on whether PHP
     1446 *     // was built with 64-bit support.
     1447 *     wp_convert_hr_to_bytes( "9223372036854775807g" ) == ??
     1448 *
     1449 * Notes:
     1450 *  - Suffix units are case-insensitive and are always determined
     1451 *    by looking at the last character in the input string.
     1452 *  - Suffix units k/m/g report powers of 1024. PHP and the IEC disagree
     1453 *    on the meaning of "kilobyte," "megabyte," and "gigabyte."
     1454 *  - This function will not fail; it stops parsing after finding
     1455 *    the last consecutive digit at the front of the trimmed string.
     1456 *  - Invalid string representations return a value of 0.
     1457 *  - This code mirrors the computation inside PHP and should only
     1458 *    change its output if PHP redefines how it parses numeric php.ini
     1459 *    directive. If you think something is wrong or should be
     1460 *    refactored it probably shouldn't be. Consult `zend_operators.c`
     1461 *    in the PHP-SRC repository for more info; specifically
     1462 *    `zend_atol()` and `_is_numeric_string_ex()`.
     1463 *  - As noted in the PHP documentation, any numeric value that overflows
     1464 *    an integer for the platform on which PHP is built will break.
    14291465 *
    14301466 * @since 2.3.0
    14311467 * @since 4.6.0 Moved from media.php to load.php.
     1468 * @since 6.1.0 Mirrors size calculation in PHP.
    14321469 *
    14331470 * @link https://www.php.net/manual/en/function.ini-get.php
    14341471 * @link https://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes
     1472 * @link https://en.wikipedia.org/wiki/Byte#Multiple-byte_units
    14351473 *
    1436  * @param string $value A (PHP ini) byte value, either shorthand or ordinary.
    1437  * @return int An integer byte value.
     1474 * @param string $value A numeric php.ini directive's byte value,
     1475 *                      either shorthand or ordinary, as returned
     1476 *                      by a call to `ini_get()`.
     1477 * @return int          Parsed numeric value represented by given string.
    14381478 */
    14391479function wp_convert_hr_to_bytes( $value ) {
    1440         $value = strtolower( trim( $value ) );
    1441         $bytes = (int) $value;
     1480        /** @var int Number of bytes in input string; we're only assessing 7-bit ASCII/Unicode characters. */
     1481        $strlen = strlen( $value );
     1482        /** @var int|float Count (of bytes) represented by value string. */
     1483        $scalar = 0;
     1484        /** @var int Sign of number represented by input, either positive (1) or negative (-1). */
     1485        $sign = 1;
     1486        /** @var int Numeric base of digits; determined by string prefix (e.g. "0x" or "0"). */
     1487        $base = 10;
     1488        /** @var int Index into input string as we walk through it and analyze each character. */
     1489        $i = 0;
     1490
     1491        // Trim leading whitespace.
     1492        for ( ; $i < $strlen; $i++ ) {
     1493                switch ( $value[ $i ] ) {
     1494                        case ' ':
     1495                        case '\t':
     1496                        case '\r':
     1497                        case '\v':
     1498                        case '\f':
     1499                                break;
     1500
     1501                        default:
     1502                                break 2;
     1503                }
     1504        }
     1505
     1506        // Handle optional sign indicator.
     1507        switch ( $value[ $i ] ) {
     1508                case '+':
     1509                        $i++;
     1510                        break;
     1511
     1512                case '-':
     1513                        $sign = -1;
     1514                        $i++;
     1515                        break;
     1516        }
    14421517
    1443         if ( false !== strpos( $value, 'g' ) ) {
    1444                 $bytes *= GB_IN_BYTES;
    1445         } elseif ( false !== strpos( $value, 'm' ) ) {
    1446                 $bytes *= MB_IN_BYTES;
    1447         } elseif ( false !== strpos( $value, 'k' ) ) {
    1448                 $bytes *= KB_IN_BYTES;
     1518        // Determine base for digit conversion, if not decimal.
     1519        $base_a = $value[ $i ];
     1520        $base_b = $i + 1 < $strlen ? $value[ $i + 1 ] : '';
     1521
     1522        if ( $base_a === '0' && ( $base_b === 'x' || $base_b === 'X' ) ) {
     1523                $base = 16;
     1524                $i += 2;
     1525        } else if ( $base_a === '0' && ctype_digit( $base_b ) ) {
     1526                $base = 8;
     1527                $i += 1;
     1528        }
     1529
     1530        // Trim leading zeros.
     1531        for ( ; $i < $strlen; $i++ ) {
     1532                if ( '0' !== $value[ $i ] ) {
     1533                        break;
     1534                }
    14491535        }
    14501536
    1451         // Deal with large (float) values which run into the maximum integer size.
    1452         return min( $bytes, PHP_INT_MAX );
     1537        /** @var array Numeric values for scanned digits. */
     1538        $digits = [
     1539                '0' =>  0, '1' =>  1, '2' =>  2, '3' =>  3, '4' =>  4,
     1540                '5' =>  5, '6' =>  6, '7' =>  7, '8' =>  8, '9' =>  9,
     1541                'A' => 10, 'a' => 10, 'B' => 11, 'b' => 11, 'C' => 12,
     1542                'c' => 12, 'D' => 13, 'd' => 13, 'E' => 14, 'e' => 14,
     1543                'F' => 15, 'f' => 15,
     1544        ];
     1545
     1546        // Build the scalar value by eating the
     1547        // next sequence of contiguous digits.
     1548        for ( ; $i < $strlen; $i++) {
     1549                $c = $value[ $i ];
     1550
     1551                // Only digits recognized in this base system can be used.
     1552                // Once we find an unrecognized digit we abort and move
     1553                // on to the next step in parsing the size suffix.
     1554                if ( ! isset( $digits[ $c ] ) || $digits[ $c ] >= $base ) {
     1555                        break;
     1556                }
     1557
     1558                $scalar = $scalar * $base + $digits[ $c ];
     1559        }
     1560
     1561        // Clamp the parsed digits to an integer value as PHP does internally.
     1562        // See `strtol()`/`LONG_MAX` for 32-bit systems and `strtoll()`/`LLONG_MAX` for 64-bit.
     1563        if ( $sign > 0 && $scalar >= PHP_INT_MAX ) {
     1564                $scalar = PHP_INT_MAX;
     1565        } else if ( $sign < 0 && $scalar >= -PHP_INT_MIN ) {
     1566                $scalar = PHP_INT_MIN;
     1567        } else if ( $sign < 0 ) {
     1568                $scalar = -$scalar;
     1569        }
     1570
     1571        // Do not use WP constants here (GB_IN_BYTES, MB_IN_BYTES, KB_IN_BYTES)
     1572        // since they are re-definable; PHP shorthand values are hard-coded
     1573        // in PHP itself and stay the same regardless of these constants.
     1574        //
     1575        // Note that we can overflow here, as happens in PHP itself.
     1576        // Overflow results will likely not match PHP's value, but
     1577        // will likely break in most cases anyway and so leaving
     1578        // this loose is the best we can do until and unless PHP
     1579        // makes a more concrete choice on how to handle overflow.
     1580        //
     1581        // Fallthrough preserved to match structure and behavior in PHP.
     1582        switch ( $value[ $strlen - 1 ] ) {
     1583                case 'g':
     1584                case 'G':
     1585                        $scalar *= 1024;
     1586                        // Fallthrough
     1587
     1588                case 'm':
     1589                case 'M':
     1590                        $scalar *= 1024;
     1591                        // Fallthrough
     1592
     1593                case 'k':
     1594                case 'K':
     1595                        $scalar *= 1024;
     1596                        // Fallthrough
     1597        }
     1598
     1599        return (int) $scalar;
    14531600}
    14541601
     1602
    14551603/**
    14561604 * Determines whether a PHP ini value is changeable at runtime.
    14571605 *