package main import ( "archive/zip" "os" "path/filepath" "strings" "testing" ) func TestSafeJoinRejectsUnsafePaths(t *testing.T) { versionDir := t.TempDir() unsafePaths := []string{ "", ".", "/outside.txt", "\\outside.txt", "..", filepath.Join("..", "outside.txt"), filepath.Join("amt", "backup", "old.txt"), } for _, p := range unsafePaths { if _, _, err := safeJoin(versionDir, p); err == nil { t.Fatalf("safeJoin(%q) returned nil error", p) } } target, cleanRel, err := safeJoin(versionDir, filepath.Join("mods", "ok.jar")) if err != nil { t.Fatalf("safeJoin valid path failed: %v", err) } if cleanRel != filepath.Join("mods", "ok.jar") { t.Fatalf("unexpected clean path: %q", cleanRel) } if !strings.HasPrefix(target, versionDir) { t.Fatalf("target %q is not under %q", target, versionDir) } } func TestBackupPathDoesNotOverwriteExistingBackup(t *testing.T) { versionDir := t.TempDir() src := filepath.Join(versionDir, "mods", "config.txt") if err := os.MkdirAll(filepath.Dir(src), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(src, []byte("first"), 0o644); err != nil { t.Fatal(err) } backupDir := filepath.Join("amt", "backup", "run") if err := backupPath(versionDir, filepath.Join("mods", "config.txt"), backupDir); err != nil { t.Fatalf("first backup failed: %v", err) } if err := os.WriteFile(src, []byte("second"), 0o644); err != nil { t.Fatal(err) } if err := backupPath(versionDir, filepath.Join("mods", "config.txt"), backupDir); err != nil { t.Fatalf("second backup failed: %v", err) } first, err := os.ReadFile(filepath.Join(versionDir, backupDir, "mods", "config.txt")) if err != nil { t.Fatal(err) } second, err := os.ReadFile(filepath.Join(versionDir, backupDir, "mods", "config.txt.1")) if err != nil { t.Fatal(err) } if string(first) != "first" || string(second) != "second" { t.Fatalf("unexpected backups: first=%q second=%q", first, second) } } func TestBackupPathRejectsSelfNestedBackup(t *testing.T) { versionDir := t.TempDir() if err := os.MkdirAll(filepath.Join(versionDir, "amt", "data"), 0o755); err != nil { t.Fatal(err) } err := backupPath(versionDir, "amt", filepath.Join("amt", "backup", "run")) if err == nil { t.Fatal("backupPath allowed backing up a directory into itself") } } func TestUnzipBacksUpOverwrittenFiles(t *testing.T) { versionDir := t.TempDir() if err := os.WriteFile(filepath.Join(versionDir, "config.txt"), []byte("old"), 0o644); err != nil { t.Fatal(err) } zipPath := filepath.Join(versionDir, "pack.zip") writeTestZip(t, zipPath, map[string]string{"config.txt": "new"}) backupDir := filepath.Join("amt", "backup", "run") if err := unzipFile(zipPath, versionDir, versionDir, backupDir); err != nil { t.Fatalf("unzipFile failed: %v", err) } current, err := os.ReadFile(filepath.Join(versionDir, "config.txt")) if err != nil { t.Fatal(err) } backup, err := os.ReadFile(filepath.Join(versionDir, backupDir, "config.txt")) if err != nil { t.Fatal(err) } if string(current) != "new" || string(backup) != "old" { t.Fatalf("unexpected files: current=%q backup=%q", current, backup) } } func TestUnzipRejectsBackupTarget(t *testing.T) { versionDir := t.TempDir() zipPath := filepath.Join(versionDir, "pack.zip") writeTestZip(t, zipPath, map[string]string{"amt/backup/evil.txt": "bad"}) err := unzipFile(zipPath, versionDir, versionDir, filepath.Join("amt", "backup", "run")) if err == nil { t.Fatal("unzipFile allowed writing into backup directory") } } func writeTestZip(t *testing.T, zipPath string, files map[string]string) { t.Helper() out, err := os.Create(zipPath) if err != nil { t.Fatal(err) } defer out.Close() w := zip.NewWriter(out) defer w.Close() for name, body := range files { f, err := w.Create(name) if err != nil { t.Fatal(err) } if _, err := f.Write([]byte(body)); err != nil { t.Fatal(err) } } }