Skip to content

Instantly share code, notes, and snippets.

@jasonrdsouza
Created February 27, 2021 16:29
Show Gist options
  • Save jasonrdsouza/141f1567880ce82b72fda726a076b16b to your computer and use it in GitHub Desktop.
Save jasonrdsouza/141f1567880ce82b72fda726a076b16b to your computer and use it in GitHub Desktop.
Demonstration of how to compress a folder and all of its contents in Go
// Original source: https://github.com/mimoo/eureka/blob/master/folders.go
package main
import (
"archive/tar"
"bytes"
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
func main() {
// tar + gzip
var buf bytes.Buffer
_ = compress("./folderToCompress", &buf)
// write the .tar.gzip
fileToWrite, err := os.OpenFile("./compressed.tar.gzip", os.O_CREATE|os.O_RDWR, os.FileMode(0666))
if err != nil {
panic(err)
}
if _, err := io.Copy(fileToWrite, &buf); err != nil {
panic(err)
}
}
func compress(src string, buf io.Writer) error {
// tar > gzip > buf
zr := gzip.NewWriter(buf)
tw := tar.NewWriter(zr)
// is file a folder?
fi, err := os.Stat(src)
if err != nil {
return err
}
mode := fi.Mode()
if mode.IsRegular() {
// get header
header, err := tar.FileInfoHeader(fi, src)
if err != nil {
return err
}
// write header
if err := tw.WriteHeader(header); err != nil {
return err
}
// get content
data, err := os.Open(src)
if err != nil {
return err
}
if _, err := io.Copy(tw, data); err != nil {
return err
}
} else if mode.IsDir() { // folder
// walk through every file in the folder
filepath.Walk(src, func(file string, fi os.FileInfo, err error) error {
// generate tar header
header, err := tar.FileInfoHeader(fi, file)
if err != nil {
return err
}
// must provide real name
// (see https://golang.org/src/archive/tar/common.go?#L626)
header.Name = filepath.ToSlash(file)
// write header
if err := tw.WriteHeader(header); err != nil {
return err
}
// if not a dir, write file content
if !fi.IsDir() {
data, err := os.Open(file)
if err != nil {
return err
}
if _, err := io.Copy(tw, data); err != nil {
return err
}
}
return nil
})
} else {
return fmt.Errorf("error: file type not supported")
}
// produce tar
if err := tw.Close(); err != nil {
return err
}
// produce gzip
if err := zr.Close(); err != nil {
return err
}
//
return nil
}
// check for path traversal and correct forward slashes
func validRelPath(p string) bool {
if p == "" || strings.Contains(p, `\`) || strings.HasPrefix(p, "/") || strings.Contains(p, "../") {
return false
}
return true
}
func decompress(src io.Reader, dst string) error {
// ungzip
zr, err := gzip.NewReader(src)
if err != nil {
return err
}
// untar
tr := tar.NewReader(zr)
// uncompress each element
for {
header, err := tr.Next()
if err == io.EOF {
break // End of archive
}
if err != nil {
return err
}
target := header.Name
// validate name against path traversal
if !validRelPath(header.Name) {
return fmt.Errorf("tar contained invalid name error %q", target)
}
// add dst + re-format slashes according to system
target = filepath.Join(dst, header.Name)
// if no join is needed, replace with ToSlash:
// target = filepath.ToSlash(header.Name)
// check the type
switch header.Typeflag {
// if its a dir and it doesn't exist create it (with 0755 permission)
case tar.TypeDir:
if _, err := os.Stat(target); err != nil {
if err := os.MkdirAll(target, 0755); err != nil {
return err
}
}
// if it's a file create it (with same permission)
case tar.TypeReg:
fileToWrite, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
return err
}
// copy over contents
if _, err := io.Copy(fileToWrite, tr); err != nil {
return err
}
// manually close here after each file operation; defering would cause each file close
// to wait until all operations have completed.
fileToWrite.Close()
}
}
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment