Skip to content

Commit b79bd12

Browse files
committed
fix(security): reject link targets a browser would normalize, and move exception messages to the Exception\Message enum.
1 parent 794d827 commit b79bd12

4 files changed

Lines changed: 29 additions & 27 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## 0.1.2 Under development
99

10+
- fix(security): reject link targets a browser would normalize, and move exception messages to the `Exception\Message` enum.
11+
1012
## 0.1.1 September 13, 2026
1113

1214
- feat(panel-view): add filterable tables, trace frames, safe links, and SQL text styling.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
/**
1010
* Exception message templates authored by this package.
1111
*
12-
* Use {@see Message::getMessage()} to format a template with `sprintf()` arguments.
12+
* Use {@see PanelViewPanelViewMessage::getMessage()} to format a template with `sprintf()` arguments.
1313
*/
14-
enum Message: string
14+
enum PanelViewMessage: string
1515
{
1616
/**
1717
* Indicates that a column style is not a {@see \PHPForge\Debug\ColumnStyle} case.

‎src/PanelView.php‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
use InvalidArgumentException;
88
use JsonSerializable;
9-
use PHPForge\Debug\Exception\Message;
9+
use PHPForge\Debug\Exception\PanelViewMessage;
1010

1111
use function count;
1212
use function in_array;
@@ -491,7 +491,7 @@ public static function trace(array $frames): array
491491
foreach ($frames as $frame) {
492492
if (is_array($frame) === false) {
493493
throw new InvalidArgumentException(
494-
Message::TRACE_FRAME_INVALID->getMessage(),
494+
PanelViewMessage::TRACE_FRAME_INVALID->getMessage(),
495495
);
496496
}
497497

@@ -560,7 +560,7 @@ private static function headers(array $headers): array
560560
foreach ($headers as $header) {
561561
if (is_string($header) === false) {
562562
throw new InvalidArgumentException(
563-
Message::TABLE_HEADER_INVALID->getMessage(),
563+
PanelViewMessage::TABLE_HEADER_INVALID->getMessage(),
564564
);
565565
}
566566

@@ -699,7 +699,7 @@ private static function paragraphOf(mixed $paragraph): array
699699

700700
if (array_is_list($paragraph) === false) {
701701
throw new InvalidArgumentException(
702-
Message::PARAGRAPH_CONTENT_INVALID->getMessage(),
702+
PanelViewMessage::PARAGRAPH_CONTENT_INVALID->getMessage(),
703703
);
704704
}
705705

@@ -723,7 +723,7 @@ private static function rows(array $rows, int $columns): array
723723
foreach ($rows as $row) {
724724
if (is_array($row) === false || array_is_list($row) === false || count($row) !== $columns) {
725725
throw new InvalidArgumentException(
726-
Message::TABLE_ROW_WIDTH_INVALID->getMessage(),
726+
PanelViewMessage::TABLE_ROW_WIDTH_INVALID->getMessage(),
727727
);
728728
}
729729

@@ -756,13 +756,13 @@ private static function styles(array $styles, int $columns): array
756756
foreach ($styles as $column => $style) {
757757
if (is_int($column) === false || $column < 0 || $column >= $columns) {
758758
throw new InvalidArgumentException(
759-
Message::COLUMN_STYLE_KEY_INVALID->getMessage(),
759+
PanelViewMessage::COLUMN_STYLE_KEY_INVALID->getMessage(),
760760
);
761761
}
762762

763763
if ($style instanceof ColumnStyle === false) {
764764
throw new InvalidArgumentException(
765-
Message::COLUMN_STYLE_INVALID->getMessage(ColumnStyle::class),
765+
PanelViewMessage::COLUMN_STYLE_INVALID->getMessage(ColumnStyle::class),
766766
);
767767
}
768768

@@ -789,23 +789,23 @@ private static function target(string $href): string
789789
{
790790
if (strpbrk($href, "\t\n\r") !== false || trim($href, "\x00..\x20") !== $href) {
791791
throw new InvalidArgumentException(
792-
Message::LINK_TARGET_NORMALIZED->getMessage(),
792+
PanelViewMessage::LINK_TARGET_NORMALIZED->getMessage(),
793793
);
794794
}
795795

796796
$parts = parse_url($href);
797797

798798
if ($parts === false) {
799799
throw new InvalidArgumentException(
800-
Message::LINK_TARGET_UNPARSABLE->getMessage(),
800+
PanelViewMessage::LINK_TARGET_UNPARSABLE->getMessage(),
801801
);
802802
}
803803

804804
$scheme = $parts['scheme'] ?? null;
805805

806806
if ($scheme !== null && in_array(strtolower($scheme), ['http', 'https', 'mailto'], true) === false) {
807807
throw new InvalidArgumentException(
808-
Message::LINK_TARGET_SCHEME_INVALID->getMessage($scheme),
808+
PanelViewMessage::LINK_TARGET_SCHEME_INVALID->getMessage($scheme),
809809
);
810810
}
811811

@@ -822,7 +822,7 @@ private static function target(string $href): string
822822
private static function unsupportedInline(mixed $value): InvalidArgumentException
823823
{
824824
return new InvalidArgumentException(
825-
Message::INLINE_CONTENT_INVALID->getMessage(get_debug_type($value)),
825+
PanelViewMessage::INLINE_CONTENT_INVALID->getMessage(get_debug_type($value)),
826826
);
827827
}
828828
}

‎tests/PanelViewTest.php‎

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
use InvalidArgumentException;
88
use PHPForge\Debug\{ColumnStyle, PanelView, Tone};
9-
use PHPForge\Debug\Exception\Message;
9+
use PHPForge\Debug\Exception\PanelViewMessage;
1010
use PHPForge\Debug\Tests\Provider\{InlineScalarProvider, LinkTargetProvider};
1111
use PHPUnit\Framework\Attributes\DataProviderExternal;
1212
use PHPUnit\Framework\TestCase;
@@ -263,7 +263,7 @@ public function testThrowInvalidArgumentExceptionForAssociativeParagraph(): void
263263
{
264264
$this->expectException(InvalidArgumentException::class);
265265
$this->expectExceptionMessage(
266-
Message::PARAGRAPH_CONTENT_INVALID->getMessage(),
266+
PanelViewMessage::PARAGRAPH_CONTENT_INVALID->getMessage(),
267267
);
268268

269269
PanelView::create()->emptyState('Empty', ['first' => 'A']);
@@ -274,7 +274,7 @@ public function testThrowInvalidArgumentExceptionForBrowserNormalizedLinkTarget(
274274
{
275275
$this->expectException(InvalidArgumentException::class);
276276
$this->expectExceptionMessage(
277-
Message::LINK_TARGET_NORMALIZED->getMessage(),
277+
PanelViewMessage::LINK_TARGET_NORMALIZED->getMessage(),
278278
);
279279

280280
PanelView::link(
@@ -288,7 +288,7 @@ public function testThrowInvalidArgumentExceptionForExecutableLinkTarget(string
288288
{
289289
$this->expectException(InvalidArgumentException::class);
290290
$this->expectExceptionMessage(
291-
Message::LINK_TARGET_SCHEME_INVALID->getMessage($scheme),
291+
PanelViewMessage::LINK_TARGET_SCHEME_INVALID->getMessage($scheme),
292292
);
293293

294294
PanelView::link(
@@ -301,7 +301,7 @@ public function testThrowInvalidArgumentExceptionForForgedExecutableLink(): void
301301
{
302302
$this->expectException(InvalidArgumentException::class);
303303
$this->expectExceptionMessage(
304-
Message::LINK_TARGET_SCHEME_INVALID->getMessage('javascript'),
304+
PanelViewMessage::LINK_TARGET_SCHEME_INVALID->getMessage('javascript'),
305305
);
306306

307307
PanelView::create()
@@ -319,7 +319,7 @@ public function testThrowInvalidArgumentExceptionForForgedInlineValue(): void
319319
{
320320
$this->expectException(InvalidArgumentException::class);
321321
$this->expectExceptionMessage(
322-
Message::INLINE_CONTENT_INVALID->getMessage('array'),
322+
PanelViewMessage::INLINE_CONTENT_INVALID->getMessage('array'),
323323
);
324324

325325
PanelView::create()->paragraph(['kind' => 'text']);
@@ -329,7 +329,7 @@ public function testThrowInvalidArgumentExceptionForNonArrayTraceFrame(): void
329329
{
330330
$this->expectException(InvalidArgumentException::class);
331331
$this->expectExceptionMessage(
332-
Message::TRACE_FRAME_INVALID->getMessage(),
332+
PanelViewMessage::TRACE_FRAME_INVALID->getMessage(),
333333
);
334334

335335
PanelView::trace(['not a frame']);
@@ -339,7 +339,7 @@ public function testThrowInvalidArgumentExceptionForNonColumnStyle(): void
339339
{
340340
$this->expectException(InvalidArgumentException::class);
341341
$this->expectExceptionMessage(
342-
Message::COLUMN_STYLE_INVALID->getMessage(ColumnStyle::class),
342+
PanelViewMessage::COLUMN_STYLE_INVALID->getMessage(ColumnStyle::class),
343343
);
344344

345345
PanelView::create()->table(['One'], [['a']], styles: [0 => 'pill']);
@@ -349,7 +349,7 @@ public function testThrowInvalidArgumentExceptionForNonInlineObject(): void
349349
{
350350
$this->expectException(InvalidArgumentException::class);
351351
$this->expectExceptionMessage(
352-
Message::INLINE_CONTENT_INVALID->getMessage('stdClass'),
352+
PanelViewMessage::INLINE_CONTENT_INVALID->getMessage('stdClass'),
353353
);
354354

355355
PanelView::create()->paragraph(new stdClass());
@@ -359,7 +359,7 @@ public function testThrowInvalidArgumentExceptionForNonListRow(): void
359359
{
360360
$this->expectException(InvalidArgumentException::class);
361361
$this->expectExceptionMessage(
362-
Message::TABLE_ROW_WIDTH_INVALID->getMessage(),
362+
PanelViewMessage::TABLE_ROW_WIDTH_INVALID->getMessage(),
363363
);
364364

365365
PanelView::create()->table(['One'], ['not a row']);
@@ -369,7 +369,7 @@ public function testThrowInvalidArgumentExceptionForNonStringHeader(): void
369369
{
370370
$this->expectException(InvalidArgumentException::class);
371371
$this->expectExceptionMessage(
372-
Message::TABLE_HEADER_INVALID->getMessage(),
372+
PanelViewMessage::TABLE_HEADER_INVALID->getMessage(),
373373
);
374374

375375
PanelView::create()->table([1], [[1]]);
@@ -379,7 +379,7 @@ public function testThrowInvalidArgumentExceptionForRowWidthMismatch(): void
379379
{
380380
$this->expectException(InvalidArgumentException::class);
381381
$this->expectExceptionMessage(
382-
Message::TABLE_ROW_WIDTH_INVALID->getMessage(),
382+
PanelViewMessage::TABLE_ROW_WIDTH_INVALID->getMessage(),
383383
);
384384

385385
PanelView::create()->table(['One'], [[]]);
@@ -389,7 +389,7 @@ public function testThrowInvalidArgumentExceptionForUnknownStyledColumn(): void
389389
{
390390
$this->expectException(InvalidArgumentException::class);
391391
$this->expectExceptionMessage(
392-
Message::COLUMN_STYLE_KEY_INVALID->getMessage(),
392+
PanelViewMessage::COLUMN_STYLE_KEY_INVALID->getMessage(),
393393
);
394394

395395
PanelView::create()->table(['One'], [['a']], styles: [1 => ColumnStyle::PILL]);
@@ -399,7 +399,7 @@ public function testThrowInvalidArgumentExceptionForUnparsableLinkTarget(): void
399399
{
400400
$this->expectException(InvalidArgumentException::class);
401401
$this->expectExceptionMessage(
402-
Message::LINK_TARGET_UNPARSABLE->getMessage(),
402+
PanelViewMessage::LINK_TARGET_UNPARSABLE->getMessage(),
403403
);
404404

405405
PanelView::link(

0 commit comments

Comments
 (0)