Chaque partie d'une URL et ce qu'elle a le droit de contenir
Une URL est une chaîne unique qui indique quel protocole parler, à quelle machine le parler et quoi demander. La lire correctement revient surtout à savoir où s'arrête chaque partie, car les délimiteurs sont des caractères uniques et c'est le premier qui l'emporte.
https://alex:s3cret@www.example.co.uk:8443/docs/intro?lang=en&page=2#notes
│ │ │ │ │ │ └─ fragment
│ │ │ │ │ └──────────────── query
│ │ │ │ └──────────────────────────── path
│ │ │ └──────────────────────────────── port
│ │ └────────────────────────────────────────────────── host
│ └────────────────────────────────────────────────────────────── userinfo
└────────────────────────────────────────────────────────────────────── scheme
Les parties
| Partie | Délimitée par | À quoi elle sert | Parvient au serveur |
|---|---|---|---|
| Schéma | se termine au premier : | Quel protocole et quelles règles s'appliquent au reste | Détermine la connexion |
| Userinfo | après //, se termine à @ | Identifiants, dépréciés depuis longtemps pour http et https | Seulement si le client choisit de les envoyer |
| Hôte | se termine à :, /, ? ou # | Un nom de domaine ou une adresse IP à résoudre puis à joindre | Oui, dans l'en-tête Host |
| Port | après :, se termine à /, ? ou # | Le port TCP, 80 par défaut pour http et 443 pour https | Sert à établir la connexion |
| Chemin | commence à /, se termine à ? ou # | Quelle ressource sur cet hôte | Oui, dans la ligne de requête |
| Requête | commence à ?, se termine à # | Les paramètres de cette ressource | Oui, dans la ligne de requête |
| Fragment | commence à #, va jusqu'à la fin | Un emplacement à l'intérieur de la ressource | Non |
L'hôte et le port forment ensemble l'autorité. Le fragment fait exception : il est retiré avant la construction de la requête, si bien qu'un serveur ne le voit jamais. Les navigateurs s'en servent pour les ancres et le routage côté client, et certains flux d'authentification renvoient délibérément des jetons dans un fragment afin que ces jetons n'apparaissent jamais dans un journal serveur. Il finit tout de même dans l'historique du navigateur : il n'est donc pas privé, seulement non transmis.
Caractères réservés et non réservés
L'encodage pourcent écrit un octet sous la forme % suivi de deux chiffres hexadécimaux, à partir des octets UTF-8 du caractère. é devient %C3%A9, deux octets et deux échappements.
Quatre caractères sont non réservés au même titre que les lettres et les chiffres, et n'ont jamais besoin d'être encodés où que ce soit : - . _ ~. Tout le reste est soit réservé, c'est-à-dire porteur d'un rôle structurel quelque part dans une URL, soit obligatoirement encodé. L'ensemble réservé est : / ? # [ ] @ ainsi que ! $ & ' ( ) * + , ; =.
Le mot « réservé » ne signifie pas « toujours interdit ». Il signifie que le caractère est un délimiteur dans une partie donnée, et qu'il ne doit être encodé que dans les parties où il serait lu comme tel.
| Caractère | Dans un segment de chemin | Dans une valeur de requête |
|---|---|---|
| Espace | %20, toujours | %20 ou + |
/ | %2F, sinon le segment est coupé | Autorisé tel quel |
? | %3F, sinon le chemin s'arrête là | Autorisé tel quel |
# | %23, toujours | %23, toujours |
& et = | Autorisés tels quels | %26 et %3D, sinon la paire est coupée |
+ | Autorisé, et désigne un plus | %2B, sinon il risque d'être décodé comme un espace |
D'où la règle pratique : encodez le caractère qui mettrait fin à la partie où vous vous trouvez. Un espace dans un chemin doit s'écrire %20, parce qu'un espace brut termine l'URL dans la plupart des analyseurs, alors qu'un espace dans une valeur de requête peut s'écrire %20 ou avec l'ancien +. Un Encodeur d'URL est le moyen fiable d'y parvenir, car encoder une URL entière et encoder un seul composant sont deux opérations différentes, et c'est presque toujours la seconde que l'on veut.
Les chaînes de requête sont une convention
La spécification dit seulement que la requête est tout ce qui se trouve entre ? et #, pris parmi les caractères autorisés. La forme key=value&key=value vient de l'encodage des formulaires HTML, pas de la norme URI. Rien n'empêche un serveur d'analyser ?a:1;b:2 comme bon lui semble.
Comme cette forme n'est qu'une convention, les clés répétées n'ont pas de sens défini, et chaque pile technique a retenu sa propre réponse :
Comportement pour ?id=1&id=2 | Où |
|---|---|
| Les deux valeurs, sous forme de liste | Python parse_qs, Node querystring, Express |
| La première valeur seulement | Go Query().Get, Java getParameter |
| La dernière valeur seulement | PHP, Rails |
Réunies en 1,2 | Les collections de requête ASP.NET |
Les tableaux héritent du même problème. ids=1&ids=2, ids[]=1&ids[]=2, ids[0]=1&ids[1]=2 et ids=1,2 sont tous largement répandus, et seul le serveur décide de celui qu'il comprend. La notation à crochets doit être encodée pour être strictement légale (ids%5B%5D=1), même si la plupart des serveurs acceptent les crochets bruts. Choisissez la forme que documente le destinataire, puis tenez-vous-y.
Le signe plus mérite un avertissement à part. Dans des données application/x-www-form-urlencoded, c'est-à-dire un corps de requête encodé comme un formulaire, + signifie un espace. Dans un chemin d'URL, il signifie un plus littéral. Dans une chaîne de requête, cela dépend de l'analyseur, et la plupart des frameworks web y appliquent le décodage de formulaire : + devient donc silencieusement un espace. Toute valeur pouvant légitimement contenir un plus, comme une sortie Base64 standard ou un numéro de téléphone, doit être envoyée sous la forme %2B.
Références relatives
Une référence relative se résout par rapport à une URL de base en remplaçant tout ce qui suit la dernière / de cette base. Cette dernière barre oblique constitue toute la règle, et c'est pourquoi une barre finale compte.
| Base | Référence | Résultat |
|---|---|---|
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 |
La dernière ligne est une référence relative au schéma : deux barres obliques initiales conservent le schéma de la base et remplacent l'autorité. Une référence qui commence par ? conserve le chemin et abandonne l'ancienne requête, et une qui commence par # conserve les deux.
Quand deux URL désignent la même ressource
Le schéma et l'hôte sont insensibles à la casse : HTTPS://Example.COM et https://example.com sont donc une seule et même adresse. Tout ce qui suit l'hôte est sensible à la casse du point de vue de la norme, même si tel ou tel serveur choisit de l'ignorer.
Ces paires sont équivalentes :
https://example.com:443/aethttps://example.com/a, parce que le port est celui par défauthttps://example.comethttps://example.com/, parce qu'un chemin vide désigne la racine/a/%7Euseret/a/~user, parce que~est non réservé et que l'encoder en pourcent ne change rien
Ces paires ne le sont pas :
/docset/docs/, qui sont deux ressources différentes, même si la plupart des serveurs redirigent l'une vers l'autre/p?a=1&b=2et/p?b=2&a=1, puisque l'ordre des paramètres fait partie de la chaîne/index.htmlet/, sauf indication contraire du serveur
Les caches et les CDN prennent les octets exacts comme clé : un paramètre de suivi ajouté ou une requête réordonnée scinde donc un objet mis en cache en deux et divise par deux le taux de succès. Les moteurs de recherche traitent les variantes comme des pages dupliquées, à moins qu'un lien canonique ne désigne une forme choisie. La solution consiste à retenir une seule forme, à y rediriger les autres avec un 301, et à supprimer les paramètres qui ne changent pas la réponse.
Longueur, journaux et confidentialité
Aucune limite de longueur ne figure dans la spécification, mais il en existe partout ailleurs. Les réglages par défaut courants des serveurs plafonnent toute la ligne de requête à environ 8 Ko, certains proxys et boîtiers à 4 Ko, et les clients plus anciens à 2 Ko environ. Tout ce qui doit survivre aux clients de messagerie, aux QR codes et aux raccourcisseurs de liens gagne à rester sous les 2000 caractères.
L'argument le plus fort en faveur d'URL courtes est qu'une chaîne de requête n'est pas privée. Elle est inscrite dans les journaux d'accès, transmise à des tiers dans l'en-tête Referer, conservée dans l'historique du navigateur, enregistrée avec les favoris, et copiée chaque fois que quelqu'un partage le lien. Les jetons de session, les codes de réinitialisation de mot de passe, les clés d'API et les données personnelles n'y ont donc pas leur place. Les données volumineuses ou sensibles vont dans un corps de requête, qui n'a pas de plafond de taille en pratique et n'est pas journalisé par défaut.
Lire un lien avant de cliquer dessus
L'autorité commence après :// et se termine au tout premier /, ? ou #. Lisez cette portion, puis lisez ses deux ou trois dernières étiquettes. C'est là le véritable hôte, et rien de ce qui se trouve à sa gauche ne le change.
https://www.paypal.com@198.51.100.7/secure/login
^^^^^^^^^^^^^^ ^^^^^^^^^^^^
userinfo real host
Habillages courants à reconnaître :
- Le texte placé avant un
@est le userinfo, jamais l'hôte, et il peut reprendre n'importe quelle marque. paypal.com.secure-login.exampleest un sous-domaine desecure-login.example. Les étiquettes se lisent de droite à gauche.https://short.example/https://www.bank.com/loginplace une URL entière et convaincante dans le chemin.- Un hôte commençant par
xn--est du Punycode, la forme ASCII d'un nom de domaine internationalisé produite par IDNA.münchen.des'écritxn--mnchen-3ya.desur le réseau, ce qui est légitime, mais le même mécanisme rend les homographes possibles : leаcyrillique (U+0430) est visuellement identique aualatin, et un domaine qui l'emploie s'encode en quelque chose commexn--pple-43d.com. Les navigateurs affichent la forme Punycode quand une étiquette mélange les écritures, mais les règles varient et n'offrent aucune garantie. - Des délimiteurs encodés comme
%2Fou%40à l'intérieur d'un hôte sont une tentative délibérée d'égarer un analyseur.
Coller le lien dans un Analyseur d'URL tranche la question en une seule étape, car un vrai analyseur applique la même règle du premier délimiteur que le navigateur et affiche l'hôte isolément. Pour vérifier de nombreux liens d'un coup, un Testeur de regex permet de confirmer rapidement lesquels possèdent bien l'autorité attendue.