| 1 |
<?php |
| 2 |
|
| 3 |
if (!defined('ABSPATH')) { |
| 4 |
exit; |
| 5 |
} |
| 6 |
|
| 7 |
/** |
| 8 |
* Reads provider admission counters exposed to the PHP worker environment. |
| 9 |
* |
| 10 |
* Both PHP server variables and the process environment are checked because |
| 11 |
* LSAPI deployments do not consistently populate the same boundary. A miss |
| 12 |
* preserves the result of each source instead of collapsing it to an empty |
| 13 |
* array that could be mistaken for a healthy zero. |
| 14 |
*/ |
| 15 |
final class ABJ_404_Solution_HostServerCounterProbe { |
| 16 |
|
| 17 |
/** |
| 18 |
* @param array<string, mixed> $environment |
| 19 |
* @return array<string, mixed> |
| 20 |
*/ |
| 21 |
public static function capture(string $pattern, array $environment): array { |
| 22 |
$sources = array('$_SERVER' => $_SERVER); |
| 23 |
if (($environment['status'] ?? '') === 'available' |
| 24 |
&& is_array($environment['values'] ?? null)) { |
| 25 |
$sources['getenv()'] = $environment['values']; |
| 26 |
} |
| 27 |
|
| 28 |
$values = array(); |
| 29 |
$attemptedPaths = array(); |
| 30 |
$matched = false; |
| 31 |
foreach ($sources as $sourceName => $source) { |
| 32 |
$scan = self::scanSource($pattern, $source); |
| 33 |
$values += $scan['values']; |
| 34 |
$attemptedPaths[$sourceName] = $scan['outcome']; |
| 35 |
$matched = $matched || $scan['matched']; |
| 36 |
} |
| 37 |
if (!array_key_exists('getenv()', $attemptedPaths)) { |
| 38 |
$attemptedPaths['getenv()'] = self::environmentOutcome($environment); |
| 39 |
} |
| 40 |
|
| 41 |
ksort($values); |
| 42 |
if ($values !== array()) { |
| 43 |
return array('status' => 'available', 'values' => $values); |
| 44 |
} |
| 45 |
$reason = self::unavailableReason($environment, $matched); |
| 46 |
return array( |
| 47 |
'status' => 'unavailable', |
| 48 |
'reason' => $reason, |
| 49 |
'attempted_paths' => $attemptedPaths, |
| 50 |
'context' => array('php_sapi_name' => php_sapi_name()), |
| 51 |
); |
| 52 |
} |
| 53 |
|
| 54 |
/** |
| 55 |
* @param array<mixed> $source |
| 56 |
* @return array{values: array<string, string>, outcome: string, matched: bool} |
| 57 |
*/ |
| 58 |
private static function scanSource(string $pattern, array $source): array { |
| 59 |
$values = array(); |
| 60 |
$matched = false; |
| 61 |
foreach ($source as $key => $value) { |
| 62 |
if (!is_string($key) || preg_match($pattern, $key) !== 1) { |
| 63 |
continue; |
| 64 |
} |
| 65 |
$matched = true; |
| 66 |
$readable = self::readableScalar($value, 64); |
| 67 |
if ($readable !== null) { |
| 68 |
$values[$key] = $readable; |
| 69 |
} |
| 70 |
} |
| 71 |
$outcome = !$matched |
| 72 |
? 'no_matching_variables' |
| 73 |
: ($values === array() ? 'no_readable_counters' : 'readable_counter_found'); |
| 74 |
return array('values' => $values, 'outcome' => $outcome, 'matched' => $matched); |
| 75 |
} |
| 76 |
|
| 77 |
/** @param array<string, mixed> $environment */ |
| 78 |
private static function environmentOutcome(array $environment): string { |
| 79 |
$reason = $environment['reason'] ?? 'environment_unavailable'; |
| 80 |
return is_scalar($reason) ? (string)$reason : 'environment_unavailable'; |
| 81 |
} |
| 82 |
|
| 83 |
/** @param array<string, mixed> $environment */ |
| 84 |
private static function unavailableReason(array $environment, bool $matched): string { |
| 85 |
if ($matched) { |
| 86 |
return 'no_readable_counters'; |
| 87 |
} |
| 88 |
if (($environment['status'] ?? '') === 'available') { |
| 89 |
return 'no_matching_variables'; |
| 90 |
} |
| 91 |
$reason = $environment['reason'] ?? null; |
| 92 |
return is_string($reason) && $reason !== '' ? $reason : 'environment_unavailable'; |
| 93 |
} |
| 94 |
|
| 95 |
/** @param mixed $value */ |
| 96 |
private static function readableScalar($value, int $maxLength): ?string { |
| 97 |
if (!is_scalar($value)) { |
| 98 |
return null; |
| 99 |
} |
| 100 |
$string = is_bool($value) ? ($value ? '1' : '0') : (string)$value; |
| 101 |
$ascii = preg_replace('/[^\x20-\x7E]/', '?', $string); |
| 102 |
if (!is_string($ascii)) { |
| 103 |
return null; |
| 104 |
} |
| 105 |
$truncated = substr($ascii, 0, $maxLength); |
| 106 |
return is_string($truncated) ? $truncated : null; |
| 107 |
} |
| 108 |
} |
| 109 |
|