« Back to Index

Go: unzip(.zip) and untar(.tar.gz) contents of archive to specified destination

View original Gist on GitHub

Tags: #go

Golang untar contents of tar.gz archive to specified destination.go

// untar will decompress a tar.gz archive, moving all files and folders
// within the archive (parameter 1) to an output directory (parameter 2).
func untar(src io.Reader, dst string) error {
	gr, err := gzip.NewReader(src)
	if err != nil {
		return fmt.Errorf("error creating gzip reader: %w", err)
	}
	defer gr.Close()

	tr := tar.NewReader(gr)

	for {
		header, err := tr.Next()

		switch {

		// If no more files are found return
		case err == io.EOF:
			return nil

		// Return any other error
		case err != nil:
			return err

		// If the header is nil, skip it
		case header == nil:
			continue

		// Skip the any files duplicated as hidden files
		case strings.HasPrefix(header.Name, "._"):
			continue
		}

		// The target location where the dir/file should be created
		segs := strings.Split(header.Name, string(filepath.Separator))
		segs = segs[1:]
		target := filepath.Join(dst, filepath.Join(segs...))

		fi := header.FileInfo()

		if fi.IsDir() {
			os.MkdirAll(target, os.ModePerm)
			continue
		}

		if err = os.MkdirAll(filepath.Dir(target), os.ModePerm); err != nil {
			return err
		}

		fd, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode())
		if err != nil {
			return err
		}

		// NOTE: We use looped CopyN() not Copy() to avoid gosec G110 (CWE-409):
		// Potential DoS vulnerability via decompression bomb.
		for {
			_, err := io.CopyN(fd, tr, 1024)
			if err != nil {
				if err == io.EOF {
					break
				}
				return err
			}
		}

		fd.Close()
	}
}

Golang unzip contents of zip archive to specified destination.go

// unzip will decompress a zip archive, moving all files and folders
// within the zip file (parameter 1) to an output directory (parameter 2).
func unzip(src string, dst string) ([]string, error) {
	var filenames []string

	r, err := zip.OpenReader(src)
	if err != nil {
		return filenames, err
	}
	defer r.Close()

	for _, f := range r.File {
		// The zip contains a folder, and inside that folder are the files we're
		// interested in. So while looping over the files (whose .Name field is the
		// full path including the containing folder) we strip out the first path
		// segment to ensure the files we need are extracted to the current directory.
		segs := strings.Split(f.Name, string(filepath.Separator))
		segs = segs[1:]
		fpath := filepath.Join(dst, filepath.Join(segs...))
		filenames = append(filenames, fpath)

		if f.FileInfo().IsDir() {
			os.MkdirAll(fpath, os.ModePerm)
			continue
		}

		if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
			return filenames, err
		}

		fd, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
		if err != nil {
			return filenames, err
		}

		rc, err := f.Open()
		if err != nil {
			return filenames, err
		}

		// NOTE: We use looped CopyN() not Copy() to avoid gosec G110 (CWE-409):
		// Potential DoS vulnerability via decompression bomb.
		for {
			_, err := io.CopyN(fd, rc, 1024)
			if err != nil {
				if err == io.EOF {
					break
				}
				return filenames, err
			}
		}

		fd.Close()
		rc.Close()

		if err != nil {
			return filenames, err
		}
	}

	return filenames, nil
}