Vous avez à traiter le format Base64 ? Alors ce site est parfait pour vous ! Utilisez notre outil en ligne super pratique pour encoder ou décoder vos données.

Décodage Base64 en Dart : un guide complet

Il arrive dans une réponse API, au milieu d'une URL, ou collé dans un ticket de support : une longue suite de lettres et de chiffres, avec de-ci de-là un +, un /, un - ou un _, et parfois un ou deux = en traîne à la fin. Quelqu'un appelle ça du Base64, et vous avez besoin de ce qui est dedans. Ce guide est la recette Dart pour le récupérer. Petite orientation, car la page d'accueil détaille le format en profondeur : Base64 réécrit chaque trio d'octets d'entrée en quatre caractères tirés d'un alphabet de 64 caractères, et accroche un ou deux = à la fin quand le dernier bloc est court. Décoder, c'est le sens qui réduit dans cet échange : quatre caractères entrent, trois octets sortent, si bien que le résultat prend toujours environ un quart d'espace de moins que l'entrée.

La bonne nouvelle : il n'y a rien à installer. Base64 est livré dans la bibliothèque dart:convert depuis Dart 1.13 en 2015, et l'API est stable depuis Dart 2.0 en 2018. Un seul import vous donne un décodeur rapide et strict, qui lit aussi bien l'alphabet standard que l'alphabet URL-safe.

Une limite honnête : c'est ici le côté décodeur de l'histoire. Vous allez apprendre ce que le décodeur accepte et refuse, comment le padding fonctionne, comment retransformer des octets en texte sans charabia, et comment croiser le Base64 dans les JWT, les data URIs, les fichiers, les streams, l'email, la configuration et la ligne de commande. L'autre direction, emballer des octets dans une chaîne, a son propre guide, lié à la fin de celui-ci.

Les quatre portes d'une seule machine stricte

Voici toute la surface publique que vous allez utiliser, et tout est dans dart:convert :

Entrée Ce que c'est Quand y recourir
base64Decode(source) Fonction de premier niveau, décode vers un Uint8List Le décodage au quotidien, presque toujours celle-ci
base64.decode(source) La méthode de décodage du codec, comportement identique Vous voulez le codec pour fuse ou des transformations de stream
base64Url.decode(source) La méthode de décodage du codec URL-safe L'entrée est documentée comme URL-safe (la machine est la même)
base64Url.normalize(source) Valide et répare une chaîne, la renvoie avec padding L'entrée peut manquer de padding, mélanger les alphabets ou utiliser des échappements en pourcentage

Deux choses à remarquer. D'abord, les quatre chemins mènent au même décodeur : une machine à états stricte avec une seule table de correspondance. Deuxièmement, la dernière ligne n'est pas un décodeur du tout. C'est un poste de réparation, et il se rendra utile la première fois qu'un JWT dépouillé de son padding ou une valeur de configuration à moitié nettoyée se présentera.

Votre premier décodage

Quatre-vingt-dix pour cent de la vie du décodage tiennent en cinq lignes. Voici le plus petit exemple qui montre toute la forme du travail :

import 'dart:convert';
void main() {
  final bytes = base64Decode('TWFu');
  final text = utf8.decode(bytes);
  print(text); // Man
}

Trois phrases sur ce qui vient de se passer. D'abord, le point d'entrée renvoie des octets, pas du texte : base64Decode retourne un Uint8List, et c'est volontaire, car le payload peut être une phrase, un JPEG ou un hash, et aucun de ces trois ne doit être traité pareil avant de savoir ce que vous avez. Deuxièmement, le saut des octets vers le texte est une étape séparée et explicite, avec un encodage explicite, et c'est dans cette étape que « café » tourne au charabia si vous n'êtes pas attentif. Troisièmement, la chaîne vide est une valeur de plein droit : base64Decode('') vous donne une liste de longueur nulle, sans exception et sans histoire.

Ce que le décodeur accepte et refuse

Le décodeur de Dart est strict par conception. La RFC 4648 dit que les implémentations doivent rejeter les entrées qui contiennent des caractères hors de l'alphabet, et Dart suit cette lecture à la lettre : pas de saut des espaces, pas d'ignorance des retours à la ligne, pas de seconde chance. Quand l'entrée est fausse, vous obtenez une FormatException qui montre l'entrée et pointe le caractère exact. Voici le comportement sur les gêneurs classiques :

