Created
February 27, 2021 16:29
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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