El uso de atob de Javascript para decodificar base64 no decodifica correctamente las cadenas utf-8

106

Estoy usando la window.atob()función Javascript para decodificar una cadena codificada en base64 (específicamente el contenido codificado en base64 de la API de GitHub). El problema es que estoy recuperando caracteres codificados en ASCII (como en â¢lugar de ). ¿Cómo puedo manejar correctamente la transmisión entrante codificada en base64 para que se decodifique como utf-8?

brandonscript
fuente
3
La página MDN que vinculó tiene un párrafo que comienza con la frase "Para usar con cadenas Unicode o UTF-8".
Puntiagudo
1
¿Estás en el nodo? Hay mejores soluciones queatob
Bergi

Respuestas:

269

Hay un gran artículo sobre los documentos MDN de Mozilla que describe exactamente este problema:

El "Problema Unicode" Dado que los DOMStrings son cadenas codificadas de 16 bits, en la mayoría de los navegadores que invocan window.btoauna cadena Unicode generará un Character Out Of Range exceptionsi un carácter excede el rango de un byte de 8 bits (0x00 ~ 0xFF). Hay dos métodos posibles para resolver este problema:

  • el primero es escapar de la cadena completa (con UTF-8, ver encodeURIComponent) y luego codificarla;
  • el segundo es convertir el UTF-16 DOMStringen una matriz de caracteres UTF-8 y luego codificarlo.

Una nota sobre soluciones anteriores: el artículo de MDN sugirió originalmente usar unescapey escapepara resolver el problema de la Character Out Of Rangeexcepción, pero desde entonces han quedado obsoletos. Algunas otras respuestas aquí han sugerido trabajar en torno a esto con decodeURIComponenty encodeURIComponent, esto ha demostrado ser poco confiable e impredecible. La actualización más reciente de esta respuesta utiliza funciones de JavaScript modernas para mejorar la velocidad y modernizar el código.

Si está tratando de ahorrar algo de tiempo, también podría considerar usar una biblioteca:

Codificación 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=="

Decodificación 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 solución anterior a 2018 (funcional, y aunque probablemente mejor soporte para navegadores más antiguos, no actualizada)

Aquí está la recomendación actual, directa de MDN, con compatibilidad adicional con TypeScript a través de @ 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 solución original (obsoleta)

Este usado escapey unescape(que ahora está en desuso, aunque esto todavía funciona en todos los 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"

Y una última cosa: encontré este problema por primera vez al llamar a la API de GitHub. Para que esto funcione correctamente en Safari (móvil), tuve que eliminar todos los espacios en blanco de la fuente base64 antes de poder decodificar la fuente. Si esto sigue siendo relevante o no en 2017, no lo sé:

function b64_to_utf8( str ) {
    str = str.replace(/\s/g, '');    
    return decodeURIComponent(escape(window.atob( str )));
}
brandonscript
fuente
1
w3schools.com/jsref/jsref_unescape.asp "La función unescape () fue obsoleta en la versión 1.5 de JavaScript. Use decodeURI () o decodeURIComponent () en su lugar."
Tedd Hansen
1
Me salvaste los días, hermano
Mr Neo
2
Actualización: Solución # 1 en MDN El "Problema Unicode" se solucionó, b64DecodeUnicode('4pyTIMOgIGxhIG1vZGU=');ahora
muestra
2
Otra forma de decodificar sería decodeURIComponent(atob('4pyTIMOgIGxhIG1vZGU=').split('').map(x => '%' + x.charCodeAt(0).toString(16)).join('')) No es el código de mayor rendimiento, pero es lo que es.
daniel.gindi
2
return String.fromCharCode(parseInt(p1, 16));tener compatibilidad con TypeScript.
Martin Schneider
20

Las cosas cambian. Los métodos de escape / unescape han quedado obsoletos.

Puede codificar la cadena con URI antes de codificarla en Base64. Tenga en cuenta que esto no produce UTF8 codificado en Base64, sino datos codificados con URL codificados en Base64. Ambas partes deben acordar la misma codificación.

Vea el ejemplo de trabajo aquí: 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 el problema de OP, una biblioteca de terceros como js-base64 debería resolver el problema.

Tedd Hansen
fuente
1
Me gustaría señalar que no está produciendo la base64 de la cadena de entrada, sino su componente codificado. Entonces, si lo envía, la otra parte no puede decodificarlo como "base64" y obtener la cadena original
Riccardo Galli
3
Tienes razón, he actualizado el texto para señalar eso. Gracias. La alternativa parece ser implementar base64 usted mismo, usar una biblioteca de terceros (como js-base64) o recibir "Error: No se pudo ejecutar 'btoa' en 'Ventana': la cadena que se codificará contiene caracteres fuera del rango Latin1. "
Tedd Hansen
14

Si lo suyo es tratar las cadenas como bytes, puede utilizar las siguientes funciones

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
fuente
1
Gracias. Su respuesta fue crucial para ayudarme a que esto funcionara, lo que me llevó muchas horas durante varios días. +1. stackoverflow.com/a/51814273/470749
Ryan
Para una solución mucho más rápida y entre navegadores (pero esencialmente el mismo resultado), consulte stackoverflow.com/a/53433503/5601591
Jack Giffin
u_atob y u_btoa usan funciones disponibles en todos los navegadores desde IE10 (2012), me parece sólido (si se refiere a TextEncoder, eso es solo un ejemplo)
Riccardo Galli
5

Aquí está la solución actualizada de 2018 como se describe en los Recursos de desarrollo de Mozilla

PARA CODIFICAR DE UNICODE A 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 A 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
fuente
4

El artículo completo que me funciona: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding

La parte donde codificamos desde Unicode / UTF-8 es

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 es uno de los métodos más utilizados en la actualidad.

rika
fuente
Ese es el mismo enlace que la respuesta aceptada.
brandonscript
3

Asumiría que uno podría querer una solución que produzca un URI base64 ampliamente utilizable. Visite data:text/plain;charset=utf-8;base64,4pi44pi54pi64pi74pi84pi+4pi/para ver una demostración (copie la uri de datos, abra una nueva pestaña, pegue la URI de datos en la barra de direcciones y luego presione Intro para ir a la página). A pesar de que este URI está codificado en base64, el navegador aún puede reconocer los puntos de código alto y decodificarlos correctamente. El codificador + decodificador minificado es 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)

