如何解析 zip 文件

前言

最近在做 zip 包解析的相关工作,总结 zip 包相关协议以及 jszip 的解析流程是怎么样的。

什么是 zip 文件

zip 文件是一种压缩文件格式,它可以将多个文件压缩成一个文件,从而节省空间。

如何解析

在解析 zip 文件之前,我们在项目中创建了一个 zip 文件,并添加了 1 个 txt 文件。

本文的 zip 案例文件

读取 zip 文件内容

html 复制代码
<body>
    <div>
        <div>上传一个zip文件,并在控制台查看内存存储方式</div>
        <input id="uploadInput" type="file" multiple="">
        <br>
    </div>
    <script>
        const uploadInput = document.getElementById("uploadInput");
        uploadInput.addEventListener(
            "change",
            () => {
                const file = uploadInput.files[0];
                console.log('File 对象,只读不能写:', file)
                const reader = new FileReader();
                reader.onload = function (e) {
                    console.log('Array Buffer对象,不能读写:', e.target.result)
                    const uint8arr = new Uint8Array(e.target.result);
                    console.log('Uint8Array对象,可读写:', uint8arr);
                };
                reader.onerror = function (e) {
                    console.log("error", e.target.error);
                };
                reader.readAsArrayBuffer(file);
            },
            false
        );

    </script>
</body>

zip 文件协议格式

zip文件官方规范可以看这里。从 官方文档 可以看出,

组成标准zip文件:本地文件头 + 中央目录头 + 中央目录记录结尾

js 复制代码
[local file header 1] // 本地文件头
[file data 1] 

[local file header 2] // 本地文件头
[file data 2] 

[local file header 3] // 本地文件头
[file data 3] 

[central directory header 1] // 中央目录头
[central directory header 2] // 中央目录头
[central directory header 3] // 中央目录头

[end of central directory record] // 中央目录记录结尾

本地文件头

本地文件头是 zip 文件的第一个部分,它包含了文件的名称、大小、压缩方式等信息。其格式如下:

js 复制代码
local file header signature     4 bytes  (0x04034b50) // 本地文件头签名
version needed to extract       2 bytes // 版本需要提取
general purpose bit flag        2 bytes // 通用目的位标志
compression method              2 bytes // 压缩方法
last mod file time              2 bytes // 最后修改文件时间
last mod file date              2 bytes // 最后修改文件日期
crc-32                          4 bytes // CRC-32
compressed size                 4 bytes // 压缩大小
uncompressed size               4 bytes // 未压缩大小
file name length                2 bytes // 文件名称长度
extra field length              2 bytes // 额外字段长度
file name (variable size) // 文件名称 (可变大小)
extra field (variable size) // 额外字段 (可变大小)

以当前的 zip 文件为例,我们可以看到本地文件头的内容如下:

js 复制代码
local file header signature: 50 4B 03 04 // 本地文件头签名,因为是小端模式,也就是 16 进制的 0x04034b50
Version needed to extract: 0A 00 // 10
general purpose bit flag:  00 00 // 通用目的位标志
compression method: 00 00 // 压缩方法
last mod file time: 1B 7C // 最后修改文件时间
last mod file date: 4F 5B // 最后修改文件日期
crc-32: 52 9E D6 8B // CRC-32
compressed size: 0B 00 00 00 // 压缩大小
uncompressed size: 0B 00 00 00 // 未压缩大小
file name length: 09 00 // 文件名称长度
extra field length: 1C 00 // 额外字段长度
file name: 68 65 6C 6C 6F 2E 74 78 74 // 文件名称
extra field: 00 00 00 00 // 额外字段 这里可以根据 extra field length 的长度获取

// file Data.. 

中央目录头

matlab 复制代码
central file header signature   4 bytes  (0x02014b50)
version made by                 2 bytes
version needed to extract       2 bytes
general purpose bit flag        2 bytes
compression method              2 bytes
last mod file time              2 bytes
last mod file date              2 bytes
crc-32                          4 bytes
compressed size                 4 bytes
uncompressed size               4 bytes
file name length                2 bytes
extra field length              2 bytes
file comment length             2 bytes
disk number start               2 bytes
internal file attributes        2 bytes
external file attributes        4 bytes
relative offset of local header 4 bytes

file name (variable size)
extra field (variable size)
file comment (variable size)

大部分字段本地文件头协议差不多,需要留意的是 relative offset of local header 字段,他标识了本地文件头的所在位置。

以当前的 zip 文件为例,我们可以看到 中央目录头 的内容如下:

