Navigation
 Startseite
 Fachbücher
 Anzeigenmarkt
 Forum
 Webmaster News
 Script Newsletter
 Push Meldungen
 Kontakt
 Script Installation
 Php
 Php Tutorials
 Lernpfade
 Webhoster Vergleich
 Impressum

Community-Bereich
 kostenlos Registrieren
 Anmelden
 Benutzerliste

Script Datenbank
 Script Archiv
 Script Top 20
 Screenshots
 Testberichte

Suche
 

Unsere Php Scripts
 Counter Script
 Umfrage Script
 Bilder Upload Script
 Terminverwaltung
 Simple PHP Forum
 RSS Grabber

Tools und Generatoren
 .htpasswd Generator
 md5 Generator
 base64 Generator
 Markdown to HTML
 Colorpicker
 Unix timestamp Tool
 Unit Test Generator
 TLD Liste
 Webkatalog‑Verzeichnis

Artfiles.de
Bietet Serviceorientierte Internetdienstleistungen...
https://www.Artfiles.de
Hosterplus.de
Bekommen Sie Speicherplatz (Webspace), Domains und...
https://www.Hosterplus.de
 
 
 

PHP idn_to_ascii(): Umlautdomains in Punycode umwandeln

Sie befinden sich: Home > Php Tutorial > PHP idn_to_ascii(): Umlautd...

PHP idn_to_ascii(): Umlautdomains in Punycode umwandeln
Veröffentlicht am:
04.11.2026
Hits / Besucher:
9
Sprache:
  Deutsch
Tutorial Art:
eigenes
Eingetragen von:
Merkliste:
 
Beschreibung

Eine Kundin hat die Domain bäckerei-mayer.de bestellt. Im Hosting-Panel steht dafür xn--bckerei-mayer-bfb.de, und das Monitoring-Skript meldet seit Tagen, die Domain gebe es nicht. Beides hat dieselbe Ursache: Das DNS kennt keine Umlaute, und PHP fragt nur dann richtig nach, wenn der Name vorher umgewandelt wurde. Wie man eine E-Mail-Adresse mit Umlautdomain prüft, zeigt das Tutorial zu filter_validate_email. Hier geht es um die Umwandlung selbst, also darum, was PHP idn_to_ascii() mit einem Domainnamen macht, welche Optionen das Ergebnis verändern und wann die Funktion false liefert.

Illustration zum Tutorial: PHP idn_to_ascii(): Umlautdomains in Punycode umwandeln

Alle Beispiele zu PHP idn_to_ascii() liefen unter Windows und Linux mit PHP 8.4. Nur bei einer Domain mit ß kamen auf den beiden Systemen verschiedene Ergebnisse heraus.

Was PHP idn_to_ascii() mit einer IDN macht

Internationalisierte Domainnamen, kurz IDN, dürfen Umlaute, Akzente oder ganz andere Schriften enthalten. Hostnamen im DNS bestehen aber nur aus Buchstaben von a bis z, Ziffern und Bindestrichen. Damit beide Welten zusammenpassen, kodiert Punycode jeden Teil des Namens mit Sonderzeichen als ASCII-Folge und setzt das Präfix xn-- davor. Aus müller.de wird so xn--mller-kva.de. Ein solcher Teil zwischen zwei Punkten heißt Label, und PHP idn_to_ascii() behandelt jedes Label für sich. Vorher wird der Name nach dem Standard UTS #46 aufbereitet: Großbuchstaben werden klein, Sonderformen wie vollbreite Zeichen oder ein zerlegter Umlaut werden vereinheitlicht. Wie diese Normalisierung bei gewöhnlichen Texten funktioniert, erklärt das Tutorial zum PHP Normalizer.

Der Weg eines Namens durch die Funktion sieht so aus:

flowchart TD
    A[Domain in UTF-8] --> B[klein, normalisiert]
    B --> C[Zerlegen in Labels am Punkt]
    C --> D{Label nur ASCII?}
    D -->|ja| E[Label bleibt stehen]
    D -->|nein| F["Punycode mit Präfix xn--"]
    E --> G{Regel oder Länge verletzt?}
    F --> G
    G -->|ja| H[false, Grund in idna_info]
    G -->|nein| I[Labels wieder zusammensetzen]

Die Signatur im Original: Parameter und Rückgabewert

Beide Richtungen haben dieselben Parameter:

idn_to_ascii(

string $domain,
int $flags = IDNA_DEFAULT,
int $variant = INTL_IDNA_VARIANT_UTS46,
array &$idna_info = null
): string|false

