Skip to content

Commit

Permalink
Add 7z support (#6)
Browse files Browse the repository at this point in the history
* Add 7z support
* add exmaple app
  • Loading branch information
davidnewhall authored Jun 5, 2021
1 parent 6474529 commit 7440b0d
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 10 deletions.
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
*~
*.zip
*.rar
*.r00
*.r01
*.7z
*.tar
*.gz
*.tgz
*.bz2
*.tbz2
/cmd/xt/xt
/xt
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ language: go
go:
- 1.16.x
install:
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin latest
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.40.1
script:
- make test
63 changes: 63 additions & 0 deletions 7z.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package xtractr

import (
"errors"
"fmt"
"io"
"os"
"strings"

"github.com/saracen/go7z"
)

// Extract7z extracts a 7zip archive. This wraps https://github.com/saracen/go7z.
func Extract7z(x *XFile) (int64, []string, error) {
sz, err := go7z.OpenReader(x.FilePath)
if err != nil {
return 0, nil, fmt.Errorf("os.Open: %w", err)
}
defer sz.Close()

return x.un7zip(sz)
}

func (x *XFile) un7zip(szreader *go7z.ReadCloser) (int64, []string, error) {
files := []string{}
size := int64(0)

for {
header, err := szreader.Next()

switch {
case errors.Is(err, io.EOF):
return size, files, nil
case err != nil:
return size, files, fmt.Errorf("szreader.Next: %w", err)
case header == nil:
return size, files, fmt.Errorf("%w: %s", ErrInvalidHead, x.FilePath)
}

wfile := x.clean(header.Name)
if !strings.HasPrefix(wfile, x.OutputDir) {
// The file being written is trying to write outside of our base path. Malicious archive?
return size, files, fmt.Errorf("%s: %w: %s (from: %s)", x.FilePath, ErrInvalidPath, wfile, header.Name)
}

// https://github.com/saracen/go7z/blob/9c09b6bd7fda869ef48ff6f693744a65f477816b/README.md#usage
if header.IsEmptyStream && !header.IsEmptyFile {
if err = os.MkdirAll(wfile, x.DirMode); err != nil {
return size, files, fmt.Errorf("os.MkdirAll: %w", err)
}

continue
}

s, err := writeFile(wfile, szreader, x.FileMode, x.DirMode)
if err != nil {
return size, files, err
}

files = append(files, wfile)
size += s
}
}
10 changes: 6 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ test: lint
go test -race -covermode=atomic ./...
# Test 32 bit OSes.
GOOS=linux GOARCH=386 go build .
GOOS=windows GOARCH=386 go build .
GOOS=freebsd GOARCH=386 go build .

lint:
GOOS=linux golangci-lint run --enable-all -D nlreturn,exhaustivestruct
GOOS=darwin golangci-lint run --enable-all -D nlreturn,exhaustivestruct
GOOS=windows golangci-lint run --enable-all -D nlreturn,exhaustivestruct
GOOS=freebsd golangci-lint run --enable-all -D nlreturn,exhaustivestruct
golangci-lint --version
GOOS=linux golangci-lint run --enable-all -D nlreturn,exhaustivestruct,interfacer,golint,scopelint,maligned
GOOS=darwin golangci-lint run --enable-all -D nlreturn,exhaustivestruct,interfacer,golint,scopelint,maligned
GOOS=windows golangci-lint run --enable-all -D nlreturn,exhaustivestruct,interfacer,golint,scopelint,maligned
GOOS=freebsd golangci-lint run --enable-all -D nlreturn,exhaustivestruct,interfacer,golint,scopelint,maligned
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# `xtractr`

Go Library for Queuing and Extracting ZIP, RAR, GZ, BZ2, TAR, TGZ, TBZ2 files.
Go Library for Queuing and Extracting ZIP, RAR, GZ, BZ2, TAR, TGZ, TBZ2, 7Z files.
Can also be used ad-hoc for direct decompression and extraction. See docs.

- [GoDoc](https://pkg.go.dev/golift.io/xtractr)

- Works on Linux, Windows, FreeBSD and macOS **without Cgo**.
- Supports 32 and 64 bit architectures.

# Examples

Expand Down Expand Up @@ -96,6 +97,7 @@ know the file type you may call the direct method instead:
- `ExtractBzip(*XFile)`
- `ExtractTarGzip(*XFile)`
- `ExtractTarBzip(*XFile)`
- `Extract7z(*XFile)`

```golang
package main
Expand Down
7 changes: 7 additions & 0 deletions cmd/xt/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# xt

This is an example app you may compile and use to extract files or whole directories.

```shell
go get -u golift.io/xtractr/cmd/xt
```
74 changes: 74 additions & 0 deletions cmd/xt/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Package main is a binary used for demonstration purposes. It works, but lacks
// the features you can program into your own application. This is just a quick
// sample provided to show one way to interface this library.
package main

import (
"flag"
"log"
"os"
"strings"
"time"

"golift.io/xtractr"
)

func main() {
pwd, _ := os.Getwd()
output := flag.String("output", pwd, "Output directory, default is current directory")

flag.Parse()
log.SetFlags(0)

inputFiles := flag.Args()
if len(inputFiles) < 1 {
log.Printf("If you pass a directory, this app will extract every archive in it.")
log.Fatalf("Usage: %s [-output <path>] <path> [paths...]", os.Args[0])
}

processInput(inputFiles, *output)
}

func processInput(paths []string, output string) {
log.Printf("==> Output Path: %s", output)

archives := getArchives(paths)
for i, f := range archives {
log.Printf("==> Extracting Archive (%d/%d): %s", i, len(archives), f)

start := time.Now()

size, files, _, err := xtractr.ExtractFile(&xtractr.XFile{
FilePath: f, // Path to archive being extracted.
OutputDir: output, // Folder to extract archive into.
FileMode: 0644, // nolint:gomnd // Write files with this mode.
DirMode: 0755, // nolint:gomnd // Write folders with this mode.
Password: "", // (RAR) Archive password. Blank for none.
})
if err != nil {
log.Printf("[ERROR] Archive: %s: %v", f, err)
continue
}

elapsed := time.Since(start).Round(time.Millisecond)
log.Printf("==> Extracted Archive %s in %v: bytes: %d, files: %d", f, elapsed, size, len(files))
log.Printf("==> Files:\n - %s", strings.Join(files, "\n - "))
}
}

func getArchives(paths []string) []string {
archives := []string{}

for _, f := range paths {
switch fileInfo, err := os.Stat(f); {
case err != nil:
log.Fatalf("[ERROR] Reading Path: %s: %s", f, err)
case fileInfo.IsDir():
archives = append(archives, xtractr.FindCompressedFiles(f)...)
default:
archives = append(archives, f)
}
}

return archives
}
4 changes: 3 additions & 1 deletion files.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func getCompressedFiles(hasrar bool, path string, fileList []os.FileInfo) []stri
files = append(files, FindCompressedFiles(filepath.Join(path, file.Name()))...)
case strings.HasSuffix(lowerName, ".zip") || strings.HasSuffix(lowerName, ".tar") ||
strings.HasSuffix(lowerName, ".tgz") || strings.HasSuffix(lowerName, ".gz") ||
strings.HasSuffix(lowerName, ".bz2"):
strings.HasSuffix(lowerName, ".bz2") || strings.HasSuffix(lowerName, ".7z"):
files = append(files, filepath.Join(path, file.Name()))
case strings.HasSuffix(lowerName, ".rar"):
// Some archives are named poorly. Only return part01 or part001, not all.
Expand Down Expand Up @@ -146,6 +146,8 @@ func ExtractFile(x *XFile) (int64, []string, []string, error) { //nolint:cyclop
switch s := strings.ToLower(x.FilePath); {
case strings.HasSuffix(s, ".rar"), strings.HasSuffix(s, ".r00"):
return ExtractRAR(x)
case strings.HasSuffix(s, ".7z"):
size, files, err = Extract7z(x)
case strings.HasSuffix(s, ".zip"):
size, files, err = ExtractZIP(x)
case strings.HasSuffix(s, ".tar.gz") || strings.HasSuffix(s, ".tgz"):
Expand Down
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@ module golift.io/xtractr

go 1.16

require github.com/nwaples/rardecode v1.1.0
require (
github.com/nwaples/rardecode v1.1.0
github.com/saracen/go7z v0.0.0-20191010121135-9c09b6bd7fda // indirect
github.com/saracen/solidblock v0.0.0-20190426153529-45df20abab6f // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/saracen/go7z v0.0.0-20191010121135-9c09b6bd7fda h1:h+YpzUB/bGVJcLqW+d5GghcCmE/A25KbzjXvWJQi/+o=
github.com/saracen/go7z v0.0.0-20191010121135-9c09b6bd7fda/go.mod h1:MSotTrCv1PwoR8QgU1JurEx+lNNbtr25I+m0zbLyAGw=
github.com/saracen/solidblock v0.0.0-20190426153529-45df20abab6f h1:1cJITU3JUI8qNS5T0BlXwANsVdyoJQHQ4hvOxbunPCw=
github.com/saracen/solidblock v0.0.0-20190426153529-45df20abab6f/go.mod h1:LyBTue+RWeyIfN3ZJ4wVxvDuvlGJtDgCLgCb6HCPgps=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
2 changes: 1 addition & 1 deletion rar.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/nwaples/rardecode"
)

// ExtractRAR extracts a rar file.. to a destination. Simple enough.
// ExtractRAR extracts a rar file. to a destination. This wraps github.com/nwaples/rardecode.
func ExtractRAR(x *XFile) (int64, []string, []string, error) {
rarReader, err := rardecode.OpenReader(x.FilePath, x.Password)
if err != nil {
Expand Down

0 comments on commit 7440b0d

Please sign in to comment.