| 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 | /* |
| | 1481 | * Ideally callers would check for the absence of any configured value |
| | 1482 | * before passing `false` but it's commonly called with an inline call |
| | 1483 | * to get the value: e.g. `wp_convert_hr_to_bytes( ini_get( 'post_size_max' ) );` |
| | 1484 | */ |
| | 1485 | if ( false === $value ) { |
| | 1486 | return 0; |
| | 1487 | } |
| | 1488 | |
| | 1489 | /** |
| | 1490 | * Number of bytes in input string; we're only assessing 7-bit ASCII/Unicode characters. |
| | 1491 | * @var int |
| | 1492 | */ |
| | 1493 | $strlen = strlen( $value ); |
| | 1494 | |
| | 1495 | /** |
| | 1496 | * Count (of bytes) represented by value string. |
| | 1497 | * @var int|float |
| | 1498 | */ |
| | 1499 | $scalar = 0; |
| | 1500 | |
| | 1501 | /** |
| | 1502 | * Sign of number represented by input, either positive (1) or negative (-1). |
| | 1503 | * @var int |
| | 1504 | */ |
| | 1505 | $sign = 1; |
| | 1506 | |
| | 1507 | /** |
| | 1508 | * Numeric base of digits; determined by string prefix (e.g. "0x" or "0"). |
| | 1509 | * @var int |
| | 1510 | */ |
| | 1511 | $base = 10; |
| 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; |
| | 1513 | /** |
| | 1514 | * Index into input string as we walk through it and analyze each character. |
| | 1515 | * @var int |
| | 1516 | */ |
| | 1517 | $i = 0; |
| | 1518 | |
| | 1519 | // Trim leading whitespace. |
| | 1520 | for ( ; $i < $strlen; $i++ ) { |
| | 1521 | switch ( $value[ $i ] ) { |
| | 1522 | case ' ': |
| | 1523 | case "\t": |
| | 1524 | case "\r": |
| | 1525 | case "\v": |
| | 1526 | case "\f": |
| | 1527 | break; |
| | 1528 | |
| | 1529 | default: |
| | 1530 | break 2; |
| | 1531 | } |
| | 1532 | } |
| | 1533 | |
| | 1534 | // Handle optional sign indicator. |
| | 1535 | switch ( $value[ $i ] ) { |
| | 1536 | case '+': |
| | 1537 | $i++; |
| | 1538 | break; |
| | 1539 | |
| | 1540 | case '-': |
| | 1541 | $sign = -1; |
| | 1542 | $i++; |
| | 1543 | break; |
| 1451 | | // Deal with large (float) values which run into the maximum integer size. |
| 1452 | | return min( $bytes, PHP_INT_MAX ); |
| | 1546 | // Determine base for digit conversion, if not decimal. |
| | 1547 | $base_a = $i < $strlen ? $value[ $i ] : ''; |
| | 1548 | $base_b = $i + 1 < $strlen ? $value[ $i + 1 ] : ''; |
| | 1549 | |
| | 1550 | if ( $base_a === '0' && ( $base_b === 'x' || $base_b === 'X' ) ) { |
| | 1551 | $base = 16; |
| | 1552 | $i += 2; |
| | 1553 | } else if ( $base_a === '0' && ctype_digit( $base_b ) ) { |
| | 1554 | $base = 8; |
| | 1555 | $i += 1; |
| | 1556 | } |
| | 1557 | |
| | 1558 | // Trim leading zeros. |
| | 1559 | for ( ; $i < $strlen; $i++ ) { |
| | 1560 | if ( '0' !== $value[ $i ] ) { |
| | 1561 | break; |
| | 1562 | } |
| | 1563 | } |
| | 1564 | |
| | 1565 | /** |
| | 1566 | * Numeric values for scanned digits. |
| | 1567 | * @var array |
| | 1568 | */ |
| | 1569 | $digits = [ |
| | 1570 | '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, |
| | 1571 | '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, |
| | 1572 | 'A' => 10, 'a' => 10, 'B' => 11, 'b' => 11, 'C' => 12, |
| | 1573 | 'c' => 12, 'D' => 13, 'd' => 13, 'E' => 14, 'e' => 14, |
| | 1574 | 'F' => 15, 'f' => 15, |
| | 1575 | ]; |
| | 1576 | |
| | 1577 | /* |
| | 1578 | * Build the scalar value by eating the |
| | 1579 | * next sequence of contiguous digits. |
| | 1580 | */ |
| | 1581 | for ( ; $i < $strlen; $i++) { |
| | 1582 | $c = $value[ $i ]; |
| | 1583 | |
| | 1584 | /* |
| | 1585 | * Only digits recognized in this base system can be used. |
| | 1586 | * Once we find an unrecognized digit we abort and move |
| | 1587 | * on to the next step in parsing the size suffix. |
| | 1588 | */ |
| | 1589 | if ( ! isset( $digits[ $c ] ) || $digits[ $c ] >= $base ) { |
| | 1590 | break; |
| | 1591 | } |
| | 1592 | |
| | 1593 | $scalar = $scalar * $base + $digits[ $c ]; |
| | 1594 | } |
| | 1595 | |
| | 1596 | // Clamp the parsed digits to an integer value as PHP does internally. |
| | 1597 | if ( $sign > 0 && $scalar >= PHP_INT_MAX ) { |
| | 1598 | $scalar = PHP_INT_MAX; |
| | 1599 | } else if ( $sign < 0 && $scalar >= -PHP_INT_MIN ) { |
| | 1600 | $scalar = PHP_INT_MIN; |
| | 1601 | } else if ( $sign < 0 ) { |
| | 1602 | $scalar = -$scalar; |
| | 1603 | } |
| | 1604 | |
| | 1605 | /* |
| | 1606 | * Do not use WP constants here (GB_IN_BYTES, MB_IN_BYTES, KB_IN_BYTES) |
| | 1607 | * since they are re-definable; PHP shorthand values are hard-coded |
| | 1608 | * in PHP itself and stay the same regardless of these constants. |
| | 1609 | * |
| | 1610 | * Note that we can overflow here, as happens in PHP itself. |
| | 1611 | * Overflow results will likely not match PHP's value, but |
| | 1612 | * will likely break in most cases anyway and so leaving |
| | 1613 | * this loose is the best we can do until and unless PHP |
| | 1614 | * makes a more concrete choice on how to handle overflow. |
| | 1615 | */ |
| | 1616 | switch ( $value[ $strlen - 1 ] ) { |
| | 1617 | case 'g': |
| | 1618 | case 'G': |
| | 1619 | $scalar *= 1073741824; // 1024^3 |
| | 1620 | break; |
| | 1621 | |
| | 1622 | case 'm': |
| | 1623 | case 'M': |
| | 1624 | $scalar *= 1048576; // 1024^2 |
| | 1625 | break; |
| | 1626 | |
| | 1627 | case 'k': |
| | 1628 | case 'K': |
| | 1629 | $scalar *= 1024; |
| | 1630 | break; |
| | 1631 | } |
| | 1632 | |
| | 1633 | return (int) $scalar; |