響應頭包含
Transfer-Encoding: chunked,**數據體為分塊傳輸編碼**,而非直接的 gzip 流。直接將 chunked 原始數據餵給 zlib 會導致Z_DATA_ERROR。
HTTP/1.1 200 OK
Content-Encoding: gzip ← 第 1 層:內容編碼 (gzip 壓縮)
Transfer-Encoding: chunked ← 第 2 層:傳輸編碼 (分塊傳輸)
↓
[Chunk Size][CRLF][Chunk Data][CRLF]...[0][CRLF][CRLF] ← 原始 Socket 讀取數據
↓
去除 Chunk 封裝 → 純 gzip 壓縮流
↓
zlib 解壓 → 原始 HTML/JSON
Content-Length 或 Transfer-Encoding: chunked 讀取完整 Body。Size\r\nData\r\n 格式,拼接成連續字節流。gzip/deflate,調用 zlib 解壓。Content-Encoding: gzip, deflate,按順序解碼。
// 1. Chunked 解碼 (簡化版)
char* decode_chunked(const char* src, size_t len, size_t* out_len) {
char* buf = malloc(len); // 最大不超過原長
size_t pos = 0, w = 0;
while (pos < len) {
// 讀取十六進制尺寸
char* end = strstr(src + pos, "\r\n");
if (!end) break;
size_t chunk_len = strtoul(src + pos, NULL, 16);
pos = (end - src) + 2; // 跳過 \r\n
if (chunk_len == 0) break; // 最後一塊
memcpy(buf + w, src + pos, chunk_len);
w += chunk_len;
pos += chunk_len + 2; // 跳過 Data + \r\n
}
*out_len = w;
return buf;
}
// 2. zlib 解壓 gzip (需設置 windowBits = 31)
int decompress_gzip(const char* src, size_t src_len, char** dst, size_t* dst_len) {
z_stream strm = {0};
strm.next_in = (Bytef*)src;
strm.avail_in = src_len;
// windowBits = 15 + 16 = 31 表示 gzip 封裝 (含 header/trailer)
if (inflateInit2(&strm, 31) != Z_OK) return -1;
char* out = malloc(src_len * 4); // 預估擴大 4 倍
strm.next_out = (Bytef*)out;
strm.avail_out = src_len * 4;
int ret = inflate(&strm, Z_FINISH);
if (ret != Z_STREAM_END) { inflateEnd(&strm); free(out); return -1; }
*dst = out;
*dst_len = strm.total_out;
inflateEnd(&strm);
return 0;
}
Firefox 在
nsHTTPCompressConv.cpp中處理順序:
1. 接收原始 Socket 數據 → 2.nsIHttpChunkedDecoder解碼 Chunked → 3.nsIContentEncodingDecoder(gzip/deflate) 解壓 → 4. 交給 HTML 解析器。
這證明必須先去 Chunked 再解壓。
inflateInit2(&strm, 15) 而非 31Content-Encoding: deflate (無 gzip header)inflateInit2(&strm, -15) (wbits 負值表示無 header)。Transfer-Encoding: identity 或無該頭# 1. 保存原始響應體 (含 chunked)
curl -v -H "Accept-Encoding: gzip" https://www.baidu.com --output raw.bin
# 2. 手動去 chunked (Python)
python3 -c "
import sys, re
data = open('raw.bin','rb').read()
# 簡易去 chunked
body = b''
i = 0
while i < len(data):
nl = data.find(b'\r\n', i)
size = int(data[i:nl], 16)
if size == 0: break
i = nl + 2
body += data[i:i+size]
i += size + 2
open('ungz.bin','wb').write(body)
"
# 3. 解壓驗證
gzip -dc ungz.bin | head -c 200