mirror of
https://github.com/c4milo/unpackit.git
synced 2025-09-27 03:05:55 +08:00
Initial import
This commit is contained in:
28
.editorconfig
Normal file
28
.editorconfig
Normal file
@@ -0,0 +1,28 @@
|
||||
root = true
|
||||
|
||||
; Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
|
||||
; Golang
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
; Golang
|
||||
[*.c]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
; YAML
|
||||
[*.yaml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
BIN
fixtures/cfgdrv.iso
Normal file
BIN
fixtures/cfgdrv.iso
Normal file
Binary file not shown.
BIN
fixtures/test.tar
Normal file
BIN
fixtures/test.tar
Normal file
Binary file not shown.
BIN
fixtures/test.tar.bzip2
Normal file
BIN
fixtures/test.tar.bzip2
Normal file
Binary file not shown.
BIN
fixtures/test.tar.gz
Normal file
BIN
fixtures/test.tar.gz
Normal file
Binary file not shown.
BIN
fixtures/test.zip
Normal file
BIN
fixtures/test.zip
Normal file
Binary file not shown.
231
unzipit.go
Normal file
231
unzipit.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package unzipit
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"compress/bzip2"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var (
|
||||
magicZIP []byte = []byte{0x50, 0x4b, 0x03, 0x04}
|
||||
magicGZ []byte = []byte{0x1f, 0x8b}
|
||||
magicBZIP []byte = []byte{0x42, 0x5a}
|
||||
magicTAR []byte = []byte{0x75, 0x73, 0x74, 0x61, 0x72} // at offset 257
|
||||
)
|
||||
|
||||
// Check whether a file has the magic number for tar, gzip, bzip2 or zip files
|
||||
//
|
||||
// 50 4b 03 04 for pkzip format
|
||||
// 1f 8b for .gz
|
||||
// 42 5a for bzip
|
||||
// 75 73 74 61 72 at offset 257 for tar files
|
||||
func magicNumber(reader io.ReaderAt, offset int64) (string, error) {
|
||||
magic := make([]byte, 5, 5)
|
||||
|
||||
reader.ReadAt(magic, offset)
|
||||
|
||||
if bytes.Equal(magicTAR, magic) {
|
||||
return "tar", nil
|
||||
}
|
||||
|
||||
if bytes.Equal(magicZIP, magic[0:4]) {
|
||||
return "zip", nil
|
||||
}
|
||||
|
||||
if bytes.Equal(magicGZ, magic[0:2]) {
|
||||
return "gzip", nil
|
||||
} else if bytes.Equal(magicBZIP, magic[0:2]) {
|
||||
return "bzip", nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Unpacks a compressed and archived file and places result output in destination
|
||||
// path.
|
||||
//
|
||||
// File formats supported are:
|
||||
// - .tar.gz
|
||||
// - .tar.bzip2
|
||||
// - .zip
|
||||
// - .tar
|
||||
//
|
||||
// If it cannot recognize the file format, it will save the file, as is, to the
|
||||
// destination path.
|
||||
func Unpack(file *os.File, destPath string) (string, error) {
|
||||
if file == nil {
|
||||
return "", errors.New("You must provide a valid file to unpack")
|
||||
}
|
||||
|
||||
var err error
|
||||
if destPath == "" {
|
||||
destPath, err = ioutil.TempDir(os.TempDir(), "unpackit-")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Makes sure despPath exists
|
||||
os.MkdirAll(destPath, 0740)
|
||||
|
||||
// Makes sure file cursor is at index 0
|
||||
_, err = file.Seek(0, 0)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Reads magic number from file so we can better determine how to proceed
|
||||
ftype, err := magicNumber(file, 0)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
data := bytes.NewBuffer(nil)
|
||||
switch ftype {
|
||||
case "gzip":
|
||||
data, err = Gunzip(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
case "bzip":
|
||||
data, err = Bunzip2(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
case "zip":
|
||||
// Like TAR, ZIP is also an archiving format, therefore we can just return
|
||||
// after it finishes
|
||||
return Unzip(file, destPath)
|
||||
default:
|
||||
io.Copy(data, file)
|
||||
}
|
||||
|
||||
// Check magic number in offset 257 too see if this is also a TAR file
|
||||
ftype, err = magicNumber(bytes.NewReader(data.Bytes()), 257)
|
||||
if ftype == "tar" {
|
||||
return Untar(data, destPath)
|
||||
}
|
||||
|
||||
// If it's not a TAR archive then save it to disk as is.
|
||||
destRawFile := filepath.Join(destPath, path.Base(file.Name()))
|
||||
|
||||
// Creates destination file
|
||||
destFile, err := os.Create(destRawFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
// Copies data to destination file
|
||||
if _, err := io.Copy(destFile, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return destPath, nil
|
||||
}
|
||||
|
||||
// Decompresses a bzip2 data stream and returns the decompressed stream
|
||||
func Bunzip2(file *os.File) (*bytes.Buffer, error) {
|
||||
data := bzip2.NewReader(file)
|
||||
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
io.Copy(buffer, data)
|
||||
|
||||
return buffer, nil
|
||||
}
|
||||
|
||||
// Decompresses a gzip data stream and returns the decompressed stream
|
||||
func Gunzip(file *os.File) (*bytes.Buffer, error) {
|
||||
data, err := gzip.NewReader(file)
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
io.Copy(buffer, data)
|
||||
|
||||
return buffer, nil
|
||||
}
|
||||
|
||||
// Decompresses and unarchives a ZIP archive, returning the final path or an error
|
||||
func Unzip(file *os.File, destPath string) (string, error) {
|
||||
// Open a zip archive for reading.
|
||||
r, err := zip.OpenReader(file.Name())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
// Iterate through the files in the archive,
|
||||
// printing some of their contents.
|
||||
for _, f := range r.File {
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
file, err := os.Create(filepath.Join(destPath, f.Name))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err := io.CopyN(file, rc, int64(f.UncompressedSize64)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer rc.Close()
|
||||
}
|
||||
return destPath, nil
|
||||
}
|
||||
|
||||
// Unarchives a TAR archive and returns the final destination path or an error
|
||||
func Untar(data io.Reader, destPath string) (string, error) {
|
||||
// Makes sure destPath exists
|
||||
os.MkdirAll(destPath, 0740)
|
||||
|
||||
tr := tar.NewReader(data)
|
||||
|
||||
// Iterate through the files in the archive.
|
||||
rootdir := destPath
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
// end of tar archive
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return rootdir, err
|
||||
}
|
||||
|
||||
if hdr.FileInfo().IsDir() {
|
||||
d := filepath.Join(destPath, hdr.Name)
|
||||
if rootdir == destPath {
|
||||
rootdir = d
|
||||
}
|
||||
os.Mkdir(d, 0740)
|
||||
continue
|
||||
}
|
||||
|
||||
file, err := os.Create(filepath.Join(destPath, hdr.Name))
|
||||
if err != nil {
|
||||
return rootdir, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err := io.Copy(file, tr); err != nil {
|
||||
return rootdir, err
|
||||
}
|
||||
}
|
||||
|
||||
return rootdir, nil
|
||||
}
|
138
unzipit_test.go
Normal file
138
unzipit_test.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package unzipit
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// assert fails the test if the condition is false.
|
||||
func assert(tb testing.TB, condition bool, msg string, v ...interface{}) {
|
||||
if !condition {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...)
|
||||
tb.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// ok fails the test if an err is not nil.
|
||||
func ok(tb testing.TB, err error) {
|
||||
if err != nil {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error())
|
||||
tb.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// equals fails the test if exp is not equal to act.
|
||||
func equals(tb testing.TB, exp, act interface{}) {
|
||||
if !reflect.DeepEqual(exp, act) {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act)
|
||||
tb.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnpack(t *testing.T) {
|
||||
var tests = []struct {
|
||||
filepath string
|
||||
files int
|
||||
}{
|
||||
{"./fixtures/test.tar.bzip2", 2},
|
||||
{"./fixtures/test.tar.gz", 2},
|
||||
{"./fixtures/test.zip", 2},
|
||||
{"./fixtures/test.tar", 2},
|
||||
{"./fixtures/cfgdrv.iso", 1},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
tempDir, err := ioutil.TempDir(os.TempDir(), "unpackit-tests-"+path.Base(test.filepath)+"-")
|
||||
ok(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
file, err := os.Open(test.filepath)
|
||||
ok(t, err)
|
||||
defer file.Close()
|
||||
|
||||
destPath, err := Unpack(file, tempDir)
|
||||
ok(t, err)
|
||||
|
||||
finfo, err := ioutil.ReadDir(destPath)
|
||||
ok(t, err)
|
||||
|
||||
length := len(finfo)
|
||||
assert(t, length == test.files, fmt.Sprintf("%d != %d for %s", length, test.files, destPath))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMagicNumber(t *testing.T) {
|
||||
var tests = []struct {
|
||||
filepath string
|
||||
offset int64
|
||||
ftype string
|
||||
}{
|
||||
{"./fixtures/test.tar.bzip2", 0, "bzip"},
|
||||
{"./fixtures/test.tar.gz", 0, "gzip"},
|
||||
{"./fixtures/test.zip", 0, "zip"},
|
||||
{"./fixtures/test.tar", 257, "tar"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
file, err := os.Open(test.filepath)
|
||||
ok(t, err)
|
||||
|
||||
ftype, err := magicNumber(file, test.offset)
|
||||
file.Close()
|
||||
ok(t, err)
|
||||
|
||||
assert(t, ftype == test.ftype, ftype+" != "+test.ftype)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUntar(t *testing.T) {
|
||||
// Create a buffer to write our archive to.
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// Create a new tar archive.
|
||||
tw := tar.NewWriter(buf)
|
||||
|
||||
// Add some files to the archive.
|
||||
var files = []struct {
|
||||
Name, Body string
|
||||
}{
|
||||
{"readme.txt", "This archive contains some text files."},
|
||||
{"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"},
|
||||
{"todo.txt", "Get animal handling licence."},
|
||||
}
|
||||
for _, file := range files {
|
||||
hdr := &tar.Header{
|
||||
Name: file.Name,
|
||||
Size: int64(len(file.Body)),
|
||||
}
|
||||
err := tw.WriteHeader(hdr)
|
||||
ok(t, err)
|
||||
|
||||
_, err = tw.Write([]byte(file.Body))
|
||||
ok(t, err)
|
||||
}
|
||||
|
||||
// Make sure to check the error on Close.
|
||||
err := tw.Close()
|
||||
ok(t, err)
|
||||
|
||||
// Open the tar archive for reading.
|
||||
r := bytes.NewReader(buf.Bytes())
|
||||
destDir, err := ioutil.TempDir(os.TempDir(), "terraform-vix")
|
||||
ok(t, err)
|
||||
defer os.RemoveAll(destDir)
|
||||
|
||||
_, err = Untar(r, destDir)
|
||||
ok(t, err)
|
||||
}
|
Reference in New Issue
Block a user