encodeURI ou encodeURIComponent : laquelle utiliser ?
encodeURIComponent encode 11 caractères qu'encodeURI laisse intacts, et ce sont ces 11 caractères qui décident si une valeur reste bien à l'intérieur de son paramètre.
JavaScript propose deux fonctions natives pour l'encodage URL (percent-encoding), et elles ne diffèrent que sur un point : la part de la syntaxe d'URL qu'elles laissent intacte. encodeURI part du principe qu'on lui passe une URL complète et conserve les caractères qui donnent sa structure à l'URL, comme /, ? et &. encodeURIComponent part du principe qu'on lui passe une seule donnée et encode aussi ces caractères, pour que la donnée ne puisse pas être prise pour de la structure. En bref : pour insérer une valeur dans une URL, utilisez encodeURIComponent ou, mieux encore, l'API URL. Si vous découvrez le sujet, notre guide de l'encodage URL en présente les bases.
La différence exacte
Les deux fonctions convertissent en UTF-8 chaque caractère qu'elles ne laissent pas intact, puis écrivent chaque octet sous la forme % suivi de deux chiffres hexadécimaux en majuscules : é devient ainsi %C3%A9 et € devient %E2%82%AC. La seule différence tient à l'ensemble des caractères ASCII imprimables que chacune laisse intacts :
| Caractères | encodeURI | encodeURIComponent |
|---|---|---|
A-Z a-z 0-9 - _ . ! ~ * ' ( ) | Laissés intacts | Laissés intacts |
# $ & + , / : ; = ? @ | Laissés intacts | Encodés |
Espace et " % < > [ \ ] ^ ` { | } | Encodés | Encodés |
| Caractères de contrôle et tout ce qui sort de l'ASCII | Encodés en octets UTF-8 | Encodés en octets UTF-8 |
Tout se joue sur la ligne du milieu. Ces 11 caractères séparent les parties d'une URL, si bien que les laisser intacts à l'intérieur d'une valeur change le sens de l'URL :
const s = 'Tom & Jerry/2?';
encodeURI(s); // 'Tom%20&%20Jerry/2?'
encodeURIComponent(s); // 'Tom%20%26%20Jerry%2F2%3F'
Quand utiliser l'une ou l'autre
Utilisez encodeURIComponent pour tout ce que vous insérez dans une URL : la valeur ou le nom d'un paramètre de requête, un segment de chemin, un fragment, ou une URL entière passée en paramètre, comme redirect=. Voici ce qui se passe avec l'autre fonction :
const q = 'rock & roll';
// Wrong: the & starts a new parameter
encodeURI('https://example.com/search?q=' + q);
// 'https://example.com/search?q=rock%20&%20roll'
// The server sees q = 'rock ' and a second parameter ' roll'
// Right
'https://example.com/search?q=' + encodeURIComponent(q);
// 'https://example.com/search?q=rock%20%26%20roll'
encodeURI ne sert que dans un cas précis : vous disposez déjà d'une URL complète et correctement structurée, qui contient seulement des espaces ou des caractères non ASCII, comme https://example.com/café?name=Zoë. Même dans ce cas, elle cache deux pièges :
- Elle encode
%. Une URL déjà partiellement encodée se retrouve encodée deux fois :encodeURI('https://example.com/a%20b')renvoiehttps://example.com/a%2520b. - Elle encode
[et]. Ces crochets entourent les adresses IPv6 :http://[::1]:8080/devient donchttp://%5B::1%5D:8080/, qui n'est plus un hôte valide.
Si vous construisez une URL à partir de ses parties, new URL() est en général le meilleur outil : il encode chaque partie selon l'endroit où elle se trouve.
const url = new URL('https://example.com/search');
url.searchParams.set('q', 'café & thé');
url.href; // 'https://example.com/search?q=caf%C3%A9+%26+th%C3%A9'
Comparaison avec la RFC 3986
La RFC 3986 définit deux groupes de caractères. Les caractères non réservés (section 2.3) n'ont jamais besoin d'être encodés : les lettres, les chiffres et - . _ ~. Les caractères réservés (section 2.2) jouent un rôle syntaxique : les délimiteurs généraux : / ? # [ ] @ et les sous-délimiteurs ! $ & ' ( ) * + , ; =.
Aucune des deux fonctions JavaScript ne suit exactement ce découpage. encodeURIComponent laisse cinq sous-délimiteurs non encodés : ! ' ( ) *. Les navigateurs et la plupart des serveurs n'y prêtent pas attention, contrairement aux systèmes qui calculent une signature sur l'URL encodée. Les signatures OAuth 1.0 (RFC 5849) et AWS Signature Version 4 exigent toutes deux que chaque caractère soit encodé, à l'exception des caractères non réservés : une valeur qui contient une apostrophe ou des parenthèses peut donc produire une signature qui ne correspond pas. La correction habituelle :
function encodeRFC3986(str) {
return encodeURIComponent(str).replace(/[!'()*]/g,
c => '%' + c.charCodeAt(0).toString(16).toUpperCase());
}
encodeRFC3986("it's (fine)!*"); // 'it%27s%20%28fine%29%21%2A'
URLSearchParams et le signe plus
URLSearchParams, et donc url.searchParams, n'utilise ni l'une ni l'autre de ces fonctions. Il suit le format application/x-www-form-urlencoded qu'envoient les formulaires HTML, qui diffère sur deux points : une espace devient +, et seuls les lettres, les chiffres et * - . _ restent intacts, si bien que ! ' ( ) ~ sont eux aussi encodés.
encodeURIComponent('a b+c'); // 'a%20b%2Bc'
new URLSearchParams({ q: 'a b+c' }).toString(); // 'q=a+b%2Bc'
Les deux résultats sont corrects dans une chaîne de requête. Les ennuis commencent quand le côté qui encode et le côté qui décode ne suivent pas la même règle. decodeURIComponent ne transforme pas + en espace : decodeURIComponent('a+b%2Bc') renvoie a+b+c, alors que new URLSearchParams('q=a+b%2Bc').get('q') renvoie a b+c. Décodez les chaînes de requête avec URLSearchParams, ou remplacez + par une espace avant d'appeler decodeURIComponent. Dans un chemin, + n'est qu'un simple signe plus.
decodeURI vs decodeURIComponent
Les fonctions de décodage sont le miroir des fonctions d'encodage. decodeURIComponent décode toutes les séquences d'échappement. decodeURI laisse intactes les séquences des caractères que encodeURI n'aurait pas encodés, # $ & + , / : ; = ? @, afin que le décodage d'une URL ne modifie pas sa structure : decodeURI('%C3%A9%20%2F%3F') renvoie é %2F%3F, tandis que decodeURIComponent renvoie é /?.
Les deux lèvent URIError: URI malformed (le libellé de V8, utilisé par Chrome et Node.js) lorsqu'un % n'est pas suivi de deux chiffres hexadécimaux, comme dans 100%, ou lorsque les octets ne forment pas de l'UTF-8 valide, comme %E9 venant d'un système qui a encodé é en Latin-1. Les fonctions d'encodage lèvent la même erreur face à une moitié isolée de paire de substitution (surrogate pair), par exemple dans une chaîne coupée au milieu d'un emoji.
escape() n'est pas une alternative
escape() est une fonction héritée, conservée pour le code ancien. Elle laisse @ * _ + - . / intacts et n'utilise pas UTF-8 : les caractères jusqu'à U+00FF deviennent un seul octet Latin-1, et tous ceux au-delà une séquence non standard %u. escape('café €') renvoie caf%E9%20%u20AC, que les serveurs qui attendent de l'UTF-8 ne savent pas décoder, et decodeURIComponent('%E9') lève une erreur. Remplacez-la par encodeURIComponent, et unescape() par decodeURIComponent, sans oublier que les données déjà stockées dans l'ancien format doivent être converties.
Le double encodage
Toutes les fonctions d'encodage encodent le % lui-même en %25, si bien qu'en appliquer une deux fois produit des séquences comme %2520 : encodeURIComponent(encodeURIComponent('a b')) renvoie a%2520b. Encodez les valeurs brutes une seule fois, au moment où elles entrent dans l'URL, et n'encodez jamais une chaîne reçue déjà encodée. Le seul cas légitime est celui d'une URL placée dans une autre URL : encoder https://example.com/?q=a%20b comme paramètre donne correctement ...q%3Da%2520b, et le destinataire la décode une fois pour retrouver l'URL intérieure.
Pour comparer les deux modes sur vos propres données, l'encodeur d'URL propose un sélecteur entre encodeURIComponent et encodeURI. Le décodeur d'URL décode au fil de la saisie et traite + comme une espace, à la manière des données de formulaire : une valeur qui contient un vrai signe plus doit donc le porter sous la forme %2B.