Files
florian.wilde 649f6ba247 fix: Lauffaehigkeit unter PHP 8.4 und 8.5 herstellen
Auf einem Hetzner-Webhosting mit PHP 8.5 brach die Anwendung beim ersten
Aufruf ab. Ausloeser war session.sid_length: seit PHP 8.4 als veraltet
gemarkt. Die Meldung wurde ausgegeben, damit waren Header raus - und
danach liess sich weder eine ini-Einstellung aendern noch eine Session
starten. Aus einer Randnotiz wurde so ein Totalausfall.

Behoben auf drei Ebenen:

1. Die veralteten Aufrufe sind weg. session.sid_length und
   session.sid_bits_per_character werden nicht mehr gesetzt; die
   Voreinstellungen liefern mit 128 Bit ohnehin genug Entropie.
   Ebenso entfallen curl_close(), imagedestroy() und finfo_close() -
   seit PHP 8.0 bzw. 8.5 wirkungslos und als veraltet gemeldet.

2. public/index.php puffert die Ausgabe von der ersten Zeile an. Viele
   Hoster erzwingen display_errors und lassen sich per ini_set() nicht
   davon abbringen. Gepuffert laesst sich Fremdausgabe vor dem Senden
   verwerfen und ins Fehlerprotokoll umleiten, statt die Antwort zu
   zerstoeren. Das schuetzt auch Binaerdownloads: eine einzelne
   Meldung haette bisher jedes PNG beschaedigt. Session::start()
   meldet ausserdem klar, wenn doch schon Ausgaben raus sind, statt
   eine Kaskade aus Warnungen zu erzeugen.

3. endroid/qr-code von 5.1 auf 6.0 angehoben. Version 5 markiert
   Parameter implizit als nullable, was PHP 8.4 bemaengelt - bei jedem
   erzeugten QR-Code. Version 6.1 waere ebenfalls verfuegbar, verlangt
   aber PHP 8.4+ und wuerde Hoster mit 8.2 oder 8.3 ausschliessen;
   6.0 laeuft ab 8.1.

Getestet wird jetzt gegen PHP 8.3, 8.4 und 8.5, jeweils mit
display_errors=On und error_reporting=E_ALL, damit Deprecations
sichtbar werden statt still im Log zu landen. Alle 232 Tests laufen auf
allen drei Versionen ohne eine einzige Meldung durch. Zusaetzlich ist
das Release-Paket in einer PHP-8.5-Umgebung mit fremdem Webroot
geprueft: Ersteinrichtung, Kurzlinks, Weiterleitung, QR-Codes und
Statistik, ohne Streuausgabe in Seiten oder Downloads.
2026-08-12 18:01:30 +02:00

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
{
// Kein setAccessible(): seit PHP 8.1 wirkungslos, seit 8.5 veraltet.
$method = new \ReflectionMethod(Punycode::class, 'encodeLabel');
$labels = array_map(
static fn (string $label): string => (string) $method->invoke(null, $label),
explode('.', $host),
);
return implode('.', $labels);
}
}