L'utilisation de l'atob de Javascript pour décoder base64 ne décode pas correctement les chaînes utf-8

106

J'utilise la window.atob()fonction Javascript pour décoder une chaîne encodée en base64 (en particulier le contenu encodé en base64 de l'API GitHub). Le problème est que je récupère les caractères encodés en ASCII (comme â¢au lieu de ). Comment puis-je gérer correctement le flux entrant encodé en base64 pour qu'il soit décodé en utf-8?

brandonscript
la source
3
La page MDN que vous avez liée comporte un paragraphe commençant par la phrase «À utiliser avec des chaînes Unicode ou UTF-8».
Pointy
1
Êtes-vous sur le nœud? Il existe de meilleures solutions queatob
Bergi

Réponses:

269

Il existe un excellent article sur la documentation MDN de Mozilla qui décrit exactement ce problème:

Le "problème Unicode" depuis DOMString s sont des chaînes codées sur 16 bits, dans la plupart des navigateurs appelant window.btoaune chaîne Unicode, cela entraînera un Character Out Of Range exceptionsi un caractère dépasse la plage d'un octet de 8 bits (0x00 ~ 0xFF). Il existe deux méthodes possibles pour résoudre ce problème:

  • le premier est d'échapper à la chaîne entière (avec UTF-8, voir encodeURIComponent) et de l'encoder ensuite;
  • le second est de convertir l'UTF-16 DOMString en un tableau UTF-8 de caractères, puis à l'encoder.

Une note sur les solutions précédentes: l'article de MDN suggérait à l'origine d'utiliser unescapeet escapede résoudre lesCharacter Out Of Range problème d'exception, mais ils sont depuis obsolètes. Certaines autres réponses ici ont suggéré de contourner ce problème avec decodeURIComponentet encodeURIComponent, cela s'est avéré peu fiable et imprévisible. La mise à jour la plus récente de cette réponse utilise des fonctions JavaScript modernes pour améliorer la vitesse et moderniser le code.

Si vous essayez de gagner du temps, vous pouvez également envisager d'utiliser une bibliothèque:

Encodage UTF8 ⇢ base64

function b64EncodeUnicode(str) {
    // first we use encodeURIComponent to get percent-encoded UTF-8,
    // then we convert the percent encodings into raw bytes which
    // can be fed into btoa.
    return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
        function toSolidBytes(match, p1) {
            return String.fromCharCode('0x' + p1);
    }));
}

b64EncodeUnicode('✓ à la mode'); // "4pyTIMOgIGxhIG1vZGU="
b64EncodeUnicode('\n'); // "Cg=="

Décodage base64 ⇢ UTF8

function b64DecodeUnicode(str) {
    // Going backwards: from bytestream, to percent-encoding, to original string.
    return decodeURIComponent(atob(str).split('').map(function(c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
    }).join(''));
}

b64DecodeUnicode('4pyTIMOgIGxhIG1vZGU='); // "✓ à la mode"
b64DecodeUnicode('Cg=='); // "\n"

La solution pré-2018 (fonctionnelle, et bien que probablement meilleure prise en charge pour les navigateurs plus anciens, pas à jour)

Voici la recommandation actuelle, directement de MDN, avec une compatibilité TypeScript supplémentaire via @ MA-Maddin:

// Encoding UTF8 ⇢ base64

function b64EncodeUnicode(str) {
    return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
        return String.fromCharCode(parseInt(p1, 16))
    }))
}

b64EncodeUnicode('✓ à la mode') // "4pyTIMOgIGxhIG1vZGU="
b64EncodeUnicode('\n') // "Cg=="

// Decoding base64 ⇢ UTF8

function b64DecodeUnicode(str) {
    return decodeURIComponent(Array.prototype.map.call(atob(str), function(c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
    }).join(''))
}

b64DecodeUnicode('4pyTIMOgIGxhIG1vZGU=') // "✓ à la mode"
b64DecodeUnicode('Cg==') // "\n"

La solution d'origine (obsolète)

Ceci utilisé escapeet unescape(qui sont maintenant obsolètes, bien que cela fonctionne toujours dans tous les navigateurs modernes):

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"

