Usar o atob do Javascript para decodificar base64 não decodifica adequadamente strings utf-8

106

Estou usando a window.atob()função Javascript para decodificar uma string codificada em base64 (especificamente o conteúdo codificado em base64 da API do GitHub). O problema é que estou recebendo caracteres codificados em ASCII de volta (como em â¢vez de ). Como posso controlar adequadamente o fluxo de entrada codificado em base64 para que seja decodificado como utf-8?

brandonscript
fonte
3
A página MDN vinculada tem um parágrafo que começa com a frase "Para uso com strings Unicode ou UTF-8".
Pointy
1
Você está no nó? Existem soluções melhores do queatob
Bergi

Respostas:

269

Há um ótimo artigo sobre os documentos MDN da Mozilla que descreve exatamente esse problema:

O "Problema Unicode" Como DOMStrings são strings codificadas de 16 bits, na maioria dos navegadores, a chamada window.btoade uma string Unicode causará um erro Character Out Of Range exceptionse um caractere exceder o intervalo de um byte de 8 bits (0x00 ~ 0xFF). Existem dois métodos possíveis para resolver este problema:

  • a primeira é escapar de toda a string (com UTF-8, veja encodeURIComponent) e então codificá-la;
  • o segundo é converter o UTF-16 DOMStringem um array UTF-8 de caracteres e então codificá-lo.

Uma observação sobre soluções anteriores: o artigo MDN originalmente sugeriu usar unescapee escaperesolver o Character Out Of Rangeproblema de exceção, mas eles foram descontinuados. Algumas outras respostas aqui sugeriram contornar isso com decodeURIComponente encodeURIComponent, isso se provou não confiável e imprevisível. A atualização mais recente para esta resposta usa funções JavaScript modernas para melhorar a velocidade e modernizar o código.

Se você está tentando economizar tempo, também pode considerar o uso de uma biblioteca:

Codificação 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=="

Decodificando 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"

A solução pré-2018 (funcional e, embora provavelmente com melhor suporte para navegadores mais antigos, não está atualizada)

Aqui está a recomendação atual, direto do MDN, com alguma compatibilidade TypeScript adicional 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"

A solução original (obsoleta)

Este usado escapee unescape(que agora estão obsoletos, embora ainda funcione em todos os navegadores modernos):

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"

E uma última coisa: encontrei esse problema pela primeira vez ao chamar a API do GitHub. Para fazer isso funcionar no Safari (móvel) corretamente, eu realmente tive que remover todo o espaço em branco da fonte base64 antes mesmo de decodificar a fonte. Se isso ainda é relevante ou não em 2017, não sei:

function b64_to_utf8( str ) {
    str = str.replace(/\s/g, '');    
    return decodeURIComponent(escape(window.atob( str )));
}
brandonscript
fonte
1
w3schools.com/jsref/jsref_unescape.asp "A função unescape () foi descontinuada na versão 1.5 do JavaScript. Use decodeURI () ou decodeURIComponent () em seu lugar."
Tedd Hansen
1
Você salvou meus dias, mano
Sr. Neo
2
Atualização: Solução # 1 em MDNs O "Problema Unicode" foi corrigido, b64DecodeUnicode('4pyTIMOgIGxhIG1vZGU=');agora a saída correta "✓ à la mode"
weeix
2
Outra maneira de decodificar seria decodeURIComponent(atob('4pyTIMOgIGxhIG1vZGU=').split('').map(x => '%' + x.charCodeAt(0).toString(16)).join('')) Não o código de melhor desempenho, mas é o que é.
daniel.gindi
2
return String.fromCharCode(parseInt(p1, 16));para ter compatibilidade com TypeScript.
Martin Schneider
20

As coisas mudam. Os métodos de escape / unescape foram descontinuados.

Você pode codificar a string em URI antes de codificá-la em Base64. Observe que isso não produz UTF8 codificado em Base64, mas sim dados codificados em URL codificados em Base64. Ambos os lados devem concordar com a mesma codificação.

Veja o exemplo de trabalho aqui: http://codepen.io/anon/pen/PZgbPW

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

Para o problema de OP, uma biblioteca de terceiros, como js-base64, deve resolver o problema.

Tedd Hansen
fonte
1
Gostaria de salientar que você não está produzindo a base64 da string de entrada, mas de seu componente codificado. Portanto, se você mandá-lo embora, a outra parte não poderá decodificá-lo como "base64" e obter a string original
Riccardo Galli,
3
Você está correto, eu atualizei o texto para apontar isso. Obrigado. A alternativa parece ser você mesmo implementar base64, usando uma biblioteca de terceiros (como js-base64) ou recebendo "Erro: Falha ao executar 'btoa' em 'Janela': A string a ser codificada contém caracteres fora do intervalo Latin1. "
Tedd Hansen
14

Se você prefere tratar strings como bytes, você pode usar as seguintes funções

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
fonte
1
Obrigado. Sua resposta foi crucial para me ajudar a fazer isso funcionar, o que me levou muitas horas em vários dias. +1. stackoverflow.com/a/51814273/470749
Ryan
Para obter uma solução mais rápida e cruzada para navegadores (mas essencialmente a mesma saída), consulte stackoverflow.com/a/53433503/5601591
Jack Giffin
u_atob e u_btoa usam funções disponíveis em todos os navegadores desde o IE10 (2012), parece sólido para mim (se você se referir a TextEncoder, isso é apenas um exemplo)
Riccardo Galli
5

Aqui está a solução atualizada de 2018, conforme descrito em Recursos de desenvolvimento do Mozilla

