从xhr.responseText恢复ArrayBuffer

问题描述 投票:2回答:1

我需要从http请求中获取一个数组缓冲区,向我发送base64答案。对于这个请求,我不能使用XMLHttpRequest.responseType="arraybuffer"

我从这个请求得到的回复是通过xhr.responseText读取的。因此它被编码为DOMString。我试图将其作为数组缓冲区恢复。

我试图使用btoa(mysString)window.btoa(unescape(encodeURIComponent(str)))从DOMString返回base64,但第一个选项失败,而第二个选项不提供相同的base64。每个base64的前几个字符的示例:

传入:UEsDBBQACAgIACp750oAAAAAAAAAAAAAAAALAAAAX3JlbHMvLnJlbH

经过第二次处理:UEsDBBQACAgIAO+/ve+/ve+/vUoAAAAAAAAAAAAAAAALAAAAX3JlbHMvLnJlbH

正如你所看到的那样,它的一部分是相似的,但是有些部分是关闭的。我错过了什么才能做到正确?

javascript base64
1个回答
0
投票

我也有同样的问题。

The solution (I ran at Chrome(68.0.3440.84))

let url = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='

let iso_8859_15_table = { 338: 188, 339: 189, 352: 166, 353: 168, 376: 190, 381: 180, 382: 184, 8364: 164 }

function iso_8859_15_to_uint8array(iso_8859_15_str) {
    let buf = new ArrayBuffer(iso_8859_15_str.length);
    let bufView = new Uint8Array(buf);
    for (let i = 0, strLen = iso_8859_15_str.length; i < strLen; i++) {
        let octet = iso_8859_15_str.charCodeAt(i);
        if (iso_8859_15_table.hasOwnProperty(octet))
            octet = iso_8859_15_table[octet]
        bufView[i] = octet;
        if(octet < 0 || 255 < octet)
            console.error(`invalid data error`)
    }
    return bufView
}

req = new XMLHttpRequest();
req.overrideMimeType('text/plain; charset=ISO-8859-15');
req.onload = () => {
    console.log(`Uint8Array : `)
    var uint8array = iso_8859_15_to_uint8array(req.responseText)
    console.log(uint8array)
}
req.open("get", url);
req.send();

下面解释我学到的解决方法。

Explanation

为什么有些零件是关闭的?

因为TextDecoder导致数据丢失(你的情况是utf-8)。

例如,让我们谈谈UTF-8

  • Unicode的可变宽度字符编码。
  • 由于可变长度特性和ASCII兼容性等原因,它有rules(这将成为问题。)。
  • 因此,解码器可以将不符合的字符替换为替换字符,例如U + 003F(?,问号)或U + FFFD( ,Unicode替换字符)。
  • 在utf-8情况下,0~127的值是稳定的,128~255的值是不稳定的。 128~255将转换为U + FFFD

除UTF-8外,其他文本解码器是否安全?

不是。在大多数情况下,rules不安全。

UTF-8也是不可恢复的。 (128~255设置为U + FFFD)

如果二进制数据和解码结果可以一一对应,则可以恢复它们。

怎么解决?

  1. 查找可恢复的文本解码器。
  2. 强制MIME类型为传入数据的可恢复字符集。 xhr_object.overrideMimeType('text/plain; charset=ISO-8859-15')
  3. 收到时,使用recover table从字符串中恢复二进制数据。

Finds recoverable Text Decoders.

要恢复,请避免解码结果重复时的情况。

以下代码是一个简单的示例,因此可能缺少可恢复的文本解码器,因为它只考虑Uint8Array。

let bufferView = new Uint8Array(256);
for (let i = 0; i < 256; i++)
    bufferView[i] = i;

let recoverable = []
let decoding = ['utf-8', 'ibm866', 'iso-8859-2', 'iso-8859-3', 'iso-8859-4', 'iso-8859-5', 'iso-8859-6', 'iso-8859-7', 'iso-8859-8', 'iso-8859-8i', 'iso-8859-10', 'iso-8859-13', 'iso-8859-14', 'iso-8859-15', 'iso-8859-16', 'koi8-r', 'koi8-u', 'macintosh', 'windows-874', 'windows-1250', 'windows-1251', 'windows-1252', 'windows-1253', 'windows-1254', 'windows-1255', 'windows-1256', 'windows-1257', 'windows-1258', 'x-mac-cyrillic', 'gbk', 'gb18030', 'hz-gb-2312', 'big5', 'euc-jp', 'iso-2022-jp', 'shift-jis', 'euc-kr', 'iso-2022-kr', 'utf-16be', 'utf-16le', 'x-user-defined', 'ISO-2022-CN', 'ISO-2022-CN-ext']
for (let dec of decoding) {
    try {
        let decodedText = new TextDecoder(dec).decode(bufferView);
        let loss = 0
        let recoverTable = {}
        let unrecoverable = 0
        for (let i = 0; i < decodedText.length; i++) {
            let charCode = decodedText.charCodeAt(i)
            if (charCode != i)
                loss++

            if (!recoverTable[charCode])
                recoverTable[charCode] = i
            else
                unrecoverable++
        }
        let tableCnt = 0
        for (let props in recoverTable) {
            tableCnt++
        }
        if (tableCnt == 256 && unrecoverable == 0){
            recoverable.push(dec)
            setTimeout(()=>{
                console.log(`[${dec}] : err(${loss}/${decodedText.length}, ${Math.round(loss / decodedText.length * 100)}%) alive(${tableCnt}) unrecoverable(${unrecoverable})`)
            },10)
        }
        else {
            console.log(`!! [${dec}] : err(${loss}/${decodedText.length}, ${Math.round(loss / decodedText.length * 100)}%) alive(${tableCnt}) unrecoverable(${unrecoverable})`)
        }
    } catch (e) {
        console.log(`!! [${dec}] : not supported.`)
    }
}

setTimeout(()=>{
    console.log(`recoverable Charset : ${recoverable}`)
}, 10)

在我的控制台中,这个回归

可恢复的Charset:ibm866,iso-8859-2,iso-8859-4,iso-8859-5,iso-8859-10,iso-8859-13,iso-8859-14,iso-8859-15,iso-8859 -16,KOI8-R,Koi8-U,苹果,窗-1250,窗-1251,窗口1252,窗口-1254,窗口1256,窗口-1258,X-MAC-西里尔,X-用户定义

我在这个答案的开头使用了iso-8859-15。 (它的表大小最小。)


Additional test) Comparison between UTF-8's and ISO-8859-15's result

使用ISO-8859-15时,检查U + FFFD确实消失了。

function requestAjax(url, charset) {
    let req = new XMLHttpRequest();
    if (charset)
        req.overrideMimeType(`text/plain; charset=${charset}`);
    else
        charset = 'utf-8';
    req.open('get', url);
    req.onload = () => {
        console.log(`==========\n${charset}`)
        console.log(`${req.responseText.split('', 50)}\n==========`);
        console.log('\n')
    }
    req.send();
}

var url = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
requestAjax(url, 'ISO-8859-15');
requestAjax(url);

底线

  • 从字符串中恢复二进制数据需要一些额外的工作。 查找可恢复的文本编码器/解码器。 制作一张恢复表 用表恢复。 (您可以参考最顶层的代码。)
  • 要使用此技巧,请将MIME类型的传入数据强制转换为所需的字符集。
© www.soinside.com 2019 - 2024. All rights reserved.