| 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. |
| 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 | } |
| 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 | } |
| 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; |