Saltar al contenido

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

Solución:

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

El “problema de Unicode” desde DOMStrings son cadenas codificadas en 16 bits, en la mayoría de los navegadores que llaman window.btoa en una cadena Unicode provocará un Character Out Of Range exception si 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 toda la cadena (con UTF-8, consulte encodeURIComponent) y luego codificarlo;
  • el segundo es convertir el UTF-16 DOMString a una matriz de caracteres UTF-8 y luego codificarlo.

Una nota sobre soluciones anteriores: el artículo de MDN sugirió originalmente usar unescape y escape para resolver el Character Out Of Range problema de excepción, pero desde entonces han quedado obsoletos. Algunas otras respuestas aquí han sugerido trabajar en torno a esto con decodeURIComponent y 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:

  • js-base64 (NPM, excelente para Node.js)
  • base64-js

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 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 escape y unescape (que ahora están 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 quitar todos los espacios en blanco de la fuente base64 antes de Incluso podría 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 )));
}

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.

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)))
¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *