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èresencodeURIencodeURIComponent
A-Z a-z 0-9 - _ . ! ~ * ' ( )Laissés intactsLaissés intacts
# $ & + , / : ; = ? @Laissés intactsEncodés
Espace et " % < > [ \ ] ^ ` { | }EncodésEncodés
Caractères de contrôle et tout ce qui sort de l'ASCIIEncodés en octets UTF-8Encodé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') renvoie https://example.com/a%2520b.
  • Elle encode [ et ]. Ces crochets entourent les adresses IPv6 : http://[::1]:8080/ devient donc http://%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.

Questions Fréquentes

Quels caractères encodeURIComponent n'encode-t-il pas ?

Les lettres, les chiffres et - _ . ! ~ * ' ( ). Tout le reste est encodé en octets UTF-8 au format pourcent, y compris / ? & = # et l'espace.

Faut-il utiliser encodeURI sur une URL complète ?

Seulement si l'URL est déjà correctement structurée et contient simplement des espaces ou des caractères non ASCII. Pour construire une URL à partir de ses parties, encodez chaque partie avec encodeURIComponent ou utilisez les API URL et URLSearchParams.

Pourquoi URLSearchParams produit-il + au lieu de %20 ?

Il utilise le format application/x-www-form-urlencoded des formulaires HTML, dans lequel + représente une espace. Les serveurs décodent les chaînes de requête de cette façon, donc les deux formes fonctionnent dans une chaîne de requête, mais decodeURIComponent seul ne retransforme pas + en espace.

escape() est-il équivalent à encodeURIComponent ?

Non. escape() est une fonction héritée qui encode les caractères non ASCII en Latin-1 ou en séquences %uXXXX au lieu d'UTF-8, et qui laisse @ + / non encodés. Utilisez encodeURIComponent à la place.