diff --git a/CHANGELOG.md b/CHANGELOG.md index 63ff934..c73969e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 0.1.2 Under development +- fix(security): reject link targets a browser would normalize, and move exception messages to the `Exception\Message` enum. + ## 0.1.1 September 13, 2026 - feat(panel-view): add filterable tables, trace frames, safe links, and SQL text styling. diff --git a/src/Exception/Message.php b/src/Exception/PanelViewMessage.php similarity index 96% rename from src/Exception/Message.php rename to src/Exception/PanelViewMessage.php index bc32e54..a8cf989 100644 --- a/src/Exception/Message.php +++ b/src/Exception/PanelViewMessage.php @@ -9,9 +9,9 @@ /** * Exception message templates authored by this package. * - * Use {@see Message::getMessage()} to format a template with `sprintf()` arguments. + * Use {@see PanelViewMessage::getMessage()} to format a template with `sprintf()` arguments. */ -enum Message: string +enum PanelViewMessage: string { /** * Indicates that a column style is not a {@see \PHPForge\Debug\ColumnStyle} case. diff --git a/src/PanelView.php b/src/PanelView.php index 6cf6145..279bd00 100644 --- a/src/PanelView.php +++ b/src/PanelView.php @@ -6,7 +6,7 @@ use InvalidArgumentException; use JsonSerializable; -use PHPForge\Debug\Exception\Message; +use PHPForge\Debug\Exception\PanelViewMessage; use function count; use function in_array; @@ -491,7 +491,7 @@ public static function trace(array $frames): array foreach ($frames as $frame) { if (is_array($frame) === false) { throw new InvalidArgumentException( - Message::TRACE_FRAME_INVALID->getMessage(), + PanelViewMessage::TRACE_FRAME_INVALID->getMessage(), ); } @@ -560,7 +560,7 @@ private static function headers(array $headers): array foreach ($headers as $header) { if (is_string($header) === false) { throw new InvalidArgumentException( - Message::TABLE_HEADER_INVALID->getMessage(), + PanelViewMessage::TABLE_HEADER_INVALID->getMessage(), ); } @@ -699,7 +699,7 @@ private static function paragraphOf(mixed $paragraph): array if (array_is_list($paragraph) === false) { throw new InvalidArgumentException( - Message::PARAGRAPH_CONTENT_INVALID->getMessage(), + PanelViewMessage::PARAGRAPH_CONTENT_INVALID->getMessage(), ); } @@ -723,7 +723,7 @@ private static function rows(array $rows, int $columns): array foreach ($rows as $row) { if (is_array($row) === false || array_is_list($row) === false || count($row) !== $columns) { throw new InvalidArgumentException( - Message::TABLE_ROW_WIDTH_INVALID->getMessage(), + PanelViewMessage::TABLE_ROW_WIDTH_INVALID->getMessage(), ); } @@ -756,13 +756,13 @@ private static function styles(array $styles, int $columns): array foreach ($styles as $column => $style) { if (is_int($column) === false || $column < 0 || $column >= $columns) { throw new InvalidArgumentException( - Message::COLUMN_STYLE_KEY_INVALID->getMessage(), + PanelViewMessage::COLUMN_STYLE_KEY_INVALID->getMessage(), ); } if ($style instanceof ColumnStyle === false) { throw new InvalidArgumentException( - Message::COLUMN_STYLE_INVALID->getMessage(ColumnStyle::class), + PanelViewMessage::COLUMN_STYLE_INVALID->getMessage(ColumnStyle::class), ); } @@ -789,7 +789,7 @@ private static function target(string $href): string { if (strpbrk($href, "\t\n\r") !== false || trim($href, "\x00..\x20") !== $href) { throw new InvalidArgumentException( - Message::LINK_TARGET_NORMALIZED->getMessage(), + PanelViewMessage::LINK_TARGET_NORMALIZED->getMessage(), ); } @@ -797,7 +797,7 @@ private static function target(string $href): string if ($parts === false) { throw new InvalidArgumentException( - Message::LINK_TARGET_UNPARSABLE->getMessage(), + PanelViewMessage::LINK_TARGET_UNPARSABLE->getMessage(), ); } @@ -805,7 +805,7 @@ private static function target(string $href): string if ($scheme !== null && in_array(strtolower($scheme), ['http', 'https', 'mailto'], true) === false) { throw new InvalidArgumentException( - Message::LINK_TARGET_SCHEME_INVALID->getMessage($scheme), + PanelViewMessage::LINK_TARGET_SCHEME_INVALID->getMessage($scheme), ); } @@ -822,7 +822,7 @@ private static function target(string $href): string private static function unsupportedInline(mixed $value): InvalidArgumentException { return new InvalidArgumentException( - Message::INLINE_CONTENT_INVALID->getMessage(get_debug_type($value)), + PanelViewMessage::INLINE_CONTENT_INVALID->getMessage(get_debug_type($value)), ); } } diff --git a/tests/PanelViewTest.php b/tests/PanelViewTest.php index 2ea0d51..55eef16 100644 --- a/tests/PanelViewTest.php +++ b/tests/PanelViewTest.php @@ -6,7 +6,7 @@ use InvalidArgumentException; use PHPForge\Debug\{ColumnStyle, PanelView, Tone}; -use PHPForge\Debug\Exception\Message; +use PHPForge\Debug\Exception\PanelViewMessage; use PHPForge\Debug\Tests\Provider\{InlineScalarProvider, LinkTargetProvider}; use PHPUnit\Framework\Attributes\DataProviderExternal; use PHPUnit\Framework\TestCase; @@ -263,7 +263,7 @@ public function testThrowInvalidArgumentExceptionForAssociativeParagraph(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - Message::PARAGRAPH_CONTENT_INVALID->getMessage(), + PanelViewMessage::PARAGRAPH_CONTENT_INVALID->getMessage(), ); PanelView::create()->emptyState('Empty', ['first' => 'A']); @@ -274,7 +274,7 @@ public function testThrowInvalidArgumentExceptionForBrowserNormalizedLinkTarget( { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - Message::LINK_TARGET_NORMALIZED->getMessage(), + PanelViewMessage::LINK_TARGET_NORMALIZED->getMessage(), ); PanelView::link( @@ -288,7 +288,7 @@ public function testThrowInvalidArgumentExceptionForExecutableLinkTarget(string { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - Message::LINK_TARGET_SCHEME_INVALID->getMessage($scheme), + PanelViewMessage::LINK_TARGET_SCHEME_INVALID->getMessage($scheme), ); PanelView::link( @@ -301,7 +301,7 @@ public function testThrowInvalidArgumentExceptionForForgedExecutableLink(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - Message::LINK_TARGET_SCHEME_INVALID->getMessage('javascript'), + PanelViewMessage::LINK_TARGET_SCHEME_INVALID->getMessage('javascript'), ); PanelView::create() @@ -319,7 +319,7 @@ public function testThrowInvalidArgumentExceptionForForgedInlineValue(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - Message::INLINE_CONTENT_INVALID->getMessage('array'), + PanelViewMessage::INLINE_CONTENT_INVALID->getMessage('array'), ); PanelView::create()->paragraph(['kind' => 'text']); @@ -329,7 +329,7 @@ public function testThrowInvalidArgumentExceptionForNonArrayTraceFrame(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - Message::TRACE_FRAME_INVALID->getMessage(), + PanelViewMessage::TRACE_FRAME_INVALID->getMessage(), ); PanelView::trace(['not a frame']); @@ -339,7 +339,7 @@ public function testThrowInvalidArgumentExceptionForNonColumnStyle(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - Message::COLUMN_STYLE_INVALID->getMessage(ColumnStyle::class), + PanelViewMessage::COLUMN_STYLE_INVALID->getMessage(ColumnStyle::class), ); PanelView::create()->table(['One'], [['a']], styles: [0 => 'pill']); @@ -349,7 +349,7 @@ public function testThrowInvalidArgumentExceptionForNonInlineObject(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - Message::INLINE_CONTENT_INVALID->getMessage('stdClass'), + PanelViewMessage::INLINE_CONTENT_INVALID->getMessage('stdClass'), ); PanelView::create()->paragraph(new stdClass()); @@ -359,7 +359,7 @@ public function testThrowInvalidArgumentExceptionForNonListRow(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - Message::TABLE_ROW_WIDTH_INVALID->getMessage(), + PanelViewMessage::TABLE_ROW_WIDTH_INVALID->getMessage(), ); PanelView::create()->table(['One'], ['not a row']); @@ -369,7 +369,7 @@ public function testThrowInvalidArgumentExceptionForNonStringHeader(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - Message::TABLE_HEADER_INVALID->getMessage(), + PanelViewMessage::TABLE_HEADER_INVALID->getMessage(), ); PanelView::create()->table([1], [[1]]); @@ -379,7 +379,7 @@ public function testThrowInvalidArgumentExceptionForRowWidthMismatch(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - Message::TABLE_ROW_WIDTH_INVALID->getMessage(), + PanelViewMessage::TABLE_ROW_WIDTH_INVALID->getMessage(), ); PanelView::create()->table(['One'], [[]]); @@ -389,7 +389,7 @@ public function testThrowInvalidArgumentExceptionForUnknownStyledColumn(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - Message::COLUMN_STYLE_KEY_INVALID->getMessage(), + PanelViewMessage::COLUMN_STYLE_KEY_INVALID->getMessage(), ); PanelView::create()->table(['One'], [['a']], styles: [1 => ColumnStyle::PILL]); @@ -399,7 +399,7 @@ public function testThrowInvalidArgumentExceptionForUnparsableLinkTarget(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - Message::LINK_TARGET_UNPARSABLE->getMessage(), + PanelViewMessage::LINK_TARGET_UNPARSABLE->getMessage(), ); PanelView::link(