5 erreurs courantes de décodage d'URL et comment les corriger
Les erreurs de décodage d'URL peuvent transformer une expérience utilisateur fluide en cauchemar de débogage. Basé sur des années d'expérience en développement web et des milliers de rapports de bugs, voici les 5 erreurs de décodage d'URL les plus courantes - et exactement comment les corriger.
Erreur #1 : Format d'encodage en pourcentage incorrect
Le problème
Toutes les chaînes qui ressemblent à des URL encodées ne sont pas réellement valides. Les séquences de pourcentage invalides provoqueront l'échec du décodage.
Modèles invalides courants :
hello%world // Chiffres hexadécimaux manquants
test%2 // Séquence incomplète (nécessite 2 chiffres hexadécimaux)
data%ZZ // Caractères hexadécimaux invalides
url%GG%20test // Mélange d'invalide (%GG) et de valide (%20)
Ce qui se passe
// Cela lancera une erreur !
decodeURIComponent('hello%world');
// URIError: URI malformed
decodeURIComponent('test%2');
// URIError: URI mal formée
La cause profonde
- Construction manuelle d'URL sans encodage approprié
- URL tronquées (erreurs de copier-coller)
- Données non-URL confondues avec des chaînes encodées
- Systèmes hérités qui ne suivent pas RFC 3986
La solution
Correctif #1 : Valider avant de décoder
function isValidEncoded(str) {
// Vérifier les modèles de pourcentage invalides
const invalidPattern = /%(?![0-9A-Fa-f]{2})|%[0-9A-Fa-f](?![0-9A-Fa-f])/;
if (invalidPattern.test(str)) {
return false;
}
// Essayer de décoder - si ça lance une erreur, c'est invalide
try {
decodeURIComponent(str);
return true;
} catch (e) {
return false;
}
}
// Utilisation
const userInput = params.get('search');
if (isValidEncoded(userInput)) {
const decoded = decodeURIComponent(userInput);
} else {
console.error('Encodage d\'URL invalide détecté');
// Gérer l'erreur de manière appropriée
}
Correctif #2 : Assainir les encodages malformés
function sanitizeEncoding(str) {
// Remplacer les séquences de pourcentage incomplètes ou invalides
return str.replace(/%(?![0-9A-Fa-f]{2})/g, '%25');
// Convertit % en %25 quand non suivi par 2 chiffres hexadécimaux
}
// Exemple
sanitizeEncoding('hello%world'); // → 'hello%25world'
decodeURIComponent(sanitizeEncoding('hello%world')); // → 'hello%world'
Correctif #3 : Prétraiter avec regex
function safelyDecode(str) {
try {
return decodeURIComponent(str);
} catch (e) {
// Solution de secours : remplacer manuellement les modèles courants
return str
.replace(/%20/g, ' ')
.replace(/%21/g, '!')
.replace(/%40/g, '@')
.replace(/%23/g, '#')
.replace(/%25/g, '%');
// Note : Ceci n'est pas exhaustif, juste une solution de secours
}
}
Prévention
Toujours utiliser les fonctions d'encodage appropriées :
// ✅ Correct
const query = encodeURIComponent(userInput);
const url = `/search?q=${query}`;
// ❌ Mauvais - construction manuelle d'URL
const url = `/search?q=${userInput.replace(/ /g, '%20')}`;
Test rapide
// Cas de test pour la validation
const testCases = [
{ input: 'hello%20world', valid: true },
{ input: 'hello%world', valid: false },
{ input: 'test%2', valid: false },
{ input: '%E4%B8%AD%E6%96%87', valid: true },
{ input: 'normal-text', valid: true }, // Pas d'encodage est aussi valide
];
testCases.forEach(({ input, valid }) => {
const result = isValidEncoded(input);
console.assert(result === valid, `Échec pour : ${input}`);
});
Erreur #2 : Incompatibilités de codage de caractères
Le problème
Encoder une chaîne dans un jeu de caractères (par ex., ISO-8859-1) et la décoder dans un autre (UTF-8) produit du charabia ou le caractère de remplacement �.
Symptômes :
Attendu : café
Obtenu : café
Attendu : 中文
Obtenu : ���
Attendu : Ñoño
Obtenu : �o�o
Ce qui se passe
// Si le serveur a encodé en ISO-8859-1 mais vous décodez en UTF-8 :
const encoded = '%C3%A9'; // é en UTF-8
decodeURIComponent(encoded); // → 'é' (correct en UTF-8)
// Mais si c'était réellement encodé ISO-8859-1 comme %E9 :
const wrongEncoding = '%E9';
decodeURIComponent(wrongEncoding); // → 'é' mais s'affiche mal
La cause profonde
- Systèmes hérités utilisant des encodages non-UTF-8
- Encodage mixte dans différentes parties de l'application
- Base de données configurée avec le mauvais charset
- En-têtes HTTP spécifiant un encodage incorrect
La solution
Correctif #1 : Standardiser sur UTF-8 partout
<!-- En HTML -->
<meta charset="UTF-8">
<!-- Dans les en-têtes HTTP -->
Content-Type: text/html; charset=UTF-8
// Dans Express.js
app.use(express.urlencoded({ extended: true, charset: 'utf-8' }));
-- Dans MySQL
CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
Correctif #2 : Détecter les incompatibilités d'encodage
function looksLikeMojibake(str) {
// Modèles courants d'UTF-8 interprété comme ISO-8859-1
const suspiciousPatterns = [
/é|è|à |ç/, // Courant en français
/£|¥|©/, // Devises et symboles
/�/, // Caractère de remplacement
];
return suspiciousPatterns.some(pattern => pattern.test(str));
}
// Utilisation
const decoded = decodeURIComponent(encoded);
if (looksLikeMojibake(decoded)) {
console.warn('Possible incompatibilité d\'encodage détectée !');
}
Correctif #3 : Ré-encoder si nécessaire
// Si vous savez que la source était Latin-1 mais a été décodée en UTF-8 :
function fixLatin1ToUTF8(str) {
// C'est une opération complexe, utilisez une bibliothèque si possible
const encoder = new TextEncoder();
const decoder = new TextDecoder('iso-8859-1');
const bytes = encoder.encode(str);
return decoder.decode(bytes);
}
Prévention
Imposer UTF-8 à chaque couche :
- Base de données : UTF-8 (ou utf8mb4 pour MySQL)
- En-têtes HTTP :
Content-Type: charset=UTF-8 - HTML :
<meta charset="UTF-8"> - Fichiers source : Enregistrer en UTF-8
- APIs : Accepter et retourner UTF-8
Test rapide
// Tester avec des caractères internationaux
const tests = [
{ text: 'café', lang: 'Français' },
{ text: '中文', lang: 'Chinois' },
{ text: 'العربية', lang: 'Arabe' },
{ text: '😀', lang: 'Emoji' },
];
tests.forEach(({ text, lang }) => {
const encoded = encodeURIComponent(text);
const decoded = decodeURIComponent(encoded);
console.assert(decoded === text, `Encodage ${lang} échoué`);
});
Erreur #3 : Décodage incomplet (problèmes multi-couches)
Le problème
Les URL encodées plusieurs fois nécessitent plusieurs opérations de décodage. S'arrêter trop tôt laisse des séquences de pourcentage dans la sortie.
Exemple :
Original : Hello World
Encodé 1 fois : Hello%20World
Encodé 2 fois : Hello%2520World
Encodé 3 fois : Hello%252520World
// Si vous décodez qu'une seule fois :
decodeURIComponent('Hello%252520World') // → 'Hello%2520World' (encore encodé !)
Ce qui se passe
const doubleEncoded = 'search%253Dhello%2520world';
// Décoder une fois
const once = decodeURIComponent(doubleEncoded);
console.log(once); // 'search%3Dhello%20world' - contient encore %3D et %20 !
// Décoder deux fois
const twice = decodeURIComponent(once);
console.log(twice); // 'search=hello world' - correct !
La cause profonde
- Multiples redirections chacune encodant l'URL
- Chaînes de middleware qui encodent de manière répétée
- Copier-coller utilisateur d'URL déjà encodées
- Auto-encodage du framework en plus de l'encodage manuel
La solution
Correctif #1 : Décodage itératif jusqu'à stabilisation
function fullyDecode(str) {
let decoded = str;
let previous = '';
let iterations = 0;
const MAX_ITERATIONS = 5; // Limite de sécurité
while (decoded !== previous && iterations < MAX_ITERATIONS) {
previous = decoded;
try {
const temp = decodeURIComponent(decoded);
// Continuer seulement si quelque chose a réellement changé
if (temp !== decoded) {
decoded = temp;
} else {
break;
}
} catch (e) {
// Arrêter en cas d'erreur
console.error('Décodage arrêté à cause d\'une erreur :', e);
break;
}
iterations++;
}
console.log(`Décodé ${iterations} fois`);
return decoded;
}
// Utilisation
fullyDecode('Hello%252520World'); // → 'Hello World' (3 itérations)
Correctif #2 : Compter les couches d'encodage
function countLayers(str) {
let count = 0;
let current = str;
while (/%[0-9A-Fa-f]{2}/.test(current) && count < 10) {
try {
const decoded = decodeURIComponent(current);
if (decoded === current) break; // Aucun changement
current = decoded;
count++;
} catch (e) {
break;
}
}
return count;
}
// Utilisation
console.log(countLayers('Hello%20World')); // 1
console.log(countLayers('Hello%2520World')); // 2
console.log(countLayers('Hello%252520World')); // 3
Correctif #3 : Détecter et avertir
function decodeWithWarning(str) {
const layers = countLayers(str);
if (layers > 1) {
console.warn(`Encodage multi-couches détecté : ${layers} couches`);
}
return fullyDecode(str);
}
Prévention
Éviter le double encodage :
// ❌ Ne faites pas ça
const alreadyEncoded = encodeURIComponent(userInput);
const doubleEncoded = encodeURIComponent(alreadyEncoded); // Mauvais !
// ✅ Encoder une seule fois
const encoded = encodeURIComponent(userInput);
// ✅ Ou vérifier si déjà encodé
function encodeOnce(str) {
// Vérification simple : si contient %, supposer qu'il est encodé
if (/%[0-9A-Fa-f]{2}/.test(str)) {
return str; // Déjà encodé
}
return encodeURIComponent(str);
}
Test rapide
const multilayerTests = [
{ input: 'Hello%20World', layers: 1 },
{ input: 'Hello%2520World', layers: 2 },
{ input: '%25252525', layers: 4 }, // %25 encodé 4 fois
];
multilayerTests.forEach(({ input, layers }) => {
const detected = countLayers(input);
console.assert(detected === layers, `Échec : attendu ${layers}, obtenu ${detected}`);
});
Erreur #4 : Confusion des caractères réservés
Le problème
Ne pas savoir quels caractères sont réservés conduit à des décisions incorrectes d'encodage/décodage.
Erreurs courantes :
Encoder ? dans une chaîne de requête // Mauvais ! ? est le délimiteur de requête
Ne pas encoder & dans une valeur // Mauvais ! & sépare les paramètres
Encoder / dans un chemin // Généralement mauvais ! / est le séparateur de chemin
Ce qui se passe
// Mauvais : encoder le délimiteur de requête
const wrongUrl = `/search${encodeURIComponent('?q=test')}`;
// → /search%3Fq%3Dtest (le ? est encodé !)
// Mauvais : ne pas encoder & dans une valeur
const name = 'Tom & Jerry';
const badUrl = `/search?query=${name}`;
// → /search?query=Tom & Jerry
// Le navigateur interprète comme : query=Tom et un paramètre nommé "Jerry"
// Correct :
const goodUrl = `/search?query=${encodeURIComponent(name)}`;
// → /search?query=Tom%20%26%20Jerry
La cause profonde
- Confusion sur la structure des URL
- Mauvaise fonction d'encodage (
encodeURIvsencodeURIComponent) - Construction manuelle d'URL sans comprendre les caractères réservés
Caractères réservés dans les URL
| Caractère | Signification | Encoder dans les valeurs ? |
|---|---|---|
: | Séparateur protocole/port | Oui (dans les valeurs) |
/ | Séparateur de chemin | Non (dans les chemins), Oui (dans les valeurs) |
? | Début de chaîne de requête | Non (comme délimiteur), Oui (dans les valeurs) |
# | Identifiant de fragment | Non (comme délimiteur), Oui (dans les valeurs) |
& | Séparateur de paramètres | Non (comme séparateur), Oui (dans les valeurs) |
= | Séparateur clé-valeur | Non (comme séparateur), Oui (dans les valeurs) |
@ | Séparateur d'info utilisateur | Oui (généralement) |
La solution
Correctif #1 : Utiliser la bonne fonction d'encodage
// Pour encoder des URL COMPLÈTES
const fullUrl = 'https://example.com/path with spaces/file.html';
const encoded = encodeURI(fullUrl);
// → 'https://example.com/path%20with%20spaces/file.html'
// Note : /, :, ? ne sont PAS encodés
// Pour encoder des COMPOSANTS d'URL (valeurs de requête, segments de chemin)
const value = 'hello/world?test=value';
const encoded = encodeURIComponent(value);
// → 'hello%2Fworld%3Ftest%3Dvalue'
// Note : TOUS les caractères spéciaux sont encodés
Correctif #2 : Construire les URL correctement
// ❌ Mauvaise façon
const search = 'hello & goodbye';
const url = '/search?q=' + search; // Se casse sur &
// ✅ Bonne façon - encoder la valeur
const url = '/search?q=' + encodeURIComponent(search);
// ✅ Mieux - utiliser l'API URL
const url = new URL('/search', window.location.origin);
url.searchParams.set('q', search); // Encodage automatique
console.log(url.href);
Correctif #3 : Analyser les URL correctement
// ❌ Mauvais - analyse manuelle
const query = window.location.search; // ?name=Tom%20%26%20Jerry
const value = query.split('=')[1]; // 'Tom%20%26%20Jerry'
// Si vous oubliez de décoder, vous afficherez la version encodée
// ✅ Bon - utiliser l'API URL
const params = new URLSearchParams(window.location.search);
const value = params.get('name'); // Automatiquement décodé : 'Tom & Jerry'
Prévention
Utiliser les utilitaires URL :
// Node.js ou navigateurs modernes
const { URL, URLSearchParams } = require('url'); // Node.js
// Ou simplement utiliser URL et URLSearchParams globaux dans les navigateurs
// Construire des URL en toute sécurité
const url = new URL('https://example.com/search');
url.searchParams.append('query', 'hello & goodbye');
url.searchParams.append('page', '1');
console.log(url.toString());
// → https://example.com/search?query=hello+%26+goodbye&page=1
Test rapide
const reservedCharTests = [
{ char: '&', desc: 'Esperluette' },
{ char: '=', desc: 'Égal' },
{ char: '?', desc: 'Point d\'interrogation' },
{ char: '#', desc: 'Dièse' },
{ char: '/', desc: 'Barre oblique' },
];
reservedCharTests.forEach(({ char, desc }) => {
const value = `avant${char}après`;
const encoded = encodeURIComponent(value);
const decoded = decodeURIComponent(encoded);
console.log(`${desc} (${char}) :`);
console.log(` Original : ${value}`);
console.log(` Encodé : ${encoded}`);
console.log(` Décodé : ${decoded}`);
console.assert(decoded === value, `${desc} a échoué l'aller-retour`);
});
Erreur #5 : Utiliser les mauvaises fonctions/méthodes de décodage
Le problème
Différents langages et frameworks ont différentes fonctions de décodage. Utiliser la mauvaise produit des résultats incorrects.
Erreurs courantes
JavaScript :
// ❌ Mauvais pour les paramètres de requête
decodeURI('hello%20world%26test');
// → 'hello world%26test' (ne décode pas &)
// ✅ Correct
decodeURIComponent('hello%20world%26test');
// → 'hello world&test'
Python :
# ❌ Mauvais - quote() au lieu de unquote()
from urllib.parse import quote
result = quote('hello%20world')
# → 'hello%2520world' (double encodé !)
# ✅ Correct
from urllib.parse import unquote
result = unquote('hello%20world')
# → 'hello world'
PHP :
// Les signes plus (+) représentent des espaces dans les données de formulaire
$encoded = 'hello+world';
// ❌ urldecode() traite + comme espace
$result = urldecode($encoded);
// → 'hello world'
// ✅ Utiliser rawurldecode() pour garder + comme littéral
$result = rawurldecode($encoded);
// → 'hello+world'
// Ou utiliser urldecode() si + doit être espace (données de formulaire)
La solution
Correctif #1 : Connaître vos fonctions
JavaScript :
decodeURI()- pour les URL entières (ne décode pas&,=,?, etc.)decodeURIComponent()- pour les parties d'URL (décode tout)
Python :
urllib.parse.unquote()- décodage standardurllib.parse.unquote_plus()- décoder + comme espace (pour données de formulaire)
PHP :
urldecode()- décoder + comme espacerawurldecode()- ne pas décoder +
Correctif #2 : Gérer correctement les signes plus
// Si vous traitez des données encodées en formulaire où + signifie espace :
function decodeFormData(str) {
return decodeURIComponent(str.replace(/\+/g, ' '));
}
// Utilisation
decodeFormData('hello+world'); // → 'hello world'
decodeURIComponent('hello+world'); // → 'hello+world' (+ non décodé)
Correctif #3 : Tester votre fonction de décodage
const testStrings = [
'hello%20world', // Espace
'hello+world', // Plus
'hello%2Bworld', // Plus encodé
'test%26value', // Esperluette
'%E4%B8%AD%E6%96%87', // UTF-8
];
testStrings.forEach(str => {
console.log(`Entrée : ${str}`);
console.log(`decodeURI : ${decodeURI(str)}`);
console.log(`decodeURIComponent : ${decodeURIComponent(str)}`);
console.log('---');
});
Prévention
Créer des fonctions wrapper :
// Standardiser le décodage dans votre application
function safeDecodeParam(str) {
if (!str) return '';
try {
// Remplacer + par espace pour les données de formulaire, puis décoder
return decodeURIComponent(str.replace(/\+/g, ' '));
} catch (e) {
console.error('Erreur de décodage :', e);
return str; // Retourner l'original en cas d'erreur
}
}
// Utiliser de manière cohérente
const userQuery = safeDecodeParam(params.get('q'));
Test rapide
// Tester toutes les fonctions de décodage avec la même entrée
const testInput = 'hello%20world%26test';
console.log('Test :', testInput);
console.log('decodeURI : ', decodeURI(testInput));
console.log('decodeURIComponent :', decodeURIComponent(testInput));
console.log('Attendu : hello world&test');
Liste de vérification pour le débogage
Lorsque vous rencontrez des problèmes de décodage d'URL, utilisez cette liste de vérification :
- Encodage valide ? Vérifier les séquences de pourcentage malformées (
%ZZ,%2) - Charset correct ? Vérifier UTF-8 dans toute la pile
- Simple ou multi-couches ? Compter combien de fois c'est encodé
- Caractères réservés ? Assurer la bonne gestion de
&,=,?, etc. - Bonne fonction ? Utiliser
decodeURIComponent()vsdecodeURI()? - Signes plus ? Sont-ils censés être des espaces ou des
+littéraux ? - Gestion d'erreur ? Enveloppé dans try-catch ?
- Assaini ? Validé et assaini après décodage ?
Outils pour le débogage
- Notre décodeur d'URL : Outil en ligne gratuit avec détection multi-couches
- DevTools du navigateur :
console.log(decodeURIComponent(str)) - Analyseur d'URL : Visualiser les composants d'URL
- Visualiseurs hex : Voir les valeurs d'octets réelles
Résumé
| Erreur | Correctif rapide | Prévention |
|---|---|---|
| #1 Format incorrect | Valider avant de décoder | Utiliser les fonctions d'encodage appropriées |
| #2 Incompatibilité encodage | Standardiser sur UTF-8 | UTF-8 partout |
| #3 Décodage incomplet | Décoder jusqu'à stabilisation | Éviter le double encodage |
| #4 Caractères réservés | Utiliser encodeURIComponent() | Utiliser l'API URL |
| #5 Mauvaise fonction | Connaître vos fonctions | Créer des wrappers |
En comprenant et en corrigeant ces 5 erreurs courantes, vous gérerez le décodage d'URL comme un pro. Rappelez-vous : validez les entrées, décodez avec précaution et testez toujours avec des cas limites !
Évitez ces erreurs instantanément avec notre outil gratuit de décodage d'URL qui gère tous les cas limites automatiquement !