Entrée Ce qui ne va pas Erreur exacte
'SGVs bG8s' un espace s'est glissé FormatException: Invalid character (at character 5)
'SGVs\nbG8s' un retour à la ligne s'est glissé FormatException: Invalid character (at character 5)
'SGVs$bG8s' un signe dollar n'est pas dans l'alphabet FormatException: Invalid character (at character 5)
'Zm8' aucun padding FormatException: Invalid length, must be multiple of four (at character 4)
'Zm8==' deux pads là où un seul est attendu FormatException: Invalid padding character (at character 5)
'Zm=8' du padding au milieu des données FormatException: Invalid encoding before padding (at character 3)
'Zm8=xx' des caractères parasites après les pads FormatException: Invalid padding character (at character 5)
'Zé' un caractère non ASCII FormatException: Invalid character (at character 2)

La position dans le message est un comptage de caractères qui commence à 1, et l'entrée est imprimée juste sous le chevron, si bien que bissecter un payload corrompu est rapide. Une agréable surprise se cache dans cette stricte rigueur : le décodeur accepte les deux alphabets. Un - ou un _ au milieu d'une chaîne standard ne pose pas de problème, et un + ou un / dans une chaîne URL-safe non plus. Le choix d'alphabet ne compte que quand c'est vous qui produisez le texte, pas quand vous le lisez.

Le padding : le point non négociable

Voici la règle qui surprend le plus de monde : le décodeur de Dart exige un padding correct. L'entrée doit avoir une longueur multiple de quatre, et les signes = de fin doivent être présents en quantité exacte. Il n'y a pas de mode tolérant, pas d'option pour assouplir, et aucun réglage à modifier. Les raisons sont solides : le décodage sans padding est ambigu dans les cas limites, et la RFC met en garde contre le fait qu'un décodage libéral peut ouvrir un canal caché, si bien que la lecture stricte est la plus sûre. Concrètement, cela donne :

Entrée Résultat
'' Uint8List vide, pas d'erreur
'QQ==' 1 octet : A
'QUI=' 2 octets : AB
'QUJD' 3 octets : ABC
'Zm8' FormatException : longueur invalide
'Zm8==' FormatException : caractère de padding invalide

Quand l'entrée vient d'un système qui supprime le padding - et les JWT regorgent de valeurs sans padding -, l'étape de réparation est un simple appel à normalize. Il valide la chaîne, convertit les caractères URL-safe vers l'alphabet standard, et ajoute les pads manquants :

import 'dart:convert';
void main() {
  final stripped = '-__--Q';
  final repaired = base64Url.normalize(stripped);
  print(repaired); // +//++Q==
  final bytes = base64Decode(repaired);
  print('decoded ${bytes.length} bytes'); // decoded 4 bytes
}

La surprise du signe pourcentage

Celui-ci est une originalité de Dart. Quand du Base64 apparaît dans une data URI, certains outils encodent le padding en pourcentage, écrivant %3D au lieu de =, car un = nu peut signifier « séparateur de paramètre » dans la syntaxe des URL. La plupart des langages voudraient d'abord que vous retiriez l'échappement. Le décodeur de Dart, non : sa table de correspondance traite %3D comme une écriture native du caractère de padding, si bien que vous pouvez lui passer le payload brut :

import 'dart:convert';
void main() {
  final fromDataUri = 'SGVsbG8%3D';
  final bytes = base64Decode(fromDataUri);
  print(utf8.decode(bytes)); // Hello
}

L'échappement est accepté exactement là où le padding est légal, c'est-à-dire en position de fin. Mettez %3D là où un = serait rejeté et il sera rejeté de la même façon, et %25 échoue sur la vérification du padding à la place - % est le caractère d'échappement du padding natif de Dart, si bien que le décodeur le lit comme un = échappé et rejette le 2 avec Invalid padding character. Concrètement, un payload ;base64, copié directement des outils de développement d'un navigateur se décode sans aucun prétraitement, une petite astuce mais réellement pratique.

Le Base64 URL-safe

