Skip to content

Guide

Codifica URL Spiegata nel Dettaglio

Metà dei bug nella gestione degli URL vengono dalla codifica della cosa sbagliata — o dalla doppia codifica.

By Published

La codifica URL è uno di quei temi che sembra banale finché la produzione non inizia a restituire 400 Bad Request su ogni ricerca che contiene un accento francese. Le regole sono semplici, ma sono distribuite tra quattro specifiche che non concordano completamente, e le funzioni di supporto JavaScript hanno nomi che ingannano attivamente. Questa guida raccoglie le regole in un unico posto e contrassegna i punti in cui gli standard discordano silenziosamente.

Cosa significa davvero “URL encoding”

Gli URL sono limitati a un piccolo sottoinsieme di ASCII. Qualsiasi carattere al di fuori di quel set — o qualsiasi carattere che ha un significato strutturale che il parser altrimenti interpreterebbe — deve essere rappresentato come uno o più byte preceduti da %. Questo è il percent-encoding, e le regole su cosa codificare provengono da RFC 3986. Potete provare qualsiasi esempio con il nostro encoder URL.

RFC 3986 divide i caratteri in tre gruppi. Il set non riservato è A-Z a-z 0-9 - . _ ~; questi sono sempre sicuri e non richiedono mai la codifica. Il set riservato (: / ? # [ ] @ ! $ & ' ( ) * + , ; =) ha un significato strutturale in qualche parte dell’URL; codificateli quando intendete il carattere letterale, lasciateli intatti quando intendete il delimitatore. Tutto il resto — inclusi spazio, ", <, >,{ e tutto il non-ASCII — deve essere percent-encoded.

Dove ogni carattere deve essere codificato

Lo stesso byte può o meno richiedere la codifica a seconda di quale componente dell’URL appare. Un segmento di percorso permette : e @ non codificati; un valore di query non ha bisogno di codificare / o ? (perché il parser si ferma a #); un frammento è il componente più permissivo di tutti. In pratica, quasi tutti ignorano queste distinzioni e codificano qualsiasi cosa al di fuori del set non riservato quando interpolano un valore in un URL. Questo è conservativo e corretto.

L’unico punto in cui questo è importante: non codificate i caratteri / che separano i segmenti del vostro percorso. Codificate ogni segmento, poi uniteli con /. Se codificate anche la barra, ottenete %2F nel percorso, che la maggior parte dei server rifiuterà per ragioni di sicurezza (altrimenti permetterebbe agli attaccanti di contrabbandare sequenze ..oltre i controlli di directory).

encodeURI vs encodeURIComponent in JavaScript

JavaScript include due encoder, e la differenza tra essi è la fonte di un’enorme quantità di tempo di debug sprecato.

  • encodeURI(value) assume che value sia già un URL completo. Lascia intatti i caratteri riservati (; / ? : @ & = + $ , #) in modo che l’URL rimanga analizzabile. Codifica spazi, accenti e il piccolo insieme di caratteri che non sono mai permessi in nessuna parte di un URL.
  • encodeURIComponent(value) assume che value sia un singolo componente che viene inserito in un URL — di solito un segmento di percorso o un valore di query. Codifica tutto tranne il set non riservato più ! ' ( ) *.

La regola generale: se state costruendo un URL per concatenazione, usate encodeURIComponent su ogni valore interpolato. Se state normalizzando un URL già costruito, encodeURI è lo strumento giusto. Non c’è quasi nessun caso in cui vogliate applicare entrambi.

Dati form: l’eccezione +/spazio

Gli invii di form HTML codificano usando il media type application/x-www-form-urlencoded, definito nello standard URL WHATWG. Quel formato diverge da RFC 3986 in due modi. Primo, gli spazi diventano + invece di %20. Secondo, un + letterale nel valore viene codificato come %2B in modo che il decoder possa distinguerli.

Sia %20 che + si decodificano in uno spazio in pratica — ogni parser server-side mainstream accetta entrambi in una query string. Ma gli emettitori dovrebbero essere coerenti. Usate + nei corpi dei form e nelle query string che imitano i corpi dei form; usate %20 altrove, specialmente nei segmenti di percorso dove + è un segno più letterale.

Array e oggetti annidati: non esiste uno standard

Le query string sono state progettate per coppie chiave=valore piatte. Le strutture dati annidate devono essere appiattite, e la regola di appiattimento è una decisione per-framework. Le quattro convenzioni ampiamente usate:

  1. Chiavi ripetute. ?tag=js&tag=ts diventa un array sul server. La convenzione più semplice; default in Express, Go's net/url e la maggior parte dei parser Node.
  2. Bracket stile PHP. ?tag[]=js&tag[]=ts per gli array; ?user[name]=alex per oggetti annidati. I bracket devono essere percent-encoded (%5B / %5D) per essere strettamente conformi agli standard, sebbene la maggior parte dei server accetti la forma grezza.
  3. Notazione punto Rails. ?user.name=alex&user.age=30. Meno comune oggi; compare in alcune API Ruby e .NET più vecchie.
  4. Separato da virgola. ?tag=js,ts. Comune nelle API REST (OpenAPI lo elenca come stileform/explode=false). Conciso ma ambiguo se i valori stessi possono contenere virgole.

Scegliete una convenzione per API e documentatela. I bug più dolorosi in quest’area provengono da un client che emette bracket parlando con un server che si aspetta chiavi ripetute: entrambi sembrano funzionare, ma uno di essi conserva silenziosamente solo l’ultimo valore.

CJK, emoji e altri non-ASCII

Ogni carattere al di fuori dell’ASCII deve essere codificato come suoi byte UTF-8, poi percent-encoded byte per byte. Il carattere 北 (U+5317) sono i tre byte E5 8C 97 in UTF-8, che diventano %E5%8C%97 in un URL. Le emoji seguono la stessa regola: 🔥 (U+1F525) sono i quattro byte F0 9F 94 A5, codificati come %F0%9F%94%A5.

I sistemi legacy a volte emettono lo stesso carattere in una codifica diversa — GBK per il cinese, Shift-JIS per il giapponese, Windows-1251 per il cirillico. Se state integrando con un backend precedente al 2010 e il vostro testo decodificato risulta incomprensibile, provate a interpretare i byte come Windows-1252 o la codifica nativa del locale prima di assumere che l’URL sia corrotto.

Nomi di dominio internazionalizzati

I nomi host seguono una regola diversa. I nomi host non ASCII vengono codificati come Punycode (RFC 3492), non percent-encoded. Il dominio 例え.jp diventa xn--r8jz45g.jp nell’effettiva query DNS. I browser mostrano la forma Unicode per gli script riconosciuti ma trasmettono la forma Punycode sul wire. Se vi trovate a costruire un URL con un host non-ASCII, convertite l’host con un encoder Punycode, non con l’encoder URL.

Insidie comuni

  • Doppia codifica. La codifica di un valore e poi il passaggio del risultato attraverso un altro encoder produce %2520 invece di %20. Tracciate ogni passaggio di codifica esplicitamente; non “codificate tanto per sicurezza” su un valore che non avete prodotto voi.
  • I frammenti hash sono solo lato client. Tutto ciò che viene dopo # non viene mai inviato al server. Mettere lo stato nel frammento è una scelta deliberata (privacy, routing di app a pagina singola); farlo accidentalmente nasconde i bug finché il server non inizia a dipendere da esso.
  • I segni più nei link mailto. mailto:[email protected] funziona perché gli URL mailto usano le regole RFC 3986, non la codifica dei form. Costruite i link mailto a mano, non con un form-encoder.
  • Il set non riservato esclude storicamente la tilde. RFC 2396 (il predecessore di 3986) trattava ~come riservato. I vecchi encoder emettono ancora %7E; lo standard moderno lo considera non riservato. Entrambi si decodificano ugualmente.
  • I limiti di lunghezza sono reali. La maggior parte dei server limita gli URL a 8 KB. I browser variano da 2 KB (IE precedente) a 32 KB (Chrome moderno). Se la vostra query string si avvicina a quel limite, spostate il payload in un corpo POST.

Un flusso di lavoro pratico

Quando si costruiscono URL nel codice: costruire un oggetto che rappresenti i componenti (schema, host, segmenti di percorso, mappa delle query), poi serializzare una volta con una libreria di fiducia — URL e URLSearchParams nei browser, gli equivalenti in ogni altro linguaggio principale. Gli URL assemblati a mano sono i bug.

Quando si esegue il debug di un URL codificato che qualcuno ha inviato, incollatelo nel nostro URL encoder/decoder e cambiate la direzione. Lo strumento decodifica un clic alla volta, quindi se dovete cliccare due volte per ottenere testo leggibile avete confermato la doppia codifica a monte.

La conclusione onesta

Usate encodeURIComponent su ogni valore interpolato, mai su un URL completo. Abbinate la vostra codifica dei form (spazio-come-più) al vostro contesto di trasporto. Scegliete una convenzione di array per API e documentatela. Fidatevi di UTF-8. E quando qualcosa sembra doppiamente codificato, quasi certamente lo è — tracciate il secondo encoder invece di coprirlo con un passaggio di decodifica.

Frequently asked questions

Devo codificare l'intero URL o solo le sue parti?
Codificate solo le parti che controllate: i segmenti del percorso e i valori delle query. Non codificate mai un intero URL con encodeURIComponent perché danneggerà il separatore dello schema (`://`) e i delimitatori `?` e `&`. Codificate ogni segmento del percorso e ogni valore di query indipendentemente e assemblateli dopo.
Perché il mio URL mostra + invece di %20 per gli spazi?
Questo è il comportamento di `application/x-www-form-urlencoded`, usato dalle invii di form HTML. In quel media type uno spazio viene codificato come `+` e un `+` letterale come `%2B`. Nei percorsi URL RFC 3986, gli spazi sono sempre `%20`. I server di solito decodificano entrambi, ma se costruite un URL a mano, preferite `%20` ovunque eccetto nel corpo dei form.
È mai la scelta giusta usare encodeURI() in JavaScript?
Raramente. `encodeURI()` lascia intatti i caratteri riservati come `?`, `#`, `&` e `/` perché assume che stiate codificando un URL completo. Questo non è quasi mai quello che volete: di solito volete codificare un valore che verrà inserito in un URL, il che è il compito di `encodeURIComponent`. Usate `encodeURI` solo quando sanitizzate un URL letterale digitato dall'utente contenente caratteri non ASCII.
Come vengono codificati i caratteri CJK?
RFC 3986 richiede UTF-8 seguito da percent-encoding di ogni byte. Il carattere `日` (U+65E5) è `E6 97 A5` in UTF-8, quindi si codifica come `%E6%97%A5`. I sistemi più vecchi a volte usavano GBK o Shift-JIS, ecco perché lo scraping di siti CJK legacy occasionalmente produce `%C8%D5` per lo stesso carattere. I browser moderni emettono sempre UTF-8.
Come codifico gli array nelle query string?
Non esiste un unico standard. PHP e Rails usano la notazione bracket (`tags[]=a&tags[]=b`); Express e la maggior parte dei framework Node accettano sia i bracket che le chiavi ripetute (`tags=a&tags=b`); alcune API richiedono valori separati da virgola (`tags=a,b`). Controllate il parser del consumatore prima di scegliere una convenzione e documentate la scelta nel riferimento API.
Perché ottengo valori doppiamente codificati come %2520?
Qualcosa ha codificato il valore, poi qualcos'altro ha codificato il risultato. `%20` (uno spazio codificato) è diventato `%2520` perché il `%` è stato ri-codificato come `%25`. La soluzione è trovare il secondo passaggio di codifica e rimuoverlo — non decodificate e poi ri-codificate come soluzione, perché perderete informazioni per i valori che contenevano legittimamente `%25` prima della codifica.

Related

Published May 31, 2026