Et une dernière chose: j'ai rencontré ce problème pour la première fois lors de l'appel de l'API GitHub. Pour que cela fonctionne correctement sur Safari (Mobile), j'ai dû supprimer tout l'espace blanc de la source base64 avant même de pouvoir décoder la source. Que ce soit toujours d'actualité ou non en 2017, je ne sais pas:

function b64_to_utf8( str ) {
    str = str.replace(/\s/g, '');    
    return decodeURIComponent(escape(window.atob( str )));
}
brandonscript
la source
1
w3schools.com/jsref/jsref_unescape.asp "La fonction unescape () était obsolète dans JavaScript version 1.5. Utilisez plutôt decodeURI () ou decodeURIComponent ()."
Tedd Hansen
1
Vous avez sauvé mes jours, frère
Mr Neo
2
Mise à jour: Solution n ° 1 dans MDN Le "problème Unicode" a été corrigé, b64DecodeUnicode('4pyTIMOgIGxhIG1vZGU=');
affiche
2
Une autre façon de décoder ne serait decodeURIComponent(atob('4pyTIMOgIGxhIG1vZGU=').split('').map(x => '%' + x.charCodeAt(0).toString(16)).join('')) pas le code le plus performant, mais c'est ce qu'il est.
daniel.gindi
2
return String.fromCharCode(parseInt(p1, 16));pour avoir la compatibilité TypeScript.
Martin Schneider
20

Les choses changent. Les méthodes escape / unescape sont obsolètes.

Vous pouvez encoder la chaîne par URI avant de l'encoder en Base64. Notez que cela ne produit pas de données UTF8 encodées en Base64, mais plutôt des données encodées en URL encodées en Base64. Les deux parties doivent s'entendre sur le même encodage.

Voir l'exemple de travail ici: http://codepen.io/anon/pen/PZgbPW

// encode string
var base64 = window.btoa(encodeURIComponent('€ 你好 æøåÆØÅ'));
// decode string
var str = decodeURIComponent(window.atob(tmp));
// str is now === '€ 你好 æøåÆØÅ'

Pour le problème d'OP, une bibliothèque tierce telle que js-base64 devrait résoudre le problème.

Tedd Hansen
la source
1
Je tiens à souligner que vous ne produisez pas la base64 de la chaîne d'entrée, mais de son composant codé. Donc, si vous l'envoyez, l'autre partie ne peut pas le décoder en "base64" et obtenir la chaîne d'origine
Riccardo Galli
3
Vous avez raison, j'ai mis à jour le texte pour le souligner. Merci. L'alternative semble implémenter base64 vous-même, en utilisant une bibliothèque tierce (telle que js-base64) ou en recevant "Erreur: Impossible d'exécuter 'btoa' sur 'Window': La chaîne à encoder contient des caractères en dehors de la plage Latin1. "
Tedd Hansen
14

Si vous préférez traiter les chaînes comme des octets, vous pouvez utiliser les fonctions suivantes

function u_atob(ascii) {
    return Uint8Array.from(atob(ascii), c => c.charCodeAt(0));
}

function u_btoa(buffer) {
    var binary = [];
    var bytes = new Uint8Array(buffer);
    for (var i = 0, il = bytes.byteLength; i < il; i++) {
        binary.push(String.fromCharCode(bytes[i]));
    }
    return btoa(binary.join(''));
}


// example, it works also with astral plane characters such as '𝒞'
var encodedString = new TextEncoder().encode('✓');
var base64String = u_btoa(encodedString);
console.log('✓' === new TextDecoder().decode(u_atob(base64String)))
Riccardo Galli
la source
1
Merci. Votre réponse a été cruciale pour m'aider à faire fonctionner cela, ce qui m'a pris de nombreuses heures sur plusieurs jours. +1. stackoverflow.com/a/51814273/470749
Ryan
Pour une solution beaucoup plus rapide et plus multi-navigateurs (mais essentiellement le même résultat), veuillez consulter stackoverflow.com/a/53433503/5601591
Jack Giffin
u_atob et u_btoa utilisent des fonctions disponibles dans tous les navigateurs depuis IE10 (2012), me semble solide (si vous vous référez à TextEncoder, ce n'est qu'un exemple)
Riccardo Galli
5

