From 9de3d78d1d9679573cc125ac53aab871ab114c3d Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 13 Oct 2020 03:43:11 -0600 Subject: [PATCH 1/2] revert incorrect read error changes --- unarr.go | 32 +++++++++++++++++++------------- unarr_test.go | 21 +++++++++++++++++++++ 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/unarr.go b/unarr.go index 4b5015e..dc81fbb 100644 --- a/unarr.go +++ b/unarr.go @@ -9,7 +9,6 @@ import "C" import ( "errors" - "fmt" "io" "io/ioutil" "os" @@ -48,7 +47,8 @@ func NewArchive(path string) (a *Archive, err error) { func NewArchiveFromMemory(b []byte) (a *Archive, err error) { a = new(Archive) - a.stream = C.ar_open_memory(unsafe.Pointer(&b[0]), C.size_t(len(b))) + n := len(b) + a.stream = C.ar_open_memory(unsafe.Pointer(&b[0]), C.size_t(n)) if a.stream == nil { err = errors.New("unarr: Open memory failed") return @@ -138,7 +138,10 @@ func (a *Archive) EntryFor(name string) error { // // Returns the actual number of bytes read. func (a *Archive) Read(b []byte) (n int, err error) { - r := bool(C.ar_entry_uncompress(a.archive, unsafe.Pointer(&b[0]), C.size_t(len(b)))) + // TODO capture size info on Entry() and escalate non-EOF read errors here + // instead of in ReadAll() + n = len(b) + r := bool(C.ar_entry_uncompress(a.archive, unsafe.Pointer(&b[0]), C.size_t(n))) n = len(b) if !r || n == 0 { @@ -191,25 +194,28 @@ func (a *Archive) ModTime() time.Time { // ReadAll reads current entry and returns data func (a *Archive) ReadAll() ([]byte, error) { - var err error - var n int - size := a.Size() + read := size + b := make([]byte, size) for size > 0 { - n, err = a.Read(b) - if err != nil { - if err != io.EOF { - return nil, err - } + // this Read comes from C, not Go + n, err := a.Read(b) + if err != nil && err != io.EOF { + return nil, err } size -= n + + if err != io.EOF { + read -= n + } } - if size > 0 { - return nil, fmt.Errorf("unarr read failure: %w", err) + if read > 0 { + err := errors.New("unarr: Error Read") + return nil, err } return b, nil diff --git a/unarr_test.go b/unarr_test.go index a9e6512..361815b 100644 --- a/unarr_test.go +++ b/unarr_test.go @@ -442,3 +442,24 @@ func TestListCorrupted(t *testing.T) { a.Close() } + +/* +func TestReadAllWrongSize(t *testing.T) { + a, err := NewArchive(filepath.Join("testdata", "test_wrongsize.zip")) + if err != nil { + t.Error(err) + } + + err = a.Entry() + if err != nil { + t.Error(err) + } + + _, err = a.ReadAll() + if err == nil { + t.Error("Wrong size archive readed") + } + + a.Close() +} +*/ From 69e968717bca115aa48a44edfc429f6032dbb14e Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 13 Oct 2020 03:44:49 -0600 Subject: [PATCH 2/2] add 7z CLI --- cmd/un7z/un7z.go | 355 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100644 cmd/un7z/un7z.go diff --git a/cmd/un7z/un7z.go b/cmd/un7z/un7z.go new file mode 100644 index 0000000..fad9459 --- /dev/null +++ b/cmd/un7z/un7z.go @@ -0,0 +1,355 @@ +package main + +import ( + "flag" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/gen2brain/go-unarr" +) + +// version info as per https://goreleaser.com/customization/build/ +var ( + version = "0.0.0-LOCAL" + commit = "g0000000" + date = "0000-00-00T00:00:00Z" +) + +func usage() { + fmt.Println("un7z xvf ") +} + +func main() { + if 2 == len(os.Args) && "version" == os.Args[1] { + fmt.Printf("un7z v%s %s (%s)\n", version, commit, date) + } + + if len(os.Args) <= 1 { + usage() + os.Exit(1) + } + + // We've got some funny business here to allow + // this to have tar-like flags, while still (mostly) + // using the go 'flag' package for parsing + + // look for first file + var fileIndex = 0 + args := os.Args[1:] + for i, arg := range args { + if "-" == arg { + fileIndex = i + break + } + stat, err := os.Stat(arg) + if nil != err { + continue + } + ext := filepath.Ext(stat.Name()) + if stat.Mode().IsRegular() && len(ext) > 0 { + fileIndex = i + break + } + } + + // expand arguments like xvf into -x -v -f + // (with a special exception for -C) + var captureNext bool + newArgs := make([]string, len(os.Args[:1])) + _ = copy(newArgs, os.Args[:1]) + for i, arg := range args { + if i >= fileIndex { + break + } + + if captureNext { + captureNext = false + newArgs = append(newArgs, arg) + continue + } + + // handle --extract + if strings.HasPrefix(arg, "--") { + newArgs = append(newArgs, arg) + continue + } + + // handle -xvf (turn into xvf) + if "-C" == arg { + newArgs = append(newArgs, arg) + captureNext = true + continue + } + + // handle -xvf (turn into xvf) + if strings.HasPrefix(arg, "-") { + arg = arg[1:] + } + + if len(arg) <= 0 { + newArgs = append(newArgs, "") + } + + // handle xvf (turn into -x, -v, -f) + // (special exception for -C) + for _, c := range arg { + newArgs = append(newArgs, "-"+string(c)) + if "C" == arg { + captureNext = true + break + } + } + } + os.Args = append(newArgs, os.Args[fileIndex+1:]...) + + // normal 'flag' parsing + var extract bool + var extractX bool + var extractE bool + var extractExtract bool + var list bool + var listT bool + var listList bool + var filename string + var verboseV bool + var verboseVerbose bool + var verbose bool + wd, err := os.Getwd() + if nil != err { + fmt.Fprintf(os.Stderr, "could not get working directory: %v\n", err) + os.Exit(1) + } + flag.StringVar(&wd, "C", wd, "change destination directory") + flag.BoolVar(&listT, "t", false, "list files in the archive") + flag.BoolVar(&listList, "list", false, "alias for -t (list)") + flag.BoolVar(&extractX, "x", false, "extract the given file") + flag.BoolVar(&extractE, "e", false, "alias for -x (extract)") + flag.BoolVar(&extractExtract, "extract", false, "alias for -x (extract)") + flag.BoolVar(&verboseV, "v", false, "verbose mode") + flag.BoolVar(&verboseVerbose, "verbose", false, "alias for -v (verbose)") + flag.StringVar(&filename, "f", "", "path to archive file") + flag.Parse() + + // if any of the verbose arguments are specified, verbose should be true + verbose = verboseV || verboseVerbose + + // if any of the list arguments are specified, list should be true + list = listT || listList + extract = extractX || extractE || extractExtract + + if len(filename) <= 0 { + filename = flag.Arg(0) + } + if flag.NArg() > 1 { + // TODO handle extracting specific files + usage() + os.Exit(1) + } + + if verbose { + fmt.Printf("@%s\n", strings.Join(os.Args, " ")) + } + + if list { + if extract { + fmt.Fprintf(os.Stderr, + "error: must list (-t) or extract (-x), not both") + os.Exit(1) + } + if err := listArchive(filename); nil != err { + fmt.Fprintf(os.Stderr, "failed to list %q: %v\n", filename, err) + os.Exit(1) + } + return + } + + // default is to extract + if err := extractArchive(filename, wd, verbose); nil != err { + fmt.Fprintf(os.Stderr, "failed to extract %q: %v\n", filename, err) + os.Exit(1) + } + return +} + +func listArchive(src string) error { + a, err := unarr.NewArchive(src) + if err != nil { + return err + } + defer a.Close() + + filenames, err := a.List() + if err != nil { + return err + } + + for _, name := range filenames { + fmt.Println(name) + } + fmt.Println("total ", len(filenames)) + return nil +} + +func extractArchive(src, dst string, verbose bool) error { + a, err := unarr.NewArchive(src) + if err != nil { + return err + } + defer a.Close() + + filenames, err := a.Extract(dst) + if nil != err { + return err + } + if verbose { + total := len(filenames) + for _, name := range filenames { + fmt.Println(name) + } + fmt.Println("total", total) + } + return nil +} + +/* +func extractArchive(src, dst string, verbose bool) error { + a, err := unarr.NewArchive(src) + if err != nil { + return err + } + defer a.Close() + + if verbose { + fmt.Printf("extracting %q to %q ...\n", src, dst) + } + + defer func() { + // print trailing newline + if verbose { + fmt.Printf("\n") + } + }() + + total := 0 + for { + if err := a.Entry(); nil != err { + if io.EOF == err { + break + } + return err + } + + name := a.Name() + sizeb := a.Size() + if verbose { + // x 0b name + // x 1000b name2 + size := byteCountIEC(int64(sizeb)) + fmt.Printf("x %s\t%s ...", leftPad2Len(size, " ", 10), name) + } + + dirname, err := secureJoin(dst, filepath.Dir(name)) + if nil != err { + return err + } + if err := os.MkdirAll(dirname, 0755); nil != err { + return err + } + + dst := filepath.Join(dirname, filepath.Base(name)) + w, err := os.Create(dst) + if nil != err { + return err + } + defer w.Close() + + if err := copyFile(a, sizeb, w); nil != err { + return err + } + total++ + + if verbose { + fmt.Printf("\n") + } + } + + if verbose { + // no trailing \n, that will be added + fmt.Printf("total %d", total) + } + + return nil +} + +func secureJoin(base, path string) (string, error) { + abs, err := filepath.Abs(base) + if nil != err { + return "", err + } + + cleanpath := filepath.Join(abs, path) + // /path/to/evil => /path/to/evil/ + // /path/to/evildoer => /path/to/evildoer/ + if !strings.HasPrefix(cleanpath+"/", abs+"/") { + return "", errors.New("insecure path") + } + + return cleanpath, nil +} + +// leftPad2Len https://github.com/DaddyOh/golang-samples/blob/master/pad.go +func leftPad2Len(s string, padStr string, overallLen int) string { + var padCountInt = 1 + ((overallLen - len(padStr)) / len(padStr)) + var retStr = strings.Repeat(padStr, padCountInt) + s + return retStr[(len(retStr) - overallLen):] +} + +// taken from https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format/ +func byteCountIEC(b int64) string { + const unit = 1024 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %ciB", + float64(b)/float64(div), "KMGTPE"[exp]) +} + +// taken from https://opensource.com/article/18/6/copying-files-go +func copyFile(r io.Reader, size int, w io.Writer) error { + fmt.Println("got here") + buf := make([]byte, 2048) + for { + n, err := r.Read(buf) + fmt.Println("read n, err:", n, err) + if nil != err { + if io.EOF != err { + return err + } + } + if n == 0 { + fmt.Println("n break") + break + } + + if size < n { + n = size + } + size -= n + if _, err := w.Write(buf[:n]); err != nil { + fmt.Println("w break") + return err + } + if io.EOF == err { + break + } + } + return nil +} +*/