Warum URLs kodiert werden müssen
URLs dürfen laut RFC 3986 nur eine begrenzte Menge von ASCII-Zeichen enthalten: Buchstaben (A–Z, a–z), Ziffern (0–9) und eine Handvoll Sonderzeichen (-._~). Alles andere — Leerzeichen, Umlaute, Emojis, und sogar Zeichen wie & oder = die in URLs eine besondere Bedeutung haben — muss kodiert werden.
Die Kodierung funktioniert simpel: Jedes nicht erlaubte Byte wird als Prozentzeichen gefolgt von zwei Hex-Ziffern dargestellt. Ein Leerzeichen wird zu %20, ein ä (UTF-8: 0xC3 0xA4) wird zu %C3%A4. Daher der technische Name "Percent-Encoding".
Das Problem entsteht, wenn Zeichen wie & oder = sowohl als Daten als auch als Struktur-Trennzeichen vorkommen. In der URL ?name=Tom&Jerry ist unklar ob "&Jerry" ein zweiter Parameter oder Teil des Namens ist. Korrekt kodiert wäre ?name=Tom%26Jerry — das & als Daten wird zu %26.
Moderne Browser zeigen URLs in der Adressleiste dekodiert an (Punycode-Domains, Unicode-Pfade), aber intern wird die kodierte Form verwendet. Das führt dazu, dass Entwickler denken, Kodierung sei optional — bis ihre API bei Sonderzeichen abbricht.
encodeURI vs encodeURIComponent: Der entscheidende Unterschied
JavaScript bietet zwei Funktionen für URL-Kodierung, und die falsche Wahl ist einer der häufigsten Webentwicklungs-Fehler. encodeURI() kodiert einen vollständigen URI und lässt daher Strukturzeichen wie ://?#[]@!$&'()*+,;= intakt. encodeURIComponent() kodiert einen einzelnen Wert und kodiert auch diese Zeichen.
Faustregel: Verwende encodeURIComponent() für Query-Parameter-Werte und Pfad-Segmente. Verwende encodeURI() nur wenn du eine komplette URL hast, die bereits korrekt strukturiert ist, aber nicht-ASCII-Zeichen im Pfad enthält.
Ein konkretes Beispiel: Du willst die URL "https://example.com/search" als Parameter übergeben. encodeURI("https://example.com/search") lässt alles unverändert (alles erlaubt). encodeURIComponent("https://example.com/search") ergibt "https%3A%2F%2Fexample.com%2Fsearch" — die Slashes und der Doppelpunkt werden kodiert, was korrekt ist wenn es ein Parameterwert sein soll.
Der häufigste Fehler: encodeURI() für Parameter-Werte verwenden. Dann bleiben & und = unkodiert und zerstören die Query-String-Struktur. Zweithäufigster Fehler: encodeURIComponent() auf die gesamte URL anwenden — dann werden auch die Strukturzeichen kodiert und die URL ist kaputt.
// ❌ FALSCH: encodeURI für Parameter-Werte
const query = 'name=Hans & Franz';
const bad = `/search?${encodeURI(query)}`;
// "/search?name=Hans%20&%20Franz" ← & bleibt → kaputter Parameter
// ✅ RICHTIG: encodeURIComponent für jeden Wert einzeln
const name = 'Hans & Franz';
const good = `/search?name=${encodeURIComponent(name)}`;
// "/search?name=Hans%20%26%20Franz" ← & wird zu %26
// ❌ FALSCH: encodeURIComponent auf ganze URL
encodeURIComponent('https://example.com/path?q=test');
// "https%3A%2F%2Fexample.com%2Fpath%3Fq%3Dtest" ← unbrauchbar
// ✅ RICHTIG: URL-Klasse verwenden (moderne Variante)
const url = new URL('https://example.com/search');
url.searchParams.set('q', 'Hans & Franz');
url.searchParams.set('redirect', 'https://other.com');
url.toString();
// "https://example.com/search?q=Hans+%26+Franz&redirect=https%3A%2F%2Fother.com"Die URL-Klasse: Der moderne Ansatz
Seit ES2015 bietet JavaScript die URL-Klasse, die URL-Parsing und -Konstruktion korrekt handhabt. new URL(string) parst eine URL in ihre Bestandteile (protocol, hostname, pathname, searchParams). Das Encoding wird automatisch korrekt angewendet.
URLSearchParams übernimmt die Query-String-Kodierung: url.searchParams.set("key", "value") kodiert den Wert automatisch. Keine manuelle Kodierung nötig, keine Fehler bei Sonderzeichen. Das ist der empfohlene Ansatz für alle modernen Projekte.
Wichtig: URLSearchParams kodiert Leerzeichen als + statt %20 (beides ist in Query-Strings gültig laut RFC). Manche Server-Implementierungen haben damit Probleme. Wenn du %20 brauchst, verwende encodeURIComponent() und baue den Query-String manuell.
Die URL-Klasse validiert auch: new URL("nicht-eine-url") wirft einen TypeError. Das macht sie nützlich für Input-Validierung — wrapp den Aufruf in try/catch und du hast eine robuste URL-Prüfung ohne Regex.
// URL-Klasse für sicheres URL-Building
const base = new URL('https://api.example.com/v2/search');
base.searchParams.set('q', 'Ärger & Probleme');
base.searchParams.set('page', '1');
base.searchParams.set('filter', 'status=active');
console.log(base.toString());
// "https://api.example.com/v2/search?q=%C3%84rger+%26+Probleme&page=1&filter=status%3Dactive"
// URL-Validierung
function isValidURL(str) {
try {
new URL(str);
return true;
} catch {
return false;
}
}
// Relative URLs auflösen
const absolute = new URL('/api/users', 'https://example.com');
// → "https://example.com/api/users"Pfad-Segmente vs Query-Parameter: Verschiedene Regeln
Ein häufiges Missverständnis: Die Kodierungsregeln sind für verschiedene URL-Teile unterschiedlich. Im Pfad (/pfad/segment) sind Slashes Trennzeichen und dürfen nicht kodiert werden. In Query-Werten (?key=value) sind = und & Trennzeichen. In Fragment-Identifiern (#section) gelten wieder andere Regeln.
Pfad-Segmente: Wenn ein Dateiname einen Slash enthält (z.B. "AC/DC"), muss der Slash als %2F kodiert werden, damit er nicht als Pfadtrenner interpretiert wird. encodeURIComponent() erledigt das korrekt. Manche Webserver (Apache mit AllowEncodedSlashes off) lehnen %2F im Pfad allerdings ab.
Query-Strings: Das Pluszeichen + hat eine Sonderrolle — es repräsentiert ein Leerzeichen (historisch aus HTML-Formularen). Wenn du ein echtes + als Daten übertragen willst, muss es als %2B kodiert werden. encodeURIComponent() macht das korrekt, URLSearchParams ebenfalls.
Fragmente (#hash): Werden nie an den Server gesendet — sie existieren nur im Browser. Trotzdem müssen Sonderzeichen kodiert werden, damit der Browser den Fragment-Identifier korrekt parst. In Single-Page-Apps mit Hash-Routing ist das besonders relevant.
Internationale Zeichen und Punycode
Nicht-ASCII-Zeichen in URLs werden UTF-8-kodiert und dann percent-encoded. Der String "München" wird zu "M%C3%BCnchen" (ü = UTF-8 0xC3 0xBC). Moderne Browser zeigen die dekodierte Form in der Adressleiste, aber der HTTP-Request verwendet die kodierte Version.
Domainnamen sind ein Sonderfall: Sie verwenden Punycode statt Percent-Encoding. Die Domain "münchen.de" wird zu "xn--mnchen-3ya.de". Das Prefix "xn--" signalisiert eine Punycode-kodierte Domain. Browser zeigen die Unicode-Version an, die DNS-Auflösung verwendet Punycode.
Internationalisierte Domains (IDN) sind ein Phishing-Risiko: "аpple.com" (mit kyrillischem а) sieht identisch aus wie "apple.com" (mit lateinischem a), ist aber eine andere Domain. Browser schützen teilweise durch Punycode-Anzeige bei gemischten Skripten, aber nicht alle Fälle werden erkannt.
In E-Mails werden internationale Domainnamen durch EAI (Email Address Internationalization, RFC 6531) unterstützt, aber die Unterstützung ist noch lückenhaft. Für maximale Kompatibilität verwende ASCII-Domains in E-Mail-Adressen und international kodierte Pfade nur im URL-Pfad.
Häufige Fehler und wie man sie vermeidet
Doppelt-Kodierung: Wenn du einen bereits kodierten String nochmal kodierst, wird %20 zu %2520 (das % wird zu %25). Das passiert oft wenn Frameworks automatisch kodieren und du zusätzlich manuell kodierst. Prüfe ob dein Framework die Kodierung bereits übernimmt, bevor du encodeURIComponent() aufrufst.
Plus-Zeichen in Formularen: HTML-Formulare mit method="GET" kodieren Leerzeichen als +. Wenn du den Query-String serverseitig mit decodeURIComponent() dekodierst, bleiben die +-Zeichen erhalten (werden nicht zu Leerzeichen). Verwende stattdessen URLSearchParams oder ersetze + durch %20 vor dem Dekodieren.
Pfad-Traversal: Unkodierte ../-Sequenzen in URLs können zu Directory-Traversal-Angriffen führen. Wenn du User-Input in URL-Pfade einsetzt, normalisiere den Pfad und prüfe ob das Ergebnis noch innerhalb des erlaubten Verzeichnisses liegt. Verlasse dich nicht allein auf URL-Kodierung für Sicherheit.
JSON in Query-Parametern: Manche APIs akzeptieren JSON als Query-Parameter (?filter={"status":"active"}). Die geschweiften Klammern, Anführungszeichen und Doppelpunkte müssen alle kodiert werden. Besser: Verwende Base64url-kodiertes JSON oder flache Parameter (filter[status]=active).
// Doppelt-Kodierung erkennen und vermeiden
const alreadyEncoded = 'Hallo%20Welt';
encodeURIComponent(alreadyEncoded);
// "Hallo%2520Welt" ← FALSCH! %25 ist das kodierte %
// Prüfen ob ein String bereits kodiert ist
function isEncoded(str) {
return str !== decodeURIComponent(str);
}
// Plus-Problem bei Formulardaten
const queryString = 'name=Hans+Meier&city=M%C3%BCnchen';
// ❌ decodeURIComponent('Hans+Meier') → "Hans+Meier" (+ bleibt)
// ✅ new URLSearchParams(queryString).get('name') → "Hans Meier"
// Sichere Pfad-Konstruktion
function safePath(userInput) {
const encoded = encodeURIComponent(userInput);
// Zusätzlich: keine ../ erlauben
if (encoded.includes('..')) throw new Error('Invalid path segment');
return `/files/${encoded}`;
}URL-Kodierung in verschiedenen Kontexten
HTML-Attribute: In href-Attributen müssen URLs HTML-Entity-kodiert sein, wenn sie & enthalten. <a href="/search?a=1&b=2"> — das & ist HTML-Kodierung, nicht URL-Kodierung. Frameworks wie React erledigen das automatisch, aber in Template-Strings musst du aufpassen.
CSS url()-Funktionen: url(pfad mit leerzeichen.png) muss entweder zitiert werden url("pfad mit leerzeichen.png") oder kodiert url(pfad%20mit%20leerzeichen.png). In der Praxis: Verwende immer Anführungszeichen, das ist lesbarer und sicherer.
Server-seitig (Node.js): Das querystring-Modul ist deprecated seit Node.js 14. Verwende die globale URLSearchParams-Klasse oder das url-Modul mit new URL(). Express.js parsed Query-Strings automatisch in req.query — aber nur eine Ebene tief. Nested Objects (?a[b]=1) erfordern die qs-Bibliothek.
Redirects: Wenn du eine Redirect-URL als Parameter übergibst (?redirect=https://...), muss sie doppelt kodiert sein wenn der Redirect-Handler sie dekodiert bevor er sie als Location-Header setzt. Das ist ein häufiger Angriffsvektor für Open-Redirect-Schwachstellen — validiere immer die Ziel-Domain.