Voici la solution mise à jour 2018 telle que décrite dans les ressources de développement de Mozilla

POUR ENCODER DE UNICODE À B64

function b64EncodeUnicode(str) {
    // first we use encodeURIComponent to get percent-encoded UTF-8,
    // then we convert the percent encodings into raw bytes which
    // can be fed into btoa.
    return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
        function toSolidBytes(match, p1) {
            return String.fromCharCode('0x' + p1);
    }));
}

b64EncodeUnicode('✓ à la mode'); // "4pyTIMOgIGxhIG1vZGU="
b64EncodeUnicode('\n'); // "Cg=="

POUR DÉCODER DE B64 À UNICODE

function b64DecodeUnicode(str) {
    // Going backwards: from bytestream, to percent-encoding, to original string.
    return decodeURIComponent(atob(str).split('').map(function(c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
    }).join(''));
}

b64DecodeUnicode('4pyTIMOgIGxhIG1vZGU='); // "✓ à la mode"
b64DecodeUnicode('Cg=='); // "\n"
Manuel G
la source
4

L'article complet qui fonctionne pour moi: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding

La partie où nous encodons à partir d'Unicode / UTF-8 est

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"

C'est l'une des méthodes les plus utilisées de nos jours.

rika
la source
C'est le même lien que la réponse acceptée.
brandonscript
3

Je suppose que l'on pourrait vouloir une solution qui produit un URI base64 largement utilisable. Veuillez visiter data:text/plain;charset=utf-8;base64,4pi44pi54pi64pi74pi84pi+4pi/pour voir une démonstration (copiez l'URI de données, ouvrez un nouvel onglet, collez l'URI de données dans la barre d'adresse, puis appuyez sur Entrée pour aller à la page). Malgré le fait que cet URI soit encodé en base64, le navigateur est toujours capable de reconnaître les points de code élevés et de les décoder correctement. Le codeur minifié + décodeur est de 1058 octets (+ Gzip → 589 octets)

!function(e){"use strict";function h(b){var a=b.charCodeAt(0);if(55296<=a&&56319>=a)if(b=b.charCodeAt(1),b===b&&56320<=b&&57343>=b){if(a=1024*(a-55296)+b-56320+65536,65535<a)return d(240|a>>>18,128|a>>>12&63,128|a>>>6&63,128|a&63)}else return d(239,191,189);return 127>=a?inputString:2047>=a?d(192|a>>>6,128|a&63):d(224|a>>>12,128|a>>>6&63,128|a&63)}function k(b){var a=b.charCodeAt(0)<<24,f=l(~a),c=0,e=b.length,g="";if(5>f&&e>=f){a=a<<f>>>24+f;for(c=1;c<f;++c)a=a<<6|b.charCodeAt(c)&63;65535>=a?g+=d(a):1114111>=a?(a-=65536,g+=d((a>>10)+55296,(a&1023)+56320)):c=0}for(;c<e;++c)g+="\ufffd";return g}var m=Math.log,n=Math.LN2,l=Math.clz32||function(b){return 31-m(b>>>0)/n|0},d=String.fromCharCode,p=atob,q=btoa;e.btoaUTF8=function(b,a){return q((a?"\u00ef\u00bb\u00bf":"")+b.replace(/[\x80-\uD7ff\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]?/g,h))};e.atobUTF8=function(b,a){a||"\u00ef\u00bb\u00bf"!==b.substring(0,3)||(b=b.substring(3));return p(b).replace(/[\xc0-\xff][\x80-\xbf]*/g,k)}}(""+void 0==typeof global?""+void 0==typeof self?this:self:global)

Vous trouverez ci-dessous le code source utilisé pour le générer.

