// header.mbt
//
// 明文头数据库(kHeader)解析:属性树 → StreamsInfo(PackInfo / UnpackInfo /
// SubStreamsInfo)+ FilesInfo。
//
// 依据 py7zr《.7z format specification》"Header" 一节:
// Header := kHeader(0x01) [MainStreamsInfo] [FilesInfo] kEnd(0x00)
// StreamsInfo := kPackInfo(0x06) [kUnpackInfo(0x07)] [kSubStreamsInfo(0x08)] kEnd
// FilesInfo := kFilesInfo(0x05) numFiles 属性列表 kEnd
// FilesInfo 的每个属性都是 ` `
//
// 与 libarchive 的一处兼容差异(实测 bsdtar 写出的归档):
// kName 块的内容比"所有名字的 UTF-16LE 字节数"多一个前导 0x00 字节。
// 因此这里先按规范的偏移 0 严格解析,名字个数不符时再按偏移 1 重试。
///|
/// 归档中的一个条目。
pub struct Entry {
/// 归档内路径(kName 解码后的字符串)。
name : String
/// 解包后大小(字节);目录为 0。
size : Int64
/// 内容 CRC-32;归档未声明时为 `None`,目录恒为 `None`。
crc : Int64?
/// 是否为目录(无数据流且不属于"空文件")。
is_dir : Bool
}
///|
/// 单个 coder(解压 / 过滤算子)。
pub struct Coder {
/// coder 标识:COPY=0x00、LZMA=0x030101、LZMA2=0x21、BCJ=0x04 等。
id : Int64
/// coder 属性(LZMA 为 5 字节 props,LZMA2 为 1 字节字典大小)。
props : Bytes
num_in : Int32
num_out : Int32
}
///|
/// Folder:一组 coder 组成的解压单元,也是 Solid 压缩的边界。
pub struct Folder {
coders : Array[Coder]
/// 每个输出流的解压大小。
unpack_sizes : Array[Int64]
num_sub_streams : Int32
sub_sizes : Array[Int64]
sub_crcs : Array[Int64?]
}
///|
/// 归档的流信息(PackInfo + UnpackInfo + SubStreamsInfo)。
pub struct Streams {
/// 打包数据区相对签名头末尾的偏移(绝对位置 = 32 + pack_pos)。
pack_pos : Int64
pack_sizes : Array[Int64]
pack_crcs : Array[Int64?]
folder_crcs : Array[Int64?]
folders : Array[Folder]
}
///|
/// 文件表(与流信息合并前)。
priv struct FileTable {
names : Array[String]
empty_stream : Array[Bool]
empty_file : Array[Bool]
}
///|
/// 头数据库的解析结果:条目表 + 流信息 + "条目 → 子流"的映射。
priv struct Plan {
entries : Array[Entry]
streams : Streams
/// 与 `entries` 等长;有数据流的条目给出 `(folder 下标, 子流下标)`,目录为 `None`。
mapping : Array[(Int32, Int32)?]
}
///|
/// 空的流信息(归档没有 kMainStreamsInfo 时使用)。
fn empty_streams() -> Streams {
{ pack_pos: 0L, pack_sizes: [], pack_crcs: [], folder_crcs: [], folders: [], }
}
///|
/// 解析头数据库,返回归档中的条目列表。
///
/// 输入:`SevenZ`(起始头已校验)。
/// 输出:条目数组,顺序与归档内文件表一致。
/// 失败:编码头(F5 尚未实现)/ 属性树非法 / 截断 / 名字数与文件数不符。
pub fn SevenZ::entries(self : SevenZ) -> Array[Entry] raise SevenZError {
let plan = self.parse_plan()
plan.entries
}
///|
/// 归档的流信息(打包位置与大小、Folder / coder 链、子流大小与 CRC)。
///
/// 输入:`SevenZ`(起始头已校验)。
/// 输出:`Streams`,供解压(后续增量)与压缩方式检查使用。
pub fn SevenZ::streams(self : SevenZ) -> Streams raise SevenZError {
let plan = self.parse_plan()
plan.streams
}
///|
/// 解析头数据库,一次得到条目表、流信息与条目到子流的映射。
fn SevenZ::parse_plan(self : SevenZ) -> Plan raise SevenZError {
let sh = self.start_header
let mut header = copy_range(
self.data,
sh.absolute_offset().to_int(),
sh.absolute_end().to_int(),
)
if header.length() == 0 {
raise Malformed(reason="头数据库为空")
}
if header.unsafe_get(0) == 0x17 {
header = self.decode_encoded_header(header)
}
let r = ByteReader::new(header)
let first = r.read_byte()
if first != 0x01 {
raise Malformed(
reason="明文头应以 kHeader(0x01) 开头,实际 " + hex_byte(first),
)
}
let mut streams = empty_streams()
let mut table : FileTable? = None
while true {
let pid = r.read_byte()
if pid == 0x00 {
break
}
if pid == 0x04 {
streams = parse_streams(r)
} else if pid == 0x05 {
table = Some(parse_files(r))
} else if pid == 0x02 {
// kArchiveProperties:本实现用不到,按声明长度整段跳过
let size = r.read_number()
r.skip(size.to_int())
} else {
raise Malformed(reason="Header 中出现未知属性 " + hex_byte(pid))
}
}
match table {
Some(t) => {
let (entries, mapping) = build_plan(t, streams)
{ entries, streams, mapping, }
}
None => raise Malformed(reason="缺少 kFilesInfo(0x05)")
}
}
///|
/// 解码被压缩的头数据库(kEncodedHeader 0x17)。
///
/// 编码头自己带一段 StreamsInfo,用它的 PackInfo / Folder 指向文件里的 packed
/// 头流;解出来就是明文头(kHeader 0x01)。默认档 7z(bsdtar 默认压缩)的头
/// 就是这种形态,所以这一步是"普通 7z 可列可解"的必要条件。
fn SevenZ::decode_encoded_header(
self : SevenZ,
encoded : Bytes,
) -> Bytes raise SevenZError {
let r = ByteReader::new(encoded)
let id = r.read_byte()
if id != 0x17 {
raise Malformed(reason="不是 kEncodedHeader:" + hex_byte(id))
}
let streams = parse_streams(r)
if streams.folders.length() != 1 || streams.pack_sizes.length() != 1 {
raise Unsupported(what="编码头包含多个 Folder / pack stream")
}
let start = 32L + streams.pack_pos
let end = start + streams.pack_sizes[0]
if end > self.data.length().to_int64() {
raise Malformed(reason="编码头的 packed 区间越界")
}
let packed = copy_range(self.data, start.to_int(), end.to_int())
let plain = decode_folder_bytes(streams.folders[0], packed)
if streams.folder_crcs.length() > 0 {
match streams.folder_crcs[0] {
Some(expected) => {
let actual = crc32(plain, 0, plain.length())
if actual != expected {
raise BadCrc(what="encoded header", expected~, actual~)
}
}
None => ()
}
}
plain
}
///|
/// 解析 StreamsInfo。
fn parse_streams(r : ByteReader) -> Streams raise SevenZError {
let pid = r.read_byte()
if pid != 0x06 {
raise Malformed(
reason="StreamsInfo 应以 kPackInfo(0x06) 开头,实际 " +
hex_byte(pid),
)
}
let pack_pos = r.read_number()
let num_pack = r.read_number().to_int()
if num_pack < 0 {
raise Malformed(reason="kPackInfo 的打包流个数为负")
}
let pack_sizes : Array[Int64] = []
let mut pack_crcs : Array[Int64?] = []
while true {
let p = r.read_byte()
if p == 0x00 {
break
}
if p == 0x09 {
for _ in 0.. (Array[Folder], Array[Int64?]) raise SevenZError {
let folders : Array[Folder] = []
let mut folder_crcs : Array[Int64?] = []
let mut sizes : Array[Array[Int64]] = []
while true {
let p = r.read_byte()
if p == 0x00 {
break
}
if p == 0x0B {
let num = r.read_number().to_int()
if num < 0 {
raise Malformed(reason="Folder 个数为负")
}
let external = r.read_byte()
if external != 0 {
raise Unsupported(what="外部 Folder 定义(kFolder external=1)")
}
for _ in 0.. Folder raise SevenZError {
let num_coders = r.read_number().to_int()
if num_coders <= 0 || num_coders > 64 {
raise Malformed(reason="coder 个数非法:" + num_coders.to_string())
}
let coders : Array[Coder] = []
let mut total_in = 0
let mut total_out = 0
for _ in 0.. 8 {
raise Malformed(
reason="coder 标识长度非法:" + id_size.to_string(),
)
}
let id_bytes = r.read_bytes(id_size)
let mut id = 0L
for i in 0.. 1 {
for _ in 0.. Array[Folder] raise SevenZError {
let n = folders.length()
let num_sub : Array[Int32] = []
let sizes : Array[Array[Int64]] = []
let crcs : Array[Array[Int64?]] = []
for _ in 0.. 0 {
folders[i].unpack_sizes[0]
} else {
0L
}
let mut acc = 0L
for _ in 0..<(k - 1) {
let s = r.read_number()
sizes[i].push(s)
acc += s
}
sizes[i].push(total - acc)
}
} else if p == 0x0A {
// 只为"文件夹级 CRC 未覆盖"的子流读摘要:
// 当 folder 只有一个子流且 folder CRC 已定义时,该子流的 CRC 就是 folder CRC。
let needs : Array[(Int32, Int32)] = []
for i in 0.. Array[Folder] {
let out : Array[Folder] = []
for i in 0.. 0 {
f.unpack_sizes[0]
} else {
0L
},
)
// SubStreamsInfo 已给出 CRC 时以它为准;否则回落到 folder 级 CRC
// (folder 只有一个子流时,规范允许把 CRC 写在 UnpackInfo 里)。
if sub_crcs.length() == 0 {
sub_crcs = []
sub_crcs.push(
if i < folder_crcs.length() {
folder_crcs[i]
} else {
None
},
)
}
}
out.push({
coders: f.coders,
unpack_sizes: f.unpack_sizes,
num_sub_streams: k,
sub_sizes,
sub_crcs,
})
}
out
}
///|
/// 解析 FilesInfo(文件名 / 空流 / 空文件标志)。
fn parse_files(r : ByteReader) -> FileTable raise SevenZError {
let num_files = r.read_number().to_int()
if num_files < 0 {
raise Malformed(reason="kFilesInfo 的文件数为负")
}
let mut empty_stream : Array[Bool] = []
for _ in 0.. Array[String] raise SevenZError {
let (names_zero, used_zero) = split_names(content, 0)
if names_zero.length() == num_files && used_zero == content.length() {
return names_zero
}
let (names_one, used_one) = split_names(content, 1)
if names_one.length() == num_files && used_one + 1 == content.length() {
return names_one
}
raise Malformed(
reason="kName 块无法解析:numFiles=" +
num_files.to_string() +
",偏移 0 得到 " +
names_zero.length().to_string() +
" 个名字(消费 " +
used_zero.to_string() +
"/" +
content.length().to_string() +
" 字节)",
)
}
///|
/// 从 `start` 偏移起按 UTF-16LE 切分名字,返回(名字列表, 消费字节数)。
fn split_names(content : Bytes, start : Int32) -> (Array[String], Int32) {
let out : Array[String] = []
let mut buf = @buffer.Buffer()
let mut i = start
while i + 1 < content.length() {
if content.unsafe_get(i).to_int() == 0 &&
content.unsafe_get(i + 1).to_int() == 0 {
out.push(
@utf16.decode_lossy(buf.to_bytes(), endianness=@utf16.Endian::Little),
)
buf = @buffer.Buffer()
} else {
buf.write_byte(content.unsafe_get(i))
buf.write_byte(content.unsafe_get(i + 1))
}
i += 2
}
let rest = buf.to_bytes()
if rest.length() > 0 {
out.push(@utf16.decode_lossy(rest, endianness=@utf16.Endian::Little))
i += rest.length()
}
(out, i - start)
}
///|
/// 把文件表与流信息合并成条目列表,同时给出"条目 → 子流"的映射。
fn build_plan(
table : FileTable,
streams : Streams,
) -> (Array[Entry], Array[(Int32, Int32)?]) raise SevenZError {
let sizes : Array[Int64] = []
let crcs : Array[Int64?] = []
let owners : Array[(Int32, Int32)] = []
for fi in 0..= sizes.length() {
raise Malformed(
reason="有数据流的文件数多于子流数(文件 " +
table.names[i] +
")",
)
}
out.push({
name: table.names[i],
size: sizes[next],
crc: crcs[next],
is_dir: false,
})
mapping.push(Some(owners[next]))
next += 1
}
}
(out, mapping)
}
///|
/// `Folder` 的输出流总数(等于 kCodersUnpackSize 中该 folder 的条目数)。
fn Folder::out_count(self : Folder) -> Int32 {
let mut total = 0
for c in self.coders {
total += c.num_out
}
total
}
///|
/// `Int64?` 是否为 `Some`。
fn is_some(value : Int64?) -> Bool {
match value {
Some(_) => true
None => false
}
}
///|
/// 统计布尔数组里 `true` 的个数。
fn count_true(values : Array[Bool]) -> Int32 {
let mut n = 0
for v in values {
if v {
n += 1
}
}
n
}