PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / diagnostics / HostServerCounterProbe.php

HostServerCounterProbe.php in 404 Solution trunk, at includes/diagnostics/HostServerCounterProbe.php

109 lines 3.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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