var fromCharCode = String.fromCharCode;
var btoaUTF8 = (function(btoa, replacer){"use strict";
    return function(inputString, BOMit){
        return btoa((BOMit ? "\xEF\xBB\xBF" : "") + inputString.replace(
            /[\x80-\uD7ff\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]?/g, replacer
        ));
    }
})(btoa, function(nonAsciiChars){"use strict";
    // make the UTF string into a binary UTF-8 encoded string
    var point = nonAsciiChars.charCodeAt(0);
    if (point >= 0xD800 && point <= 0xDBFF) {
        var nextcode = nonAsciiChars.charCodeAt(1);
        if (nextcode !== nextcode) // NaN because string is 1 code point long
            return fromCharCode(0xef/*11101111*/, 0xbf/*10111111*/, 0xbd/*10111101*/);
        // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
        if (nextcode >= 0xDC00 && nextcode <= 0xDFFF) {
            point = (point - 0xD800) * 0x400 + nextcode - 0xDC00 + 0x10000;
            if (point > 0xffff)
                return fromCharCode(
                    (0x1e/*0b11110*/<<3) | (point>>>18),
                    (0x2/*0b10*/<<6) | ((point>>>12)&0x3f/*0b00111111*/),
                    (0x2/*0b10*/<<6) | ((point>>>6)&0x3f/*0b00111111*/),
                    (0x2/*0b10*/<<6) | (point&0x3f/*0b00111111*/)
                );
        } else return fromCharCode(0xef, 0xbf, 0xbd);
    }
    if (point <= 0x007f) return nonAsciiChars;
    else if (point <= 0x07ff) {
        return fromCharCode((0x6<<5)|(point>>>6), (0x2<<6)|(point&0x3f));
    } else return fromCharCode(
        (0xe/*0b1110*/<<4) | (point>>>12),
        (0x2/*0b10*/<<6) | ((point>>>6)&0x3f/*0b00111111*/),
        (0x2/*0b10*/<<6) | (point&0x3f/*0b00111111*/)
    );
});

Ensuite, pour décoder les données base64, HTTP récupère les données sous forme d'URI de données ou utilise la fonction ci-dessous.

var clz32 = Math.clz32 || (function(log, LN2){"use strict";
    return function(x) {return 31 - log(x >>> 0) / LN2 | 0};
})(Math.log, Math.LN2);
var fromCharCode = String.fromCharCode;
var atobUTF8 = (function(atob, replacer){"use strict";
    return function(inputString, keepBOM){
        inputString = atob(inputString);
        if (!keepBOM && inputString.substring(0,3) === "\xEF\xBB\xBF")
            inputString = inputString.substring(3); // eradicate UTF-8 BOM
        // 0xc0 => 0b11000000; 0xff => 0b11111111; 0xc0-0xff => 0b11xxxxxx
        // 0x80 => 0b10000000; 0xbf => 0b10111111; 0x80-0xbf => 0b10xxxxxx
        return inputString.replace(/[\xc0-\xff][\x80-\xbf]*/g, replacer);
    }
})(atob, function(encoded){"use strict";
    var codePoint = encoded.charCodeAt(0) << 24;
    var leadingOnes = clz32(~codePoint);
    var endPos = 0, stringLen = encoded.length;
    var result = "";
    if (leadingOnes < 5 && stringLen >= leadingOnes) {
        codePoint = (codePoint<<leadingOnes)>>>(24+leadingOnes);
        for (endPos = 1; endPos < leadingOnes; ++endPos)
            codePoint = (codePoint<<6) | (encoded.charCodeAt(endPos)&0x3f/*0b00111111*/);
        if (codePoint <= 0xFFFF) { // BMP code point
          result += fromCharCode(codePoint);
        } else if (codePoint <= 0x10FFFF) {
          // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
          codePoint -= 0x10000;
          result += fromCharCode(
            (codePoint >> 10) + 0xD800,  // highSurrogate
            (codePoint & 0x3ff) + 0xDC00 // lowSurrogate
          );
        } else endPos = 0; // to fill it in with INVALIDs
    }
    for (; endPos < stringLen; ++endPos) result += "\ufffd"; // replacement character
    return result;
});

L'avantage d'être plus standard est que cet encodeur et ce décodeur sont plus largement applicables car ils peuvent être utilisés comme une URL valide qui s'affiche correctement. Observer.

