Décoder et encoder en base64

Base64 est un groupe de schéma pour encoder des données binaires sous forme d'un texte au format ASCII grâce à la représentation de ces données en base 64. Le terme base64 vient à l'origine de l'encodage utiliser pour transférer certains contenus MIME.

Les schémas d'encodage en base64 sont principalement utilisés lorsqu'il s'agit d'enregistrer ou d'envoyer des données binaires via un media qui a été conçu pour gérer du texte. Cette transformation permet que les données envoyées ne sont pas modifiées lors du transport. Base64 est utilisé par plusieurs applications, notamment celles qui gèrent les courriels avec MIME, et le stockage de données complexes en XML.

Pour JavaScript, il existe deux fonctions utilisées pour encoder et décoder des chaînes en base64 :

La fonction atob() permet de décoder des données encodées en une chaîne de caractère en base 64. La fonction btoa(), quant à elle, permet de créer une chaîne ASCII en base64 à partir d'une « chaîne » de données binaires.

Les deux méthodes, atob() et btoa(), fonctionnent sur des chaînes de caractères. Si vous voulez utiliser des ArrayBuffers, lisez ce paragraphe.

Documentation

URIs de données
Les URIs de données, définies par la RFC 2397, permettent aux créateurs de contenus d'intégrer des fichiers en ligne dans des documents.
Base64
Article Wikipédia sur l'encodage en base64.
atob()
Méthode permettant de décoder une chaîne de donnée qui a été encodée en base64.
btoa()
Méthode permettant de créer une chaîne ASCII en base64 à partir d'une « chaîne » de données binaires.
Le « problème Unicode »
Pour la plupart des navigateurs, l'utilisation de btoa() sur une chaîne de caractères Unicode entraînera une exception Character Out Of Range. Ce paragraphe indique quelques solutions.
URIScheme
Une liste de schémas URI supportés par Mozilla.
StringView
Dans cet article, nous publions une bibliothèque dont les buts sont :
  • de créer une interface pour les chaînes de caractères à la façon du langage C (i.e. un tableau de code de caractères — ArrayBufferView en JavaScript) basée sur l'interface JavaScript ArrayBuffer,
  • de créer un ensemble de méthodes pour ces objets qui fonctionnent sur des tableaux de nombres plutôt que sur chaînes de caractères JavaScript immuables,
  • de travailler avec d'autres encodages Unicode, y compris ceux différent d'UTF-16 qui est l'encodage par défaut de JavaScript pour les DOMString.

Voir toutes les pages sur base64...

Outils

Voir toutes les pages sur base64...

Le « problème Unicode »

Les objets DOMString sont des chaînes de caractères encodées sur 16 bits. Pour la plupart des navigateurs, lorsqu'on appelle window.btoa sur une chaîne Unicode, cela entraîne une exception Character Out Of Range si la représentation du caractère dépasse les 8 bits ASCII. Deux méthodes existent pour résoudre le problème :

  • échapper la chaîne dans son intégralité puis l'encoder,
  • convertir la chaîne UTF-16 DOMString en un tableau UTF-8 de caractères puis l'encoder.

Voici ces deux méthodes :

Première solution  – échapper la chaîne avant de l'encoder

function utf8_to_b64( str ) {
  return window.btoa(unescape(encodeURIComponent( str )));
}

function b64_to_utf8( str ) {
  return decodeURIComponent(escape(window.atob( str )));
}

// Usage:
utf8_to_b64('✓ à la mode'); // "4pyTIMOgIGxhIG1vZGU="
b64_to_utf8('4pyTIMOgIGxhIG1vZGU='); // "✓ à la mode"

Cette solution a été proposée dans un article de Johan Sundström.

Seconde solution – réécrire atob() et btoa() en utilisant des TypedArray avec de l'UTF-8

Note : Le code suivant peut également être utilisé pour obtenir un ArrayBuffer depuis une chaîne en base 64 (et vice-versa, voir ci-après). Pour un article concernant une bibliothèque complète sur les tableaux typés, voir cet article.
"use strict";

