Ogni parte di un URL e cosa può contenere
Un URL è una singola stringa che dice quale protocollo parlare, con quale macchina parlarlo e cosa chiedere. Leggerlo correttamente è soprattutto una questione di sapere dove finisce ciascuna parte, perché i delimitatori sono caratteri singoli e vince il primo.
https://alex:s3cret@www.example.co.uk:8443/docs/intro?lang=en&page=2#notes
│ │ │ │ │ │ └─ fragment
│ │ │ │ │ └──────────────── query
│ │ │ │ └──────────────────────────── path
│ │ │ └──────────────────────────────── port
│ │ └────────────────────────────────────────────────── host
│ └────────────────────────────────────────────────────────────── userinfo
└────────────────────────────────────────────────────────────────────── scheme
Le parti
| Parte | Delimitata da | A cosa serve | Arriva al server |
|---|---|---|---|
| Schema | finisce al primo : | Quale protocollo e quali regole valgono per il resto | Determina la connessione |
| Userinfo | dopo //, finisce a @ | Credenziali, deprecate da tempo per http e https | Solo se il client sceglie di inviarle |
| Host | finisce a :, /, ? o # | Un nome di dominio o un indirizzo IP da risolvere e a cui connettersi | Sì, nell'header Host |
| Porta | dopo :, finisce a /, ? o # | La porta TCP, che in assenza vale 80 per http e 443 per https | Usata per la connessione |
| Percorso | inizia a /, finisce a ? o # | Quale risorsa su quell'host | Sì, nella riga di richiesta |
| Query | inizia a ?, finisce a # | Parametri per quella risorsa | Sì, nella riga di richiesta |
| Frammento | inizia a #, arriva fino alla fine | Una posizione all'interno della risorsa | No |
Host e porta insieme formano l'autorità. Il frammento è l'eccezione: viene rimosso prima che la richiesta sia costruita, quindi un server non lo vede mai. I browser lo usano per le ancore e per il routing lato client, e alcuni flussi di autenticazione restituiscono deliberatamente i token in un frammento, così quei token non compaiono mai in un log del server. Finisce comunque nella cronologia del browser, quindi non è privato, solo non inviato.
Caratteri riservati e non riservati
La codifica percentuale scrive un byte come % seguito da due cifre esadecimali, usando i byte UTF-8 del carattere. é diventa %C3%A9, due byte e due sequenze di escape.
Quattro caratteri sono non riservati accanto a lettere e cifre, e non vanno mai codificati da nessuna parte: - . _ ~. Tutto il resto è riservato, cioè ha un compito strutturale da qualche parte in un URL, oppure va codificato. L'insieme riservato è : / ? # [ ] @ e ! $ & ' ( ) * + , ; =.
La parola «riservato» non significa «sempre vietato». Significa che il carattere è un delimitatore in qualche parte, e va codificato solo nelle parti in cui verrebbe letto come tale.
| Carattere | In un segmento di percorso | In un valore di query |
|---|---|---|
| Spazio | %20, sempre | %20 oppure + |
/ | %2F, altrimenti divide il segmento | Valido così com'è |
? | %3F, altrimenti il percorso finisce lì | Valido così com'è |
# | %23, sempre | %23, sempre |
& e = | Validi così come sono | %26 e %3D, altrimenti la coppia si spezza |
+ | Valido, e significa un più | %2B, altrimenti può essere decodificato come uno spazio |
La regola pratica è questa: codifica il carattere che chiuderebbe la parte in cui ti trovi. Uno spazio in un percorso deve essere %20, perché nella maggior parte dei parser uno spazio grezzo chiude l'URL, mentre uno spazio in un valore di query può essere %20 o il più vecchio +. Un Codificatore URL è il modo affidabile per farlo, dato che codificare un URL intero e codificare un singolo componente sono operazioni diverse, e quasi sempre serve la seconda.
Le query string sono una convenzione
La specifica dice soltanto che la query è tutto ciò che sta fra ? e #, scelto fra i caratteri ammessi. La forma key=value&key=value viene dalla codifica dei form HTML, non dallo standard URI. Nulla impedisce a un server di interpretare ?a:1;b:2 come preferisce.
Poiché la forma è una convenzione, le chiavi ripetute non hanno un significato definito, e ogni stack ha scelto la propria risposta:
Comportamento con ?id=1&id=2 | Dove |
|---|---|
| Entrambi i valori, come lista | Python parse_qs, Node querystring, Express |
| Solo il primo valore | Go Query().Get, Java getParameter |
| Solo l'ultimo valore | PHP, Rails |
Uniti in 1,2 | Le request collection di ASP.NET |
Gli array ereditano lo stesso problema. ids=1&ids=2, ids[]=1&ids[]=2, ids[0]=1&ids[1]=2 e ids=1,2 sono tutti molto diffusi, e solo il server decide quale di essi capisce. La notazione con parentesi quadre va codificata per essere rigorosamente valida (ids%5B%5D=1), anche se la maggior parte dei server accetta le parentesi grezze. Scegli la forma documentata da chi riceve, poi mantienila coerente.
Il segno più merita un avviso a parte. Nei dati application/x-www-form-urlencoded, cioè un corpo di richiesta codificato come un form, + significa spazio. In un percorso URL significa un più letterale. In una query string dipende dal parser, e la maggior parte dei framework web vi applica la decodifica dei form, quindi + diventa silenziosamente uno spazio. Qualsiasi valore che possa legittimamente contenere un più, come l'output Base64 standard o un numero di telefono, va inviato come %2B.
Riferimenti relativi
Un riferimento relativo si risolve rispetto a un URL di base sostituendo tutto ciò che segue l'ultima / della base. Quell'ultima barra è tutta la regola, ed è il motivo per cui la barra finale conta.
| Base | Riferimento | Risultato |
|---|---|---|
https://ex.com/docs/intro | guide | https://ex.com/docs/guide |
https://ex.com/docs/intro/ | guide | https://ex.com/docs/intro/guide |
https://ex.com/docs/intro | /guide | https://ex.com/guide |
https://ex.com/docs/intro/ | ../guide | https://ex.com/docs/guide |
https://ex.com/docs/intro?a=1 | ?b=2 | https://ex.com/docs/intro?b=2 |
https://ex.com/docs/intro?a=1 | #top | https://ex.com/docs/intro?a=1#top |
https://ex.com/docs/intro | //cdn.ex.com/x.js | https://cdn.ex.com/x.js |
L'ultima riga è un riferimento relativo allo schema: due barre iniziali mantengono lo schema della base e sostituiscono l'autorità. Un riferimento che inizia con ? mantiene il percorso e scarta la vecchia query, mentre uno che inizia con # li mantiene entrambi.
Quando due URL sono la stessa risorsa
Schema e host non distinguono fra maiuscole e minuscole, quindi HTTPS://Example.COM e https://example.com sono un solo indirizzo. Tutto ciò che segue l'host distingue fra maiuscole e minuscole per quanto riguarda lo standard, anche se un server specifico può decidere di ignorare la differenza.
Queste coppie sono equivalenti:
https://example.com:443/aehttps://example.com/a, perché la porta è quella predefinitahttps://example.comehttps://example.com/, perché un percorso vuoto significa la radice/a/%7Eusere/a/~user, perché~non è riservato e codificarlo in percentuale non cambia nulla
Queste invece non lo sono:
/docse/docs/, che sono risorse diverse, anche se la maggior parte dei server reindirizza l'una all'altra/p?a=1&b=2e/p?b=2&a=1, dato che l'ordine dei parametri fa parte della stringa/index.htmle/, a meno che il server non dica il contrario
Le cache e le CDN si basano sui byte esatti, quindi un parametro di tracciamento aggiunto o una query riordinata spezza un oggetto in cache in due e dimezza il tasso di hit. I motori di ricerca trattano le varianti come pagine duplicate, a meno che un link canonical non punti a una forma scelta. La soluzione è scegliere una forma unica, reindirizzare le altre con un 301 e togliere i parametri che non cambiano la risposta.
Lunghezza, log e privacy
Nella specifica non esiste alcun limite di lunghezza, ma i limiti esistono ovunque altrove. Le impostazioni predefinite più comuni dei server fermano l'intera riga di richiesta intorno agli 8 KB, alcuni proxy e apparati a 4 KB e i client più vecchi intorno ai 2 KB. Tutto ciò che deve sopravvivere a client di posta, codici QR e accorciatori di link sta più al sicuro sotto i 2000 caratteri circa.
L'argomento più forte per tenere corti gli URL è che una query string non è privata. Viene scritta nei log di accesso, passata a terze parti nell'header Referer, conservata nella cronologia del browser, salvata con i segnalibri e copiata ogni volta che qualcuno condivide il link. Token di sessione, codici di reimpostazione della password, chiavi API e dati personali non vanno quindi messi lì. I dati voluminosi o sensibili vanno nel corpo della richiesta, che non ha un tetto pratico di dimensione e per impostazione predefinita non finisce nei log.
Leggere un link prima di cliccarlo
L'autorità inizia dopo :// e finisce al primissimo /, ? o #. Leggi quel tratto, poi leggi le sue ultime due o tre etichette. Quello è l'host reale, e niente di ciò che sta alla sua sinistra lo cambia.
https://www.paypal.com@198.51.100.7/secure/login
^^^^^^^^^^^^^^ ^^^^^^^^^^^^
userinfo real host
Travestimenti comuni da riconoscere:
- Il testo che precede una
@è userinfo, mai l'host, e può essere il nome di qualunque marchio. paypal.com.secure-login.exampleè un sottodominio disecure-login.example. Le etichette si leggono da destra a sinistra.https://short.example/https://www.bank.com/loginmette un intero URL convincente dentro il percorso.- Un host che inizia con
xn--è in Punycode, la forma ASCII di un nome di dominio internazionalizzato prodotta da IDNA.münchen.desulla rete èxn--mnchen-3ya.de, il che è legittimo, ma lo stesso meccanismo rende possibili gli omografi: laаcirillica (U+0430) è visivamente identica allaalatina, e un dominio che la usa si codifica in qualcosa comexn--pple-43d.com. I browser mostrano la forma Punycode quando un'etichetta mescola più alfabeti, anche se le regole variano e non sono una garanzia. - I delimitatori codificati come
%2Fo%40all'interno di un host sono un tentativo deliberato di confondere un parser.
Incollare il link in un Analizzatore di URL chiude la questione in un passaggio, perché un vero parser applica la stessa regola del primo delimitatore che vince che applicherà il browser e mostra l'host per conto suo. Per controllare molti link in una volta, un Tester di regex è un modo rapido per confermare quali di essi abbiano davvero l'autorità che ti aspetti.