(function(window){
    "use strict";
    var sourceEle = document.getElementById("source");
    var urlBarEle = document.getElementById("urlBar");
    var mainFrameEle = document.getElementById("mainframe");
    var gotoButton = document.getElementById("gotoButton");
    var parseInt = window.parseInt;
    var fromCodePoint = String.fromCodePoint;
    var parse = JSON.parse;
    
    function unescape(str){
        return str.replace(/\\u[\da-f]{0,4}|\\x[\da-f]{0,2}|\\u{[^}]*}|\\[bfnrtv"'\\]|\\0[0-7]{1,3}|\\\d{1,3}/g, function(match){
          try{
            if (match.startsWith("\\u{"))
              return fromCodePoint(parseInt(match.slice(2,-1),16));
            if (match.startsWith("\\u") || match.startsWith("\\x"))
              return fromCodePoint(parseInt(match.substring(2),16));
            if (match.startsWith("\\0") && match.length > 2)
              return fromCodePoint(parseInt(match.substring(2),8));
            if (/^\\\d/.test(match)) return fromCodePoint(+match.slice(1));
          }catch(e){return "\ufffd".repeat(match.length)}
          return parse('"' + match + '"');
        });
    }
    
    function whenChange(){
      try{ urlBarEle.value = "data:text/plain;charset=UTF-8;base64," + btoaUTF8(unescape(sourceEle.value), true);
      } finally{ gotoURL(); }
    }
    sourceEle.addEventListener("change",whenChange,{passive:1});
    sourceEle.addEventListener("input",whenChange,{passive:1});
    
    // IFrame Setup:
    function gotoURL(){mainFrameEle.src = urlBarEle.value}
    gotoButton.addEventListener("click", gotoURL, {passive: 1});
    function urlChanged(){urlBarEle.value = mainFrameEle.src}
    mainFrameEle.addEventListener("load", urlChanged, {passive: 1});
    urlBarEle.addEventListener("keypress", function(evt){
      if (evt.key === "enter") evt.preventDefault(), urlChanged();
    }, {passive: 1});
    
        
    var fromCharCode = String.fromCharCode;
    var btoaUTF8 = (function(btoa, replacer){
		    "use strict";
        return function(inputString, BOMit){
        	return btoa((BOMit?"\xEF\xBB\xBF":"") + inputString.replace(
        		/[\x80-\uD7ff\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]?/g, replacer
    		));
    	}
    })(btoa, function(nonAsciiChars){
		"use strict";
    	// make the UTF string into a binary UTF-8 encoded string
    	var point = nonAsciiChars.charCodeAt(0);
    	if (point >= 0xD800 && point <= 0xDBFF) {
    		var nextcode = nonAsciiChars.charCodeAt(1);
    		if (nextcode !== nextcode) { // NaN because string is 1code point long
    			return fromCharCode(0xef/*11101111*/, 0xbf/*10111111*/, 0xbd/*10111101*/);
    		}
    		// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
    		if (nextcode >= 0xDC00 && nextcode <= 0xDFFF) {
    			point = (point - 0xD800) * 0x400 + nextcode - 0xDC00 + 0x10000;
    			if (point > 0xffff) {
    				return fromCharCode(
    					(0x1e/*0b11110*/<<3) | (point>>>18),
    					(0x2/*0b10*/<<6) | ((point>>>12)&0x3f/*0b00111111*/),
    					(0x2/*0b10*/<<6) | ((point>>>6)&0x3f/*0b00111111*/),
    					(0x2/*0b10*/<<6) | (point&0x3f/*0b00111111*/)
    				);
    			}
    		} else {
    			return fromCharCode(0xef, 0xbf, 0xbd);
    		}
    	}
    	if (point <= 0x007f) { return inputString; }
    	else if (point <= 0x07ff) {
    		return fromCharCode((0x6<<5)|(point>>>6), (0x2<<6)|(point&0x3f/*00111111*/));
    	} else {
    		return fromCharCode(
    			(0xe/*0b1110*/<<4) | (point>>>12),
    			(0x2/*0b10*/<<6) | ((point>>>6)&0x3f/*0b00111111*/),
    			(0x2/*0b10*/<<6) | (point&0x3f/*0b00111111*/)
    		);
    	}
    });
    setTimeout(whenChange, 0);
})(window);
img:active{opacity:0.8}
<center>
<textarea id="source" style="width:66.7vw">Hello \u1234 W\186\0256ld!
Enter text into the top box. Then the URL will update automatically.
</textarea><br />
<div style="width:66.7vw;display:inline-block;height:calc(25vw + 1em + 6px);border:2px solid;text-align:left;line-height:1em">
<input id="urlBar" style="width:calc(100% - 1em - 13px)" /><img id="gotoButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABsAAAAeCAMAAADqx5XUAAAAclBMVEX///9NczZ8e32ko6fDxsU/fBoSQgdFtwA5pAHVxt+7vLzq5ex23y4SXABLiiTm0+/c2N6DhoQ6WSxSyweVlZVvdG/Uz9aF5kYlbwElkwAggACxs7Jl3hX07/cQbQCar5SU9lRntEWGum+C9zIDHwCGnH5IvZAOAAABmUlEQVQoz7WS25acIBBFkRLkIgKKtOCttbv//xdDmTGZzHv2S63ltuBQQP4rdRiRUP8UK4wh6nVddQwj/NtDQTvac8577zTQb72zj65/876qqt7wykU6/1U6vFEgjE1mt/5LRqrpu7oVsn0sjZejMfxR3W/yLikqAFcUx93YxLmZGOtElmEu6Ufd9xV3ZDTGcEvGLbMk0mHHlUSvS5svCwS+hVL8loQQyfpI1Ay8RF/xlNxcsTchGjGDIuBG3Ik7TMyNxn8m0TSnBAK6Z8UZfp3IbAonmJvmsEACum6aNv7B0CnvpezDcNhw9XWsuAr7qnRg6dABmeM4dTgn/DZdXWs3LMspZ1KDMt1kcPJ6S1icWNp2qaEmjq6myx7jbQK3VKItLJaW5FR+cuYlRhYNKzGa9vF4vM5roLW3OSVjkmiGJrPhUq301/16pVKZRGFYWjTP50spTxBN5Z4EKnSonruk+n4tUokv1aJSEl/MLZU90S3L6/U6o0J142iQVp3HcZxKSo8LfkNRCtJaKYFSRX7iaoAAUDty8wvWYR6HJEepdwAAAABJRU5ErkJggg==" style="width:calc(1em + 4px);line-height:1em;vertical-align:-40%;cursor:pointer" />
<iframe id="mainframe" style="width:66.7vw;height:25vw" frameBorder="0"></iframe>
</div>
</center>