idn_to_utf8(string $domain, ...): string|false

/* Converts a domain name from Unicode to IDNA ASCII.
domain - a unicode domain name encoded in
UTF-8, the domain name to convert
flags - IDNA_* options, combined with |
variant - only INTL_IDNA_VARIANT_UTS46 can be
used; the IDNA 2003 variant was
deprecated and is removed
idna_info - array with result,
isTransitionalDifferent and errors
return - the domain name in the IDNA
ASCII-compatible format (punycode),
or false on error.
This function is part of the intl module, the
package has to be installed and enabled. */

Bei einem Fehler gibt die Funktion nur false zurück, ohne Warnung und ohne Eintrag in error_get_last(). Den Grund erfährt nur, wer das Array $idna_info als vierten Parameter übergibt. Die alte Variante INTL_IDNA_VARIANT_2003 gibt es in PHP 8 nicht mehr, im Test war die Konstante gar nicht definiert.

Von Unicode zu ASCII und zurück mit idn_to_utf8()

Für die lesbare Form ist idn_to_utf8() zuständig. Das folgende Beispiel zeigt beide Richtungen mit den Ausgaben aus dem Test:

<?php

echo idn_to_ascii('müller.de'), PHP_EOL;
/* xn--mller-kva.de */

echo idn_to_ascii('shop.müller-bäckerei.de'), PHP_EOL;
/* shop.xn--mller-bckerei-hfb76a.de */

echo idn_to_ascii('MÜLLER.DE'), PHP_EOL;
/* xn--mller-kva.de */

echo idn_to_utf8('xn--mller-kva.de');
/* müller.de */

Das Label shop bleibt stehen, nur der Teil mit Umlauten wird kodiert. Großbuchstaben verschwinden, auch bei reinen ASCII-Namen: Aus EXAMPLE.COM wurde example.com. Ein Punkt am Ende bleibt erhalten.

Ob eine Umwandlung überhaupt nötig ist, lässt sich vorher prüfen. Ein regulärer Ausdruck wie preg_match('/[^\x00-\x7F]/', $domain) findet Zeichen außerhalb von ASCII. Nötig ist die Prüfung aber nicht. PHP idn_to_ascii() ließ im Test eine reine ASCII-Domain und eine schon umgewandelte Domain unverändert, abgesehen von der Kleinschreibung. Bei einer gültigen Domain schadet ein zusätzlicher Aufruf also nie, und das Skript wird einfacher, wenn es jede Domain umwandelt.

Voraussetzung: die Erweiterung intl

Die Funktion gehört zur Erweiterung intl, die auf der ICU-Bibliothek aufbaut. Fehlt sie, bricht das Skript mit Fatal error: Uncaught Error: Call to undefined function idn_to_ascii() ab. Die Meldung nennt die Erweiterung nicht, was die Suche erschwert. Unter Windows lag die Datei php_intl.dll im Test schon bei, in der php.ini stand die Zeile aber auskommentiert als ;extension=intl. Das Semikolon entfernen und den Webserver neu starten, etwa Apache unter XAMPP. Auf Ubuntu und Debian kommt die Erweiterung über das Paket php-intl. Im offiziellen Docker-Image genügt docker-php-ext-install intl allein nicht: Im Image php:8.4-cli brach der Bau mit Package requirements (icu-uc >= 50.1 icu-io icu-i18n) were not met ab. Vorher gehören die ICU-Entwicklerdateien ins Image, im Container oder in einem RUN-Schritt des Dockerfiles:

apt-get update

apt-get install -y libicu-dev
docker-php-ext-install intl

Danach war intl aktiv, mit ICU 76.1. Im Skript selbst prüft eine Zeile, ob die Funktion bereitsteht:

<?php

if (!function_exists('idn_to_ascii')) {
exit('Die Erweiterung intl fehlt.');
}

Der Fall straße.de: transitional oder nontransitional

Beim ß gibt es zwei Lesarten. Die ältere, transitional genannt, macht daraus ss, wie es die erste Fassung des IDN-Standards vorsah. Die neuere, nontransitional, behält das ß und kodiert es. Welche davon PHP idn_to_ascii() ohne Optionen wählt, hängt nicht an PHP, sondern an der ICU-Version, mit der PHP gebaut wurde:

