URL coderen

Plak een tekst en krijg hem terug met procentontsnapping, klaar voor gebruik in een URL. De enige beslissing die ertoe doet krijg je als keuze in plaats van als gok: codeer je een waarde die binnen een adres komt te staan, of een heel adres waarvan de schuine strepen en vraagtekens structuur zijn en moeten overleven. Wie die twee verwart, krijgt een URL die er goed uitziet en ergens anders heen gaat.

Een waarde ontsnapt ? & = / en #. Een heel adres houdt ze, want daar zijn ze structuur.

Resultaat

Het antwoord verschijnt hier terwijl je typt.

  • Waar het draait

    Er wordt niets geüpload, want er is geen bestand — het wordt in deze pagina uitgerekend.

  • Geen wachtrij, geen account

    Het antwoordt zo snel als je machine kan, en vraagt nooit wie je bent.

  • Zo vaak als je wilt

    Er wordt niets geteld en niets begrensd — nog eens antwoorden kost ons niets.

Hoe het werkt

  1. Plak de tekst in het veld.
  2. Zeg wat het is: een waarde binnen een URL of een heel adres. Dat bepaalt of ? en & ontsnapt worden.
  3. Kopieer het resultaat. Er is niets geüpload.

De enige vraag die dit hulpmiddel stelt

Een URL bestaat uit delen, en de tekens die ze scheiden — `/`, `?`, `&`, `=`, `#` — zijn structuur wanneer ze die rol vervullen en gegevens wanneer ze dat niet doen. Een heel adres coderen moet ze met rust laten of het is geen adres meer; een losse waarde coderen moet ze ontsnappen of de waarde houdt op en er begint iets anders.

Dat is precies het verschil tussen `encodeURI` en `encodeURIComponent`, en daar komen de meeste kapotte doorstuurparameters op het web vandaan: een `?terug=https://voorbeeld.nl/a?b=c` waar het tweede vraagteken nooit is ontsnapt, zodat alles erachter bij de buitenste URL hoort. De link werkt in de test, waar het doel geen query string heeft, en faalt in productie, waar het die wel heeft.

Procenten dragen bytes, geen tekens

Een procentreeks is een `%` gevolgd door twee hexadecimale cijfers en staat voor één byte. Tekens buiten ASCII hebben er meerdere nodig: een `ë` is `%C3%AB` en niet `%EB`, omdat UTF-8 hem in twee bytes schrijft. Een emoji heeft vier reeksen nodig.

De oude functie `escape()` produceerde `%EB` en is juist daarom verouderd — ze stamt van vóór het besluit dat het web UTF-8 spreekt. Haar uitvoer duikt nog op in oude code en in enkele overgeërfde koppelingen, waar ze decodeert naar het verkeerde teken op alles wat geen Latin-1-backend is. Hier is het van begin tot eind UTF-8.

De vijf tekens die handtekeningen breken

`encodeURIComponent` laat het uitroepteken, de apostrof, de twee haakjes en het sterretje ongemoeid. RFC 3986 noemt die als gereserveerd, dus een strikt conforme codeerder ontsnapt ze wel — en daarmee verschillen de twee op vijf tekens die in gewone tekst voortdurend voorkomen.

Het valt pas op zodra er een handtekening in het spel is. OAuth 1.0 en AWS Signature versie 4 rekenen een hash over de gecodeerde tekst, zodat één niet-ontsnapte apostrof de hash verandert en het verzoek wordt geweigerd — met een foutmelding over inloggegevens die je naar de verkeerde plek stuurt. De strikte stand hier ontsnapt alle vijf.

Spaties: %20 of een plusteken

Allebei komen ze voor en ze volgen niet dezelfde regel. `%20` is procentcodering en is overal in een URL juist. Het plusteken komt uit `application/x-www-form-urlencoded`, de vorm waarin een HTML-formulier verstuurt, waar een spatie als `+` wordt geschreven — een conventie van vóór de huidige specificatie die blijft bestaan omdat formulieren het blijven doen.

In een pad is `+` een echte plus. In een query string lezen vrijwel alle servers hem als spatie, want zo staat het in de formuliercodering — waardoor een echte plus in een querywaarde als `%2B` geschreven moet worden of hij verdwijnt. Deze codeerder levert `%20`, dat op geen van beide plekken dubbelzinnig is.

Twee keer coderen, en hoe je het herkent

Een al gecodeerde tekst nog eens coderen ontsnapt het `%` zelf, zodat `%20` verandert in `%2520`. Voor geen van de betrokken onderdelen is dat een fout — het is een keurige codering van de letterlijke tekst «%20» — en daarom overleeft dubbele codering tot het punt waarop een bezoeker `Hallo%20wereld` in een kop ziet staan.

Het signaal is een `%25` waar een `%` hoort. Zie je er een, dan codeert iets in de keten een al gecodeerde waarde, meestal omdat een framework het voor je doet en een sjabloon het nog eens overdoet. De oplossing is een van de twee plekken weghalen, niet aan het eind twee keer decoderen.

Wat je nooit moet ontsnappen

