zip 文件格式
1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 |
本地文件头签名(Local File Header Signature) 值固定:[80, 75, 3, 4] |
Version Needed to Extract | General Purpose Bit Flag 是否用了加密、压缩 |
Compression Method 压缩算法 如:0代表无压缩,8代表使用Deflate算法 |
最后修改文件时间 | 最后修改文件日期 | CRC-32校验 | |||||||||
接上CRC-32校验 | 压缩后大小 | 压缩前大小 | 文件1文件名长度 | 文件1额外字段长度 | 文件1文件名(长度不固定) | ||||||||||
文件1额外字段(长度不固定) | 文件2文件名长度 | 文件2额外字段长度 | 文件2文件名(长度不固定) | 文件2额外字段(长度不固定) | … | ||||||||||
正文:文件本身内容…(大量数据) | |||||||||||||||
【中央目录起始位置】 文件1文件名长度 |
文件1额外字段长度 | 文件1的文件注释长度 | 文件1的文件名 (长度不固定) |
文件1的额外字段 (长度不固定) 结构为:2字节的keyId+2字节数据长度+2字节数据 |
文件1的注释(长度不固定) | ||||||||||
文件2文件名长度 | 文件2额外字段长度 | 文件2的文件注释长度 | 文件2的文件名 | 文件2的额外字段 | 文件2的注释 | ||||||||||
文件3… | |||||||||||||||
中央目录记录签名 值固定:[80, 75, 5, 6] |
所在的磁盘编号 (用于跨磁盘zip文件) 单磁盘zip为0 |
中央目录起始部分的磁盘编号 单磁盘为0 |
本磁盘上中央目录文件数量 单磁盘zip值同后一个 |
中央目录中文件数量 | 中央目录总大小 | ||||||||||
中央目录起始位置 | zip注释的长度 | zip的注释 (长度不固定) |
文件名、文件属性、文件注释在整个zip文件的头尾重复,原因是
- 在文件头,就允许“流式”处理
- 在文件尾放中央目录,使其可以对整个文件做完整性检查。
用 rust 把这些信息打印出来:
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
let ori_filename = "./files/zip_file.zip";
let data_bytes = fs::read(ori_filename).unwrap();
// 确保文件至少有固定大小的文件头部分
if data_bytes.len() < 30 {
return Err("文件太小,不是zip".to_string());
}
// 提取各个字段
let local_file_header_signature = &data_bytes[0..4];
let version_needed_to_extract = &data_bytes[4..6];
let general_purpose_bit_flag = &data_bytes[6..8];
let compression_method = &data_bytes[8..10];
let last_mod_file_time = &data_bytes[10..12];
let last_mod_file_date = &data_bytes[12..14];
let crc_32 = &data_bytes[14..18];
let compressed_size = &data_bytes[18..22];
let uncompressed_size = &data_bytes[22..26];
let file_name_length = &data_bytes[26..28];
let extra_field_length = &data_bytes[28..30];
// 打印各个字段
println!("本地文件头签名 (Local File Header Signature): {:?}", local_file_header_signature);
println!("版本需要 (Version Needed to Extract): {:?}", version_needed_to_extract);
println!("通用位标志 (General Purpose Bit Flag): {:?}", general_purpose_bit_flag);
println!("压缩方法 (Compression Method): {:?}", compression_method);
println!("最后修改文件时间 (Last Mod File Time): {:?}", last_mod_file_time);
println!("最后修改文件日期 (Last Mod File Date): {:?}", last_mod_file_date);
println!("CRC-32校验 (CRC-32): {:?}", crc_32);
println!("压缩后大小 (Compressed Size): {:?}", compressed_size);
println!("未压缩大小 (Uncompressed Size): {:?}", uncompressed_size);
println!("文件名长度 (File Name Length): {:?}", file_name_length);
println!("额外字段长度 (Extra Field Length): {:?}", extra_field_length);
// 根据文件名长度和额外字段长度提取并打印文件名和额外字段
let file_name_length = u16::from_le_bytes([file_name_length[0], file_name_length[1]]) as usize;
let extra_field_length = u16::from_le_bytes([extra_field_length[0], extra_field_length[1]]) as usize;
let file_name = &data_bytes[30..30 + file_name_length];
let extra_field = &data_bytes[30 + file_name_length..30 + file_name_length + extra_field_length];
println!("文件名 (File Name): {:?}", file_name);
println!("额外字段 (Extra Field): {:?}", extra_field);
println!("=========zip结尾存放中央目录============");
// 查找结束中央目录记录的签名
let eocd_signature = b"\x50\x4b\x05\x06";
if let Some(eocd_pos) = data_bytes.windows(4).position(|window| window == eocd_signature) {
// 本磁盘上中央目录记录的文件数量
let num_entries = u16::from_le_bytes([data_bytes[eocd_pos + 8], data_bytes[eocd_pos + 9]]);
// 中央文件的起始位置
let central_dir_offset = u32::from_le_bytes([
data_bytes[eocd_pos + 16],
data_bytes[eocd_pos + 17],
data_bytes[eocd_pos + 18],
data_bytes[eocd_pos + 19],
]) as usize;
// 遍历中央目录记录
let mut offset = central_dir_offset;
for _ in 0..num_entries {
let central_dir_record = &data_bytes[offset..];
// 解析中央目录记录
let file_name_length = u16::from_le_bytes([central_dir_record[28], central_dir_record[29]]) as usize;
let extra_field_length = u16::from_le_bytes([central_dir_record[30], central_dir_record[31]]) as usize;
let file_comment_length = u16::from_le_bytes([central_dir_record[32], central_dir_record[33]]) as usize;
// extra_field:额外字段,根据需求放入信息,例如,文件的扩展属性,例如文件的创建时间、修改时间、访问时间。
// extra_field:结构通常由两部分组成:一个两字节的标识符(Header ID)和一个两字节的数据长度字段(Data Size),后面跟随实际的数据(Data)。标识符用于标明extra_field的类型或用途,数据长度字段指示随后的数据部分的长度。
// file_comment:文件的文本注释,
// 提取文件名
let file_name = ¢ral_dir_record[46..46 + file_name_length];
// 打印文件名
println!("文件名: {}", String::from_utf8_lossy(file_name));
// 更新offset以跳转到下一条目录记录
offset += 46 + file_name_length + extra_field_length + file_comment_length;
}
7z
7z介绍
- 使用LZMA和LZMA2压缩算法,提供了高压缩比
- 支持包括AES-256加密在内的强加密功能
- 支持自解压档案,分卷压缩等特性。
- 7-Zip软件是开源的
文件结构类似zip,但复杂很多
- header,包含一些元信息,例如文件列表、压缩方法、文件属性
- 压缩块,每个压缩块可以有自己的压缩算法,
- 每个压缩块可以有多个 stream,stream 存放了文件信息,也因此支持多线程压缩和解压缩
- end header,提供了压缩包整体的附加信息,有时 header 是加密的,就需要用 end header 来解密
1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 |
---|---|---|---|---|---|---|---|
文件头签名 值为[55, 122, 188, 175, 39, 28] |
7z版本 2字节 |
||||||
Start header的CRC 4字节 |
|||||||
下一个头的偏移量 8字节 |
|||||||
下一个头的长度 8字节 |
|||||||
下一个头的 CRC 4字节 |
|||||||
次头结构,包含ID和多个属性 | |||||||
压缩数据 | |||||||
结尾签名 |
png
png 的结尾是:
let png_end = [0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82]
- 长度:前4个字节表示数据长度,对于IEND块,这四个字节的值都是0(即十六进制的 00 00 00 00),因为IEND块不包含任何数据。
- 类型:接下来的4个字节指示块的类型。对于IEND块,这四个字节为 49 45 4E 44(即ASCII码中的IEND)。
- CRC:最后是4个字节的CRC(循环冗余检验)检查值,用于检测在传输过程中块是否有损坏。IEND块的CRC值是固定的,十六进制表示为 AE 42 60 82。
JPG
开头是 0xFFD8
,结尾是 0xFFD9
- APP0,开头是 FFE0 。可以含有以下信息:
- 标识字符串(如 “JFIF” 表示文件遵循 JFIF 约定)
- 版本号
- 像素密度设置
- 缩略图信息
- APP1,开头是 FFE1,存储 EXIF,包括但不限于:
- 拍照时间和日期
- 相机制造商和型号
- 图像方向
- 拍摄参数(如快门速度、焦距、光圈大小等)
- GPS 信息(纬度、经度和其他相关参数)
- APP2,开头是 FFE2 存储其他元信息,例如 ICC 颜色档案
- APPn,开头是 FFEn