Aufruf ICU 75.1 (Windows) ICU 76.1 (Linux)
Wert von IDNA_DEFAULT 0 48
straße.de ohne Option strasse.de xn--strae-oqa.de
straße.de mit IDNA_NONTRANSITIONAL_TO_ASCII xn--strae-oqa.de xn--strae-oqa.de
idn_to_utf8() auf straße.de ohne Option strasse.de straße.de
großes Eszett ẞ.de ohne Option ss.de xn--zca.de

Die Erklärung steht in der Header-Datei der ICU-Bibliothek: Seit ICU 76 bedeutet die Vorgabe nontransitional, weil Unicode die Lesart transitional abgekündigt hat. Davor setzte IDNA_DEFAULT gar keine Option. Derselbe PHP-Code liefert also nach einem Serverumzug andere Werte, und ein Abgleich mit alten Datensätzen geht schief. In $idna_info['isTransitionalDifferent'] stand in allen diesen Fällen true, ein Hinweis, dass die beiden Lesarten hier auseinandergehen.

<?php

echo INTL_ICU_VERSION, ' / ', IDNA_DEFAULT, PHP_EOL;
/* 75.1 / 0 oder 76.1 / 48 */

echo idn_to_ascii('straße.de'), PHP_EOL;
/* ICU 75.1: strasse.de
ICU 76.1: xn--strae-oqa.de */

const IDN_OPT = IDNA_NONTRANSITIONAL_TO_ASCII;

echo idn_to_ascii('straße.de', IDN_OPT);
/* überall: xn--strae-oqa.de */

Welche ICU-Version der eigene Server mitbringt, verrät INTL_ICU_VERSION. Die Option gehört unabhängig davon immer ausdrücklich in den Aufruf, für die Gegenrichtung entsprechend IDNA_NONTRANSITIONAL_TO_UNICODE. Dann rechnet jedes System gleich.

Strenger prüfen mit IDNA_USE_STD3_RULES und Co.

Ohne weitere Optionen ist die Funktion erstaunlich großzügig. Die übrigen Schalter lassen sich mit | kombinieren und haben im Test Folgendes bewirkt:

Option Wirkung im Test
IDNA_USE_STD3_RULES lehnt Unterstrich, Leerzeichen, Doppelpunkt, Schrägstrich und @ ab
IDNA_CHECK_BIDI lehnt ein Label mit lateinischen und hebräischen Zeichen ab, ein rein arabischer Name ging durch
IDNA_CHECK_CONTEXTJ lehnt unsichtbare Verbindungszeichen ab, aber nur zusammen mit nontransitional, sonst werden sie vorher entfernt
IDNA_ALLOW_UNASSIGNED keine Wirkung, nicht vergebene Zeichen blieben ein Fehler

Für Domains aus einem Bestellformular empfiehlt sich deshalb eine feste Kombination als Konstante:

<?php

const IDN_STRENG = IDNA_NONTRANSITIONAL_TO_ASCII
| IDNA_USE_STD3_RULES
| IDNA_CHECK_BIDI
| IDNA_CHECK_CONTEXTJ;

var_dump(idn_to_ascii('mein_host.de'));
/* string(12) "mein_host.de" */

var_dump(idn_to_ascii('mein_host.de', IDN_STRENG));
/* bool(false) */

var_dump(idn_to_ascii('Bäckerei.de', IDN_STRENG));
/* string(18) "xn--bckerei-5wa.de" */

Ohne STD3 bleibt der Unterstrich stehen. Für technische Namen wie _dmarc ist das gewollt, für Domains, die ein Kunde registrieren will, nicht. Emoji-Domains lehnt keine der Optionen ab: 😀.com wurde in jedem Fall zu xn--e28h.com. Ob eine Registry so etwas vergibt, entscheidet die Funktion nicht.

Wenn PHP idn_to_ascii() false liefert

Das Array $idna_info enthält drei Einträge: result mit dem Zwischenergebnis, isTransitionalDifferent und errors. Der letzte ist eine Bitmaske aus Konstanten wie IDNA_ERROR_LABEL_TOO_LONG (2), IDNA_ERROR_LEADING_HYPHEN (8) oder IDNA_ERROR_DISALLOWED (128). Die häufigsten Bits mit je einem gemessenen Auslöser:

Konstante (Wert) Gemessener Auslöser
IDNA_ERROR_EMPTY_LABEL (1) müller..de
IDNA_ERROR_LABEL_TOO_LONG (2) Label mit mehr als 63 Zeichen in der ASCII-Form
IDNA_ERROR_DOMAIN_NAME_TOO_LONG (4) Name mit 254 Zeichen
IDNA_ERROR_LEADING_HYPHEN (8) -müller.de
IDNA_ERROR_TRAILING_HYPHEN (16) müller-.de
IDNA_ERROR_HYPHEN_3_4 (32) ab--cd.de
IDNA_ERROR_DISALLOWED (128) mein_host.de mit STD3, Bytes in ISO-8859-1

