首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用Javascript的atob解码base64不能正确解码utf-8字符串

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

Stack Overflow用户
提问于 2015-05-07 16:12:52
回答 12查看 306.6K关注 0票数 205

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

EN

回答 12

Stack Overflow用户

回答已采纳

发布于 2015-05-07 16:16:58

Unicode问题

虽然JavaScript (ECMAScript)已经成熟,但是Base64、ASCII和Unicode编码的脆弱性已经引起了很多人的头疼(其中大部分都在这个问题的历史上)。

请考虑以下示例:

代码语言:javascript
复制
const ok = "a";
console.log(ok.codePointAt(0).toString(16)); //   61: occupies < 1 byte

const notOK = "✓"
console.log(notOK.codePointAt(0).toString(16)); // 2713: occupies > 1 byte

console.log(btoa(ok));    // YQ==
console.log(btoa(notOK)); // error

我们为什么会遇到这个?

根据设计,Base64期望二进制数据作为它的输入。就JavaScript字符串而言,这意味着每个字符只占用一个字节的字符串。因此,如果您将一个字符串传递到btoa()中,其中包含占用多个字节的字符,您将得到一个错误,因为这不被认为是二进制数据。

资料来源:MDN (2021年)

最初的MDN文章还介绍了window.btoa.atob的支离破碎性质,它们后来在现代ECMAScript中得到了修正。最初的、现已死亡的MDN文章解释道:

由于DOMString是16位编码字符串,所以"Unicode问题“,在大多数浏览器中,如果一个字符超过了8位字节(0x00~0xFF)的范围,那么对Unicode字符串调用window.btoa将导致Character Out Of Range exception

具有二进制互操作性的解决方案

(继续滚动ASCII base64解决方案)

资料来源:MDN (2021年)

MDN推荐的解决方案实际上是对二进制字符串表示形式进行编码:

UTF8⇢二进制编码

代码语言:javascript
复制
// convert a Unicode string to a string in which
// each 16-bit unit occupies only one byte
function toBinary(string) {
  const codeUnits = new Uint16Array(string.length);
  for (let i = 0; i < codeUnits.length; i++) {
    codeUnits[i] = string.charCodeAt(i);
  }
  return btoa(String.fromCharCode(...new Uint8Array(codeUnits.buffer)));
}

// a string that contains characters occupying > 1 byte
let encoded = toBinary("✓ à la mode") // "EycgAOAAIABsAGEAIABtAG8AZABlAA=="

二进制⇢UTF-8解码

代码语言:javascript
复制
function fromBinary(encoded) {
  const binary = atob(encoded);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < bytes.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return String.fromCharCode(...new Uint16Array(bytes.buffer));
}

// our previous Base64-encoded string
let decoded = fromBinary(encoded) // "✓ à la mode"

这一点有点失败的地方是,您将注意到编码的字符串EycgAOAAIABsAGEAIABtAG8AZABlAA==不再匹配上一种解决方案的字符串4pyTIMOgIGxhIG1vZGU=。这是因为它是二进制编码字符串,而不是UTF-8编码字符串.如果这对您不重要(也就是说,您没有从另一个系统转换以UTF-8表示的字符串),那么您就可以继续了。但是,如果您想保留UTF-8功能,最好使用下面描述的解决方案。

ASCII base64互操作性解决方案

这个问题的整个历史表明,这些年来,我们有多少种不同的方法来解决被破坏的编码系统。虽然原来的MDN文章已经不存在了,但是这个解决方案仍然可以说是一个更好的解决方案,它在解决“Unicode问题”方面做了很大的工作,同时维护了您可以解码的纯文本base64字符串,比如https://www.base64decode.org/

解决这个问题有两种可能的方法:

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

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

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

编码UTF8⇢base64

代码语言:javascript
复制
    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=="

解码base64⇢UTF8

代码语言:javascript
复制
    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"

(为什么我们需要这样做呢?('00' + c.charCodeAt(0).toString(16)).slice(-2)将0加到单个字符串,例如,当c == \n返回a时,c.charCodeAt(0).toString(16)会将a表示为0a)。

TypeScript支持

这里有一些附加的TypeScript兼容性的解决方案(通过@Maddin):

代码语言:javascript
复制
// 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))
    }))
}

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

第一种解决方案(已被废弃)

这使用了escapeunescape (现在已不再推荐它们,尽管这在所有现代浏览器中仍然有效):

代码语言:javascript
复制
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时。要使它正确地用于(移动) Safari,我实际上必须从base64源代码中删除所有空白,然后才能解码源代码。这在2021年是否仍然相关,我不知道:

代码语言:javascript
复制
function b64_to_utf8( str ) {
    str = str.replace(/\s/g, '');    
    return decodeURIComponent(escape(window.atob( str )));
}
票数 445
EN

Stack Overflow用户

发布于 2016-02-17 07:03:59

一切都变了。逃逸/逃逸方法已被废弃。

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

参见这里的工作示例:http://codepen.io/anon/pen/PZgbPW

代码语言:javascript
复制
// encode string
var base64 = window.btoa(encodeURIComponent('€ 你好 æøåÆØÅ'));
// decode string
var str = decodeURIComponent(window.atob(tmp));
// str is now === '€ 你好 æøåÆØÅ'

对于OP的问题,第三方库(如js 64 )应该解决这个问题。

票数 35
EN

Stack Overflow用户

发布于 2020-06-23 14:51:15

为我工作的完整文章:解码

我们从Unicode/UTF-8编码的部分是

代码语言:javascript
复制
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"

这是当今最常用的方法之一。

票数 23
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/30106476

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档