Parser e decodificatore di URL
Analizza qualsiasi URL nei suoi componenti · protocollo, host, porta, percorso, parametri di query e frammento.
Anatomia di un URL
Un URL viene analizzato in sei parti concettuali: scheme://userinfo@host:port/path?query#fragment. Lo scheme dice al client quale protocollo usare (https, http, ftp, mailto, file, data) ed è l'unica parte che è sempre presente. La componente userinfo (username:password@) è rara nell'uso moderno; i browser generalmente la rimuovono dagli URL visualizzati perché è stata un vettore di phishing dagli anni '90. L'host è la posizione di rete: un nome di dominio registrato, un indirizzo IP (IPv4 in dotted-quad o IPv6 in parentesi quadre) o un nome speciale come localhost. La port è la porta TCP/UDP (80 default per HTTP, 443 per HTTPS, ecc.); quando omessa, si applica il default dello scheme. Il path è la gerarchia separata da slash che identifica la risorsa all'interno dell'host. La query string (tutto dopo il ?) trasporta coppie chiave-valore separate da &, usata per filtri, paginazione, tracking, sottomissione di form. Il fragment (tutto dopo #) è l'unica parte dell'URL che non viene mai inviata al server: viene elaborato interamente lato client dal browser per scorrere a una sezione specifica o, nelle app single-page, per indicare lo stato di route.
Il formato della query string in sé ha una biforcazione: il tradizionale ?key=value&key2=value2 con valori percent-encoded per RFC 3986, contro la più vecchia convenzione form-encoded application/x-www-form-urlencoded dove + significa uno spazio (originariamente per le sottomissioni di form HTML). La maggior parte dei parser gestisce entrambi, ma la conversione è asimmetrica: %20 si decodifica sempre in uno spazio; + si decodifica in uno spazio solo dentro una query string, mai dentro un path. Questo è uno dei bug di parsing URL più comuni in giro.
Breve storia dell'URL
L'URL, originariamente "Universal Document Identifier", poi "Universal Resource Locator", fu inventato da Tim Berners-Lee tra il suo memo "Information Management: A Proposal" del marzo 1989 al CERN (quello che il suo capo Mike Sendall annotò "Vague but exciting") e le prime pagine web pubblicamente navigabili dell'agosto 1991. L'URL canonico era http://info.cern.ch/hypertext/WWW/TheProject.html, postato il 6 agosto 1991. Le discussioni IETF del 1992 rinominarono UDI in URL per evitare una battaglia di vocabolario. RFC 1738 ("Uniform Resource Locators"), redatta da Berners-Lee, Masinter e McCahill, fu pubblicata nel dicembre 1994 come prima sintassi formale per gli URL. RFC 2396 seguì nell'agosto 1998, generalizzando gli URL nel concetto più ampio di URI. La specifica canonica attuale è RFC 3986 ("URI Generic Syntax"), pubblicata gennaio 2005, curata da Berners-Lee, Roy Fielding e Larry Masinter: uno STD 66 Internet Standard, il più alto livello di maturità dell'IETF. RFC 3986 è ciò che ogni parser di URL nominalmente prende come riferimento. In pratica i browser moderni divergono da RFC 3986 in numerosi casi limite, motivo per cui WHATWG mantiene una URL Living Standard separata su url.spec.whatwg.org; la specifica WHATWG mira esplicitamente a rendere obsolete RFC 3986 e RFC 3987 nel tempo, e le due divergono ancora su cose come la gestione dello spazio bianco finale, i set di percent-encoding e la normalizzazione Unicode.
Caratteri non riservati, riservati e percent-encoded
RFC 3986 §2.3 definisce i caratteri non riservati: gli unici caratteri garantiti sicuri in qualunque componente URI senza percent-encoding: A-Z, a-z, 0-9, trattino (-), punto (.), underscore (_) e tilde (~). 66 caratteri in totale. Tutto il resto è o un carattere riservato con significato strutturale in qualche componente: gen-delims (:/?#[]@) e sub-delims (!$&'()*+,;=): o "altro" e deve essere percent-encoded se appare in un URI. Il percent-encoding (RFC 3986 §2.1) prende la sequenza di byte di un carattere (in UTF-8 a meno che lo scheme non dica diversamente) e sostituisce ogni byte con %HH dove HH è il valore esadecimale a due cifre del byte. Quindi una é codificata in UTF-8 (byte 0xC3 0xA9) diventa %C3%A9; la parola russa привет diventa %D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82: due byte per carattere, sei triplette %XX e 36 caratteri percent-encoded di URL per sei lettere cirilliche.
I browser visualizzano i path percent-encoded in due modi: la maggior parte dei browser moderni (Chrome, Firefox, Safari) decodifica e renderizza i glifi Unicode originali nella barra degli indirizzi quando la codifica è UTF-8 valido, ma copia la forma percent-encoded letterale quando l'utente copia l'URL. I browser più vecchi e molti log web mostrano solo la forma percent-encoded, motivo per cui gli "URL Unicode belli" possono essere fuorvianti: sembrano belli nella barra degli indirizzi e brutti in qualunque testo dove vengono condivisi. RFC 3987 ("Internationalized Resource Identifiers", IRI), pubblicata gennaio 2005, ha formalizzato gli URL Unicode nella loro forma non codificata; Punycode (RFC 3492, marzo 2003) definisce come i nomi di dominio internazionalizzati vengono codificati in ASCII per il DNS, label per label: il label di primo livello cinese 中国 diventa xn--fiqs8s, quindi example.中国 si risolve a livello DNS come example.xn--fiqs8s. La dimostrazione canonica sono gli URL IRI di Wikipedia: https://ja.wikipedia.org/wiki/東京 funziona in qualsiasi browser moderno anche se la richiesta sottostante codifica il path come /wiki/%E6%9D%B1%E4%BA%AC.
L'URL Standard di WHATWG: cosa fanno effettivamente i browser
L'RFC 3986 dell'IETF dice una cosa; i browser fanno qualcosa di leggermente diverso. WHATWG (l'organismo di standard dei vendor di browser) mantiene una URL Living Standard separata su url.spec.whatwg.org che descrive la macchina a stati algoritmica che i browser eseguono effettivamente: inclusa la gestione dello spazio bianco iniziale, dei caratteri di controllo, dei set di percent-encoding che variano per componente e della normalizzazione Unicode. La specifica WHATWG è ciò che il costruttore URL del browser (new URL(input)) implementa, e su cosa Node.js, Deno e Bun si sono tutti convergiti per il loro parsing URL integrato. Il parser URL Ada, scritto in C++ da Yagiz Nizipli, Daniel Lemire e altri, è diventato il parser conforme a WHATWG che alimenta il parsing URL di Node.js da Node.js 18.16.0 (aprile 2023), sostituendo il vecchio percorso url.parse(); è misurabilmente più veloce di ogni implementazione precedente ed è lo standard de facto per il parsing URL ad alte prestazioni nel 2026. RFC 3986 e la specifica WHATWG non sono ancora completamente riconciliate, e la divergenza storica si vede ancora nei percorsi di codice legacy e nelle versioni di runtime più vecchie.
La query string e l'API URLSearchParams
La query string è tecnicamente solo "tutto dopo il ? e prima del #": la specifica non definisce in realtà come interpretarla. La convenzione ?key=value&key=value con separatori & è convenzione, non requisito. In pratica, due formati di query string dominano: application/x-www-form-urlencoded (il formato di sottomissione di form HTML default, dove + significa uno spazio) e la convenzione di query URI standard (dove lo spazio è sempre %20). L'API URLSearchParams del browser (parte dell'URL Living Standard di WHATWG, baseline in tutti i browser moderni dal 2017 circa) gestisce entrambi i formati in modo trasparente per il parsing ed emette la variante form-encoded quando si stringa. Le chiavi ripetute sono legali: ?tag=red&tag=blue&tag=green è valido, e URLSearchParams.getAll('tag') restituisce ['red', 'blue', 'green']. Diversi framework web gestiscono il caso delle chiavi ripetute in modo diverso: Rails ed Express raccolgono le chiavi ripetute in array, mentre PHP sovrascrive i valori precedenti con quelli successivi a meno che la chiave non usi la convenzione delle parentesi name[]: che è una fonte costante di bug cross-framework nelle integrazioni API.
Trabocchetti comuni del parsing URL
- Doppia codifica. Codificare un URL già codificato produce
%2520(il%stesso viene codificato come%25). Questo si presenta quando gli URL viaggiano attraverso più strati (frontend → backend → analytics) e ogni strato "utilmente" codifica ancora una volta. La correzione è decodificare fino in fondo prima di ricodificare una volta. - + contro %20 nelle query string.
+significa uno spazio dentro una query string form-encoded ma significa un letterale+dentro un path o fragment. Mescolare le convenzioni produce bug difficili da debuggare dove "John+Doe" diventa "John Doe" nella query ma resta come "John+Doe" nel path. - Sensibilità al maiuscolo/minuscolo. L'host è case-insensitive (
EXAMPLE.comedexample.comsono lo stesso host); il path è case-sensitive sulla maggior parte dei server (Linux/Unix) ma case-insensitive su altri (Windows IIS di default). Questo significa che lo stesso URL può risolvere in contenuti diversi a seconda del server. - IPv6 negli URL. Gli indirizzi IPv6 contengono due punti, che sono in conflitto con il separatore host:port. La correzione è avvolgere l'indirizzo IPv6 in parentesi quadre:
http://[2001:db8::1]:8080/path. Molti parser URL storicamente fallivano su questo; i browser moderni e il parser WHATWG lo gestiscono correttamente. - Token fragment OAuth. Il flusso implicit grant di OAuth 2.0 restituiva i token di accesso nel fragment dell'URL (
#access_token=...) così che non apparissero nei log del server. Le linee guida moderne di OAuth scoraggiano questo flusso in favore del codice di autorizzazione con PKCE, ma i sistemi legacy emettono ancora token fragment. - Non-identità del round-trip. Analizzare un URL e ri-stringificare non sempre produce la stringa originale: il parser normalizza (decodifica caratteri non riservati percent-encoded, mette l'host in minuscolo, ordina i parametri della query in alcune implementazioni). Non assumere che
parse(url).toString() === url.
Casi d'uso comuni
- Debug delle richieste API. Un endpoint REST con una lunga query string è difficile da leggere; analizzarla mostra ogni parametro sulla propria riga.
- Ispezione di callback OAuth. Gli URL del flusso di autenticazione trasportano state, code, scope e access token codificati che hanno bisogno di decodifica per il debug senza esporli a un server.
- Tracciamento di catene di redirect. Quando un URL reindirizza attraverso più URL intermedi, analizzare ognuno aiuta a seguire la catena e identificare dove un redirect si rompe.
- Audit di tag UTM. Gli URL di analytics (
?utm_source=...&utm_medium=...&utm_campaign=...) sono più facili da leggere parametro per parametro che come un muro di query string. - Audit di sicurezza. Cercare pattern di SQL injection o sequenze di traversal di path nei parametri URL; il parsing espone ogni valore separatamente per la review.
- Parsing di body form-encoded. Lo stesso formato usato nelle query string degli URL è usato nei body POST
application/x-www-form-urlencoded. - Ispezione di deep link. I deep link delle app mobile e i route delle web app codificano stato complesso nel path o nella query; il parsing rende la struttura visibile.
- Review di link affiliati / di condivisione. I link di tracciamento da campagne email o programmi affiliati trasportano URL di redirect codificati e ID di tracciamento che beneficiano della decodifica.
Privacy: gli URL trasportano veri segreti
Gli URL non sono generalmente trattati come segreti, ma spesso trasportano dati che lo sono. Gli URL di callback OAuth includono token di accesso. Gli URL di login magic-link includono token di autenticazione monouso. I link di password-reset includono token di reset. Gli URL di API interne includono hostname interni e percorsi di routing che rivelano l'infrastruttura. Anche gli URL di applicazione ordinari rivelano il comportamento dell'utente attraverso i parametri della query: termini di ricerca, selezioni di filtro, ID di profilo, identificatori di sessione. L'header Referer trapela l'URL precedente a ogni sito linkato, mitigato dall'header Referrer-Policy introdotto come W3C Candidate Recommendation nel 2017 (i default dei browser variano ancora). Gli URL finiscono nei log di accesso del server, nella cronologia del browser, nei segnalibri del browser, nei log della CDN, nelle pipeline di analytics, negli anteprime di link delle app di chat. Un parser URL lato server vede ogni URL incollato dentro; un parser solo browser no. Per gli URL di API interne, callback OAuth con token, link di password-reset, o qualsiasi URL che non vorresti vedere copiato sul disco di uno sconosciuto, un parser solo browser è l'architettura giusta. Verifica nel pannello Network di DevTools mentre analizzi, oppure metti la pagina offline (modalità aereo) dopo che si è caricata.
Domande frequenti
Quali parti contiene un URL?
Sei parti concettuali: scheme (https, http, ftp, mailto), userinfo (raro nell'uso moderno, perlopiù rimosso dai browser come mitigazione anti-phishing), host (dominio o IP), port (default a 80 per HTTP, 443 per HTTPS), path (gerarchia separata da slash), query (coppie chiave-valore dopo ?) e fragment (dopo #, mai inviato al server). La grammatica completa è in RFC 3986 §3 (gennaio 2005, STD 66) e nell'URL Living Standard di WHATWG.
Come decodifico i caratteri URL-encoded?
Il percent-encoding sostituisce caratteri non sicuri con un % seguito dal codice esadecimale del byte: uno spazio è %20, due punti è %3A, una barra è %2F, una e commerciale è %26, la chiocciola è %40. I caratteri multi-byte UTF-8 sono codificati byte per byte, quindi é diventa %C3%A9 (due byte). Il parser decodifica automaticamente tutti i caratteri percent-encoded nell'output visualizzato. Le funzioni JavaScript standard sono encodeURIComponent() per codificare i singoli valori e decodeURIComponent() per decodificare.
Cos'è un fragment URL?
Il fragment (tutto dopo #) è l'unica parte dell'URL elaborata interamente lato client: non viene mai inviata al server web nelle richieste HTTP. Scopo originale: scorrere il browser a un elemento ancora con quell'ID. Gli usi moderni includono lo stato di route delle applicazioni single-page (#/dashboard/profile), i token OAuth di flusso implicit (ora scoraggiati in favore del codice di autorizzazione con PKCE) e la navigazione di pagine PDF (file.pdf#page=5). Poiché i fragment non raggiungono il server, sono un posto dove nascondere valori che non dovrebbero apparire nei log del server.
Perché + a volte significa uno spazio e a volte significa +?
Esistono due convenzioni di codifica. application/x-www-form-urlencoded (il formato di sottomissione di form HTML di default) codifica gli spazi come +; il percent-encoding standard (per RFC 3986) codifica gli spazi come %20. Entrambi sono validi nelle query string; solo %20 è valido nei path e nei fragment. URLSearchParams gestisce entrambi in modo trasparente. Il bug cross-context sorge quando il codice usa encodeURIComponent (che codifica lo spazio come %20) per parametri di query che il server si aspetta in forma form-encoded, o viceversa.
Gestisce gli URL relativi?
Il parser si aspetta un URL completo con uno scheme. Per un percorso relativo come /api/users, anteponi un URL di base (https://example.com/api/users) per analizzarlo. Un certo parsing di URL relativi (risolvere contro un URL di base nel modo in cui il browser fa per gli attributi href) è nella roadmap: la forma a due argomenti del costruttore URL di WHATWG (new URL(relative, base)) gestisce questo ed è ciò che il codice di produzione dovrebbe usare.
I miei URL vengono inviati da qualche parte?
No. Il parsing gira interamente nel tuo browser tramite il costruttore URL di WHATWG: l'URL che incolli non lascia mai il tuo dispositivo. Verifica nel pannello Network di DevTools mentre clicchi Analizza, oppure metti la pagina offline (modalità aereo) dopo che si è caricata. Sicuro per URL di callback OAuth contenenti token di accesso, link di password-reset contenenti token monouso, URL di API interne che rivelano l'infrastruttura, o qualsiasi URL che non vorresti vedere copiato sul disco di uno sconosciuto.