/*\
|*|
|*|  utilitairezs de manipulations de chaînes base 64 / binaires / UTF-8
|*|
|*|  https://developer.mozilla.org/fr/docs/Décoder_encoder_en_base64
|*|
\*/

/* Décoder un tableau d'octets depuis une chaîne en base64 */

function b64ToUint6 (nChr) {

  return nChr > 64 && nChr < 91 ?
      nChr - 65
    : nChr > 96 && nChr < 123 ?
      nChr - 71
    : nChr > 47 && nChr < 58 ?
      nChr + 4
    : nChr === 43 ?
      62
    : nChr === 47 ?
      63
    :
      0;

}

function base64DecToArr (sBase64, nBlocksSize) {

  var
    sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length,
    nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2, taBytes = new Uint8Array(nOutLen);

  for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
    nMod4 = nInIdx & 3;
    nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
    if (nMod4 === 3 || nInLen - nInIdx === 1) {
      for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
        taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
      }
      nUint24 = 0;

    }
  }

  return taBytes;
}

/* encodage d'un tableau en une chaîne en base64 */

function uint6ToB64 (nUint6) {

  return nUint6 < 26 ?
      nUint6 + 65
    : nUint6 < 52 ?
      nUint6 + 71
    : nUint6 < 62 ?
      nUint6 - 4
    : nUint6 === 62 ?
      43
    : nUint6 === 63 ?
      47
    :
      65;

}

function base64EncArr (aBytes) {

  var nMod3 = 2, sB64Enc = "";

  for (var nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) {
    nMod3 = nIdx % 3;
    if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += "\r\n"; }
    nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24);
    if (nMod3 === 2 || aBytes.length - nIdx === 1) {
      sB64Enc += String.fromCharCode(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63));
      nUint24 = 0;
    }
  }

  return sB64Enc.substr(0, sB64Enc.length - 2 + nMod3) + (nMod3 === 2 ? '' : nMod3 === 1 ? '=' : '==');

}

/* Tableau UTF-8 en DOMString et vice versa */