La RFC 4648 définit un second alphabet pour une raison : le standard en compte trois, +, / et =, qui entrent en collision avec la syntaxe des URL. L'alphabet URL-safe, appelé base64url dans la RFC, échange + contre - et / contre _, et supprime souvent le padding aussi. C'est l'alphabet des JWT, des identifiants d'objets, des liens de partage et de tout ce qui vit dans une URL ou un nom de fichier.

Côté décodage, Dart vous donne une seule réponse : les deux alphabets sont lus par la même machine. base64Decode et base64Url.decode sont deux noms pour le même décodeur, si bien que le seul vrai travail est le padding, car les producteurs URL-safe livrent très souvent sans. C'est exactement à cela que sert normalize :

import 'dart:convert';
void main() {
  final bytes = [0xfb, 0xff, 0xfe, 0xf9];
  final urlSafe = base64UrlEncode(bytes);
  print(urlSafe); // -__--Q==
  final repaired = base64Url.normalize(urlSafe.replaceAll('=', ''));
  print(repaired); // +//++Q==
  print(base64Decode(repaired).length); // 4
}

Deux pièges à emporter. Ne bricolez pas vous-même un remplacement de - par + avant de décoder ; c'est inutile, et normalize fait déjà la conversion d'alphabet quand c'est nécessaire. Et n'imaginez pas qu'une chaîne URL-safe arrive toujours sans padding : certains producteurs gardent les pads, et le décodeur accepte les deux, tant que le padding est correct.

Des octets au texte : la décision du charset

Le décodage Base64 vous remet des octets. Si ces octets sont du texte, vous devez choisir l'encodage qui les retransforme en String, et ce choix est à vous de le faire explicitement. L'hypothèse par défaut des systèmes modernes est l'UTF-8, et utf8.decode est le cheval de trait :

import 'dart:convert';
void main() {
  final payload = base64Encode(utf8.encode('Héllo Wörld'));
  final bytes = base64Decode(payload);
  print(utf8.decode(bytes)); // Héllo Wörld
  final legacy = base64Encode(latin1.encode('Héllo'));
  print(latin1.decode(base64Decode(legacy))); // Héllo
}

Quand les octets ne sont pas de l'UTF-8 valide, utf8.decode lève une FormatException, ce qui est le bon comportement, bien mieux qu'un charabia silencieux. Si vous savez que les données sont du texte hérité sur octet simple, utilisez l'encodage correspondant :

Encodage À utiliser pour Décoder avec
utf8 Texte moderne, JSON, tout ce qui est sur le web utf8.decode(bytes)
latin1 Données occidentales héritées sur octet simple latin1.decode(bytes)
ascii Texte 7 bits simple ascii.decode(bytes)

Un piège mérite son propre avertissement : String.fromCharCodes n'est pas un charset. Il lit les octets comme des unités de code UTF-16, si bien que si vous lui donnez les octets UTF-8 de Héllo, il affiche Héllo d'un air impassible. Si vous voyez ce motif de charabia dans votre sortie, le correctif est presque toujours utf8.decode.

Les JWT : lire le jeton

Un JSON Web Token est trois parties base64url jointes par des points : en-tête, payload, signature. Le Base64 est utilisé ici pour la compacité et la sûreté URL, pas pour le secret. N'importe qui possédant le jeton peut lire l'en-tête et le payload, et c'est voulu. La signature est ce que vous vérifiez, avec le secret partagé ou la clé publique de l'émetteur. Décoder les parties lisibles en Dart tient en quelques lignes :

import 'dart:convert';
Map<String, dynamic> readJwtPayload(String token) {
  final parts = token.split('.');
  if (parts.length != 3) {
    throw FormatException('Not a compact JWT');
  }
  final padded = base64Url.normalize(parts[1]);
  final bytes = base64Decode(padded);
  return jsonDecode(utf8.decode(bytes)) as Map<String, dynamic>;
}
void main() {
  const token =
      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'
      '.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkRhcnQgRGV2IiwiaWF0IjoxNTE2MjM5MDIyfQ'
      '.c2lnbmF0dXJl';
  print(readJwtPayload(token)['name']); // Dart Dev
}

