我正在使用Javascriptwindow.atob()
函数来解码一个base64编码的字符串(特别是GitHub API中base64编码的内容)。问题是我得到了ASCII编码的字符(像而不是
™
)。如何正确处理传入的base64编码的流,以便将其解码为UTF-8?
尽管JavaScript(ECMAScript)已经成熟,但Base64、ASCII和Unicode编码的脆弱性已经引起了很多令人头疼的问题(其中大部分问题都是在这个问题的历史上出现的)。
请考虑以下示例:
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
为什么我们会遇到这种情况?
来源:MDN(2021)
最初的MDN文章还讨论了window.btoa
和.atob
的损坏性质,它们后来在现代ECMAScript中得到了修补。MDN最初的文章解释道:
“Unicode问题”由于domString
是16位编码的字符串,在大多数调用window.bto
的浏览器中,如果某个字符超出了8位字节(0x00~0xFF)的范围,则会导致字符超出范围异常
。
(为ASCII base64解决方案保持滚动)
来源:MDN(2021)
MDN推荐的解决方案是实际对二进制字符串表示进行编码:
// 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=="
function fromBinary(encoded) {
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"
EncodeURIComponent
),然后对其进行编码;domString
转换为UTF-8字符数组,然后对其进行编码。关于以前的解决方案的注意事项:MDN文章最初建议使用unescape
和escape
来解决字符超出范围
异常问题,但后来已不推荐使用。这里的一些其他答案建议使用decodeuricomponent
和encodeuricomponent
来解决这个问题,但事实证明这是不可靠和不可预测的。这个答案的最新更新使用了现代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=="
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
时,C.charcodeat(0).ToString(16)
返回a
,迫使a
表示为0a
)。
这里有一个相同的解决方案,带有一些额外的TypeScript兼容性(通过@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))
}))
}
// 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(''))
}
这使用了escape
和unescape
(现在已不推荐使用,但在所有现代浏览器中仍然有效):
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源代码之前从源代码中去掉所有空白。这在2021年是否仍然相关,我不知道:
function b64_to_utf8( str ) {
str = str.replace(/\s/g, '');
return decodeURIComponent(escape(window.atob( str )));
}
问题内容: 我正在使用Javascript 函数解码base64编码的字符串(特别是来自GitHubAPI的base64编码的内容)。问题是我回来了ASCII编码的字符(而不是)。如何正确处理传入的以base64编码的流,以便将其解码为utf-8? 问题答案: 此问题: “ Unicode问题”由于s是16位编码的字符串,因此在大多数浏览器中,如果字符超出8位字节的范围(0x00〜0xFF),则调
我正在接收一个zip文件的内容(从一个API)作为一个base64编码的字符串。 如果我将该字符串粘贴到Notepad++中并执行 插件>MIME工具>Base64解码 生成类似的内容,但某些字符的解码方式不同(因此成为无效的zip文件)。其他方法抛出无效的URI错误。 如何在JavaScript中再现Notepad++行为?
问题内容: 如何使用Android解码utf-8字符串?我尝试使用此命令,但输出与输入相同: 问题答案: 字符串不需要编码。它只是一个Unicode字符序列。 要将字符串转换为字节序列时需要进行 编码 。您选择的字符集(UTF-8,cp1255等)确定了Character-> Byte映射。请注意,字符不必转换为单个字节。在大多数字符集中,大多数Unicode字符都转换为至少两个字节。 字符串的编
问题内容: 我在AngularJS上建立的SPA中有一个文本输入框,供用户向打印输出中添加标题。输入框的声明如下: 文本框中填充了服务器提供的默认标题。用户可以将标题更改为适合他们的名称。更改标题后,服务器将更新并在响应的标题中发送回新标题,然后替换框中的标题。这非常适合标准ASCII类型的字符。 但是,对于unicode字符(例如àßéçøö),它不起作用。文本已正确发送,在服务器上正确更新并正
我需要解码成PDF文件的Base64字符串。我使用这个代码。但是window.atob命令总是报告错误:在窗口上执行“atob”失败:要解码的字符串没有正确编码。 我知道该文件是正确的,因为我已经使用一个将base64解码为pdf的网站对其进行了解码。我不知道它是否有用,但我们正在使用Aurelia框架。 转换函数 函数的调用
问题内容: 我正在运行一个Python程序,该程序可获取UTF-8编码的网页,并使用BeautifulSoup从HTML中提取一些文本。 但是,当我将此文本写入文件(或在控制台上打印)时,它会以意外的编码方式写入。 示例程序: 运行此结果: 但是我希望Python Unicode字符串在单词中呈现为: 我已经试过了“fromEncoding”参数传递给BeautifulSoup,并试图与该对象,但