JavaScript Zlib 解压缩

11

我正在尝试解压缩zlib压缩的XML,例如以下内容:https://drive.google.com/file/d/0B52P0MZLTdw8ZzQwQzVpZGZVZWc

上传到在线解压缩服务(如http://i-tools.org/gzip)可行。

在PHP中,我使用以下代码并且它可以正常工作,我会得到XML字符串:

$raw = file_get_contents("file_here");
$uncompressed = zlib_decode($raw);

然而,我想在JavaScript中完成这个任务。

  • 该应用程序是客户端Chrome扩展程序,使用 chrome.devtools.network 从网络日志中读取数据
  • 读取二进制响应。示例请参见上面的Google Drive链接
  • JS需要将响应解压缩为原始XML,然后解析成对象

我唯一遇到的问题是zlib解压缩部分。

最新更新,解压缩库可以工作但解包不行,请跳转到底部的9月16日更新。


我已经尝试了几个JavaScript库,但仍无法使其正常工作:

Pako: https://github.com/nodeca/pako

unpack() 代码: https://codereview.stackexchange.com/questions/3569/pack-and-unpack-bytes-to-strings

function unpack(str) {
    var bytes = [];
    for(var i = 0, n = str.length; i < n; i++) {
        var char = str.charCodeAt(i);
        bytes.push(char >>> 8, char & 0xFF);
    }
    return bytes;
}

$.get("file_here", function(response){
    var charData    = unpack(response);
    var binData     = new Uint8Array(charData);
    var data        = pako.inflate(binData);
    var strData     = String.fromCharCode.apply(null, new Uint16Array(data));
    console.log(strData);
});

错误: 未捕获的不正确的头部检查

即使将响应放在其他位置也是一样:

  • new Uint8Array(response);
  • pako.inflate(response);

Imaya的zlib: https://github.com/imaya/zlib.js

$.get("file_here", function(response){
    var inflate = new Zlib.Inflate(response);
    var output = inflate.decompress();
    console.log(output);
});

错误: Uncaught Error: 不支持的压缩方法 inflate.js:60

仍在使用Imaya's zlib,结合此Stack Overflow问题: 在JavaScript中解压缩gzip和zlib字符串

$.get("file_here", function(response){
    var response = response.split('').map(function(e) {
        return e.charCodeAt(0);
    });
    var inflate = new Zlib.Inflate(response);
    var output = inflate.decompress();
    console.log(output);
});

错误: Uncaught Error: invalid fcheck flag:29 inflate.js:65


dankogai 的 js-deflate: https://github.com/dankogai/js-deflate

console.log(RawDeflate.inflate(response));

输出:


augustl的js-inflatehttps://github.com/augustl/js-inflate

console.log(JSInflate.inflate(response));

输出:


zlib-browserify: https://github.com/brianloveswords/zlib-browserify

错误:ReferenceError: exports is not defined

这只是Imaya的zlib的包装器。我认为这是requireJS?我甚至不确定如何使用它。它是否可以在没有安装任何东西的情况下仅使用jQuery/JS?所提到的应用程序是可下载的Chrome扩展,只需通过HTML导入JS文件即可。


2014年9月16日更新

问题似乎出现在JavaScript unpack( )函数上。当我使用PHP生成的ByteArray:http://pastebin.com/uDWvK94B时,JavaScript解压缩函数就能正常工作。

可工作的PHP解包代码:

$unpacked = unpack("C*", $raw);

对于我使用的JavaScript unpack( )代码,它无法正常工作,请参见本文顶部的Pako部分。

因此,新问题是,为什么JavaScript生成的ByteArray值与PHP生成的值不同。

  • 这真的是unpack( )函数的问题吗?
  • 还是JS获取文件时出现了某些编码或其他变化,导致字节混乱?
  • 最后,您建议采取什么措施来解决这个问题?

更新于2014年9月20日

通过更多的研究和一些答案提供线索:

  • Sebastian S 提出了一个观点,认为问题在于检索数据的方式,与文本编码有关
  • user3995789 提供了一个示例,即使没有unpack( )函数,也可以正常工作,但不能用于Chrome扩展的情况下
  • Isaac 在Chrome扩展程序的上下文中提供了示例,但仍然无法正常工作

通过整合所有线索,我得出了一个理论,即这背后的原因是Chrome无法通过其request.getContent函数获取“原始”数据。 请参见此处,以查看Chrome对该函数的文档。

目前,我已经将这个问题提交给了Chrome,请参见此处

更新于2015年3月24日

虽然问题没有完全解决,但我认为最有用的答案来自@Sebastian S,他提出了“取或接收数据的方式”有问题,并且错误的转换是问题的原因,这与问题非常接近。


你是否正在使用Chrome DevTools的扩展程序? - Isaac
是的,我正在使用使用 chrome.devtools.network.onRequestFinished 的 devtools,稍后会执行 request.getContent(function(response){,其中 response 是我需要解压缩的响应内容。 - dragonjet
3个回答

2

Jquery读取的是utf8格式,你需要读取原始文件,这个函数就可以使用。

function readTextFile(file)
{
    var rawFile = new XMLHttpRequest();
    rawFile.open('GET', file, true);  
    rawFile.responseType = 'arraybuffer';
    rawFile.onload = function (response)
    {
      var words = new Uint8Array(rawFile.response);
       console.log(words[1]);
      console.log(pako.ungzip(words));

    };
    rawFile.send();
}

欲了解更多信息,请参考此答案


这段代码本身是有效的,但无法在Chrome扩展环境中使用,因为我们需要从网络日志中读取响应体。有没有办法将正确的ByteArray值存储到JavaScript变量中,而不必执行XHR操作? - dragonjet
你的意思是,$.get 也执行了 XHR,如何在 Chrome 扩展中读取文件?那是什么环境?无论如何,我找到了你的问题,请接受并颁发赏金。@dragonjet - user3995789
哦,我得到了一个巨大的数组作为返回值:https://i.gyazo.com/c24144925d957f36209e1334128fd315.png。我的文件使用php中的`gzcompress`函数进行了压缩,有什么想法吗? - NiCk Newman
嗯,我按照Isaac的答案做了,它起作用了! :) 看起来我需要先在内存中将它编码为base64? - NiCk Newman
@NiCkNewman 我也有一个数组,你是怎么得到字符串的? - So4ne
不需要使用 Uint8Array,可以使用 pako.ungzip(rawFile.response, {to:'string'}) - jnnnnn

1
我了解到您想在读取网络日志的响应主体时,在Chrome扩展内部使用zlib解压缩。
首先,您需要检索要解压缩的base64。您可以使用getContent方法来实现这一点。
function zlibDecompress(base64Content){
    // var base64Content        = base64Content.split(',')[1]; // Not sure if need to keep it

    // Decode base64 (convert ascii to binary)
    var strData     = atob(base64Content);

    // Convert binary string to character-number array
    var charData    = strData.split('').map(function(x){return x.charCodeAt(0);});

    // Turn number array into byte-array
    var binData     = new Uint8Array(charData);

    // Pako inflate
    var data        = pako.inflate(binData, { to: 'string' });

    return data;

}

chrome.devtools.network.onRequestFinished.addListener(
    function(request) {
        request.getContent(
            function(content, encoding){
                if(encoding == 'base64'){
                    var output = zlibDecompress(content);
                }
            }
        );
    }
);

https://developer.chrome.com/extensions/devtools_network#type-Request

使用XMLHttpRequest:
<script type="text/javascript" src="pako.js"></script>
<script type="text/javascript">

function zlibDecompress(url){
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.responseType = 'blob';

    xhr.onload = function(oEvent) {
        // Base64 encode
        var reader = new window.FileReader();
        reader.readAsDataURL(xhr.response); 
        reader.onloadend = function() {
            base64data      = reader.result;  
            var base64      = base64data.split(',')[1];

            // Decode base64 (convert ascii to binary)
            var strData     = atob(base64);

            // Convert binary string to character-number array
            var charData    = strData.split('').map(function(x){return x.charCodeAt(0);});

            // Turn number array into byte-array
            var binData     = new Uint8Array(charData);

            // Pako inflate
            var data        = pako.inflate(binData, { to: 'string' });

            console.log(data);
        }
    };

    xhr.send();
}
zlibDecompress('fileurl');
</script>

如果您想在Chrome扩展中使用XMLHttpRequest
{
  "name": "My extension",
  ...
  "permissions": [
    "http://www.domain.com/", // The domain that hold the file
    "http://*/" // Or every domain
  ],
  ...
}

https://developer.chrome.com/extensions/xhr

如果您有任何问题,请随意提问;)


谢谢,我看到你已经编辑了你的回答。从网络日志中得到的响应是二进制数据,如Google Drive链接所示,而不是Base64。现在我最接近正确的Uint8Array是TextEncoding,但仍将Unicode字符解释为3个字节。 - dragonjet
你使用什么工具通过你的Chrome扩展程序捕获二进制数据? - Isaac
@dragonjet 你试过在替换var strData = atob(base64Content);var strData = base64Content;的时候运行我的代码了吗? - Isaac
我使用一个监听器来监听 onRequestFinished 事件,该事件返回一个请求对象,可以用于 request.getContent(function(response){})。从那里开始,response 就是二进制数据。 - dragonjet
谢谢你,Isaac,这对我很有帮助! - NiCk Newman
哎呀,我在Firefox上遇到了“未捕获的异常:不正确的头检查”,但是在Chrome上却可以工作。有什么想法吗? - NiCk Newman

0
在我看来,你真正应该问的问题是:如何检索压缩数据?一旦它变成UTF-16字符串,问题就开始了。我甚至不确定从原始字节数据到JavaScript字符串的转换是否无损。
正如你提到了PHP,我假设你正在与某种后端通信。如果是这样,有一些选项可以使用本地手段处理二进制数据。也许这可以帮助你:https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data

另一个问题是我无法控制请求。我正在开发的Chrome扩展程序通过 chrome.devtools.network 接收数据,该工具从网络日志中读取并从那里获取响应正文。我提到的PHP只是为了测试解压缩,而不是应用程序的一部分。 - dragonjet
如果你登出前几个解码字节(应该是 [120, 156, 237, 157, 91, ...]),也许你可以看到一些明显的错误,比如错误的字节序或其他什么问题? - Sebastian S
我的最新研究与此相关。正确的值是 [120,156,237,90,91,83...],而最新的JavaScript解包通过 charCodeAt() 生成了这个:[120, 65533, 65533, 90, 91, 83...],其中一些是65533,我正在研究为什么。 - dragonjet
由于字符具有可变长度,它可能是UTF-8编码。您的解包需要进行调整,因为每个字节的最高位在UTF-8中具有特殊含义。 - Sebastian S

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接