test: Testsuite fuer Kernlogik, QR-Codes, Domains und Sicherheit

232 Tests, unter anderem:
- Slug-Erzeugung, Kollisionsbehandlung und Validierung
- URL-Validierung inklusive IDN, Prozentkodierung und abgelehnter
  Protokolle; Punycode gegengeprueft gegen idn_to_ascii
- QR-Erzeugung in allen Groessen, mit Logo und Level-H-Automatik
- Weiterleitung gegen eine echte SQLite-Datenbank samt Domain-
  Aufloesung ueber den Host-Header
- Update einer bestehenden Installation auf mehrere Domains
- Angriffsseitig: gefaelschte Proxy-Header, Host-Header-Injektion,
  SVG mit Skripten, XXE und externen Referenzen
This commit is contained in:
2026-08-03 18:11:40 +02:00
parent 73a9bb09b1
commit 8a7939bee2
14 changed files with 2228 additions and 0 deletions
+100
View File
@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace App\Tests;
use App\Support\Punycode;
use PHPUnit\Framework\TestCase;
final class PunycodeTest extends TestCase
{
public function testAsciiHostsBleibenUnveraendert(): void
{
foreach (['example.com', 'sub.domain.co.uk', 'intranet', 'xn--mller-kva.de', ''] as $host) {
self::assertSame($host, Punycode::encodeDomain($host));
}
}
public function testIsAscii(): void
{
self::assertTrue(Punycode::isAscii('example.com'));
self::assertTrue(Punycode::isAscii(''));
self::assertFalse(Punycode::isAscii('müller.de'));
self::assertFalse(Punycode::isAscii('日本語.jp'));
}
/**
* @return array<string,array{0:string,1:string}>
*/
public static function domainProvider(): array
{
return [
'muellerde' => ['müller.de', 'xn--mller-kva.de'],
'buecherde' => ['bücher.de', 'xn--bcher-kva.de'],
'muenchende' => ['münchen.de', 'xn--mnchen-3ya.de'],
'nur ein Label' => ['münchen', 'xn--mnchen-3ya'],
'mehrere Umlaute' => ['grüße.example.com', 'xn--gre-6ka8l.example.com'],
'Subdomain umlautfrei' => ['www.müller.de', 'www.xn--mller-kva.de'],
];
}
/**
* @dataProvider domainProvider
*/
public function testEncodeDomain(string $unicode, string $expected): void
{
self::assertSame($expected, Punycode::encodeDomain($unicode));
}
/**
* Die eigene Implementierung muss dasselbe Ergebnis liefern wie die
* Referenz aus ext-intl. Ohne intl entfaellt die Gegenprobe.
*/
public function testStimmtMitIntlUeberein(): void
{
if (!function_exists('idn_to_ascii')) {
self::markTestSkipped('ext-intl ist nicht verfuegbar.');
}
$domains = [
'müller.de',
'bücher.de',
'münchen.de',
'grüße.example.com',
'www.müller.de',
'österreich.at',
'köln.de',
'schöne-grüße.de',
'日本語.jp',
'мосва.рф',
'ελλάδα.gr',
];
foreach ($domains as $domain) {
$reference = idn_to_ascii($domain, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46);
self::assertIsString($reference, $domain);
// Die eigene Implementierung direkt aufrufen, nicht den
// intl-Weg innerhalb von encodeDomain().
self::assertSame($reference, self::encodeWithoutIntl($domain), $domain);
}
}
/**
* Ruft den Rueckfallweg ueber Reflection auf, damit die Gegenprobe
* nicht wieder bei idn_to_ascii landet.
*/
private static function encodeWithoutIntl(string $host): string
{
$method = new \ReflectionMethod(Punycode::class, 'encodeLabel');
$method->setAccessible(true);
$labels = array_map(
static fn (string $label): string => (string) $method->invoke(null, $label),
explode('.', $host),
);
return implode('.', $labels);
}
}