En plus d'être très standardisés, les extraits de code ci-dessus sont également très rapides. Au lieu d'une chaîne de succession indirecte où les données doivent être converties plusieurs fois entre différentes formes (comme dans la réponse de Riccardo Galli), l'extrait de code ci-dessus est aussi direct que possible. Il utilise un seul String.prototype.replaceappel rapide simple pour traiter les données lors du codage, et un seul pour décoder les données lors du décodage. Un autre avantage est que (en particulier pour les grosses chaînes), String.prototype.replacepermet au navigateur de gérer automatiquement la gestion de la mémoire sous-jacente du redimensionnement de la chaîne, ce qui améliore considérablement les performances, en particulier dans les navigateurs à feuilles persistantes comme Chrome et Firefox qui optimisent fortementString.prototype.replace. Enfin, la cerise sur le gâteau est que pour les utilisateurs du script latin exclūsīvō, les chaînes qui ne contiennent aucun point de code au-dessus de 0x7f sont très rapides à traiter car la chaîne reste inchangée par l'algorithme de remplacement.

J'ai créé un référentiel github pour cette solution à l' adresse https://github.com/anonyco/BestBase64EncoderDecoder/

Jack Giffin
la source
Pouvez-vous expliquer ce que vous entendez par «méthode créée par l'utilisateur» ou «interprétable par le navigateur»? Quelle est la valeur ajoutée de l'utilisation de cette solution par rapport, disons, à ce que Mozilla recommande?
brandonscript
@brandonscript Mozilla est différent de MDN. MDN est un contenu créé par l'utilisateur. La page sur MDN qui recommande votre solution était un contenu créé par l'utilisateur, et non un contenu créé par le fournisseur du navigateur.
Jack Giffin
Votre fournisseur de solution a-t-il été créé? Je le ferais, je suggérerais de rendre hommage à l'origine. Sinon, alors il est également créé par l'utilisateur, et pas différent de la réponse de MDN?
brandonscript
@brandonscript Bon point. Vous avez raison. J'ai supprimé ce morceau de texte. Consultez également la démo que j'ai ajoutée.
Jack Giffin
0

