package testusers import ( "bytes" "fmt" "image/png" "os" "path/filepath" ) // CopyAvatars validates all sources and destination collisions before copying. // Existing identical assets are reused. Existing different files are not replaced. func CopyAvatars(sourceDir, mediaDir string) error { if sourceDir == "" || mediaDir == "" { return fmt.Errorf("both avatar source and media directories are required") } assets := make(map[string][]byte) for _, name := range Avatars() { data, err := os.ReadFile(filepath.Join(sourceDir, name)) if err != nil { return err } cfg, err := png.DecodeConfig(bytes.NewReader(data)) if err != nil || cfg.Width < 256 || cfg.Height < 256 { return fmt.Errorf("invalid or undersized PNG avatar: %s", name) } existing, err := os.ReadFile(filepath.Join(mediaDir, name)) if err == nil { if !bytes.Equal(existing, data) { return fmt.Errorf("refusing to overwrite different existing avatar: %s", name) } continue } if !os.IsNotExist(err) { return err } assets[name] = data } if err := os.MkdirAll(mediaDir, 0755); err != nil { return err } for name, data := range assets { file, err := os.OpenFile(filepath.Join(mediaDir, name), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) if err != nil { return err } _, writeErr := file.Write(data) closeErr := file.Close() if writeErr != nil { return writeErr } if closeErr != nil { return closeErr } } return nil }