Eine kleine Hilfsfunktion übersetzt die Bits in lesbare Namen:

<?php

function idna_fehler(int $bits): array
{
$namen = [];
$k = get_defined_constants(true)['intl'];
foreach ($k as $name => $wert) {
if (str_starts_with($name, 'IDNA_ERROR_')
&& ($bits & $wert)) {
$namen[] = $name;
}
}
return $namen;
}

$info = [];
$ascii = idn_to_ascii(
'müller-.de',
IDNA_NONTRANSITIONAL_TO_ASCII
| IDNA_USE_STD3_RULES,
INTL_IDNA_VARIANT_UTS46,
$info
);
if ($ascii === false) {
$bits = $info['errors'] ?? 0;
echo implode(', ', idna_fehler($bits));
}
/* IDNA_ERROR_TRAILING_HYPHEN */

Das ?? 0 ist kein Zierrat. Ab 255 Zeichen Gesamtlänge lieferte PHP idn_to_ascii() im Test false, ohne das Array überhaupt zu füllen. Bis 253 Zeichen ging alles gut, bei 254 meldete die Bitmaske den Wert 4 für einen zu langen Namen. Für das einzelne Label gilt die Grenze von 63 Zeichen, und zwar in der ASCII-Form. Ein Label aus 57 ü ergab genau 63 Zeichen Punycode und ging durch, 58 ü scheiterten. Ein Firmenname mit 60 Zeichen und vielen Umlauten wurde 71 Zeichen lang und damit abgelehnt. Wer Längen vor dem Absenden prüft, misst also das Ergebnis der Umwandlung und nicht die Eingabe.

Ein leerer String führt nicht zu false, sondern zu einer Ausnahme: ValueError: idn_to_ascii(): Argument #1 ($domain) must not be empty. Ein Bindestrich am Anfang oder am Ende eines Labels scheitert auch ohne STD3, mit dem Bit 8 beziehungsweise 16. Tückisch ist ein falsch kodierter Name. Bytes in ISO-8859-1 lehnt PHP idn_to_ascii() mit dem Bit 128 ab, doppelt kodiertes UTF-8 wie müller.de dagegen nicht. Daraus wurde ohne jeden Fehler xn--m14ller-xwa2143e.de. Wer solche verstümmelten Formen in der Datenbank findet, sucht den Fehler in der Zeichenkodierung davor, nicht bei der Umwandlung.

URLs und E-Mail-Adressen: nur den Host umwandeln

Die häufigste Fehlbedienung ist eine ganze Adresse als Eingabe. Aus https://müller.de/pfad machte PHP idn_to_ascii() ohne Fehler xn--https://mller-4ob.de/pfad, aus mia@müller.de wurde xn--mia@mller-u9a.de. Doppelpunkt, Schrägstrich und @ sind ohne STD3 erlaubte Zeichen eines Labels. Richtig ist, nur den Host umzuwandeln. Den liefert parse_url() auch mit Umlauten korrekt, im Test unverändert als müller.de. Wie die Funktion eine Adresse zerlegt, zeigt das Tutorial zum Zerlegen einer URL. Die folgende Funktion wandelt nur den Host um und setzt ihn an derselben Stelle wieder ein:

<?php

function url_ascii(string $url): string|false
{
$host = parse_url($url, PHP_URL_HOST);
if (!is_string($host) || $host === '') {
return false;
}
$ascii = idn_to_ascii(
$host,
IDNA_NONTRANSITIONAL_TO_ASCII
);
if ($ascii === false) {
return false;
}
$pos = strpos($url, $host);
return substr_replace(
$url, $ascii, $pos, strlen($host)
);
}

echo url_ascii('https://müller.de/pfad?q=ä');
/* https://xn--mller-kva.de/pfad?q=ä */
echo PHP_EOL;

echo url_ascii('https://user:pw@bücher.de:8080/x');
/* https://user:pw@xn--bcher-kva.de:8080/x */

Pfad und Query bleiben unberührt, sie gehören zu urlencode() und nicht zum DNS. Eine Adresse wie mailto:mia@müller.de hat keinen Host, die Funktion liefert dann false. Bei E-Mail-Adressen trennt strrpos($mail, '@') am letzten @, umgewandelt wird nur der Teil dahinter, das Ergebnis lautet mia@xn--mller-kva.de. Ob die Adresse danach gültig ist, prüft der Filter aus dem Tutorial zu filter_validate_email.

