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
101 lines
3.1 KiB
PHP
101 lines
3.1 KiB
PHP
<?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);
|
|
}
|
|
}
|