Guide
Encodage URL expliqué : percent-encoding, query strings et les pièges
La moitié des bugs de gestion des URL vient d’encoder la mauvaise chose — ou de l’encoder deux fois.
By Buğra SözeriPublished
L’encodage URL est l’un de ces sujets qui semble trivial jusqu’à ce que la production commence à renvoyer 400 Bad Requestsur chaque recherche contenant un accent français. Les règles sont simples, mais elles sont dispersées dans quatre spécifications qui ne sont pas entièrement d’accord, et les helpers JavaScript ont des noms qui induisent activement en erreur. Ce guide regroupe les règles en un seul endroit et marque les endroits où les standards divergent silencieusement.
Ce que “encodage URL” signifie réellement
Les URL sont limitées à un petit sous-ensemble d’ASCII. Tout caractère hors de cet ensemble — ou tout caractère qui a une signification structurelle que le parseur interpréterait autrement — doit être représenté par un ou plusieurs octets préfixés par %. C’est le percent-encoding, et les règles sur ce qu’il faut encoder viennent de RFC 3986. Vous pouvez essayer n’importe lequel des exemples ci-dessous avec notre encodeur URL.
RFC 3986 divise les caractères en trois groupes. L’ensemble non réservé est A-Z a-z 0-9 - . _ ~; ceux-ci sont toujours sûrs et n’ont jamais besoin d’encodage. L’ensemble réservé (: / ? # [ ] @ ! $ & ' ( ) * + , ; =) a une signification structurelle dans une partie de l’URL ; encodez-les quand vous voulez le caractère littéral, laissez-les seuls quand vous voulez le délimiteur. Tout le reste — y compris l’espace, ", <, >,{, et tout le non-ASCII — doit être percent-encodé.
Où chaque caractère doit être encodé
Le même octet peut nécessiter ou non un encodage selon le composant de l’URL où il apparaît. Un segment de chemin permet : et @non encodés ; une valeur de requête n’a pas besoin d’encoder / ou ?(car le parseur s’arrête à #) ; un fragment est le composant le plus permissif de tous. En pratique, presque tout le monde ignore ces distinctions et encode tout ce qui est en dehors de l’ensemble non réservé lors de l’interpolation d’une valeur dans une URL. C’est conservateur et correct.
L’endroit où cela compte : n’encodez pas les caractères / qui séparent vos segments de chemin. Encodez chaque segment, puis rejoignez avec /. Si vous encodez aussi le slash, vous obtenez %2F dans le chemin, ce que la plupart des serveurs refusera pour des raisons de sécurité (cela permettrait autrement aux attaquants de faire passer des séquences .. devant les gardes de répertoire).
encodeURI vs encodeURIComponent en JavaScript
JavaScript intègre deux encodeurs, et la différence entre eux est la source d’une quantité énorme de temps de débogage perdu.
encodeURI(valeur)suppose que valeur est déjà une URL complète. Il laisse les caractères réservés (; / ? : @ & = + $ , #) seuls pour que l’URL reste parsable. Il encode les espaces, les accents et le petit ensemble de caractères jamais autorisés dans une URL.encodeURIComponent(valeur)suppose que valeurest un seul composant inséré dans une URL — généralement un segment de chemin ou une valeur de requête. Il encode tout sauf l’ensemble non réservé plus! ' ( ) *.
La règle empirique : si vous construisez une URL par concaténation, utilisez encodeURIComponent sur chaque valeur interpolée. Si vous normalisez une URL déjà construite, encodeURIest le bon outil. Il n’y a presque aucun cas où vous voulez appliquer les deux.
Données de formulaire : l’exception +/espace
Les soumissions de formulaires HTML encodent avec le type de média application/x-www-form-urlencoded, défini dans le standard URL WHATWG. Ce format diverge de RFC 3986 de deux façons. Premièrement, les espaces deviennent + au lieu de %20. Deuxièmement, un + littéral dans la valeur est encodé comme %2B pour que le décodeur puisse les distinguer.
%20 et + se décodent tous deux en espace en pratique — chaque parseur côté serveur mainstream accepte les deux dans une query string. Mais les émetteurs doivent être cohérents. Utilisez + dans les corps de formulaire et dans les query strings qui imitent les corps de formulaire ; utilisez %20 ailleurs, surtout dans les segments de chemin où + est un signe plus littéral.
Tableaux et objets imbriqués : il n’y a pas de standard
Les query strings ont été conçues pour des paires clé=valeur plates. Les structures de données imbriquées doivent être aplaties, et la règle d’aplatissement est une décision par framework. Les quatre conventions en usage répandu :
- Clés répétées.
?tag=js&tag=tsdevient un tableau sur le serveur. La convention la plus simple ; par défaut dans Express, Go'snet/urlet la plupart des parseurs Node. - Crochets PHP.
?tag[]=js&tag[]=tspour les tableaux ;?user[name]=alexpour les objets imbriqués. Les crochets doivent eux-mêmes être percent-encodés (%5B/%5D) pour être strictement conformes aux standards, bien que la plupart des serveurs acceptent la forme brute. - Notation par points Rails.
?user.name=alex&user.age=30. Moins courante aujourd’hui ; apparaît dans certaines anciennes API Ruby et .NET. - Séparée par virgules.
?tag=js,ts. Courante dans les REST API (OpenAPI la liste comme style form/explode=false). Concise mais ambiguë si les valeurs elles-mêmes peuvent contenir des virgules.
Choisissez une convention par API et documentez-la. Les bugs les plus douloureux dans ce domaine viennent d’un client qui émet des crochets parlant à un serveur qui attend des clés répétées — les deux semblent fonctionner, mais l’un d’eux ne garde silencieusement que la dernière valeur.
CJK, emoji et autres non-ASCII
Chaque caractère hors ASCII doit être encodé comme ses octets UTF-8, puis percent-encodé octet par octet. Le caractère 北 (U+5317) est les trois octets E5 8C 97 en UTF-8, qui deviennent %E5%8C%97 dans une URL. Les emoji suivent la même règle :🔥 (U+1F525) est les quatre octets F0 9F 94 A5, encodés comme %F0%9F%94%A5.
Les systèmes legacy émettent parfois le même caractère dans un encodage différent — GBK pour le chinois, Shift-JIS pour le japonais, Windows-1251 pour le cyrillique. Si vous intégrez avec un backend d’avant 2010 et que votre texte décodé est illisible, essayez d’interpréter les octets comme Windows-1252 ou l’encodage natif de la locale avant de supposer que l’URL est corrompue.
Noms de domaine internationalisés
Les noms d’hôte suivent une règle différente. Les noms d’hôte non-ASCII sont encodés en Punycode (RFC 3492), pas en percent-encoding. Le domaine 例え.jp devient xn--r8jz45g.jpdans la requête DNS réelle. Les navigateurs affichent la forme Unicode pour les scripts reconnus mais transmettent la forme Punycode sur le réseau. Si vous construisez une URL avec un hôte non-ASCII, convertissez l’hôte avec un encodeur Punycode, pas l’encodeur URL.
Pièges courants
- Double encodage. Encoder une valeur, puis passer le résultat dans un autre encodeur, produit
%2520au lieu de%20. Suivez chaque étape d’encodage explicitement ; n’encodez jamais “par précaution” une valeur que vous n’avez pas produite. - Les fragments de hachage sont côté client uniquement. Tout après
#n’est jamais envoyé au serveur. Mettre de l’état dans le fragment est un choix délibéré (confidentialité, routage SPA) ; le faire par accident cache des bugs jusqu’à ce que le serveur en dépende. - Les plus dans les liens mailto.
mailto:[email protected]fonctionne car les URL mailto utilisent les règles RFC 3986, pas l’encodage de formulaire. Construisez les liens mailto à la main, pas avec un encodeur de formulaire. - L’ensemble non réservé exclut historiquement le tilde. RFC 2396 (prédécesseur de 3986) traitait
~comme réservé. Les anciens encodeurs émettent encore%7E; le standard moderne le considère non réservé. Les deux se décodent de la même façon. - Les limites de longueur sont réelles. La plupart des serveurs plafonnent les URL à 8 Ko. Les navigateurs varient de 2 Ko (IE plus ancien) à 32 Ko (Chrome moderne). Si votre query string approche cette plage, déplacez la charge utile dans un corps POST.
Un flux de travail pratique
Lors de la construction d’URL en code : construisez un objet représentant les composants (schéma, hôte, segments de chemin, map de requête), puis sérialisez une seule fois avec une bibliothèque de confiance — URL et URLSearchParams dans les navigateurs, les équivalents dans tous les autres langages majeurs. Les URL assemblées à la main sont les bugs.
Rappelez-vous que l’URL encodée n’est que la ligne de requête ; la même couche HTTP porte des en-têtes que votre code échappe rarement manuellement mais doit quand même traiter avec soin. L’en-tête Refererdivulgue l’URL précédente (y compris sa query string) à l’hôte suivant sauf si vous la supprimez avec une politique de référent, la chaîne User-Agentpermet aux serveurs de varier le comportement par client, et l’ adresse IP de connexion arrive dans la couche TCP ou — derrière un proxy — dans un en-tête X-Forwarded-For. Tout ce que vous mettez dans la query string est visible par les trois.
Lors du débogage d’une URL encodée que quelqu’un vous a envoyée, collez-la dans notre encodeur/décodeur URL et basculez la direction. L’outil décode une fois par clic, donc si vous devez cliquer deux fois pour obtenir un texte lisible, vous avez confirmé un double encodage en amont.
La conclusion honnête
Utilisez encodeURIComponentsur chaque valeur interpolée, jamais sur une URL complète. Faites correspondre votre encodage de formulaire (espace-comme-plus) à votre contexte de transport. Choisissez une convention de tableau par API et documentez-la. Faites confiance à UTF-8. Et quand quelque chose semble doublement encodé, c’est presque certainement le cas — tracez le second encodeur plutôt que de le masquer avec un passage de décodage.
Frequently asked questions
- Dois-je encoder toute l’URL ou seulement des parties ?
- Encodez uniquement les parties que vous contrôlez — segments de chemin et valeurs de requête. N’encodez jamais une URL entière avec encodeURIComponent car cela va mutiler le séparateur de schéma (`://`) et les délimiteurs `?` et `&`. Encodez chaque segment de chemin et chaque valeur de requête indépendamment, puis assemblez-les.
- Pourquoi mon URL affiche-t-elle + au lieu de %20 pour les espaces ?
- C’est le comportement `application/x-www-form-urlencoded`, utilisé par les soumissions de formulaires HTML. Dans ce type de média, un espace est encodé comme `+` et un `+` littéral est encodé comme `%2B`. Dans les chemins URL RFC 3986, les espaces sont toujours `%20`. Les serveurs décodent généralement les deux, mais si vous construisez une URL manuellement, préférez `%20` partout sauf dans les corps de formulaire.
- encodeURI() est-il jamais le bon choix en JavaScript ?
- Rarement. `encodeURI()` laisse les caractères réservés comme `?`, `#`, `&` et `/` intacts car il suppose que vous encodez une URL complète. Ce n’est presque jamais ce que vous voulez réellement — vous voulez généralement encoder une valeur qui sera insérée dans une URL, ce qui est le rôle d’`encodeURIComponent`. Utilisez `encodeURI` uniquement pour normaliser une URL tapée par un utilisateur contenant des caractères non-ASCII.
- Comment les caractères CJK sont-ils encodés ?
- RFC 3986 exige UTF-8 suivi du percent-encoding de chaque octet. Le caractère `日` (U+65E5) est `E6 97 A5` en UTF-8, donc il s’encode en `%E6%97%A5`. Les anciens systèmes utilisaient parfois GBK ou Shift-JIS, ce qui explique pourquoi le scraping de sites CJK legacy produit parfois `%C8%D5` pour le même caractère. Les navigateurs modernes émettent toujours UTF-8.
- Comment encoder des tableaux dans les query strings ?
- Il n’existe pas de standard unique. PHP et Rails utilisent la notation entre crochets (`tags[]=a&tags[]=b`) ; Express et la plupart des frameworks Node acceptent soit les crochets soit les clés répétées (`tags=a&tags=b`) ; certaines API exigent une liste séparée par des virgules (`tags=a,b`). Vérifiez le parseur du consommateur avant de choisir une convention et documentez-la dans votre référence API.
- Pourquoi est-ce que j’obtiens des valeurs doublement encodées comme %2520 ?
- Quelque chose a encodé la valeur, puis quelque chose d’autre a encodé le résultat. `%20` (un espace encodé) est devenu `%2520` car le `%` a été ré-encodé en `%25`. La correction consiste à trouver la deuxième étape d’encodage et à la supprimer — ne décodez jamais puis ré-encodez comme solution de contournement, car vous perdrez des informations pour les valeurs qui contenaient légitimement `%25`.
Related
Published May 31, 2026