Files
tamakyi f98d3864b8 导出:zip 用 CreateRaw 写长度/CRC,别再带 data descriptor(真机判整包损坏)
真正的病根(09-16 起每一份站点导出都被拒都是它):Go 的 zip.Writer 默认走流式写入——本地头里
的长度与 CRC 留 0、真值挪到条目尾部的数据描述符里,并在 flag 里置 bit3。**真机的读取器只认
「本地头里就写着长度」的条目**,遇到这种包直接判成损坏,报「备份恢复失败,你的当前数据未被更改」。

铁证(直接读文件头):
  站点 Go 写的每一份导出        本地头 flags=0x0008  csize/usize=0/0   → 全部被拒
  20260920-fixed-A-数据修复    本地头 flags=0x0000  有长度             → 真机接受(Python 重打包)
  真机自己的包                 本地头 flags=0x0800  有长度             → 正常(只有 UTF-8 位)

这也解释了之前为什么全都修错了方向:09-20 那次「修悬空引用后就能导入」其实是 Python 重打包顺手把
zip 写法修对了;而 09-29 一整天修的内容问题(活动颜色为 NULL、交通方式重名)都不是拦路虎。

修复:zipBackup 改用 zw.CreateRaw,先把三个文件读进内存算出 CRC32 与长度写进本地头,
不再用会置 bit3 的 zw.CreateHeader。

用例:TestZipBackupWritesNoDataDescriptor —— 直接读第 0 个条目的**本地头**,断言 bit3 为 0、
压缩/原始长度非 0、CRC 正确(变异验证:改回 CreateHeader → 立刻变红,且精确复现 0x0008 + 长度 0)。
真实数据验证:修复后新导出的本地头 flags=0x0000、长度正确。

全量验收 296 PASS / 0 FAIL / 0 SKIP。
2026-09-29 15:04:07 +08:00

67 lines
2.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package rond
import (
"archive/zip"
"bytes"
"encoding/binary"
"hash/crc32"
"os"
"path/filepath"
"reflect"
"testing"
)
// 导出包不许用「数据描述符」——zip 的 flag bit 3。
//
// Go 的 zip.Writer 默认走流式写入:本地头里的长度与 CRC 留成 0,真值挪到条目尾部的描述符里,
// 并把 bit 3 置上。真机的读取器只认**本地头里就写着长度**的条目,遇到这种包直接判成损坏,
// 报「备份恢复失败,你的当前数据未被更改」。
//
// 2026-09-16 起每一份 Go 写的导出都被拒,而唯一被真机接受过的那份(20260920-fixed-A)恰好是
// Python 重新打包的、flags 为 0x0000 —— 09-29 一整天「修内容却一直失败」的真凶就是这里。
func TestZipBackupWritesNoDataDescriptor(t *testing.T) {
dir := t.TempDir()
base := filepath.Join(dir, "LifeEasy.sqlite")
content := bytes.Repeat([]byte("TamaBox-zip-check/"), 256)
if err := os.WriteFile(base, content, 0o644); err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
if err := zipBackup(base, &buf); err != nil {
t.Fatalf("打包失败: %v", err)
}
// 1) 条目名与顺序必须与真机一致;-wal/-shm 缺失时要补 0 字节占位
zr, err := zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
if err != nil {
t.Fatalf("读回打包结果失败: %v", err)
}
var names []string
for _, f := range zr.File {
names = append(names, f.Name)
}
if want := []string{"LifeEasy.sqlite", "LifeEasy.sqlite-wal", "LifeEasy.sqlite-shm"}; !reflect.DeepEqual(names, want) {
t.Errorf("条目名/顺序不对:%v,期望 %v", names, want)
}
// 2) 直接读**第一个条目的本地头**(真机看的就是它)
b := buf.Bytes()
if len(b) < 30 {
t.Fatalf("打包结果太短(%d 字节)", len(b))
}
flags := binary.LittleEndian.Uint16(b[6:8])
crc := binary.LittleEndian.Uint32(b[14:18])
csize := binary.LittleEndian.Uint32(b[18:22])
usize := binary.LittleEndian.Uint32(b[22:26])
if flags&0x8 != 0 {
t.Errorf("本地头 flags=0x%04x 带了 data descriptor(bit3):真机会判成损坏数据", flags)
}
if csize != uint32(len(content)) || usize != uint32(len(content)) {
t.Errorf("本地头里的长度是 0 或不对:压缩后 %d / 原始 %d,期望 %d", csize, usize, len(content))
}
if crc != crc32.ChecksumIEEE(content) {
t.Errorf("本地头里的 CRC 不对:0x%08x,期望 0x%08x", crc, crc32.ChecksumIEEE(content))
}
}