Praxis: DNS-Abfragen mit Umlautdomains

Zurück zum Monitoring aus der Einleitung. Die DNS-Funktionen von PHP reichen den Namen unverändert weiter und scheitern still, wenn er Umlaute enthält:

<?php

$domain = 'bücher.de';

var_dump(gethostbyname($domain));
/* string(10) "bücher.de" (nicht gefunden) */

$ascii = idn_to_ascii(
$domain,
IDNA_NONTRANSITIONAL_TO_ASCII
);
echo gethostbyname($ascii);
/* eine IPv4-Adresse, z. B. 104.21.33.153 */

gethostbyname() gibt bei einem Fehlschlag den Namen selbst zurück, und genau das passierte mit bücher.de. checkdnsrr() lieferte false, dns_get_record() ein leeres Array. Mit xn--bcher-kva.de kamen eine Adresse und zwei A-Records zurück, wobei die Adresse zwischen zwei Testläufen wechselte. Keine der drei Funktionen gab eine Warnung aus. Vor jede Abfrage gehört deshalb ein Aufruf von PHP idn_to_ascii(), ob für A-Records, MX-Einträge oder andere Typen, wie sie das Tutorial zu dns_get_record() beschreibt. Auch filter_var() mit FILTER_VALIDATE_DOMAIN und FILTER_FLAG_HOSTNAME lehnte müller.de ab und akzeptierte erst die Punycode-Form.

Speichern, vergleichen, anzeigen mit PHP idn_to_ascii()

In der Datenbank steht die Domain am besten in der ASCII-Form. Sie ist eindeutig und kleingeschrieben, und ein zweiter Aufruf ändert nichts mehr. So findet eine Duplikatprüfung MÜLLER.DE, müller.de und xn--mller-kva.de als denselben Eintrag. Für die Anzeige wandelt idn_to_utf8() mit IDNA_NONTRANSITIONAL_TO_UNICODE zurück.

Eine Ausnahme sind Domains, die nur so aussehen wie bekannte Namen. аpple.com mit einem kyrillischen a ist in der Unicode-Form nicht von der echten Domain zu unterscheiden. PHP idn_to_ascii() macht daraus xn--pple-43d.com, prüft aber nicht, ob der Name verdächtig ist. Dafür bringt dieselbe Erweiterung die Klasse Spoofchecker mit:

<?php

$pruefer = new Spoofchecker();
$domain = 'аpple.com';

$anzeige = $domain;
if ($pruefer->isSuspicious($domain)) {
$anzeige = idn_to_ascii(
$domain,
IDNA_NONTRANSITIONAL_TO_ASCII
);
}
echo htmlspecialchars($anzeige);
/* xn--pple-43d.com */

Für müller.de meldete isSuspicious() false, die Domain erscheint dann in der gewohnten Form. Nur der Verdachtsfall wird in Punycode angezeigt, und der Leser sieht sofort, dass etwas nicht stimmt.

Fazit

PHP idn_to_ascii() wandelt jedes Label eines Domainnamens einzeln in Punycode um und schreibt dabei alles klein. Die Gegenrichtung übernimmt idn_to_utf8(). Ohne Optionen hängt das Ergebnis beim ß von der ICU-Version ab, deshalb gehört IDNA_NONTRANSITIONAL_TO_ASCII in jeden Aufruf. Für Formulareingaben kommen IDNA_USE_STD3_RULES und die beiden Prüfoptionen dazu.

Fehler meldet die Funktion nur über false und das Array $idna_info, nie mit einer Warnung. Ganze URLs und E-Mail-Adressen gehören vorher zerlegt, Längen werden an der ASCII-Form gemessen. Wer jede DNS-Abfrage mit der umgewandelten Form ausführt, die ASCII-Form speichert und nur verdächtige Namen in Punycode zeigt, hat mit Umlautdomains keine Überraschungen mehr.

 

Tags:

 

 

Kommentare (0)

Noch keine Kommentare. Sei der Erste!

Melde dich an, um einen Kommentar zu schreiben.
Bücherregal mit drei Büchern: 'PHP 4 - Grundlagen und Profiwissen' von Hanser Verlag, 'Webdesign in a Nutshell' von O'Reilly Verlag, und 'Webgestaltung' von Galileo Computing.