Files
Charon/backend/internal/services/backup_service_wave3_test.go
2026-03-04 18:34:49 +00:00

140 lines
4.1 KiB
Go

package services
import (
"archive/zip"
"bytes"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func openZipInTempDir(t *testing.T, tempDir, zipPath string) *os.File {
t.Helper()
absTempDir, err := filepath.Abs(tempDir)
require.NoError(t, err)
absZipPath, err := filepath.Abs(zipPath)
require.NoError(t, err)
relPath, err := filepath.Rel(absTempDir, absZipPath)
require.NoError(t, err)
require.False(t, relPath == ".." || strings.HasPrefix(relPath, ".."+string(filepath.Separator)))
// #nosec G304 -- absZipPath is constrained to test TempDir via Abs+Rel checks above.
zipFile, err := os.OpenFile(absZipPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600)
require.NoError(t, err)
return zipFile
}
func TestBackupService_UnzipWithSkip_SkipsDatabaseEntries(t *testing.T) {
tmp := t.TempDir()
destDir := filepath.Join(tmp, "data")
require.NoError(t, os.MkdirAll(destDir, 0o700))
zipPath := filepath.Join(tmp, "restore.zip")
zipFile := openZipInTempDir(t, tmp, zipPath)
writer := zip.NewWriter(zipFile)
for name, content := range map[string]string{
"charon.db": "db",
"charon.db-wal": "wal",
"charon.db-shm": "shm",
"caddy/config": "cfg",
"nested/file.txt": "hello",
} {
entry, createErr := writer.Create(name)
require.NoError(t, createErr)
_, writeErr := entry.Write([]byte(content))
require.NoError(t, writeErr)
}
require.NoError(t, writer.Close())
require.NoError(t, zipFile.Close())
svc := &BackupService{DataDir: destDir, DatabaseName: "charon.db"}
require.NoError(t, svc.unzipWithSkip(zipPath, destDir, map[string]struct{}{
"charon.db": {},
"charon.db-wal": {},
"charon.db-shm": {},
}))
_, err := os.Stat(filepath.Join(destDir, "charon.db"))
require.Error(t, err)
require.FileExists(t, filepath.Join(destDir, "caddy", "config"))
require.FileExists(t, filepath.Join(destDir, "nested", "file.txt"))
}
func TestBackupService_ExtractDatabaseFromBackup_ExtractWalFailure(t *testing.T) {
tmp := t.TempDir()
zipPath := filepath.Join(tmp, "invalid-wal.zip")
zipFile := openZipInTempDir(t, tmp, zipPath)
writer := zip.NewWriter(zipFile)
dbEntry, err := writer.Create("charon.db")
require.NoError(t, err)
_, err = dbEntry.Write([]byte("sqlite header placeholder"))
require.NoError(t, err)
walEntry, err := writer.Create("charon.db-wal")
require.NoError(t, err)
_, err = walEntry.Write([]byte("invalid wal content"))
require.NoError(t, err)
require.NoError(t, writer.Close())
require.NoError(t, zipFile.Close())
svc := &BackupService{DatabaseName: "charon.db"}
_, err = svc.extractDatabaseFromBackup(zipPath)
require.Error(t, err)
}
func TestBackupService_UnzipWithSkip_RejectsPathTraversal(t *testing.T) {
tmp := t.TempDir()
destDir := filepath.Join(tmp, "data")
require.NoError(t, os.MkdirAll(destDir, 0o700))
zipPath := filepath.Join(tmp, "path-traversal.zip")
zipFile := openZipInTempDir(t, tmp, zipPath)
writer := zip.NewWriter(zipFile)
entry, err := writer.Create("../escape.txt")
require.NoError(t, err)
_, err = entry.Write([]byte("evil"))
require.NoError(t, err)
require.NoError(t, writer.Close())
require.NoError(t, zipFile.Close())
svc := &BackupService{DataDir: destDir, DatabaseName: "charon.db"}
err = svc.unzipWithSkip(zipPath, destDir, nil)
require.Error(t, err)
require.Contains(t, err.Error(), "invalid file path in archive")
}
func TestBackupService_UnzipWithSkip_RejectsExcessiveUncompressedSize(t *testing.T) {
tmp := t.TempDir()
destDir := filepath.Join(tmp, "data")
require.NoError(t, os.MkdirAll(destDir, 0o700))
zipPath := filepath.Join(tmp, "oversized.zip")
zipFile := openZipInTempDir(t, tmp, zipPath)
writer := zip.NewWriter(zipFile)
entry, err := writer.Create("huge.bin")
require.NoError(t, err)
_, err = entry.Write(bytes.Repeat([]byte("a"), 101*1024*1024))
require.NoError(t, err)
require.NoError(t, writer.Close())
require.NoError(t, zipFile.Close())
svc := &BackupService{DataDir: destDir, DatabaseName: "charon.db"}
err = svc.unzipWithSkip(zipPath, destDir, nil)
require.Error(t, err)
require.Contains(t, err.Error(), "exceeded decompression limit")
}