✏️ 正在编辑: xray-profiler-collector-shortcodes.php
路径:
/opt/alt/php-xray/php/profiler/classes/xray-profiler-collector-shortcodes.php
提示:
您可以编辑任何文件(包括二进制文件),但请注意不当修改可能导致文件损坏。
<?php /** * Copyright (с) Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2022 All Rights Reserved * * Licensed under CLOUD LINUX LICENSE AGREEMENT * https://www.cloudlinux.com/legal/ */ namespace XrayProfiler; use Closure; use Exception; use ReflectionClass; use ReflectionException; use ReflectionFunction; use ReflectionObject; if (!class_exists('\XrayProfiler\CollectorShortcode')) { class CollectorShortcode extends Collector { /** * @var int */ private $next_id = 0; /** * @var array<string,array<int>> */ public $tag_id = array(); /** * @var array<string,array<string>> */ private $parsed_handlers = array(); /** * @var self|null */ private static $instance = null; private function __construct() { } private function __clone() { } /** * @return self */ public static function instance() { if (is_null(self::$instance)) { self::$instance = new self(); self::$instance->clean(); } return self::$instance; } /** * @return int */ public function getNextId() { return $this->next_id; } /** * @return int */ public function incrNextId() { return $this->next_id++; } /** * @param string $tag * @param int $id * @return void */ public function setTagId($tag, $id) { $this->tag_id[$tag][] = $id; } /** * @param string $tag * @param bool $replace * @return int */ public function popTagId($tag, $replace = true) { $id = 0; if (array_key_exists($tag, $this->tag_id)) { $ids = $this->tag_id[$tag]; if (!is_null($last = array_pop($ids))) { $id = $last; } if ($replace === true) { $this->tag_id[$tag] = $ids; } } return $id; } /** * @param string $tag * * @return array<string,string>|null */ public function getParsedHandlers($tag) { if (array_key_exists($tag, $this->parsed_handlers)) { return $this->parsed_handlers[$tag]; } return null; } /** * @param string $tag * @param array<string,string> $data * * @return void */ public function setParsedHandlers($tag, $data) { $this->parsed_handlers[$tag] = $data; } /** * @param string $tag * @param int $id * * @return array<string,float|int|string|null>|null * [ * 'shortcode_id' => (int) * 'handler' => (string) * 'name' => (string) * 'plugin' => (string) * 'duration' => (int) * 'attrs_json' => (string) * 'timer_start' => (float) *] */ public function getShortcode($tag, $id) { $data = $this->getData(); if (array_key_exists($tag, $data) && array_key_exists($id, $data[$tag])) { return $data[$tag][$id]; } return null; } /** * @param string $tag * @param int $id * @param array<string,float|int|string|null> $data * * @return void */ public function setShortcode($tag, $id, $data) { $shortcodes = $this->getData(); if (!array_key_exists($tag, $shortcodes)) { $shortcodes[$tag] = array(); } $shortcodes[$tag][$id] = $data; $this->setData($shortcodes); } /** * @param array|string $attr * * @return array<int, array<string|mixed>> */ public function prepareAttributes($attr) { $attrs = array(); if (is_array($attr) && !empty($attr)) { foreach ($attr as $key => $val) { $attrs[] = array( 'type' => 'key', 'key' => $key, 'val' => $this->redactAttributeValue($key, $val), ); } } elseif (is_string($attr) && !empty($attr)) { $attrs[] = array( 'type' => 'string', 'key' => '', 'val' => $this->redactAttributeValue('', $attr), ); } return $attrs; } /** * Benign attribute names that are NEVER redacted, regardless of any * secret-looking substring they may contain (e.g. "author" contains * "auth"). Checked first so the secret-token substring match below can * be unanchored without re-introducing collisions on presentational * attributes. * * @var array<int,string> */ private static $benign_attr_keys = array( 'author', 'id', 'ids', 'src', 'url', 'href', 'link', 'class', 'align', 'size', 'width', 'height', 'title', 'alt', 'name', 'slug', 'type', 'color', 'style', 'target', 'rel', 'caption', 'label', 'tag', 'category', 'date', // Common presentational keys that collide with a secret-token // substring (keyword/keywords -> "key") -- exempt so the substring // match below does not over-redact ordinary shortcode telemetry. 'keyword', 'keywords', ); /** * Secret-looking key tokens. Matched as case-insensitive SUBSTRINGS of * the (non-allowlisted) attribute name, so glued/camelCase secret keys * with no separators (secretkey, accesskey, apitoken, authtoken, * passphrase, passkey, userpass, consumerkey, encryptionkey, hmacsig, * SecretAccessKey, clientsecret, privatekey, ...) are caught alongside * separated variants (api_key, auth_token). Substring matching is * deliberate: under-redacting a glued secret name (data exposure) is * worse than over-redacting a rare presentational name. The benign * allowlist above exempts the common presentational keys that would * otherwise collide (author, keyword, ...). * * @var array<int,string> */ private static $secret_key_tokens = array( 'secret', 'password', 'passwd', 'pass', 'token', 'apikey', 'api_key', 'api', 'key', 'auth', 'credential', 'bearer', 'dsn', 'privatekey', 'accesskey', 'signature', 'sig', ); /** * Redact secret-looking shortcode attribute values before they are * serialized into attrs_json and exported off-box to telemetry. * Attribute names and overall structure are preserved; only values * flagged by a non-allowlisted secret-looking key name (substring * match), an email address, or a credentialed URL are replaced with a * placeholder. Allowlisted presentational keys (id/src/url/author/...) * are always kept. Retained values are capped at 256 chars as a * backstop; non-scalars are left to the JSON encoder. * * @param int|string $key * @param mixed $val * * @return mixed */ private function redactAttributeValue($key, $val) { if (!is_string($val)) { return $val; } $key_str = is_string($key) ? $key : ''; $key_lc = strtolower($key_str); // Benign-key allowlist: presentational attributes are never // redacted, which resolves the author/auth substring collision // without anchoring the secret-token match below. $is_benign = $key_lc !== '' && in_array($key_lc, self::$benign_attr_keys, true); if (!$is_benign && $key_lc !== '') { // Secret-looking key names: substring match so glued/camelCase // keys (secretkey, accesskey, apitoken, passphrase, passkey, // userpass, SecretAccessKey, ...) are caught, not just separated // variants. The benign allowlist above exempts the common // presentational keys that would otherwise collide (keyword/...). foreach (self::$secret_key_tokens as $token) { if (strpos($key_lc, $token) !== false) { return '[REDACTED]'; } } } // Value-shape rules apply even to benign-but-non-allowlisted keys, // catching secrets hiding under an innocuous attribute name. // Email address. if (preg_match('/[^\s@]+@[^\s@]+\.[^\s@]+/', $val)) { return '[REDACTED]'; } // URL embedding credentials (scheme://user:pass@host). if (preg_match('#[a-z][a-z0-9+.-]*://[^/\s:@]+:[^/\s@]+@#i', $val)) { return '[REDACTED]'; } // No bare value-length/entropy redaction: it masks public asset // paths, slug ids and long URLs. Glued secret keys are already // covered by the substring key match above, so a length heuristic // would only re-introduce over-redaction of public URLs/paths. // Backstop: cap retained value length to limit accidental leakage. if (strlen($val) > 256) { return substr($val, 0, 256) . '...[truncated]'; } return $val; } /** * @param string $tag * @param object|callable $fn * * @return array<string,string> */ public function parseHandler($tag, $fn) { if ($cache = $this->getParsedHandlers($tag)) { return $cache; } $handler = ''; $plugin = ''; if ( class_exists('ReflectionClass') && class_exists('ReflectionObject') && class_exists('ReflectionFunction') ) { try { $parse = array( 'path' => '', 'handler' => '', ); if (is_array($fn)) { // Class::method $parse = $this->parseHandlerArray($fn); } elseif (is_object($fn)) { // Object/Closure/Invoke $parse = $this->parseHandlerObject($fn); } elseif (is_string($fn)) { // Function string $parse = $this->parseHandlerString($fn); } $path = $parse['path']; $handler = $parse['handler']; if (!empty($path)) { $plugin = $this->pluginOrThemeName($path); } if (empty($handler)) { xray_profiler_log( E_USER_NOTICE, "Can't parse handler: " . print_r($fn, true), __FILE__, __LINE__ ); } } catch (Exception $e) { $message = "Catch Reflection error Exception: " . $e->getMessage() . ', with handler: ' . print_r($fn, true); xray_profiler_log(E_USER_WARNING, $message, $e->getFile(), $e->getLine()); } } else { xray_profiler_log(E_USER_NOTICE, "Can't parse handler, Reflection doesn't exists", __FILE__, __LINE__); } $result = array( 'handler' => $handler, 'plugin' => $plugin, ); $this->setParsedHandlers($tag, $result); return $result; } /** * @param array<mixed> $fn * * @return array<string,string> * @throws ReflectionException */ public function parseHandlerArray($fn) { $path = ''; $handler = ''; $fn = array_values($fn); if (!empty($fn)) { if (is_object($fn[0])) { $parse = $this->parseHandlerObject($fn[0]); } elseif (is_string($fn[0])) { $parse = $this->parseHandlerString($fn[0]); } if (!empty($parse['handler'])) { $path = $parse['path']; $handler = $parse['handler']; if (array_key_exists(1, $fn) && is_string($fn[1])) { $handler .= '::' . $fn[1]; } } } return array( 'path' => $path, 'handler' => $handler, ); } /** * @param object $fn * * @return array<string,string> * @throws ReflectionException */ public function parseHandlerObject($fn) { $ref = new ReflectionObject($fn); $name = $ref->getName(); $path = ''; $handler = ''; if (!empty($name)) { $handler = $name; $file = $ref->getFileName(); if (!empty($file)) { $path = $file; } if ($name == 'Closure' && $fn instanceof Closure) { $ref = new ReflectionFunction($fn); $name = $ref->getName(); if (!empty($name)) { $file = $ref->getFileName(); if (!empty($file)) { $path = $file; } } } } return array( 'path' => $path, 'handler' => $handler, ); } /** * @param string $fn * * @return array<string,string> */ public function parseHandlerString($fn) { $path = ''; $handler = ''; $class = $fn; $ref = null; if (strpos($fn, '::') !== false) { $parts = explode('::', $fn); $class = array_shift($parts); } if (class_exists($class)) { $ref = new ReflectionClass($class); } elseif (function_exists($class)) { $ref = new ReflectionFunction($class); } if ($ref) { $handler = $fn; $name = $ref->getName(); if (! empty($name)) { $file = $ref->getFileName(); if (! empty($file)) { $path = $file; } } } return array( 'path' => $path, 'handler' => $handler, ); } /** * @param string $path * * @return string */ public function pluginOrThemeName($path) { $name = ''; $pattern = '/wp-content\/(mu-plugins\/|plugins\/|themes\/)(.*?)(\/|.php)/is'; preg_match($pattern, $path, $matches); if (!empty($matches) && array_key_exists(2, $matches)) { if (strpos($matches[1], 'themes') === 0) { $name = 'Theme: '; } $name .= $matches[2]; } return $name; } /** * @param false|string $return * @param string $tag * @param array|string $attr * @param array $m * * @return false|string */ public function preDoShortcodeTagEarly($return, $tag, $attr, $m) { $this->incrNextId(); $id = $this->getNextId(); $this->setTagId($tag, $id); $attrs = $this->prepareAttributes($attr); $parseHandler = $this->parseHandler($tag, $GLOBALS['shortcode_tags'][$tag]); $handler = $parseHandler['handler']; $plugin = $parseHandler['plugin']; if (empty($attrs) || !($attrs_json = json_encode($attrs))) { $attrs_json = 'null'; } $data = [ 'shortcode_id' => $id, 'handler' => empty($handler) ? 'Unknown' : $handler, 'name' => $tag, 'plugin' => empty($plugin) ? 'Unknown' : $plugin, 'duration' => 0, 'attrs_json' => $attrs_json, 'timer_start' => microtime(true), ]; $this->setShortcode($tag, $id, $data); return $return; } /** * @param false|string $return * @param string $tag * @param array|string $attr * @param array $m * * @return false|string */ public function preDoShortcodeTagLate($return, $tag, $attr, $m) { if ($return !== false) { $this->popTagId($tag); } return $return; } /** * @param false|string $output * @param string $tag * @param array|string $attr * @param array $m * * @return false|string */ public function doShortcodeTagLate($output, $tag, $attr, $m) { $timer_end = microtime(true); $id = $this->popTagId($tag); $shortcode = $this->getShortcode($tag, $id); if (empty($shortcode)) { xray_profiler_log( E_USER_NOTICE, 'Can\'t find shortcode ' . $tag . ' id: ' . $id . ' in ' . __METHOD__, __FILE__, __LINE__ ); } else { $timer_start = floatval($shortcode['timer_start']); $diff = $timer_end - $timer_start; $duration = number_format($diff * 1000000, 0, '', ''); $shortcode['duration'] = $duration; unset($shortcode['timer_start']); $tag = $shortcode['name'] . ''; $this->setShortcode($tag, $id, $shortcode); } return $output; } /** * @return array */ public function getXrayData() { $shortcodes = $this->data; $items = array(); foreach ($shortcodes as $iterations) { $items = array_merge($items, $iterations); } usort($items, function ($a, $b) { if ($a['duration'] == $b['duration']) { return 0; } return ($a['duration'] < $b['duration']) ? 1 : -1; }); foreach ($items as &$item) { if (array_key_exists('timer_start', $item)) { unset($item['timer_start']); } } return array_slice($items, 0, 20); } /** * @return $this */ public function clean() { $this->data = array(); $this->next_id = 0; $this->tag_id = array(); $this->parsed_handlers = array(); return $this; } } }
💾 保存文件
← 返回文件管理器