发送以 Python 压缩的 zip 存档但尝试在 Rust 中解压缩时出现问题

problem sending zip archive compressed in Python but trying to decompress in Rust

我有这段代码可以在 Python

中用 zlib (gzip) 压缩数据
dta = bytes(str("..."))
res = zlib.compress(dta)
with open('packed.gz', 'wb') as f:
    f.write(t)

我想用 Rust 打开它

use std::io::prelude::*;
use flate2::read::GzDecoder; // flate2 = "1.0"
use std::fs::File;

fn main() {
    let f = File::open("packed.gz").unwrap();
    let mut d = GzDecoder::new(f);
    let mut s = String::new();
    d.read_to_string(&mut s).unwrap();
    println!("{}", s);
}

然后我得到

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: InvalidInput, error: "invalid gzip header" }', src/main.rs:11:30

文件格式不同有问题吗?

您的 python 程序生成的数据流实际上不是 .gz 文件,而是原始 DEFLATE-stream。如果你想要一个 .gz 文件(包括 gz-header,这是 flate2::read::GzDecoder 期望的),请使用 Python 中的 gzip 模块。如果您确实想要原始 DEFLATE-stream(由 zlib.compress 生成),请使用 flate2::read::DeflateDecoder 解压缩数据。

确实 zlib 压缩与 python stdlib documentation 提到的 gzip 兼容。那是因为 zlib 和 gzip 都使用 deflate 压缩标准。

zlib 生成原始的 deflate 压缩字节流。另一方面,gzip 是单一压缩的标准,它将压缩流包装在 deflate 上。所以如果你从 gzip 文件中删除文件 headers,你可以使用原始 deflate 库解压缩。

字符串 This is the raw stringzlib 压缩流看起来像

>>> import zlib
>>> data = bytes(str("This is the raw string"), encoding="utf-8")
>>> res = zlib.compress(data)
>>> print(res)
b'x\x9c\x0b\xc9\xc8,V\x00\xa2\x92\x8cT\x85\xa2\xc4r\x85\xe2\x92\xa2\xcc\xbct\x00ZI\x08\x17'

这是(当你把它发送到 xxd.

00000000: 789c 0bc9 c82c 5600 a292 8c54 85a2 c472  x....,V....T...r
00000010: 85e2 92a2 ccbc 7400 5a49 0817            ......t.ZI..

前 2 个字节声明文件的类型。

78 01 - No Compression/low
78 9C - Default Compression
78 DA - Best Compression 

在我们的例子中,它是 78 9C,它是具有默认压缩的 zlib,这是 python 在其文档中所说的。您可以通过 运行

来验证
file package.gz

它应该说 zlib compressed data 因为这就是文件 header 的样子。

如果我们尝试使用 gzip 压缩,让我们看看会发生什么。

>>> import gzip
>>> data = bytes(str("This is the raw string"), encoding="utf-8")
>>> gzip.compress(data)
b'\x1f\x8b\x08\x00\xd6\x1bw_\x02\xff\x0b\xc9\xc8,V\x00\xa2\x92\x8cT\x85\xa2\xc4r\x85\xe2\x92\xa2\xcc\xbct\x00&\x91\x1a\x82\x16\x00\x00\x00'

如果您注意到十六进制转储

00000000: 1f8b 0800 d61b 775f 02ff 0bc9 c82c 5600  ......w_.....,V.
00000010: a292 8c54 85a2 c472 85e2 92a2 ccbc 7400  ...T...r......t.
00000020: 2691 1a82 1600 0000                      &.......

幻数显示,1f 8b (wiki)。如果你查看 headers,你会看到两个十六进制转储包含相同的压缩数据,即 对于 zlib

00000000: .... 0bc9 c82c 5600 a292 8c54 85a2 c472  x....,V....T...r
00000010: 85e2 92a2 ccbc 7400 .... ....            ......t.ZI..

并且,对于 gzip

00000000: .... .... .... .... .... 0bc9 c82c 5600  ......w_.....,V.
00000010: a292 8c54 85a2 c472 85e2 92a2 ccbc 7400  ...T...r......t.
00000020: .... .... .... ....                      &.......

所以如果你ask rust to use the correct decoder,它肯定会起作用。这就是标准的好处。

因此,gzip 只是 zlib 容器之上的一个容器。我还没有解释 hexdump 中其他字节的含义(因为我还没有查找)。

原来flate2里有ZlibDecoder...而且有效