assertSame('admin/users?a=1&b=2', $actual); } public function testCanonicalizeRelativeSortsMultiValueQueryKeys(): void { $actual = BookmarkUrlNormalizer::canonicalizeRelative('admin/users?roles=2&roles=1&search=max'); $this->assertSame('admin/users?roles=1&roles=2&search=max', $actual); } public function testCanonicalizeRelativeRejectsAbsoluteUrl(): void { $actual = BookmarkUrlNormalizer::canonicalizeRelative('https://example.com/admin/users'); $this->assertSame('', $actual); } public function testCanonicalizeRelativeRemovesFragment(): void { $actual = BookmarkUrlNormalizer::canonicalizeRelative('admin/users?a=1#section-2'); $this->assertSame('admin/users?a=1', $actual); } public function testCanonicalizeRequestUriStripsLocaleBaseAndSortsQuery(): void { $actual = BookmarkUrlNormalizer::canonicalizeRequestUri('/de/admin/users?b=2&a=1', '/de/'); $this->assertSame('admin/users?a=1&b=2', $actual); } public function testCanonicalizeRequestUriHandlesAppBaseAndLocale(): void { $actual = BookmarkUrlNormalizer::canonicalizeRequestUri('/core/de/address-book?tenants=b&tenants=a', '/core/de/'); $this->assertSame('address-book?tenants=a&tenants=b', $actual); } public function testCanonicalizeRelativeRejectsDataUri(): void { $this->assertSame('', BookmarkUrlNormalizer::canonicalizeRelative('data:text/html,')); } public function testCanonicalizeRelativeRejectsJavaScriptUri(): void { $this->assertSame('', BookmarkUrlNormalizer::canonicalizeRelative('javascript:alert(1)')); $this->assertSame('', BookmarkUrlNormalizer::canonicalizeRelative('JavaScript:alert(document.cookie)')); } public function testCanonicalizeRelativeRejectsFileUri(): void { $this->assertSame('', BookmarkUrlNormalizer::canonicalizeRelative('file:///etc/passwd')); } public function testCanonicalizeRelativeRejectsVbScriptUri(): void { $this->assertSame('', BookmarkUrlNormalizer::canonicalizeRelative('vbscript:MsgBox("xss")')); } public function testCanonicalizeRelativeRejectsNullBytes(): void { $this->assertSame('', BookmarkUrlNormalizer::canonicalizeRelative("admin/users\x00javascript:alert(1)")); $this->assertSame('', BookmarkUrlNormalizer::canonicalizeRelative("\x00")); } public function testCanonicalizeRelativeRejectsProtocolRelativeUrl(): void { $this->assertSame('', BookmarkUrlNormalizer::canonicalizeRelative('//evil.com/payload')); } public function testCanonicalizeRelativeHandlesDeepPathTraversal(): void { $this->assertSame('', BookmarkUrlNormalizer::canonicalizeRelative('admin/users/../../../../etc/passwd')); } }