Letters, cijfers en de vier tekens `-`, `_`, `.` en `~` heten in RFC 3986 «unreserved» en horen nooit ontsnapt te worden. Een codeerder die `%2E` in plaats van een punt levert is niet strenger maar fout, want sommige systemen normaliseren dat terug en andere niet, en zo houden twee URL’s die dezelfde zouden moeten zijn op dat te zijn.

In de praktijk: een gecodeerde tekst met opvallend veel procentreeksen terwijl er vooral letters in staan, komt meestal uit een hulpmiddel dat te veel codeert. Dat is onschuldig tot iemand er een handtekening of een cachesleutel over berekent — en dan is het de reden dat twee systemen elkaar niet begrijpen.

De praktische lengtegrens van een adres

De specificatie legt geen bovengrens aan een URL op, maar alles wat hem verwerkt wel. De veilige bovengrens die in de praktijk wordt aangehouden is zo’n 2.000 tekens, omdat oude versies van Internet Explorer daar stopten en veel proxy’s en logsystemen die grens hebben overgenomen.

Coderen maakt dat probleem groter, want elke ontsnapte byte kost drie tekens in plaats van één. Een Nederlandse zoekterm met accenten en spaties groeit al snel tot het dubbele. Loop je ertegenaan, dan is het antwoord niet korter coderen maar de waarde in de body van een POST zetten, waar geen van die grenzen geldt.

Het fragment wordt nooit verstuurd

Alles achter `#` blijft in de browser. Het komt in geen enkel verzoek voor, bereikt geen server en staat in geen enkel toegangslogboek. Dat is een eigenschap van HTTP en geen privacyfunctie, en het snijdt naar twee kanten.

Het is bruikbaar omdat een waarde in het fragment de server niet bereikt — historisch de reden dat OAuth-tokens daar reisden. Het is ongemakkelijk omdat het toch in de geschiedenis van de browser blijft staan, in de referrer-keten van sommige scripts, en in elke link die iemand doorstuurt. «Bereikt de server niet» betekent niet «is privé».

De hostnaam wordt niet met procenten gecodeerd

Een domein met een trema wordt niet ontsnapt maar omgezet naar Punycode: `ideeën.example` wordt `xn--ideen-qsa.example`, en dat is de vorm die werkelijk in het DNS bestaat, want het namensysteem kent alleen ASCII.

Een codeerder die de host als een waarde behandelt, levert een adres op dat geen enkele DNS oplost. Daarom laat de stand voor een heel adres de host hier ongemoeid — en daarom is het omzetten van de host een aparte stap wanneer je een URL met een Nederlands domein samenstelt, en geen onderdeel van deze bewerking.

Coderen is geen controle

Een correct gecodeerde waarde is nog steeds precies de waarde die erin ging. Coderen maakt geen doorstuurdoel veilig, geen zoekterm onschuldig en geen bestandspad geldig; het zorgt er alleen voor dat de waarde ongeschonden aan de andere kant aankomt.

Dat onderscheid wordt vaak overgeslagen bij doorstuurparameters. Een `?terug=`-waarde die keurig is gecodeerd en naar een vreemd domein wijst, is een open redirect — en die controleer je door het doel tegen een lijst toegestane hosts te leggen, niet door beter te coderen.

AVG: de URL is het lek, niet het coderen

Wat je codeert belandt meestal in een URL, en URL’s blijven staan in het serverlogboek, in de geschiedenis van de browser en in de referrer-header van de volgende klik. Zit er een e-mailadres, een identifier of een persoonlijke zoekterm in de waarde, dan staat die na één gebruik al op meerdere plekken.

Wat deze pagina oplost is dat het coderen op je eigen machine gebeurt: de waarde wordt niet aan ons verstrekt. Wat ze niet kan oplossen is het andere, en dat verdient het om hier te staan — de plek waar een persoonsgegeven het vaakst ongewild wordt vastgelegd, is een URL-parameter.

URL coderen: veelgestelde vragen

Welke van de drie standen heb ik nodig?

Is de tekst een waarde die binnen een adres komt, de eerste: die ontsnapt ? & = / en #. Is het een heel adres, de tweede, die ze behoudt omdat ze daar structuur zijn. De derde heb je alleen nodig als er een handtekening in het spel is.

Waarom wordt mijn ë twee procentreeksen?

Omdat procentcodering bytes vervoert en UTF-8 de ë in twee bytes schrijft: %C3%AB. De oude vorm %EB kwam van escape(), dat verouderd is en decodeert naar het verkeerde teken op alles wat geen Latin-1 is.

Ik zie %2520 in mijn URL, wat betekent dat?

Dat er twee keer is gecodeerd: %20 ging opnieuw door een codeerder en zijn % werd ontsnapt. De oplossing is een van de twee stappen weghalen, niet aan het eind twee keer decoderen.

Spatie als %20 of als plusteken?

%20 is overal in een URL juist. De + betekent alleen in een query string een spatie, geërfd van de formuliercodering; in een pad is het een echte plus. Hier komt %20 uit, en dat is nergens dubbelzinnig.

Verlaat de tekst mijn machine?

Nee. Het coderen gebeurt op deze pagina. Houd er wel rekening mee dat de waarde na gebruik van de URL blijft staan in serverlogboeken, in de geschiedenis en in de referrer-header.

Andere hulpmiddelen