Remarquez la danse du padding : les JWT sont construits sans padding, si bien qu'une partie échouera en base64Decode direct chaque fois que sa longueur n'est pas un multiple de quatre. (Dans l'exemple ci-dessus, l'en-tête fait 36 caractères et se décode directement ; le payload en fait 74, et non.) L'appel à normalize rend la réparation uniforme, quelle que soit la longueur. Deux autres avertissements. Décoder n'est pas vérifier : contrôler la signature et la revendication exp est une étape séparée et obligatoire, en général avec le paquet crypto pour les algorithmes HMAC. Et méfiez-vous des jetons qui revendiquent alg: none ; un analyseur qui les accepte est une faille, pas une fonctionnalité.

Les data URIs : des fichiers déguisés en URL

Une data URI, définie par la RFC 2397, est une URL dont le payload est les données elles-mêmes : data:image/png;base64, suivi des octets encodés. Elles existent pour que les canaux texte uniquement - attributs HTML, règles CSS, documents JSON - puissent porter du binaire sans fichier séparé. Le Base64 est le format de payload de choix, car l'alternative, l'encodage en pourcentage, est bien plus long pour des données binaires.

Et Dart peut les analyser nativement : la prise en charge des data URIs est dans dart:core depuis 2016, si bien qu'aucune bibliothèque d'URI n'est nécessaire :

import 'dart:convert';
void main() {
  final uri = Uri.parse('data:image/png;base64,iVBORw0KGgo=');
  final data = uri.data!;
  print(data.mimeType); // image/png
  print(data.isBase64); // true
  print('decoded ${data.contentAsBytes().length} bytes');
  final textUri = Uri.parse('data:text/plain;base64,SGVsbG8sIERhcnQh');
  print(textUri.data!.contentAsString()); // Hello, Dart!
}

L'objet UriData vous donne le type MIME, le drapeau isBase64, le texte brut du payload, et le contenu décodé en chaîne ou en octets. Deux pièges : le type MIME déclaré peut mentir, si bien que pour un code sensible à la sécurité, vérifiez les octets magiques réels ; et les data URIs sont destinées à de petites ressources, car tout le payload voyage à l'intérieur du document qui le référence.

Les fichiers : du Base64 sur disque

Les fichiers Base64 apparaissent dans des formats d'export, des paquets de provisioning, et tout transfert texte uniquement qui doit porter du binaire. La recette est : lire le texte, l'aplatir, décoder, écrire les octets :

import 'dart:convert';
import 'dart:io';
Future<void> main() async {
  final encoded = await File('image.b64').readAsString();
  final flat = encoded.replaceAll(RegExp(r'\s+'), '');
  final bytes = base64Decode(flat);
  await File('image.png').writeAsBytes(bytes);
  print('wrote ${bytes.length} bytes');
}

Ce replaceAll fait du vrai travail. Les fichiers texte regorgent de retours à la ligne, souvent le wrapping MIME de 76 caractères, et le décodeur strict les rejette, si bien qu'il faut d'abord aplatir. L'expression régulière supprime chaque caractère de blancheur, ce qui est exactement ce qu'il faut pour un fichier base64 pur. Si le fichier peut contenir d'autres annotations, comme un en-tête PEM, supprimez-les explicitement avant de décoder, et laissez les erreurs du décodeur attraper ce qui est vraiment corrompu.

HTTP et API

Le Base64 dans HTTP porte deux déguisements. D'abord, les réponses d'API : un champ JSON qui porte du binaire sous forme de chaîne. Ensuite, l'en-tête Authorization: Basic, où les identifiants sont encodés en base64 avec l'alphabet standard et le padding :

import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
  final response = await http.get(
    Uri.parse('https://httpbin.org/get?attachment=TWFuIGlzIGhlcmU%3D&name=man.txt'),
  );
  final payload = jsonDecode(response.body) as Map<String, dynamic>;
  final args = payload['args'] as Map<String, dynamic>;
  final bytes = base64Decode(args['attachment'] as String);
  print('got ${bytes.length} bytes');
  final credentials = utf8.decode(base64Decode('b2N0b2NhdDpzZWNyZXQ='));
  print(credentials.split(':').first); // octocat
}