Les petites corrections, unescape et escape sont obsolètes, donc:

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

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


function b64_to_utf8( str ) {
    str = str.replace(/\s/g, '');    
    return decodeURIComponent(encodeURIComponent(window.atob(str)));
}
Darkves
la source
2
On dirait que le lien doc est encore différent de celui-ci maintenant, suggérant une solution regex pour le gérer.
brandonscript
2
Cela ne fonctionnera pas, car encodeURIComponentc'est l'inverse de decodeURIComponent, c'est-à-dire qu'il annulera simplement la conversion. Voir stackoverflow.com/a/31412163/1534459 pour une bonne explication de ce qui se passe avec escapeet unescape.
bodo
1
@canaaerus Je ne comprends pas votre commentaire? escape et unescape sont obsolètes, je les échange juste avec la fonction [decode | encode] URIComponent :-) Tout fonctionne très bien. Lisez d'abord la question
Darkves
1
@Darkves: La raison pour laquelle encodeURIComponentest utilisé, est de gérer correctement (toute la gamme de) chaînes Unicode. Donc par exemple window.btoa(decodeURIComponent(encodeURIComponent('€')))donne Error: String contains an invalid characterparce que c'est le même que window.btoa('€')et btoane peut pas encoder .
bodo
2
@Darkves: Oui, c'est exact. Mais vous ne pouvez pas permuter d'échappement avec EncodeURIComponent et unescape avec DecodeURIComponent, car les méthodes Encode et escape ne font pas la même chose. Idem avec decode & unescape. J'ai fait la même erreur au départ. Vous devriez remarquer que si vous prenez une chaîne, UriEncode, puis UriDecode, vous récupérez la même chaîne que vous avez entrée. Donc, faire cela serait absurde. Lorsque vous écartez une chaîne encodée avec encodeURIComponent, vous ne récupérez pas la même chaîne que celle que vous avez entrée, c'est pourquoi avec escape / unescape cela fonctionne, mais pas avec la vôtre.
Stefan Steiger
0

Voici un code à l'épreuve du temps pour les navigateurs qui peuvent manquer escape/unescape(). Notez que IE 9 et les versions antérieures ne prennent pas en charge atob/btoa(), vous devrez donc utiliser des fonctions base64 personnalisées pour eux.

// Polyfill for escape/unescape
if( !window.unescape ){
    window.unescape = function( s ){
        return s.replace( /%([0-9A-F]{2})/g, function( m, p ) {
            return String.fromCharCode( '0x' + p );
        } );
    };
}
if( !window.escape ){
    window.escape = function( s ){
        var chr, hex, i = 0, l = s.length, out = '';
        for( ; i < l; i ++ ){
            chr = s.charAt( i );
            if( chr.search( /[A-Za-z0-9\@\*\_\+\-\.\/]/ ) > -1 ){
                out += chr; continue; }
            hex = s.charCodeAt( i ).toString( 16 );
            out += '%' + ( hex.length % 2 != 0 ? '0' : '' ) + hex;
        }
        return out;
    };
}

// Base64 encoding of UTF-8 strings
var utf8ToB64 = function( s ){
    return btoa( unescape( encodeURIComponent( s ) ) );
};
var b64ToUtf8 = function( s ){
    return decodeURIComponent( escape( atob( s ) ) );
};

Un exemple plus complet d'encodage et de décodage UTF-8 peut être trouvé ici: http://jsfiddle.net/47zwb41o/

Beejor
la source
-1

y compris la solution ci-dessus si le problème persiste, essayez comme ci-dessous, considérez le cas où l'échappement n'est pas pris en charge pour TS.

blob = new Blob(["\ufeff", csv_content]); // this will make symbols to appears in excel 

pour csv_content, vous pouvez essayer comme ci-dessous.

function b64DecodeUnicode(str: any) {        
        return decodeURIComponent(atob(str).split('').map((c: any) => {
            return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
        }).join(''));
    }
Diwakar
la source