A continuación se muestra el código fuente utilizado para generarlo.

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

Luego, para decodificar los datos base64, HTTP obtiene los datos como un URI de datos o usa la función a continuación.

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

La ventaja de ser más estándar es que este codificador y este descodificador son más aplicables porque se pueden usar como una URL válida que se muestra correctamente. 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>

Además de estar muy estandarizados, los fragmentos de código anteriores también son muy rápidos. En lugar de una cadena de sucesión indirecta en la que los datos tienen que convertirse varias veces entre varias formas (como en la respuesta de Riccardo Galli), el fragmento de código anterior es tan directo como sea posible. Utiliza solo una String.prototype.replacellamada rápida simple para procesar los datos al codificar, y solo una para decodificar los datos al decodificar. Otra ventaja es que (especialmente para cadenas grandes), String.prototype.replacepermite que el navegador maneje automáticamente la gestión de la memoria subyacente para cambiar el tamaño de la cadena, lo que genera un aumento significativo del rendimiento, especialmente en navegadores perennes como Chrome y Firefox que optimizan en gran medidaString.prototype.replace. Finalmente, la guinda del pastel es que, para los usuarios de exclūsīvō de escritura latina, las cadenas que no contienen puntos de código por encima de 0x7f son más rápidas de procesar porque la cadena no se modifica por el algoritmo de reemplazo.

He creado un repositorio de github para esta solución en https://github.com/anonyco/BestBase64EncoderDecoder/

Jack Giffin
fuente
¿Puede explicar lo que quiere decir con "forma creada por el usuario" frente a "interpretable por el navegador"? ¿Cuál es el valor agregado de usar esta solución sobre, digamos, lo que recomienda Mozilla?
brandonscript
@brandonscript Mozilla es diferente de MDN. MDN es contenido creado por el usuario. La página en MDN que recomienda su solución fue contenido creado por el usuario, no contenido creado por el proveedor del navegador.
Jack Giffin
¿Se creó su proveedor de soluciones? Yo sugiero dar crédito al origen. Si no es así, ¿también es creado por el usuario y no es diferente a la respuesta de MDN?
brandonscript
@brandonscript Buen punto. Estás en lo correcto. Eliminé ese texto. Además, mira la demostración que agregué.
Jack Giffin
0

La corrección pequeña, la eliminación de escape y el escape están en desuso, por lo que:

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
fuente
2
Parece que el enlace del documento es incluso diferente de esto ahora, lo que sugiere una solución de expresiones regulares para administrarlo.
brandonscript
2
Esto no funcionará, porque encodeURIComponentes lo decodeURIComponentcontrario de , es decir, simplemente deshará la conversión. Consulte stackoverflow.com/a/31412163/1534459 para obtener una gran explicación de lo que está sucediendo con escapey unescape.
bodo
1
@canaaerus ¿No entiendo tu comentario? escape y unescape están en desuso, simplemente los cambio con la función URIComponent [decode | encode] :-) Todo funciona bien. Lea la pregunta primero
Darkves
1
@Darkves: La razón por la que encodeURIComponentse usa es para manejar correctamente (todo el rango de) cadenas Unicode. Entonces, por ejemplo, window.btoa(decodeURIComponent(encodeURIComponent('€')))da Error: String contains an invalid characterporque es lo mismo que window.btoa('€')y btoano puede codificar .
bodo
2
@Darkves: Sí, eso es correcto. Pero no puede intercambiar escape con EncodeURIComponent y no escapar con DecodeURIComponent, porque los métodos Encode y escape no hacen lo mismo. Lo mismo con decodificar y unescape. Originalmente cometí el mismo error, por cierto. Debe notar que si toma una cadena, UriEncode, luego UriDecode, obtiene la misma cadena que ingresó. Entonces, hacer eso sería una tontería. Cuando eliminas una cadena codificada con encodeURIComponent, no obtienes la misma cadena que ingresaste, por eso con escape / unescape funciona, pero no con la tuya.
Stefan Steiger
0

Aquí hay un código a prueba de futuro para los navegadores que pueden faltar escape/unescape(). Tenga en cuenta que IE 9 y versiones anteriores no son compatibles atob/btoa(), por lo que deberá usar funciones personalizadas de base64 para ellos.

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

Puede encontrar un ejemplo más completo de codificación y decodificación UTF-8 aquí: http://jsfiddle.net/47zwb41o/

Beejor
fuente
-1

incluida la solución anterior, si aún enfrenta el problema, intente lo siguiente, considere el caso en el que el escape no es compatible con TS.

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

para csv_content puedes probar como se muestra a continuación.

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