diff --git a/src/Tracy/Dumper.php b/src/Tracy/Dumper.php index 650473160..f1fe4f53e 100644 --- a/src/Tracy/Dumper.php +++ b/src/Tracy/Dumper.php @@ -21,13 +21,17 @@ class Dumper LOCATION = 'location', // show location string? (defaults to 0) OBJECT_EXPORTERS = 'exporters', // custom exporters for objects (defaults to Dumper::$objectexporters) LIVE = 'live', // will be rendered using JavaScript - DEBUGINFO = 'debuginfo'; // use magic method __debugInfo if exists (defaults to false) + DEBUGINFO = 'debuginfo', // use magic method __debugInfo if exists (defaults to false) + KEYS_TO_HIDE = 'keystohide'; // sensitive keys not displayed (defaults to []) const LOCATION_SOURCE = 0b0001, // shows where dump was called LOCATION_LINK = 0b0010, // appends clickable anchor LOCATION_CLASS = 0b0100; // shows where class is defined + const + HIDDEN_VALUE = '*****'; + /** @var array */ public static $terminalColors = [ 'bool' => '1;33', @@ -94,10 +98,12 @@ public static function toHtml($var, array $options = null) self::COLLAPSE_COUNT => 7, self::OBJECT_EXPORTERS => null, self::DEBUGINFO => false, + self::KEYS_TO_HIDE => [], ]; $loc = &$options[self::LOCATION]; $loc = $loc === true ? ~0 : (int) $loc; + $options[self::KEYS_TO_HIDE] = array_flip(array_map('strtolower', $options[self::KEYS_TO_HIDE])); $options[self::OBJECT_EXPORTERS] = (array) $options[self::OBJECT_EXPORTERS] + self::$objectExporters; uksort($options[self::OBJECT_EXPORTERS], function ($a, $b) { return $b === '' || (class_exists($a, false) && is_subclass_of($a, $b)) ? -1 : 1; @@ -215,10 +221,11 @@ private static function dumpArray(&$var, $options, $level) $var[$marker] = true; foreach ($var as $k => &$v) { if ($k !== $marker) { + $hide = is_string($k) && isset($options[self::KEYS_TO_HIDE][strtolower($k)]) ? self::HIDDEN_VALUE : null; $k = is_int($k) || preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . Helpers::escapeHtml(self::encodeString($k, $options[self::TRUNCATE])) . '"'; $out .= ' ' . str_repeat('| ', $level) . '' . '' . $k . ' => ' - . self::dumpVar($v, $options, $level + 1); + . ($hide ? self::dumpString($hide, $options) : self::dumpVar($v, $options, $level + 1)); } } unset($var[$marker]); @@ -271,10 +278,11 @@ private static function dumpObject(&$var, $options, $level) $vis = ' ' . ($k[1] === '*' ? 'protected' : 'private') . ''; $k = substr($k, strrpos($k, "\x00") + 1); } + $hide = is_string($k) && isset($options[self::KEYS_TO_HIDE][strtolower($k)]) ? self::HIDDEN_VALUE : null; $k = is_int($k) || preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . Helpers::escapeHtml(self::encodeString($k, $options[self::TRUNCATE])) . '"'; $out .= ' ' . str_repeat('| ', $level) . '' . '' . $k . "$vis => " - . self::dumpVar($v, $options, $level + 1); + . ($hide ? self::dumpString($hide, $options) : self::dumpVar($v, $options, $level + 1)); } array_pop($list); return $out . ''; @@ -330,8 +338,9 @@ private static function toJson(&$var, $options, $level = 0) $var[$marker] = true; foreach ($var as $k => &$v) { if ($k !== $marker) { + $hide = is_string($k) && isset($options[self::KEYS_TO_HIDE][strtolower($k)]); $k = is_int($k) || preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . self::encodeString($k, $options[self::TRUNCATE]) . '"'; - $res[] = [$k, self::toJson($v, $options, $level + 1)]; + $res[] = [$k, $hide ? self::HIDDEN_VALUE : self::toJson($v, $options, $level + 1)]; } } unset($var[$marker]); @@ -368,8 +377,9 @@ private static function toJson(&$var, $options, $level = 0) $vis = $k[1] === '*' ? 1 : 2; $k = substr($k, strrpos($k, "\x00") + 1); } + $hide = is_string($k) && isset($options[self::KEYS_TO_HIDE][strtolower($k)]); $k = is_int($k) || preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . self::encodeString($k, $options[self::TRUNCATE]) . '"'; - $obj['items'][] = [$k, self::toJson($v, $options, $level + 1), $vis]; + $obj['items'][] = [$k, $hide ? self::HIDDEN_VALUE : self::toJson($v, $options, $level + 1), $vis]; } } return ['object' => $obj['id']]; diff --git a/tests/Tracy/Dumper.keysToHide.phpt b/tests/Tracy/Dumper.keysToHide.phpt new file mode 100644 index 000000000..779351e0f --- /dev/null +++ b/tests/Tracy/Dumper.keysToHide.phpt @@ -0,0 +1,63 @@ + 456, + 'password' => 'secret1', + 'PASSWORD' => 'secret2', + 'Pin' => 'secret3', + 'inner' => [ + 'a' => 123, + 'password' => 'secret4', + 'PASSWORD' => 'secret5', + 'Pin' => 'secret6', + ], +]; + + +Assert::match('stdClass #%a% + a => 456 + password => "*****" (5) + PASSWORD => "*****" (5) + Pin => "*****" (5) + inner => array (4) + | a => 123 + | password => "*****" (5) + | PASSWORD => "*****" (5) + | Pin => "*****" (5) +', Dumper::toText($obj, [Dumper::KEYS_TO_HIDE => ['password', 'PIN']])); + + +Assert::match( + '
',
+	Dumper::toHtml($obj, [Dumper::KEYS_TO_HIDE => ['password', 'pin'], Dumper::LIVE => true])
+);
+
+Assert::same([
+	'01' => [
+		'name' => 'stdClass',
+		'editor' => null,
+		'items' => [
+			['a', 456, 0],
+			['password', '*****', 0],
+			['PASSWORD', '*****', 0],
+			['Pin', '*****', 0],
+			[
+				'inner',
+				[
+					['a', 123],
+					['password', '*****'],
+					['PASSWORD', '*****'],
+					['Pin', '*****'],
+				],
+				0,
+			],
+		],
+	],
+], Dumper::fetchLiveData());