使用Javascript的atob解码base64不能正确解码utf-8字符串

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Javascript的atob解码base64不能正确解码utf-8字符串相关的知识,希望对你有一定的参考价值。

我正在使用javascript window.atob()函数来解码base64编码的字符串(特别是来自GitHub API的base64编码的内容)。问题是我得到了ASCII编码的字符(比如â¢而不是)。如何正确处理传入的base64编码流,以便将其解码为utf-8?

答案

在Mozilla的MDN文档上有一个great article,它描述了这个问题:

“Unicode问题”由于DOMStrings是16位编码的字符串,因此在大多数浏览器中,如果字符超出8位字节(0x00~0xFF)的范围,则在Unicode字符串上调用window.btoa将导致Character Out Of Range exception。有两种方法可以解决这个问题:

  • 第一个是逃避整个字符串(使用UTF-8,请参阅encodeURIComponent)然后对其进行编码;
  • 第二个是将UTF-16 DOMString转换为UTF-8字符数组,然后对其进行编码。

关于以前解决方案的说明:MDN文章最初建议使用unescapeescape来解决Character Out Of Range异常问题,但它们已被弃用。这里的一些其他答案建议使用decodeURIComponentencodeURIComponent解决这个问题,事实证明这是不可靠和不可预测的。此答案的最新更新使用现代JavaScript函数来提高速度和现代化代码。

如果你想节省一些时间,你也可以考虑使用一个库:

编码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('
'); // "Cg=="

解码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=='); // "
"

The pre-2018 solution (functional, and though likely better support for older browsers, not up to date)

以下是直接来自MDN的当前建议,以及@ MA-Maddin的一些额外TypeScript兼容性:

// 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('
') // "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==') // "
"

The original solution (deprecated)

这使用了escapeunescape(现已弃用,但这仍适用于所有现代浏览器):

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"

最后一件事:我在调用GitHub API时第一次遇到这个问题。为了让它在(Mobile)Safari上正常工作,我实际上必须从base64源中删除所有空白区域才能解码源代码。这是否仍然与2017年有关,我不知道:

function b64_to_utf8( str ) {
    str = str.replace(/s/g, '');    
    return decodeURIComponent(escape(window.atob( str )));
}
另一答案

事情会改变的。 escape/unescape方法已被弃用。

您可以在对Base64进行编码之前对字符串进行URI编码。请注意,这不会产生Base64编码的UTF8,而是产生Base64编码的URL编码数据。双方必须就同一编码达成一致。

请参阅此处的工作示例:http://codepen.io/anon/pen/PZgbPW

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

对于OP的问题,js-base64等第三方库应该可以解决问题。

另一答案

如果将字符串视为字节更多是你的事情,你可以使用以下函数

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 '

以上是关于使用Javascript的atob解码base64不能正确解码utf-8字符串的主要内容,如果未能解决你的问题,请参考以下文章

javascript 使用btoa和atob来进行Base64转码和解码

JavaScript atob 与 Notepad++ Base64 解码不同

javascript 使用btoa和atob来进行Base64转码和解码

Javascript中Base64编码解码的使用实例

js base64的转码与解码

使用window.btoa和window.atob来进行Base64编码和解码