Le paquet http est le client standard, à un dart pub add http près. Pour l'authentification Basic, vous décodez la partie après le préfixe Basic . Deux pièges : certaines API envoient des valeurs URL-safe ou sans padding là où la doc dit base64, si bien que si le décodage direct lève une exception, passez d'abord la valeur par base64Url.normalize ; et n'oubliez pas que l'authentification Basic est un brouillage, pas une protection, c'est pourquoi elle n'a sa place que sur des connexions TLS.

L'email et le MIME : le problème des retours à la ligne

L'email est le client base64 le plus ancien. Le MIME enveloppe les lignes base64 à 76 caractères - 76 plus CRLF tient confortablement sur un affichage de 80 colonnes - et la RFC 2045 dit aux décodeurs d'ignorer les retours à la ligne. Le décodeur de Dart ne le fait pas, exprès : il les rejette. Le correctif est d'aplatir avant de décoder :

import 'dart:convert';
List<int> decodeMimeBody(String wrapped) {
  final flat = wrapped.replaceAll(RegExp(r'\s+'), '');
  return base64Decode(flat);
}
void main() {
  const wrapped =
      'SGVsbG8gZnJvbSBhbiBlbWFpbCBhdHRhY2htZW50LCB3cmFwcGVkIGF0IDc2IGNoYXJhY3RlcnMg'
      '\r\n'
      'dGhlIHdheSBNSU1FIHdhbnRzIGl0IHRvIGJlLCB3aXRoIENSTEYgYmV0d2VlbiB0aGUgbGluZXMu';
  print(utf8.decode(decodeMimeBody(wrapped)));
}

La règle est simple : supprimer les blancs, rien d'autre. Ne supprimez pas d'autres caractères dans l'espoir d'être utile ; le décodeur est le validateur, et vous voulez qu'il se plaigne des corruptions réelles. Si vous traitez l'email à grande échelle, l'étape d'aplatissage est bon marché, un seul passage d'expression régulière, et elle garde le reste du pipeline honnête.

Configuration et variables d'environnement

Les jetons et identifiants qui vivent dans une configuration basée sur le texte sont parfois encodés en base64 pour rester sur une ligne et ressembler à des jetons. Le cadrage honnête : le base64 est un brouillage, pas un chiffrement, si bien que cette pratique est pour la propreté, jamais pour le secret. Le schéma lui-même est trivial :

import 'dart:convert';
import 'package:dotenv/dotenv.dart';
Future<void> main() async {
  final env = DotEnv()..load();
  final encoded = env['API_TOKEN_B64'];
  if (encoded == null) {
    return;
  }
  final token = utf8.decode(base64Decode(encoded));
  print('loaded a ${token.length}-char token');
}

Avec le paquet dotenv, la valeur est posée dans un fichier .env sous la forme API_TOKEN_B64=c2stbGl2ZS1hYmMxMjM= et revient en texte brut après le décodage. La même forme fonctionne avec String.fromEnvironment pour des valeurs dart-define à la compilation, avec un avertissement : les valeurs dart-define sont brûlées dans le binaire compilé, si bien que tout ce qui est secret a sa place dans la configuration runtime ou un gestionnaire de secrets, pas là.

Les streams : bloc par bloc

Quand le texte encodé arrive en morceaux - un stream réseau, un gros fichier lu par blocs - le décodeur s'en sort. Sa machine à états porte le groupe partiel d'un bord de bloc à l'autre, si bien que les blocs n'ont pas besoin d'être alignés sur des frontières de quatre caractères :

import 'dart:convert';
Future<void> main() async {
  final incoming = Stream.fromIterable(['TWF', 'uaGVsbG8=']);
  final text = await incoming
      .transform(base64.decoder)
      .map(utf8.decode)
      .join();
  print(text); // Manhello
}

L'appel transform utilise le décodeur comme transformeur de stream ; le premier bloc, trois caractères, gare ses bits dans l'état du décodeur, et le second bloc complète le groupe. Les erreurs remontent comme erreurs de stream avec les mêmes détails de FormatException, et un stream vide produit simplement aucune sortie. Si vous préférez les sinks, base64.decoder.startChunkedConversion vous donne un StringConversionSink branché sur la même machine à états.

Les grosses données : le calcul et la mémoire

Le décodage réduit : quatre caractères deviennent trois octets, si bien que la sortie fait toujours un peu moins que trois quarts de la longueur de l'entrée. Cela veut dire que la taille de la sortie est connaissable avant de décoder, ce qui rend la mémoire prévisible. Une petite fonction d'aide la calcule à partir de la chaîne seule :

import 'dart:convert';
int decodedLength(String encoded) {
  var padding = 0;
  for (var i = encoded.length - 1; i >= 0 && padding < 2; i--) {
    if (encoded.codeUnitAt(i) == 0x3d) {
      padding++;
    } else {
      break;
    }
  }
  return (encoded.length ~/ 4) * 3 - padding;
}
void main() {
  print(decodedLength('QQ==')); // 1
  print(decodedLength('QUI=')); // 2
  print(decodedLength('QUJD')); // 3
}

Le décodeur intégré est rapide : un seul passage sur une table de correspondance, sans allocation de chaînes par caractère, si bien que les chaînes de plusieurs mégaoctets sont courantes. C'est du côté de l'entrée que le base64 vous coûte : le texte encodé est environ 33 pour cent plus gros que les données, et c'est une chaîne, qui sur la VM vit en unités de code UTF-16, soit à peu près le double de la longueur en octets des caractères encodés. Pour des payloads qui peuvent devenir grands, décodez en stream plutôt que de joindre tout en une seule grosse chaîne.

Depuis la ligne de commande

La VM de Dart fait un CLI propre du décodeur. Cet outil lit un argument fichier ou l'entrée standard, aplatit les blancs, et écrit les octets bruts sur la sortie standard :

import 'dart:convert';
import 'dart:io';
Future<void> main(List<String> args) async {
  String encoded;
  if (args.isNotEmpty) {
    encoded = await File(args[0]).readAsString();
  } else {
    encoded = await stdin
        .transform(utf8.decoder)
        .join();
  }
  final flat = encoded.replaceAll(RegExp(r'\s+'), '');
  stdout.add(base64Decode(flat));
  await stdout.flush();
}

Enregistrez-le sous bin/decode.dart et exécutez dart run bin/decode.dart image.b64 > image.png, ou passez par un pipe : cat token.b64 | dart run bin/decode.dart. L'appel stdout.add prend le Uint8List directement, sans chaîne intermédiaire, ce qui est exactement la façon dont du binaire doit circuler dans un pipeline.

Les pièges qui mordent les développeurs Dart

  • Le mur du padding. Les entrées de style JWT et d'outils URL arrivent souvent sans signes =, et le décodeur les refuse avec Invalid length, must be multiple of four. Passez l'entrée non fiable par base64Url.normalize d'abord.
  • Le piège des blancs. Fichiers texte, email et copier-coller introduisent tous des retours à la ligne, et le décodeur ne les saute jamais. Aplatissez avec replaceAll(RegExp(r'\s+'), '') avant de décoder.
  • Confiance en l'alphabet. Comme les deux alphabets se décodent partout, ne construisez pas de logique sur quel décodeur a produit une chaîne. La chaîne est le contrat, pas les réglages du producteur.
  • String.fromCharCodes n'est pas un charset. Il lit des unités de code UTF-16, si bien qu'il transforme le texte UTF-8 en charabia. Utilisez utf8.decode ou un encodage explicite.
  • Deux types d'erreur différents. Les problèmes de décodage sont des FormatException ; l'encodeur lève ArgumentError pour les valeurs hors de la plage 0 à 255. Attrapez-les séparément si vous construisez une frontière.
  • Le résultat est à longueur fixe. Uint8List ne peut pas grandir, si bien que bytes.add(1) lève une UnsupportedError. Copiez avec List<int>.from(bytes) quand vous avez besoin d'une liste extensible.
  • Ne retirez pas l'échappement de %3D à la main. Le décodeur lit le padding échappé en pourcentage nativement ; un replaceAll('%3D', '=') prématuré couple votre code à un détail que le SDK gère déjà.
  • Décoder le payload d'un JWT n'est pas le vérifier. Lire les revendications et y croire est un bug de sécurité qui n'attend qu'un utilisateur déterminé.

