Les pièges du format urlencoded
Petit guide de voyage à l'attention des randonneurs sur les terrains ardus et non-standardisés du format `urlencoded` en Javascript.Le format urlencoded
permet d’encoder simplement les champs d’un formulaire, des paramètres de navigation ou d’autres données simples 1. Cependant, comme vu dans l’article sur les formats HTTP, ce format peut vite devenir compliqué à utiliser pour de la donnée un poil plus complexe ou pour échanger entre systèmes implémentés dans des langages différents. En effet, plusieurs langages (et même chaque librairie dans un même langage) peuvent avoir des manières différentes d’encoder tel ou tel type de donnée.
Dans cet article, nous allons voir certaines de ces différences, et comment encoder et décoder une payload au format urlencoded
en javascript.
Les différentes façon d’encoder un tableau
Le format d’encodage de données en urlencoded
n’étant pas standardisé, chaque langage a ses propres spécificités. Par exemple, le tableau [ "pomme", "pêche", "poire" ]
peut être encodé ainsi :
fruits[]=apple&fruits[]=banana&fruits[]=cherry
(format “brackets”, PHP)fruits[0]=apple&fruits[1]=banana&fruits[2]=cherry
(format “index”, PHP / Python / Java)fruits=apple&fruits=banana&fruits=cherry
(format “repeat”, JS / jQuery)fruits=apple,banana,cherry
(format “comma”)
Cet exemple est le plus illustratif, mais il y a de nombreuses autres différences possible : Comment définir la clé d’un objet imbriqué ? Comment envoyer un booléen ? Est-ce qu’une clé sans champ représente une chaine de caractère vide, ou bien null
? Quel est l’encodage des caractères ? Et tant d’autres !
Il est donc important de s’assurer que les deux parties suivent les mêmes conventions, afin que les données envoyées par le client soient correctement décodées interprétées coté serveur.
Encoder et décoder une payload en urlencoded
en Javascript
En Javascript, il y a deux grandes façons d’encoder de la donnée au format urlencoded
.
Méthode 1 : URLSearchParams
pour de la donnée simple
Tous les environements js récent (ES6) ont une classe globale URLSearchParams
pour encoder et parser ce format. 2
const payload = {
myObj: { hello: 'world' },
myString: 'Hello, World!',
myNumber: 12345,
myBool: true,
myNull: null,
myArray: [ 1, 2, 3 ]
};
const searchParams = new URLSearchParams(payload);
searchParams.append("myArray2", 1);
searchParams.append("myArray2", 2);
searchParams.append("myArray2", 3);
console.log(searchParams);
Le console.log nous donne…
URLSearchParams {
'myObj' => '[object Object]',
'myString' => 'Hello, World!',
'myNumber' => '12345',
'myBool' => 'true',
'myNull' => 'null',
'myArray' => '1,2,3',
'myArray2' => '1',
'myArray2' => '2',
'myArray2' => '3'
}
On peut noter que toutes les propriétés ont été transformées en string : en particulier l’objet est devenu une string [object Object]
, null
est devenu 'null'
. Le tableau est lui aussi devenu une string. Par contre, myArray2
est listé plusieurs fois : les objets URLSearchParams peuvent en effet avoir plusieurs fois la même clé.
On peut transformer cet objet en string :
const str = searchParams.toString();
Ce qui nous donne…
myObj=[object+Object]&myString=Hello,+World!&myNumber=12345&myBool=true&myNull=null&myArray=1,2,3&myArray2=1&myArray2=2&myArray2=3
Pour correctement récupérer la donnée, il faut tout d’abord la parser et puis utiliser les méthodes spécifiques à URLSearchParams pour itérer sur myArray2
.
const parsed = new URLSearchParams(str);
const myArray2 = parsed.getAll("myArray2");
// => [ '1', '2', '3' ]
Il n’est donc pas possible d’encoder un objet JSON en urlsearchparam
sans avoir du code spécifique pour encoder et décoder les tableaux, bools, nombres, etc. Il n’est aussi pas possible d’y passer des objets imbriqués tel quel.
Méthode 2 : module qs
pour de la donnée plus complexe
Pour des usages plus avancés tels que les tableaux ou objets imbriqués, on peut utiliser le module qs
.
La même payload au dessus peut être encodée ainsi :
import * as qs from "qs";
const str2 = qs.stringify(payload);
Ce qui nous donne…
myObj[hello]=world&myString=Hello, World!&myNumber=12345&myBool=true&myNull=&myArray[0]=1&myArray[1]=2&myArray[2]=3
Ici, on peut voir que l’objet et le tableau sont correctement encodés. On peut valider ça en décodant à nouveau l’objet :
const parsed2 = qs.parse(str2);
{
myObj: { hello: "world" },
myString: "Hello, World!",
myNumber: "12345",
myBool: "true",
myNull: "",
myArray: [ "1", "2", "3" ]
}
On note cependant que ici aussi, tous les autres types sont devenus des strings : les number, le bool et le null.
La librairie qs
permet de gérer de nombreux cas spéciaux et non-standard de ce format. Par exemple :
- Encoder et décoder certaines valeurs de manière non-ambigue (
null
, tableaux ou objets vides, …) - Encodage et décodage selon plusieurs styles différents (sous-objets avec la dot notation, tableaux formatés avec des indexes, des crochets
[ ]
, des répétitions ou des virgules (ou autre séparateur), …) - Limites sur le nombre de champs dans un tableau ou objet pour éviter les attaques 3
- Encodage et décodage dans des charsets autre que utf-8
- Possibilité d’encoder les espaces selon la RFC 3986 (
%20
) ou RFC1738 (+
)
Elle est donc à préférer si vous devez gérer de la donnée complexe encodée en urlsearchparams
. Mais de manière générale, pour échanger de la donnée complexe, il est recommandé d’utiliser un autre format d’encodage (le plus souvent JSON).
Footnotes
-
Bien que le format soit nommé
urlencoded
, il peut aussi être envoyé pour par exemple envoyer de la donnée via un<form>
en méthodePOST
! ↩ -
On peut aussi y passer un élément
<form>
venu du DOM : les champs duURLSearchParams
seront alors mappés sur ceux du formulaire. ↩ -
Le parsing d’un objet très imbriqué ou d’un tableau très long peut être taxant sur le système. Si un client envoie beaucoup de requêtes, il pourrait ainsi saturer le serveur. ↩