function UTF8ArrToStr (aBytes) {

  var sView = "";

  for (var nPart, nLen = aBytes.length, nIdx = 0; nIdx < nLen; nIdx++) {
    nPart = aBytes[nIdx];
    sView += String.fromCharCode(
      nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? /* six bytes */
        /* (nPart - 252 << 32) n'est pas possible pour ECMAScript donc, on utilise un contournement... : */
        (nPart - 252) * 1073741824 + (aBytes[++nIdx] - 128 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
      : nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? /* five bytes */
        (nPart - 248 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
      : nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? /* four bytes */
        (nPart - 240 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
      : nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? /* three bytes */
        (nPart - 224 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
      : nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? /* two bytes */
        (nPart - 192 << 6) + aBytes[++nIdx] - 128
      : /* nPart < 127 ? */ /* one byte */
        nPart
    );
  }

  return sView;

}

function strToUTF8Arr (sDOMStr) {

  var aBytes, nChr, nStrLen = sDOMStr.length, nArrLen = 0;

  /* mapping... */

  for (var nMapIdx = 0; nMapIdx < nStrLen; nMapIdx++) {
    nChr = sDOMStr.charCodeAt(nMapIdx);
    nArrLen += nChr < 0x80 ? 1 : nChr < 0x800 ? 2 : nChr < 0x10000 ? 3 : nChr < 0x200000 ? 4 : nChr < 0x4000000 ? 5 : 6;
  }

  aBytes = new Uint8Array(nArrLen);

  /* transcription... */

  for (var nIdx = 0, nChrIdx = 0; nIdx < nArrLen; nChrIdx++) {
    nChr = sDOMStr.charCodeAt(nChrIdx);
    if (nChr < 128) {
      /* one byte */
      aBytes[nIdx++] = nChr;
    } else if (nChr < 0x800) {
      /* two bytes */
      aBytes[nIdx++] = 192 + (nChr >>> 6);
      aBytes[nIdx++] = 128 + (nChr & 63);
    } else if (nChr < 0x10000) {
      /* three bytes */
      aBytes[nIdx++] = 224 + (nChr >>> 12);
      aBytes[nIdx++] = 128 + (nChr >>> 6 & 63);
      aBytes[nIdx++] = 128 + (nChr & 63);
    } else if (nChr < 0x200000) {
      /* four bytes */
      aBytes[nIdx++] = 240 + (nChr >>> 18);
      aBytes[nIdx++] = 128 + (nChr >>> 12 & 63);
      aBytes[nIdx++] = 128 + (nChr >>> 6 & 63);
      aBytes[nIdx++] = 128 + (nChr & 63);
    } else if (nChr < 0x4000000) {
      /* five bytes */
      aBytes[nIdx++] = 248 + (nChr >>> 24);
      aBytes[nIdx++] = 128 + (nChr >>> 18 & 63);
      aBytes[nIdx++] = 128 + (nChr >>> 12 & 63);
      aBytes[nIdx++] = 128 + (nChr >>> 6 & 63);
      aBytes[nIdx++] = 128 + (nChr & 63);
    } else /* if (nChr <= 0x7fffffff) */ {
      /* six bytes */
      aBytes[nIdx++] = 252 + /* (nChr >>> 32) is not possible in ECMAScript! So...: */ (nChr / 1073741824);
      aBytes[nIdx++] = 128 + (nChr >>> 24 & 63);
      aBytes[nIdx++] = 128 + (nChr >>> 18 & 63);
      aBytes[nIdx++] = 128 + (nChr >>> 12 & 63);
      aBytes[nIdx++] = 128 + (nChr >>> 6 & 63);
      aBytes[nIdx++] = 128 + (nChr & 63);
    }
  }

  return aBytes;

}

Tests

/* Tests */

var entréeChaîne = "base64 \u2014 Mozilla Developer Network";

var entréeUTF8 = strToUTF8Arr(entréeChaîne);

var base64 = base64EncArr(entréeUTF8);

alert(base64);

var sortieUT8 = base64DecToArr(base64);

var sortieChaîne = UTF8ArrToStr(sortieUT8);

alert(sortieChaîne);

Annexe : Décoder une chaîne en base64 en un objet Uint8Array ou ArrayBuffer

Ces fonctions permettent de créer des objets uint8Arrays ou arrayBuffers à partir de chaînes en base64 :

var monTableau = base64DecToArr("QmFzZSA2NCDigJQgTW96aWxsYSBEZXZlbG9wZXIgTmV0d29yaw=="); // "Base 64 \u2014 Mozilla Developer Network"

var monBuffer = base64DecToArr("QmFzZSA2NCDigJQgTW96aWxsYSBEZXZlbG9wZXIgTmV0d29yaw==").buffer; // "Base 64 \u2014 Mozilla Developer Network"

alert(monBuffer.byteLength);
Note : La fonction base64DecToArr(sBase64[, nTailleBloc]) renvoie un uint8Array d'octets. Si vous souhaitez utiliser un tampon mémoire de 16 bits, 32 bits, 64 bits pour les données brutes, utilisez l'argument nTailleBloc, qui représente le nombre d'octets dont la propriété uint8Array.buffer.bytesLength doit être un multiple (1 ou pas de paramètre pour l'ASCII, les chaînes binaires ou les chaînes encodées UTF-8, 2 pour les chaînes UTF-16, 4 pour les chaînes UTF-32).

Pour une bibliothèque plus complète, voir StringView – une représentation des chaînes de caractères semblable à celle du langage C, basée sur les tableaux typés

Voir aussi

Étiquettes et contributeurs liés au document

 Contributeurs à cette page : SphinxKnight, fscholz
 Dernière mise à jour par : SphinxKnight,