Bonnes pratiques, liste courte

  • Partez sur base64Decode par défaut ; ne saisissez normalize qu'à la frontière où l'entrée est non fiable.
  • Soyez explicite sur le charset avec utf8.decode(bytes), même quand vous supposez l'UTF-8.
  • Gardez les octets en octets tant que vous ne savez pas ce que c'est ; le Uint8List voyage proprement vers File.writeAsBytes et consorts.
  • Aux frontières de confiance, attrapez FormatException et notez en journal la position de l'entrée que le message vous donne.
  • Faites passer en stream tout ce qui pourrait dépasser quelques mégaoctets.
  • Traitez le base64 comme un format, pas comme une protection : il ne cache rien à quiconque sait que c'est du base64.

Une brève histoire du Base64 dans Dart

Le décodeur que vous venez de rencontrer existe depuis plus longtemps que Dart 3, la null safety et l'ère Flutter. La version courte :

  • 18 novembre 2015, Dart 1.13 : le Base64 arrive dans dart:convert sous forme de la constante BASE64 plus les classes Base64Codec, Base64Encoder et Base64Decoder. Avant cette version, le SDK n'avait aucun base64 du tout.
  • 28 janvier 2016, Dart 1.14 : Base64Decoder.convert gagne des paramètres de plage start et end, et la même version ajoute la prise en charge des data URIs dans dart:core, le chemin Uri.parse sur lequel cet article s'appuie.
  • 26 avril 2016, Dart 1.16 : l'alphabet URL-safe rejoint la famille sous forme de BASE64URL et du constructeur Base64Codec.urlSafe.
  • 7 août 2018, Dart 2.0 : les constantes sont renommées en minuscules, base64 et base64Url, les base64Decode de premier niveau et consorts arrivent, le décodage retourne un Uint8List au lieu d'un List<int> extensible, et Base64Codec.normalize rejoint la famille, faisant de la validation et de la réparation une étape en un appel.
  • 2021, Dart 2.12 : la null safety est livrée, et toute l'histoire de dart:convert, base64 compris, devient null-safe.
  • Aujourd'hui, Dart 3.13 : les classes sont marquées final, et le comportement que vous avez rencontré plus haut est la même machine stricte, bialphabète et consciente du pourcentage qui tourne depuis 2015.

La stricte rigueur n'est pas un accident de l'implémentation. C'est le décodeur suivant l'instruction de la RFC 4648 selon laquelle les implémentations doivent rejeter les caractères hors alphabet, la tolérance de style MIME étant laissée aux applications qui en ont besoin, ce qui dans Dart veut dire une étape d'aplatissage avant le décodage.

Faits amusants

  • Le décodeur lit %3D comme padding natif. Donnez-lui le payload brut d'une data URI, échappement compris, et il décode. Très peu de machines d'exécution de langage font cela sans une étape de prétraitement.
  • base64.decoder et base64Url.decoder sont littéralement le même objet : les deux sont l'instance canonisée const Base64Decoder(). Le « décodeur URL-safe » est le décodeur standard dans un costume différent.
  • Tout le décodeur tient dans une table de correspondance à 128 entrées, un Int8List partagé entre l'interpréteur et le code compilé AOT, avec + et - qui pointent tous deux vers l'emplacement 62 de l'alphabet et / et _ qui pointent tous les deux vers 63.
  • Le base64 de Dart et sa prise en charge des data URIs sont arrivés à deux versions d'écart, dans les 1.13 et 1.14, et ils étaient clairement planifiés comme une paire : l'un pour lire le format, l'autre pour le lire directement dans une URL.
  • La chaîne vide se décode en un Uint8List vide sans erreur, et la chaîne vide s'encode en chaîne vide : le base64 traite l'absence de données comme un message parfaitement valide.
  • En 2018, quand Dart 2.0 a renommé ses constantes, BASE64 est devenu base64 dans le cadre d'un mouvement à l'échelle du SDK vers des noms de constantes en minuscules, la même vague qui vous a donné ascii, json et utf8.

Vous avez maintenant le décodeur complet : ce qu'il accepte, ce qu'il refuse, comment réparer une entrée endommagée, et comment le croiser dans les JWT, les data URIs, les fichiers, les streams, l'email et le shell. L'autre direction de l'échange, prendre des octets et produire l'un des deux alphabets, avec les décisions de padding et le calcul des tailles, est traité en détail dans le guide d'encodage Base64, lié à la fin de cette page.

Dernière mise à jour : 2026-09-08

Article associé : Encodage Base64 en Dart : un guide complet