package rond import ( "archive/zip" "bytes" "os" "strings" "testing" ) // TestCopyLimitedRejectsOverrun 守住备份包解压的体积上限。 // zip 头里的 UncompressedSize 是攻击者可控字段,不能只信声明——必须在读的时候掐住。 func TestCopyLimitedRejectsOverrun(t *testing.T) { var buf bytes.Buffer if err := copyLimited(&buf, strings.NewReader(strings.Repeat("a", 100)), 99); err == nil { t.Error("超过上限应当报错") } // 恰好等于上限要放过,内容也不能被截断 buf.Reset() if err := copyLimited(&buf, strings.NewReader(strings.Repeat("a", 100)), 100); err != nil { t.Errorf("恰好等于上限不该报错: %v", err) } if buf.Len() != 100 { t.Errorf("正常内容被截断了: %d 字节(应为 100)", buf.Len()) } buf.Reset() if err := copyLimited(&buf, strings.NewReader("短"), 100); err != nil { t.Errorf("正常内容不该报错: %v", err) } if buf.String() != "短" { t.Errorf("内容被改动了: %q", buf.String()) } } // TestUnpackArchiveEnforcesLimit 确认上限真的接在 Open 的解压路径上, // 而不只是 copyLimited 孤零零地正确。上限抽成参数就是为了能在这里用小值验一遍。 func TestUnpackArchiveEnforcesLimit(t *testing.T) { // 真上限(2 GiB)没法用一个小包走一遍,退一步把它圈在「挡得住 zip bomb、 // 又不至于误伤真备份」的区间里——顺手拦住把常量改成 1<<62 这类手滑。 if maxDBBytes < 1<<30 || maxDBBytes > 8<<30 { t.Errorf("maxDBBytes=%d 超出合理区间 [1GiB, 8GiB]", maxDBBytes) } const payload = 4096 var buf bytes.Buffer zw := zip.NewWriter(&buf) for _, name := range []string{"Backup.sqlite", "Backup.sqlite-wal"} { w, err := zw.Create(name) if err != nil { t.Fatal(err) } if _, err := w.Write(bytes.Repeat([]byte("s"), payload)); err != nil { t.Fatal(err) } } if err := zw.Close(); err != nil { t.Fatal(err) } zr, err := zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len())) if err != nil { t.Fatal(err) } dir := t.TempDir() // 上限低于条目体积:必须报错,而且不留半个文件在盘上 if _, err := unpackArchive(zr, dir, payload-1); err == nil { t.Error("条目超过上限时应当报错") } else { t.Logf("已拒绝: %v", err) } if left, _ := filepathGlob(dir); len(left) != 0 { t.Errorf("被拒绝的条目留下了残留文件: %v", left) } // 放宽到够用:正常解出主库路径 dbPath, err := unpackArchive(zr, dir, payload) if err != nil { t.Fatalf("上限够用时不该报错: %v", err) } if !strings.HasSuffix(dbPath, "Backup.sqlite") { t.Errorf("返回的主库路径不对: %s", dbPath) } } // filepathGlob 列出 dir 下的文件(测试辅助,避免为上一条再引一个包)。 func filepathGlob(dir string) ([]string, error) { entries, err := os.ReadDir(dir) if err != nil { return nil, err } var out []string for _, e := range entries { out = append(out, e.Name()) } return out, nil }