diff --git a/.testdata/data.tar.gz b/.testdata/data.tar.gz new file mode 100644 index 0000000..c34e8a9 Binary files /dev/null and b/.testdata/data.tar.gz differ diff --git a/.testdata/data.txt.bz2 b/.testdata/data.txt.bz2 new file mode 100644 index 0000000..98bab7b Binary files /dev/null and b/.testdata/data.txt.bz2 differ diff --git a/.testdata/data.txt.gz b/.testdata/data.txt.gz new file mode 100644 index 0000000..7589c39 Binary files /dev/null and b/.testdata/data.txt.gz differ diff --git a/.testdata/data.txt.xz b/.testdata/data.txt.xz new file mode 100644 index 0000000..edab696 Binary files /dev/null and b/.testdata/data.txt.xz differ diff --git a/.testdata/data.txt.zst b/.testdata/data.txt.zst new file mode 100644 index 0000000..a10e272 Binary files /dev/null and b/.testdata/data.txt.zst differ diff --git a/.testdata/data.zip b/.testdata/data.zip new file mode 100644 index 0000000..57409ae Binary files /dev/null and b/.testdata/data.zip differ diff --git a/Makefile b/Makefile index abb3da3..91cd083 100644 --- a/Makefile +++ b/Makefile @@ -33,9 +33,9 @@ vendor: mod-vendor ## Make vendored copy of dependencies test: ## Run tests ifdef COVERAGE_FILE ## Save coverage data into file (String) - go test $(VERBOSE_FLAG) -covermode=count -coverprofile=$(COVERAGE_FILE) ./tar ./tbz ./tgz ./txz ./tzst + go test $(VERBOSE_FLAG) -covermode=count -coverprofile=$(COVERAGE_FILE) ./. ./bz2 ./gz ./tar ./tbz2 ./tgz ./txz ./tzst ./xz ./zip ./zst else - go test $(VERBOSE_FLAG) -covermode=count ./tar ./tbz ./tgz ./txz ./tzst + go test $(VERBOSE_FLAG) -covermode=count ./. ./bz2 ./gz ./tar ./tbz2 ./tgz ./txz ./tzst ./xz ./zip ./zst endif mod-init: diff --git a/bz2/bz2.go b/bz2/bz2.go new file mode 100644 index 0000000..2fb17c5 --- /dev/null +++ b/bz2/bz2.go @@ -0,0 +1,70 @@ +// Package bz2 provides methods for unpacking bz2 files +package bz2 + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "bufio" + "compress/bzip2" + "fmt" + "io" + "os" + "path/filepath" + "strings" +) + +// Unpacks file to given directory +func Unpack(file, dir string) error { + switch { + case file == "": + return fmt.Errorf("Path to input file can not be empty") + case dir == "": + return fmt.Errorf("Path to output file can not be empty") + } + + fd, err := os.OpenFile(file, os.O_RDONLY, 0) + + if err != nil { + return err + } + + defer fd.Close() + + return Read( + bufio.NewReader(fd), + filepath.Join( + filepath.Clean(dir), + strings.TrimSuffix(filepath.Base(file), ".bz2"), + ), + ) +} + +// Read reads compressed data using given reader and unpacks it to +// the given directory +func Read(r io.Reader, output string) error { + switch { + case r == nil: + return fmt.Errorf("Reader can not be nil") + case output == "": + return fmt.Errorf("Path to output file can not be empty") + } + + fd, err := os.OpenFile(output, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0640) + + if err != nil { + return err + } + + bw := bufio.NewWriter(fd) + _, err = io.Copy(bw, bzip2.NewReader(r)) + + bw.Flush() + fd.Close() + + return err +} diff --git a/bz2/bz2_test.go b/bz2/bz2_test.go new file mode 100644 index 0000000..14e0dce --- /dev/null +++ b/bz2/bz2_test.go @@ -0,0 +1,68 @@ +package bz2 + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "os" + "strings" + "testing" + + "github.com/essentialkaos/ek/v12/fsutil" + "github.com/essentialkaos/ek/v12/hash" + + . "github.com/essentialkaos/check" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func Test(t *testing.T) { TestingT(t) } + +// ////////////////////////////////////////////////////////////////////////////////// // + +type BZ2Suite struct { + Dir string +} + +var _ = Suite(&BZ2Suite{}) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func (s *BZ2Suite) SetUpSuite(c *C) { + s.Dir = c.MkDir() +} + +func (s *BZ2Suite) TestUnpack(c *C) { + err := Unpack("../.testdata/data.txt.bz2", s.Dir) + + c.Assert(err, IsNil) + + c.Assert(fsutil.IsExist(s.Dir+"/data.txt"), Equals, true) + c.Assert(fsutil.GetMode(s.Dir+"/data.txt"), Equals, os.FileMode(0640)) + + c.Assert(hash.FileHash(s.Dir+"/data.txt"), Equals, "918c03a211adc19a466c9db22efa575efb6c488fd41c70e57b1ec0920f1a1d8c") +} + +func (s *BZ2Suite) TestErrors(c *C) { + err := Unpack("", "/_unknown") + c.Assert(err, NotNil) + + err = Unpack("../.testdata/data.txt.bz2", "") + c.Assert(err, NotNil) + + err = Unpack("/_unknown", s.Dir) + c.Assert(err, NotNil) + + err = Read(nil, "/_unknown") + c.Assert(err, NotNil) + + err = Read(strings.NewReader(""), "") + c.Assert(err, NotNil) + + err = Read(strings.NewReader(""), "/_unknown") + c.Assert(err, NotNil) +} diff --git a/gz/gz.go b/gz/gz.go new file mode 100644 index 0000000..52d75c7 --- /dev/null +++ b/gz/gz.go @@ -0,0 +1,77 @@ +// Package gz provides methods for unpacking gz files +package gz + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "bufio" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/klauspost/compress/gzip" +) + +// Unpacks file to given directory +func Unpack(file, dir string) error { + switch { + case file == "": + return fmt.Errorf("Path to input file can not be empty") + case dir == "": + return fmt.Errorf("Path to output file can not be empty") + } + + fd, err := os.OpenFile(file, os.O_RDONLY, 0) + + if err != nil { + return err + } + + defer fd.Close() + + return Read( + bufio.NewReader(fd), + filepath.Join( + filepath.Clean(dir), + strings.TrimSuffix(filepath.Base(file), ".gz"), + ), + ) +} + +// Read reads compressed data using given reader and unpacks it to +// the given directory +func Read(r io.Reader, output string) error { + switch { + case r == nil: + return fmt.Errorf("Reader can not be nil") + case output == "": + return fmt.Errorf("Path to output file can not be empty") + } + + fd, err := os.OpenFile(output, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0640) + + if err != nil { + return err + } + + gr, err := gzip.NewReader(r) + + if err != nil { + return err + } + + bw := bufio.NewWriter(fd) + _, err = io.Copy(bw, gr) + + bw.Flush() + fd.Close() + + return err +} diff --git a/gz/gz_test.go b/gz/gz_test.go new file mode 100644 index 0000000..89d11da --- /dev/null +++ b/gz/gz_test.go @@ -0,0 +1,71 @@ +package gz + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "os" + "strings" + "testing" + + "github.com/essentialkaos/ek/v12/fsutil" + "github.com/essentialkaos/ek/v12/hash" + + . "github.com/essentialkaos/check" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func Test(t *testing.T) { TestingT(t) } + +// ////////////////////////////////////////////////////////////////////////////////// // + +type GZSuite struct { + Dir string +} + +var _ = Suite(&GZSuite{}) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func (s *GZSuite) SetUpSuite(c *C) { + s.Dir = c.MkDir() +} + +func (s *GZSuite) TestUnpack(c *C) { + err := Unpack("../.testdata/data.txt.gz", s.Dir) + + c.Assert(err, IsNil) + + c.Assert(fsutil.IsExist(s.Dir+"/data.txt"), Equals, true) + c.Assert(fsutil.GetMode(s.Dir+"/data.txt"), Equals, os.FileMode(0640)) + + c.Assert(hash.FileHash(s.Dir+"/data.txt"), Equals, "918c03a211adc19a466c9db22efa575efb6c488fd41c70e57b1ec0920f1a1d8c") +} + +func (s *GZSuite) TestErrors(c *C) { + err := Unpack("", "/_unknown") + c.Assert(err, NotNil) + + err = Unpack("../.testdata/data.txt.gz", "") + c.Assert(err, NotNil) + + err = Unpack("/_unknown", s.Dir) + c.Assert(err, NotNil) + + err = Read(nil, "/_unknown") + c.Assert(err, NotNil) + + err = Read(strings.NewReader(""), "") + c.Assert(err, NotNil) + + err = Read(strings.NewReader(""), "/_unknown") + c.Assert(err, NotNil) + + err = Read(strings.NewReader(""), s.Dir+"/test") + c.Assert(err, NotNil) +} diff --git a/npck.go b/npck.go index c16f384..6be08f0 100644 --- a/npck.go +++ b/npck.go @@ -1,3 +1,4 @@ +// Package npck provides methods for unpacking various types of archives package npck // ////////////////////////////////////////////////////////////////////////////////// // @@ -8,19 +9,54 @@ package npck // ////////////////////////////////////////////////////////////////////////////////// // import ( + "fmt" + "path/filepath" + "strings" + + "github.com/essentialkaos/npck/bz2" + "github.com/essentialkaos/npck/gz" "github.com/essentialkaos/npck/tar" - "github.com/essentialkaos/npck/tbz" + "github.com/essentialkaos/npck/tbz2" "github.com/essentialkaos/npck/tgz" "github.com/essentialkaos/npck/txz" "github.com/essentialkaos/npck/tzst" + "github.com/essentialkaos/npck/xz" + "github.com/essentialkaos/npck/zip" + "github.com/essentialkaos/npck/zst" ) // ////////////////////////////////////////////////////////////////////////////////// // -func stub() { - tar.Unpack("", "") - tbz.Unpack("", "") - tgz.Unpack("", "") - txz.Unpack("", "") - tzst.Unpack("", "") +// Unpack unpacks given file +func Unpack(file, dir string) error { + ext := filepath.Ext(file) + + if strings.Contains(file, ".tar.") { + ext = ".tar" + ext + } + + switch ext { + case ".tgz", ".tar.gz": + return tgz.Unpack(file, dir) + case ".tbz2", ".tar.bz2": + return tbz2.Unpack(file, dir) + case ".txz", ".tar.xz": + return txz.Unpack(file, dir) + case ".tzst", ".tar.zst": + return tzst.Unpack(file, dir) + case ".zip": + return zip.Unpack(file, dir) + case ".tar": + return tar.Unpack(file, dir) + case ".gz": + return gz.Unpack(file, dir) + case ".bz2": + return bz2.Unpack(file, dir) + case ".xz": + return xz.Unpack(file, dir) + case ".zst": + return zst.Unpack(file, dir) + } + + return fmt.Errorf("Unknown or unsupported archive type") } diff --git a/npck_test.go b/npck_test.go new file mode 100644 index 0000000..58a2618 --- /dev/null +++ b/npck_test.go @@ -0,0 +1,45 @@ +package npck + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "testing" + + . "github.com/essentialkaos/check" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func Test(t *testing.T) { TestingT(t) } + +// ////////////////////////////////////////////////////////////////////////////////// // + +type NPCKSuite struct { + Dir string +} + +var _ = Suite(&NPCKSuite{}) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func (s *NPCKSuite) TestUnpack(c *C) { + c.Assert(Unpack(".testdata/data.txt.gz", c.MkDir()), IsNil) + c.Assert(Unpack(".testdata/data.txt.bz2", c.MkDir()), IsNil) + c.Assert(Unpack(".testdata/data.txt.xz", c.MkDir()), IsNil) + c.Assert(Unpack(".testdata/data.txt.zst", c.MkDir()), IsNil) + + c.Assert(Unpack(".testdata/data.tar", c.MkDir()), IsNil) + c.Assert(Unpack(".testdata/data.tgz", c.MkDir()), IsNil) + c.Assert(Unpack(".testdata/data.tbz2", c.MkDir()), IsNil) + c.Assert(Unpack(".testdata/data.txz", c.MkDir()), IsNil) + c.Assert(Unpack(".testdata/data.tzst", c.MkDir()), IsNil) + c.Assert(Unpack(".testdata/data.zip", c.MkDir()), IsNil) + c.Assert(Unpack(".testdata/data.tar.gz", c.MkDir()), IsNil) + + c.Assert(Unpack(".testdata/data.jpg", c.MkDir()), NotNil) +} diff --git a/tar/tar.go b/tar/tar.go index f02c6cd..f6bfd6e 100644 --- a/tar/tar.go +++ b/tar/tar.go @@ -30,14 +30,17 @@ func Unpack(file, dir string) error { defer fd.Close() - return UnpackReader(bufio.NewReader(fd), dir) + return Read(bufio.NewReader(fd), dir) } -// UnpackReader reads packed data using given reader and unpacks it to +// Read reads compressed data using given reader and unpacks it to // the given directory -func UnpackReader(r io.Reader, dir string) error { - if r == nil { +func Read(r io.Reader, dir string) error { + switch { + case r == nil: return fmt.Errorf("Reader can not be nil") + case dir == "": + return fmt.Errorf("Path to output directory can not be empty") } _, err := os.Stat(dir) @@ -80,8 +83,10 @@ func UnpackReader(r io.Reader, dir string) error { return err } - _, err = io.Copy(fd, tr) + bw := bufio.NewWriter(fd) + _, err = io.Copy(bw, tr) + bw.Flush() fd.Close() if err != nil { diff --git a/tar/tar_test.go b/tar/tar_test.go index 546ffb8..b44a85e 100644 --- a/tar/tar_test.go +++ b/tar/tar_test.go @@ -56,6 +56,6 @@ func (s *TarSuite) TestErrors(c *C) { err = Unpack("../.testdata/data.tar", "/unknown") c.Assert(err, NotNil) - err = UnpackReader(nil, "/unknown") + err = Read(nil, "/unknown") c.Assert(err, NotNil) } diff --git a/tbz/tbz.go b/tbz2/tbz2.go similarity index 77% rename from tbz/tbz.go rename to tbz2/tbz2.go index ae7cd6b..0052807 100644 --- a/tbz/tbz.go +++ b/tbz2/tbz2.go @@ -1,5 +1,5 @@ -// Package tbz provides method for unpacking tar.bz2 files -package tbz +// Package tbz2 provides methods for unpacking tar.bz2 files +package tbz2 // ////////////////////////////////////////////////////////////////////////////////// // // // @@ -30,15 +30,15 @@ func Unpack(file, dir string) error { defer fd.Close() - return UnpackReader(bufio.NewReader(fd), dir) + return Read(bufio.NewReader(fd), dir) } -// UnpackReader reads packed data using given reader and unpacks it to +// Read reads compressed data using given reader and unpacks it to // the given directory -func UnpackReader(r io.Reader, dir string) error { +func Read(r io.Reader, dir string) error { if r == nil { return fmt.Errorf("Reader can not be nil") } - return tar.UnpackReader(bzip2.NewReader(r), dir) + return tar.Read(bzip2.NewReader(r), dir) } diff --git a/tbz/tbz_test.go b/tbz2/tbz2_test.go similarity index 88% rename from tbz/tbz_test.go rename to tbz2/tbz2_test.go index 16609d6..c63247d 100644 --- a/tbz/tbz_test.go +++ b/tbz2/tbz2_test.go @@ -1,4 +1,4 @@ -package tbz +package tbz2 // ////////////////////////////////////////////////////////////////////////////////// // // // @@ -23,19 +23,19 @@ func Test(t *testing.T) { TestingT(t) } // ////////////////////////////////////////////////////////////////////////////////// // -type TBZSuite struct { +type TBZ2Suite struct { Dir string } -var _ = Suite(&TBZSuite{}) +var _ = Suite(&TBZ2Suite{}) // ////////////////////////////////////////////////////////////////////////////////// // -func (s *TBZSuite) SetUpSuite(c *C) { +func (s *TBZ2Suite) SetUpSuite(c *C) { s.Dir = c.MkDir() } -func (s *TBZSuite) TestUnpack(c *C) { +func (s *TBZ2Suite) TestUnpack(c *C) { err := Unpack("../.testdata/data.tbz2", s.Dir) c.Assert(err, IsNil) @@ -49,13 +49,13 @@ func (s *TBZSuite) TestUnpack(c *C) { c.Assert(hash.FileHash(s.Dir+"/data/payload.txt"), Equals, "918c03a211adc19a466c9db22efa575efb6c488fd41c70e57b1ec0920f1a1d8c") } -func (s *TBZSuite) TestErrors(c *C) { +func (s *TBZ2Suite) TestErrors(c *C) { err := Unpack("../.testdata/unknown.tbz2", s.Dir) c.Assert(err, NotNil) err = Unpack("../.testdata/data.tbz2", "/unknown") c.Assert(err, NotNil) - err = UnpackReader(nil, "/unknown") + err = Read(nil, "/unknown") c.Assert(err, NotNil) } diff --git a/tgz/tgz.go b/tgz/tgz.go index cec3bbc..9bdca8a 100644 --- a/tgz/tgz.go +++ b/tgz/tgz.go @@ -1,4 +1,4 @@ -// Package tgz provides method for unpacking tar.gz files +// Package tgz provides methods for unpacking tar.gz files package tgz // ////////////////////////////////////////////////////////////////////////////////// // @@ -31,12 +31,12 @@ func Unpack(file, dir string) error { defer fd.Close() - return UnpackReader(bufio.NewReader(fd), dir) + return Read(bufio.NewReader(fd), dir) } -// UnpackReader reads packed data using given reader and unpacks it to +// Read reads compressed data using given reader and unpacks it to // the given directory -func UnpackReader(r io.Reader, dir string) error { +func Read(r io.Reader, dir string) error { if r == nil { return fmt.Errorf("Reader can not be nil") } @@ -47,5 +47,5 @@ func UnpackReader(r io.Reader, dir string) error { return err } - return tar.UnpackReader(gr, dir) + return tar.Read(gr, dir) } diff --git a/tgz/tgz_test.go b/tgz/tgz_test.go index 06a39f7..72aad1c 100644 --- a/tgz/tgz_test.go +++ b/tgz/tgz_test.go @@ -56,6 +56,6 @@ func (s *TGZSuite) TestErrors(c *C) { err = Unpack("../.testdata/data.tgz", "/unknown") c.Assert(err, NotNil) - err = UnpackReader(nil, "/unknown") + err = Read(nil, "/unknown") c.Assert(err, NotNil) } diff --git a/txz/txz.go b/txz/txz.go index f8fffdc..07a0598 100644 --- a/txz/txz.go +++ b/txz/txz.go @@ -1,4 +1,4 @@ -// Package txz provides method for unpacking tar.xz files +// Package txz provides methods for unpacking tar.xz files package txz // ////////////////////////////////////////////////////////////////////////////////// // @@ -31,12 +31,12 @@ func Unpack(file, dir string) error { defer fd.Close() - return UnpackReader(bufio.NewReader(fd), dir) + return Read(bufio.NewReader(fd), dir) } -// UnpackReader reads packed data using given reader and unpacks it to +// Read reads compressed data using given reader and unpacks it to // the given directory -func UnpackReader(r io.Reader, dir string) error { +func Read(r io.Reader, dir string) error { if r == nil { return fmt.Errorf("Reader can not be nil") } @@ -47,5 +47,5 @@ func UnpackReader(r io.Reader, dir string) error { return err } - return tar.UnpackReader(gr, dir) + return tar.Read(gr, dir) } diff --git a/txz/txz_test.go b/txz/txz_test.go index 76f7adc..ab8a209 100644 --- a/txz/txz_test.go +++ b/txz/txz_test.go @@ -56,6 +56,6 @@ func (s *TXZSuite) TestErrors(c *C) { err = Unpack("../.testdata/data.txz", "/unknown") c.Assert(err, NotNil) - err = UnpackReader(nil, "/unknown") + err = Read(nil, "/unknown") c.Assert(err, NotNil) } diff --git a/tzst/tzst.go b/tzst/tzst.go index 555e547..c463884 100644 --- a/tzst/tzst.go +++ b/tzst/tzst.go @@ -1,4 +1,4 @@ -// Package tzst provides method for unpacking tar.zst files +// Package tzst provides methods for unpacking tar.zst files package tzst // ////////////////////////////////////////////////////////////////////////////////// // @@ -31,21 +31,21 @@ func Unpack(file, dir string) error { defer fd.Close() - return UnpackReader(bufio.NewReader(fd), dir) + return Read(bufio.NewReader(fd), dir) } -// UnpackReader reads packed data using given reader and unpacks it to +// Read reads compressed data using given reader and unpacks it to // the given directory -func UnpackReader(r io.Reader, dir string) error { +func Read(r io.Reader, dir string) error { if r == nil { return fmt.Errorf("Reader can not be nil") } - gr, err := zstd.NewReader(r) + zr, err := zstd.NewReader(r) if err != nil { return err } - return tar.UnpackReader(gr, dir) + return tar.Read(zr, dir) } diff --git a/tzst/tzst_test.go b/tzst/tzst_test.go index a951668..180275d 100644 --- a/tzst/tzst_test.go +++ b/tzst/tzst_test.go @@ -56,6 +56,6 @@ func (s *TZSTSuite) TestErrors(c *C) { err = Unpack("../.testdata/data.tzst", "/unknown") c.Assert(err, NotNil) - err = UnpackReader(nil, "/unknown") + err = Read(nil, "/unknown") c.Assert(err, NotNil) } diff --git a/xz/xz.go b/xz/xz.go new file mode 100644 index 0000000..f9f35f2 --- /dev/null +++ b/xz/xz.go @@ -0,0 +1,77 @@ +// Package xz provides methods for unpacking xz files +package xz + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "bufio" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/ulikunitz/xz" +) + +// Unpacks file to given directory +func Unpack(file, dir string) error { + switch { + case file == "": + return fmt.Errorf("Path to input file can not be empty") + case dir == "": + return fmt.Errorf("Path to output file can not be empty") + } + + fd, err := os.OpenFile(file, os.O_RDONLY, 0) + + if err != nil { + return err + } + + defer fd.Close() + + return Read( + bufio.NewReader(fd), + filepath.Join( + filepath.Clean(dir), + strings.TrimSuffix(filepath.Base(file), ".xz"), + ), + ) +} + +// Read reads compressed data using given reader and unpacks it to +// the given directory +func Read(r io.Reader, output string) error { + switch { + case r == nil: + return fmt.Errorf("Reader can not be nil") + case output == "": + return fmt.Errorf("Path to output file can not be empty") + } + + fd, err := os.OpenFile(output, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0640) + + if err != nil { + return err + } + + xr, err := xz.NewReader(r) + + if err != nil { + return err + } + + bw := bufio.NewWriter(fd) + _, err = io.Copy(bw, xr) + + bw.Flush() + fd.Close() + + return err +} diff --git a/xz/xz_test.go b/xz/xz_test.go new file mode 100644 index 0000000..01c9065 --- /dev/null +++ b/xz/xz_test.go @@ -0,0 +1,71 @@ +package xz + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "os" + "strings" + "testing" + + "github.com/essentialkaos/ek/v12/fsutil" + "github.com/essentialkaos/ek/v12/hash" + + . "github.com/essentialkaos/check" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func Test(t *testing.T) { TestingT(t) } + +// ////////////////////////////////////////////////////////////////////////////////// // + +type XZSuite struct { + Dir string +} + +var _ = Suite(&XZSuite{}) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func (s *XZSuite) SetUpSuite(c *C) { + s.Dir = c.MkDir() +} + +func (s *XZSuite) TestUnpack(c *C) { + err := Unpack("../.testdata/data.txt.xz", s.Dir) + + c.Assert(err, IsNil) + + c.Assert(fsutil.IsExist(s.Dir+"/data.txt"), Equals, true) + c.Assert(fsutil.GetMode(s.Dir+"/data.txt"), Equals, os.FileMode(0640)) + + c.Assert(hash.FileHash(s.Dir+"/data.txt"), Equals, "918c03a211adc19a466c9db22efa575efb6c488fd41c70e57b1ec0920f1a1d8c") +} + +func (s *XZSuite) TestErrors(c *C) { + err := Unpack("", "/_unknown") + c.Assert(err, NotNil) + + err = Unpack("../.testdata/data.txt.xz", "") + c.Assert(err, NotNil) + + err = Unpack("/_unknown", s.Dir) + c.Assert(err, NotNil) + + err = Read(nil, "/_unknown") + c.Assert(err, NotNil) + + err = Read(strings.NewReader(""), "") + c.Assert(err, NotNil) + + err = Read(strings.NewReader(""), "/_unknown") + c.Assert(err, NotNil) + + err = Read(strings.NewReader(""), s.Dir+"/test") + c.Assert(err, NotNil) +} diff --git a/zip/zip.go b/zip/zip.go new file mode 100644 index 0000000..1fe8166 --- /dev/null +++ b/zip/zip.go @@ -0,0 +1,96 @@ +// Package zip provides methods for unpacking zip files +package zip + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "bufio" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/klauspost/compress/zip" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +// Unpacks file to given directory +func Unpack(file, dir string) error { + fd, err := os.OpenFile(file, os.O_RDONLY, 0) + + if err != nil { + return err + } + + defer fd.Close() + + return Read(fd, dir) +} + +// Read reads compressed data using given reader and unpacks it to +// the given directory +func Read(r io.ReaderAt, dir string) error { + switch { + case r == nil: + return fmt.Errorf("Reader can not be nil") + case dir == "": + return fmt.Errorf("Path to output directory can not be empty") + } + + zr, err := zip.NewReader(r, 4096) + + if err != nil { + return err + } + + for _, file := range zr.File { + header := file.FileHeader + path := filepath.Join(dir, header.Name) + info := header.FileInfo() + + if strings.Contains(path, "..") { + return fmt.Errorf("Path \"%s\" contains directory traversal element and cannot be used", header.Name) + } + + if info.IsDir() { + err = os.MkdirAll(path, info.Mode()) + + if err != nil { + return err + } + + continue + } + + zfd, err := file.Open() + + if err != nil { + return err + } + + fd, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode()) + + if err != nil { + return err + } + + bw := bufio.NewWriter(fd) + _, err = io.Copy(bw, zfd) + + bw.Flush() + fd.Close() + + if err != nil { + return err + } + } + + return nil +} diff --git a/zip/zip_test.go b/zip/zip_test.go new file mode 100644 index 0000000..8ec64ec --- /dev/null +++ b/zip/zip_test.go @@ -0,0 +1,71 @@ +package zip + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "os" + "strings" + "testing" + + "github.com/essentialkaos/ek/v12/fsutil" + "github.com/essentialkaos/ek/v12/hash" + + . "github.com/essentialkaos/check" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func Test(t *testing.T) { TestingT(t) } + +// ////////////////////////////////////////////////////////////////////////////////// // + +type ZipSuite struct { + Dir string +} + +var _ = Suite(&ZipSuite{}) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func (s *ZipSuite) SetUpSuite(c *C) { + s.Dir = c.MkDir() +} + +func (s *ZipSuite) TestUnpack(c *C) { + err := Unpack("../.testdata/data.zip", s.Dir) + + c.Assert(err, IsNil) + + c.Assert(fsutil.IsExist(s.Dir+"/data"), Equals, true) + c.Assert(fsutil.GetMode(s.Dir+"/data"), Equals, os.FileMode(0700)) + + c.Assert(fsutil.IsExist(s.Dir+"/data/payload.txt"), Equals, true) + c.Assert(fsutil.GetMode(s.Dir+"/data/payload.txt"), Equals, os.FileMode(0644)) + + c.Assert(hash.FileHash(s.Dir+"/data/payload.txt"), Equals, "918c03a211adc19a466c9db22efa575efb6c488fd41c70e57b1ec0920f1a1d8c") +} + +func (s *ZipSuite) TestErrors(c *C) { + err := Unpack("../.testdata/unknown.zip", s.Dir) + c.Assert(err, NotNil) + + err = Unpack("../.testdata/data.zip", "/unknown") + c.Assert(err, NotNil) + + err = Read(nil, "/unknown") + c.Assert(err, NotNil) + + err = Read(nil, "/unknown") + c.Assert(err, NotNil) + + err = Read(strings.NewReader(""), "") + c.Assert(err, NotNil) + + err = Read(strings.NewReader(""), s.Dir) + c.Assert(err, NotNil) +} diff --git a/zst/zst.go b/zst/zst.go new file mode 100644 index 0000000..c0b4783 --- /dev/null +++ b/zst/zst.go @@ -0,0 +1,77 @@ +// Package zst provides methods for unpacking zst files +package zst + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "bufio" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/klauspost/compress/zstd" +) + +// Unpacks file to given directory +func Unpack(file, dir string) error { + switch { + case file == "": + return fmt.Errorf("Path to input file can not be empty") + case dir == "": + return fmt.Errorf("Path to output file can not be empty") + } + + fd, err := os.OpenFile(file, os.O_RDONLY, 0) + + if err != nil { + return err + } + + defer fd.Close() + + return Read( + bufio.NewReader(fd), + filepath.Join( + filepath.Clean(dir), + strings.TrimSuffix(filepath.Base(file), ".zst"), + ), + ) +} + +// Read reads compressed data using given reader and unpacks it to +// the given directory +func Read(r io.Reader, output string) error { + switch { + case r == nil: + return fmt.Errorf("Reader can not be nil") + case output == "": + return fmt.Errorf("Path to output file can not be empty") + } + + fd, err := os.OpenFile(output, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0640) + + if err != nil { + return err + } + + zr, err := zstd.NewReader(r) + + if err != nil { + return err + } + + bw := bufio.NewWriter(fd) + _, err = io.Copy(bw, zr) + + bw.Flush() + fd.Close() + + return err +} diff --git a/zst/zst_test.go b/zst/zst_test.go new file mode 100644 index 0000000..1f82731 --- /dev/null +++ b/zst/zst_test.go @@ -0,0 +1,68 @@ +package zst + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "os" + "strings" + "testing" + + "github.com/essentialkaos/ek/v12/fsutil" + "github.com/essentialkaos/ek/v12/hash" + + . "github.com/essentialkaos/check" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func Test(t *testing.T) { TestingT(t) } + +// ////////////////////////////////////////////////////////////////////////////////// // + +type ZSTSuite struct { + Dir string +} + +var _ = Suite(&ZSTSuite{}) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func (s *ZSTSuite) SetUpSuite(c *C) { + s.Dir = c.MkDir() +} + +func (s *ZSTSuite) TestUnpack(c *C) { + err := Unpack("../.testdata/data.txt.zst", s.Dir) + + c.Assert(err, IsNil) + + c.Assert(fsutil.IsExist(s.Dir+"/data.txt"), Equals, true) + c.Assert(fsutil.GetMode(s.Dir+"/data.txt"), Equals, os.FileMode(0640)) + + c.Assert(hash.FileHash(s.Dir+"/data.txt"), Equals, "918c03a211adc19a466c9db22efa575efb6c488fd41c70e57b1ec0920f1a1d8c") +} + +func (s *ZSTSuite) TestErrors(c *C) { + err := Unpack("", "/_unknown") + c.Assert(err, NotNil) + + err = Unpack("../.testdata/data.txt.zst", "") + c.Assert(err, NotNil) + + err = Unpack("/_unknown", s.Dir) + c.Assert(err, NotNil) + + err = Read(nil, "/_unknown") + c.Assert(err, NotNil) + + err = Read(strings.NewReader(""), "") + c.Assert(err, NotNil) + + err = Read(strings.NewReader(""), "/_unknown") + c.Assert(err, NotNil) +}