github.com/goreleaser/goreleaser@v1.25.1/pkg/archive/zip/zip.go (about)

     1  // Package zip implements the Archive interface providing zip archiving
     2  // and compression.
     3  package zip
     4  
     5  import (
     6  	"archive/zip"
     7  	"compress/flate"
     8  	"fmt"
     9  	"io"
    10  	"io/fs"
    11  	"os"
    12  	"path/filepath"
    13  
    14  	"github.com/goreleaser/goreleaser/pkg/config"
    15  )
    16  
    17  // Archive zip struct.
    18  type Archive struct {
    19  	z     *zip.Writer
    20  	files map[string]bool
    21  }
    22  
    23  // New zip archive.
    24  func New(target io.Writer) Archive {
    25  	compressor := zip.NewWriter(target)
    26  	compressor.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
    27  		return flate.NewWriter(out, flate.BestCompression)
    28  	})
    29  	return Archive{
    30  		z:     compressor,
    31  		files: map[string]bool{},
    32  	}
    33  }
    34  
    35  // New zip archive.
    36  func Copying(source *os.File, target io.Writer) (Archive, error) {
    37  	info, err := source.Stat()
    38  	if err != nil {
    39  		return Archive{}, err
    40  	}
    41  	r, err := zip.NewReader(source, info.Size())
    42  	if err != nil {
    43  		return Archive{}, err
    44  	}
    45  	w := New(target)
    46  	for _, zf := range r.File {
    47  
    48  		w.files[zf.Name] = true
    49  		hdr := zip.FileHeader{
    50  			Name:               zf.Name,
    51  			UncompressedSize64: zf.UncompressedSize64,
    52  			UncompressedSize:   zf.UncompressedSize,
    53  			CreatorVersion:     zf.CreatorVersion,
    54  			ExternalAttrs:      zf.ExternalAttrs,
    55  		}
    56  		ww, err := w.z.CreateHeader(&hdr)
    57  		if err != nil {
    58  			return Archive{}, fmt.Errorf("creating %q header in target: %w", zf.Name, err)
    59  		}
    60  		if zf.Mode().IsDir() {
    61  			continue
    62  		}
    63  		rr, err := zf.Open()
    64  		if err != nil {
    65  			return Archive{}, fmt.Errorf("opening %q from source: %w", zf.Name, err)
    66  		}
    67  		defer rr.Close()
    68  		if _, err = io.Copy(ww, rr); err != nil {
    69  			return Archive{}, fmt.Errorf("copy from %q source to target: %w", zf.Name, err)
    70  		}
    71  		_ = rr.Close()
    72  	}
    73  	return w, nil
    74  }
    75  
    76  // Close all closeables.
    77  func (a Archive) Close() error {
    78  	return a.z.Close()
    79  }
    80  
    81  // Add a file to the zip archive.
    82  func (a Archive) Add(f config.File) error {
    83  	if _, ok := a.files[f.Destination]; ok {
    84  		return &fs.PathError{Err: fs.ErrExist, Path: f.Destination, Op: "add"}
    85  	}
    86  	a.files[f.Destination] = true
    87  	info, err := os.Lstat(f.Source) // #nosec
    88  	if err != nil {
    89  		return err
    90  	}
    91  	if info.IsDir() {
    92  		return err
    93  	}
    94  	header, err := zip.FileInfoHeader(info)
    95  	if err != nil {
    96  		return err
    97  	}
    98  	header.Name = f.Destination
    99  	header.Method = zip.Deflate
   100  	if !f.Info.ParsedMTime.IsZero() {
   101  		header.Modified = f.Info.ParsedMTime
   102  	}
   103  	if f.Info.Mode != 0 {
   104  		header.SetMode(f.Info.Mode)
   105  	}
   106  	w, err := a.z.CreateHeader(header)
   107  	if err != nil {
   108  		return err
   109  	}
   110  	if info.IsDir() {
   111  		return nil
   112  	}
   113  	if info.Mode()&os.ModeSymlink != 0 {
   114  		link, err := os.Readlink(f.Source) // #nosec
   115  		if err != nil {
   116  			return fmt.Errorf("%s: %w", f.Source, err)
   117  		}
   118  		_, err = io.WriteString(w, filepath.ToSlash(link))
   119  		return err
   120  	}
   121  	file, err := os.Open(f.Source) // #nosec
   122  	if err != nil {
   123  		return err
   124  	}
   125  	defer file.Close()
   126  	_, err = io.Copy(w, file)
   127  	return err
   128  }
   129  
   130  // TODO: test fileinfo stuff