PARA CODIFICAR DE UNICODE PARA 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=="

PARA DECODIFICAR DE B64 PARA 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
fonte
4

O artigo completo que funciona para mim: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding

A parte onde codificamos de Unicode / UTF-8 é

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"

Este é um dos métodos mais utilizados atualmente.

rika
fonte
Esse é o mesmo link da resposta aceita.
brandonscript de
3

Eu diria que alguém pode querer uma solução que produza um URI base64 amplamente utilizável. Visite data:text/plain;charset=utf-8;base64,4pi44pi54pi64pi74pi84pi+4pi/para ver uma demonstração (copie o uri de dados, abra uma nova guia, cole o URI de dados na barra de endereço e pressione Enter para ir para a página). Apesar do fato de que este URI é codificado em base64, o navegador ainda é capaz de reconhecer os pontos de código elevados e decodificá-los corretamente. O codificador + decodificador minimizado tem 1058 bytes (+ Gzip → 589 bytes)

!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)

Abaixo está o código-fonte usado para gerá-lo.

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*/)
    );
});

Em seguida, para decodificar os dados base64, o HTTP obtém os dados como um URI de dados ou use a função abaixo.

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;
});

A vantagem de ser mais padrão é que esse codificador e esse decodificador são mais amplamente aplicáveis, pois podem ser usados ​​como um URL válido que é exibido corretamente. Observar.

(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>

Além de serem muito padronizados, os trechos de código acima também são muito rápidos. Em vez de uma cadeia indireta de sucessão em que os dados precisam ser convertidos várias vezes entre várias formas (como na resposta de Riccardo Galli), o trecho de código acima é o mais direto possível. Ele usa apenas uma String.prototype.replacechamada rápida simples para processar os dados durante a codificação e apenas uma para decodificar os dados durante a decodificação. Outra vantagem é que (especialmente para strings grandes), String.prototype.replacepermite que o navegador lide automaticamente com o gerenciamento de memória subjacente de redimensionamento da string, levando a um aumento significativo de desempenho, especialmente em navegadores perenes como Chrome e Firefox que otimizam fortementeString.prototype.replace. Finalmente, a cereja do bolo é que, para os usuários exclūsīvō do script latino, as strings que não contêm nenhum ponto de código acima de 0x7f são extremamente rápidas de processar porque a string permanece inalterada pelo algoritmo de substituição.

Eu criei um repositório github para esta solução em https://github.com/anonyco/BestBase64EncoderDecoder/

Jack Giffin
fonte
Você pode explicar o que quer dizer com "forma criada pelo usuário" vs. "interpretável pelo navegador"? Qual é o valor agregado de usar essa solução, digamos, o que a Mozilla recomenda?
brandonscript de
@brandonscript Mozilla é diferente de MDN. MDN é um conteúdo criado pelo usuário. A página no MDN que recomenda sua solução era conteúdo criado pelo usuário, não conteúdo criado pelo fornecedor do navegador.
Jack Giffin
O seu fornecedor de soluções foi criado? Eu sim, sugiro dar crédito à origem. Se não, ele também é criado pelo usuário e não é diferente da resposta do MDN?
brandonscript de
@brandonscript Bom argumento. Você está certo. Eu removi aquele pedaço de texto. Além disso, verifique a demonstração que adicionei.
Jack Giffin de
0

Correção pequena, unescape e escape são descontinuados, então:

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
fonte
2
Parece que o link doc é ainda diferente deste agora, sugerindo uma solução regex para gerenciá-lo.
brandonscript
2
Isso não vai funcionar, porque encodeURIComponenté o inverso de decodeURIComponent, ou seja, só vai desfazer a conversão. Consulte stackoverflow.com/a/31412163/1534459 para uma ótima explicação do que está acontecendo com escapee unescape.
bodo
1
@canaaerus Não entendi seu comentário? escape e unescape estão obsoletos, eu apenas os troco com a função URIComponent [decode | encode] :-) Tudo está funcionando perfeitamente. Leia a pergunta primeiro
Darkves
1
@Darkves: O motivo pelo qual encodeURIComponenté usado é para lidar corretamente (com toda a gama de) strings Unicode. Então, por exemplo, window.btoa(decodeURIComponent(encodeURIComponent('€')))Error: String contains an invalid characterporque é o mesmo que window.btoa('€')e btoanão pode codificar .
bodo
2
@Darkves: Sim, correto. Mas você não pode trocar escape com EncodeURIComponent e unescape com DecodeURIComponent, porque o Encode e os métodos de escape não fazem a mesma coisa. Mesmo com decodificar e unescape. Eu originalmente cometi o mesmo erro, aliás. Você deve notar que se pegar uma string, UriEncode e, em seguida, UriDecode, você recebe a mesma string que inseriu. Então, fazer isso seria um absurdo. Quando você unescape uma string codificada com encodeURIComponent, você não recebe a mesma string que você inseriu, então é por isso que com escape / unescape funciona, mas não com o seu.
Stefan Steiger
0

Aqui está um código à prova de futuro para navegadores que podem faltar escape/unescape(). Observe que o IE 9 e anteriores não são compatíveis atob/btoa(), portanto, você precisará usar funções base64 personalizadas para eles.

// 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 ) ) );
};

Um exemplo mais abrangente de codificação e decodificação UTF-8 pode ser encontrado aqui: http://jsfiddle.net/47zwb41o/

Beejor
fonte
-1

incluindo a solução acima, se ainda estiver enfrentando problemas, tente como abaixo. Considere o caso em que escape não é compatível com TS.

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

para csv_content você pode tentar como abaixo.

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