package backup import ( "archive/tar" "compress/gzip" "context" "os" "path/filepath" "runtime" "testing" ) // writeArchive 造一个含指定条目的 tar.gz,用于验证还原侧的路径校验与权限处理。 func writeArchive(t *testing.T, entries map[string]string) string { t.Helper() path := filepath.Join(t.TempDir(), "backup.tar.gz") file, err := os.Create(path) if err != nil { t.Fatal(err) } defer file.Close() gz := gzip.NewWriter(file) tw := tar.NewWriter(gz) for name, content := range entries { header := &tar.Header{Name: name, Mode: 0o777, Size: int64(len(content)), Typeflag: tar.TypeReg} if err := tw.WriteHeader(header); err != nil { t.Fatal(err) } if _, err := tw.Write([]byte(content)); err != nil { t.Fatal(err) } } if err := tw.Close(); err != nil { t.Fatal(err) } if err := gz.Close(); err != nil { t.Fatal(err) } return path } // TestRestoreRejectsEscapingEntries 覆盖「同前缀绕过」:旧实现用 strings.HasPrefix 判断, // DataDir=/data 时 ../data-other/x 会通过前缀匹配写到 DataDir 之外。 func TestRestoreRejectsEscapingEntries(t *testing.T) { root := t.TempDir() dataDir := filepath.Join(root, "data") if err := os.MkdirAll(dataDir, 0o750); err != nil { t.Fatal(err) } archive := writeArchive(t, map[string]string{"../data-other/evil.txt": "x"}) if err := New(dataDir).Restore(context.Background(), archive); err == nil { t.Fatal("应当拒绝逃出 DataDir 的条目") } if _, err := os.Stat(filepath.Join(root, "data-other", "evil.txt")); err == nil { t.Fatal("逃逸条目被写到了 DataDir 之外") } } func TestRestoreRejectsParentEscape(t *testing.T) { root := t.TempDir() dataDir := filepath.Join(root, "data") if err := os.MkdirAll(dataDir, 0o750); err != nil { t.Fatal(err) } archive := writeArchive(t, map[string]string{"../../evil.txt": "x"}) if err := New(dataDir).Restore(context.Background(), archive); err == nil { t.Fatal("应当拒绝带 .. 的条目") } } // TestRestoreAppliesFixedMode:归档里的权限位不照搬(构造的归档可带 0777 / 执行位)。 func TestRestoreAppliesFixedMode(t *testing.T) { dataDir := t.TempDir() archive := writeArchive(t, map[string]string{"nginx/nginx.conf": "worker_processes 1;"}) if err := New(dataDir).Restore(context.Background(), archive); err != nil { t.Fatal(err) } info, err := os.Stat(filepath.Join(dataDir, "nginx", "nginx.conf")) if err != nil { t.Fatal(err) } if got := info.Mode().Perm(); runtime.GOOS == "windows" { // Windows 不保留 Unix 权限位(可写文件统一报 0666):只断言没带上执行位 if got&0o111 != 0 { t.Fatalf("还原后的文件不应带执行位,实际 %o", got) } } else if got != restoreFileMode { t.Fatalf("还原后的权限应为 %o,实际 %o", restoreFileMode, got) } }