js 复制代码
central file header signature: 50 4B 01 02 // 中央目录头标识
version made by: 1E 03
version needed to extract: 0A 00
general purpose bit flag:  00 00 
compression method: 00
last mod file time: 1B 7C
last mod file date: 4F 5B
crc-32            : 52 9E D6 8B 
compressed size   : 0B 00 00 00 
uncompressed size : 0B 00 00 00
file name length  : 09 00 
extra field length: 18 00
file comment length : 00 00
disk number start   : 00 00
internal file attributes        01 00
external file attributes        00 00 A4 81
relative offset of local header 00 00 00 00
file name (variable size): 68 65 6C 6C 6F 2E 74 78 74
extra field: 55 54 05 00 03 26 4E EF 68 75 78 0B 00 01 04 F5 01 00 00 04 14 00 00 // 这里是根据 extra field length 的长度来获取的。

中央目录记录结尾

python 复制代码
end of central dir signature    4 bytes  (0x06054b50)
number of this disk             2 bytes
number of the disk with the start of the central directory  2 bytes
total number of entries in the central directory on this disk  2 bytes
total number of entries in the central directory           2 bytes
size of the central directory   4 bytes
offset of start of central directory with respect to the starting disk number        4 bytes
.ZIP file comment length        2 bytes
.ZIP file comment       (variable size)

以当前的 zip 文件为例,我们可以看到 中央目录记录结尾 的内容如下:

js 复制代码
end of central dir signature: 50 4B 05 06 
number of this disk: 00 00
number of the disk with the start of the central directory: 00 00
total number of entries in the central directory on this disk: 01 00 // 存储的是文件总数
total number of entries in the central directory: 01 00 // 存储的是文件总数
size of the central directory : 4F 00 00 00 // 中央目录区占据的字节大小。
offset of start of central directory with respect to the starting disk number: 4E 00 00 00 // 中央目录区开始的位置。
.ZIP file comment length: 00 00

综上,我们大概了解了 zip 包协议的内容,并根据协议的内容读取了 案例文件 对应数据。

接下来,我们看看 JSzip 是怎么 "读懂" 这段数据的。

JSZip 是如何解析的?

依旧以 当前的案例文件 为准。

JSZip 内部流程大致是这样👇

1. 定位 "50 4B 05 06",找到 中央目录记录结尾 的位置

JSZip 先从文件尾部向前扫描,寻找 0x06054B50(50 4B 05 06),这是 End of Central Directory Record(中央目录的结束标志)。

找到之后,JSZip 会根据 offset of start of central directory with respect to the starting disk number 字段,找到中央目录区的开始位置。

js 复制代码
const offset = centralDirectoryHeader.offsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber;

2. 找到 中央目录区的开始位置 + 找到 中央目录区的结束位置, 锁定中央目录区

找到中央目录区的开始位置后,JSZip 会根据 total number of entries in the central directory 字段,找到中央目录区的结束位置。

js 复制代码
const end = centralDirectoryHeader.offsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber + centralDirectoryHeader.sizeOfTheCentralDirectory;

找到中央目录区的结束位置后,JSZip 会根据 central directory header 字段,找到中央目录区的内容。

3. 遍历中央目录区,找到每个文件的本地文件头

找到每个文件的本地文件头后,JSZip 会根据 local file header 字段,找到每个文件的本地文件头的内容。

这就是 JSZip 自底向上的解析流程。

相关推荐
小徐_23333 小时前
wot-ui-cli 1.1.0 发布:支持 OpenCode、Antigravity,wot-ui 图标迁移不再靠猜
前端
__zRainy__4 小时前
Node.js Web 框架选型指南:从 Express 到 Hono 的全景对比
前端·node.js·express·koa·nestjs·egg·fastify
西瓜有点饿4 小时前
PostCSS 和 UnoCSS 的作用和区别
前端·postcss
mCell4 小时前
Lua 编程入门:从基础语法到元表
javascript·算法·lua
伟大的兔神4 小时前
我做了一个本地优先的 AI 图片工作台:Loomora v1.0.0 正式发布
前端·javascript·vue.js
90后的晨仔5 小时前
uni-app项目 Vue3 状态管理 Pinia 完全指南:从概念到实战的深度解析
前端
用户47949283569156 小时前
字节Agent开发技术分享—旧会话越聊越笨,新会话又得重讲?我给 Matt Pocock Skill 炼了套《影分身之术》:本体想清楚,分身写代码,完事回来汇报
面试·agent
90后的晨仔6 小时前
uni-app 在 iOS 平台跳转页面时移除底部安全区域的完整技术指南
前端
用户938515635076 小时前
React + JWT 登录鉴权底层原理与工程化实践
前端·react.js
zww89491116 小时前
家政派单系统开发实战:架构设计与派单算法指南
前端·系统架构