pax_global_header00006660000000000000000000000064137142447560014527gustar00rootroot0000000000000052 comment=e14379fbf1b129d2a31ff6fa96d54c4936ddf5e1 archive-0.8.0/000077500000000000000000000000001371424475600131555ustar00rootroot00000000000000archive-0.8.0/.cirrus.yml000066400000000000000000000002701371424475600152640ustar00rootroot00000000000000container: image: golang:latest test_task: env: CIRRUS_WORKING_DIR: /go/src/github.com/$CIRRUS_REPO_FULL_NAME get_script: go get -t -v ./... test_script: go test -v ./... archive-0.8.0/.gitignore000066400000000000000000000030601371424475600151440ustar00rootroot00000000000000# Created by .ignore support plugin (hsz.mobi) ### Go template # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out ### JetBrains template # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff .idea/**/workspace.xml .idea/**/tasks.xml .idea/**/usage.statistics.xml .idea/**/dictionaries .idea/**/shelf # Sensitive or high-churn files .idea/**/dataSources/ .idea/**/dataSources.ids .idea/**/dataSources.local.xml .idea/**/sqlDataSources.xml .idea/**/dynamic.xml .idea/**/uiDesigner.xml .idea/**/dbnavigator.xml # Gradle .idea/**/gradle.xml .idea/**/libraries # Gradle and Maven with auto-import # When using Gradle or Maven with auto-import, you should exclude module files, # since they will be recreated, and may cause churn. Uncomment if using # auto-import. # .idea/modules.xml # .idea/*.iml # .idea/modules # CMake cmake-build-*/ # Mongo Explorer plugin .idea/**/mongoSettings.xml # File-based project format *.iws # IntelliJ out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Cursive Clojure plugin .idea/replstate.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties # Editor-based Rest Client .idea/httpRequests .ideaarchive-0.8.0/.travis.yml000066400000000000000000000006221371424475600152660ustar00rootroot00000000000000language: go go: - "1.12.x" - master matrix: allow_failures: - go: master fast_finish: true branches: only: - develop - master env: - GO111MODULE=on CGO_CFLAGS=-I/usr/local/include CGO_LDFLAGS=-L/usr/local/lib before_install: - sudo apt-get update -qq - sudo apt-get install -qq libgpgme11 libgpgme11-dev libassuan-dev libassuan0 libgpg-error0 gnupg2 script: - make - make test archive-0.8.0/CONTRIBUTING.md000066400000000000000000000044631371424475600154150ustar00rootroot00000000000000# Contributing Guidelines ## Code of Conduct This project is intended to be a safe, welcoming space for collaboration. All contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. Thank you for being kind to each other! ## Contributions welcome! **Before spending lots of time on something, ask for feedback on your idea first!** Please search [issues](../../issues/) and [pull requests](../../pulls/) before adding something new! This helps avoid duplicating efforts and conversations. This project welcomes any kind of contribution! Here are a few suggestions: - **Ideas**: participate in an issue thread or start your own to have your voice heard. - **Writing**: contribute your expertise in an area by helping expand the included content. - **Copy editing**: fix typos, clarify language, and generally improve the quality of the content. - **Formatting**: help keep content easy to read with consistent formatting. - **Code**: help maintain and improve the project codebase. ## Code Style This repository uses `gofmt` to maintain code style and consistency, and to avoid style arguments. ## Project Governance **This is an [OPEN Open Source Project](http://openopensource.org/).** Individuals making significant and valuable contributions are given commit access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. ### Rules There are a few basic ground rules for collaborators: 1. **No `--force` pushes** or modifying the Git history in any way. 1. **Non-master branches** ought to be used for ongoing work. 1. **External API changes and significant modifications** ought to be subject to an **internal pull request** to solicit feedback from other contributors. 1. Internal pull requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor. 1. Contributors should attempt to adhere to the prevailing code style. ### Releases Declaring formal releases remains the prerogative of the project maintainer. ### Changes to this arrangement This is an experiment and feedback is welcome! This document may also be subject to pull requests or changes by contributors where you believe you have something valuable to add or change. archive-0.8.0/LICENSE.md000066400000000000000000000027421371424475600145660ustar00rootroot00000000000000Copyright (c) 2018, Ollivier Robert All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the author. archive-0.8.0/Makefile000066400000000000000000000006001371424475600146110ustar00rootroot00000000000000# Main Makefile for archive # # Copyright 2018 © by Ollivier Robert # GO= go GOBIN= ${GOPATH}/bin SRCS= archive.go utils.go OPTS= -ldflags="-s -w" -v all: build build: ${SRCS} ${GO} build ${OPTS} . test: ${GO} test -v . install: ${BIN} ${GO} install ${OPTS} . clean: ${GO} clean -v # use github.com/mgechev/revive lint: revive push: git push --all git push --tags archive-0.8.0/README.md000066400000000000000000000072401371424475600144370ustar00rootroot00000000000000# README.md # Status [![GitHub release](https://img.shields.io/github/release/keltia/archive.svg)](https://github.com/keltia/archive/releases) [![GitHub issues](https://img.shields.io/github/issues/keltia/archive.svg)](https://github.com/keltia/archive/issues) [![Go Version](https://img.shields.io/badge/go-1.10-blue.svg)](https://golang.org/dl/) [![Build Status](https://travis-ci.org/keltia/archive.svg?branch=master)](https://travis-ci.org/keltia/archive) [![GoDoc](http://godoc.org/github.com/keltia/archive?status.svg)](http://godoc.org/github.com/keltia/archive) [![SemVer](http://img.shields.io/SemVer/2.0.0.png)](https://semver.org/spec/v2.0.0.html) [![License](https://img.shields.io/pypi/l/Django.svg)](https://opensource.org/licenses/BSD-2-Clause) [![Go Report Card](https://goreportcard.com/badge/github.com/keltia/archive)](https://goreportcard.com/report/github.com/keltia/archive) # Installation As with many Go utilities, a simple go get github.com/keltia/archive is enough to fetch, build and install. Most of the time you will not need to do it explicitely, other applications will fetch it automatically as a dependency. # Dependencies * Go >= 1.10 Only standard Go modules are used. I use Semantic Versioning for all my modules. # Usage The module currently supports the following "archives": - plain text - gzip files (one file per stream, only first stream) - zip files - GPG files (either .asc or .gpg) - Tar files - Zstd files (one file per stream, only first stream) SYNOPSIS ``` go a, err := archive.New("foo.txt") content, err := a.Extract(".txt") a, err := archive.New("bar.zip") content, err := a.Extract(".txt") // extract the first .txt file a, err := archive.New("baz.txt.gz") content, err := a.Extract(".txt") // extracts baz.txt // Gpg is a bit special a, err := archive.New("xyz.zip.asc") unencrypted, err := a.Extract(".zip") // decrypt into variable // if you want to use archive.New() there too // you need to save into a temp file. var buf bytes.Buffer fmt.Fprintf(&buf, "%s", unencrypted) fh, err := os.Create("xyz.zip") _, err := io.Copy(fh, &buf) fh.Close() a1, err := archive.New("xyz.zip") ... You can have more verbose output and debug by using these functions: archive.SetVerbose() archive.SetDebug() // implies verbose ... archive.Reset() // both flags are cleared ``` # Limitations I wrote this both to simplify and my own code in `dmarc-cat` (that's also how `sandbox` got created) and to play with interfaces. It is currently only trying to extract one file at a time matching the extension provided. It will probably evolve into a more general code later. # Tests I'm trying to get to 100% coverage but some error cases are more difficult to create. ## License This is released under the BSD 2-Clause license. See `LICENSE.md`. # Contributing This project is an open Open Source project, please read `CONTRIBUTING.md`. # Feedback We welcome pull requests, bug fixes and issue reports. Before proposing a large change, first please discuss your change by raising an issue. I use Git Flow for this package so please use something similar or the usual github workflow. 1. Fork it ( https://github.com/keltia/archive/fork ) 2. Checkout the develop branch (`git checkout develop`) 3. Create your feature branch (`git checkout -b my-new-feature`) 4. Commit your changes (`git commit -am 'Add some feature'`) 5. Push to the branch (`git push origin my-new-feature`) 6. Create a new Pull Request archive-0.8.0/archive.go000066400000000000000000000216721371424475600151350ustar00rootroot00000000000000package archive import ( "archive/tar" "archive/zip" "bytes" "compress/gzip" "fmt" "io" "io/ioutil" "os" "path" "path/filepath" "strings" "github.com/klauspost/compress/zstd" "github.com/pkg/errors" "github.com/proglottis/gpgme" ) // Version number (SemVer) const ( myVersion = "0.8.0" ) var ( fVerbose = false fDebug = false ) // ------------------- Interfaces // Extracter is the main interface we have type Extracter interface { Extract(t string) ([]byte, error) } // ExtractCloser is the same with Close() type ExtractCloser interface { Extracter Close() error Type() int } const ( // ArchivePlain starts the different types ArchivePlain = 1 << iota // ArchiveGzip is for gzip archives ArchiveGzip // ArchiveZip is for zip archives ArchiveZip // ArchiveTar describes the tar ones ArchiveTar // ArchiveGpg is for openpgp archives ArchiveGpg // ArchiveZstd is for Zstd archives ArchiveZstd ) // ------------------- Plain // Plain is for plain text type Plain struct { Name string r io.Reader } func NewPlainfile(fn string) (*Plain, error) { fh, err := os.Open(fn) if err != nil { return nil, errors.Wrap(err, "NewPlainfile") } return &Plain{Name: fn, r: fh}, nil } // Extract returns the content of the file func (a Plain) Extract(t string) ([]byte, error) { if a.Name == "-" { var b bytes.Buffer _, err := io.Copy(&b, a.r) if err != nil { return nil, errors.Wrap(err, "Extract/Copy") } return b.Bytes(), nil } ext := filepath.Ext(a.Name) if ext == t || t == "" { return ioutil.ReadFile(a.Name) } return []byte{}, fmt.Errorf("wrong file type") } // Close is a no-op func (a Plain) Close() error { return nil } // Type returns the archive type obviously. func (a Plain) Type() int { return ArchivePlain } // ------------------- Zip // Zip is for pkzip/infozip files type Zip struct { fn string zfh *zip.ReadCloser } // NewZipfile open the zip file func NewZipfile(fn string) (*Zip, error) { zfh, err := zip.OpenReader(fn) if err != nil { return &Zip{}, errors.Wrap(err, "archive/zip") } return &Zip{fn: fn, zfh: zfh}, nil } // Extract returns the content of the file func (a Zip) Extract(t string) ([]byte, error) { verbose("exploring %s", a.fn) ft := strings.ToLower(t) for _, fn := range a.zfh.File { verbose("looking at %s", fn.Name) if path.Ext(fn.Name) == ft { file, err := fn.Open() if err != nil { return []byte{}, errors.Wrapf(err, "no file matching type %s", t) } return ioutil.ReadAll(file) } } return []byte{}, fmt.Errorf("no file matching type %s", t) } // Close does something here func (a Zip) Close() error { return a.zfh.Close() } // Type returns the archive type obviously. func (a Zip) Type() int { return ArchiveZip } // ------------------- Tar // Tar is a tar archive :) type Tar struct { fn string tfh *tar.Reader } func NewTarfile(fn string) (*Tar, error) { var fh io.Reader if fn == "-" { tfh := tar.NewReader(os.Stdin) return &Tar{fn: fn, tfh: tfh}, nil } fh, err := os.Open(fn) if err != nil { return &Tar{}, errors.Wrap(err, "NewTarfile") } return &Tar{fn: fn, tfh: tar.NewReader(fh)}, nil } func (a Tar) Extract(t string) ([]byte, error) { for { hdr, err := a.tfh.Next() if err == io.EOF { break // End of archive } if err != nil { return []byte{}, errors.Wrap(err, "read") } debug("found %s", hdr.Name) var buf bytes.Buffer if strings.HasSuffix(hdr.Name, t) { n, err := io.Copy(&buf, a.tfh) if err != nil { return []byte{}, errors.Wrap(err, "copy") } debug("read %d bytes", n) return buf.Bytes(), nil } } return nil, errors.New("not found") } // Close does something here func (a Tar) Close() error { return nil } // Type returns the archive type obviously. func (a *Tar) Type() int { return ArchiveTar } // ------------------- Gzip // Gzip is a gzip-compressed file type Gzip struct { fn string unc string gfh io.Reader } // NewGzipfile stores the uncompressed file name func NewGzipfile(fn string) (*Gzip, error) { base := filepath.Base(fn) pc := strings.Split(base, ".") unc := strings.Join(pc[0:len(pc)-1], ".") gfh, err := os.Open(fn) if err != nil { return &Gzip{}, errors.Wrap(err, "NewGzipFile") } return &Gzip{fn: fn, unc: unc, gfh: gfh}, nil } // Extract returns the content of the file func (a Gzip) Extract(t string) ([]byte, error) { zfh, err := gzip.NewReader(a.gfh) if err != nil { return []byte{}, errors.Wrap(err, "gunzip") } content, err := ioutil.ReadAll(zfh) defer zfh.Close() return content, err } // Close is a no-op func (a Gzip) Close() error { return nil } // Type returns the archive type obviously. func (a Gzip) Type() int { return ArchiveGzip } // ------------------- Zstd // Zstd is a gzip-compressed file type Zstd struct { fn string unc string gfh io.Reader } // NewZstdfile stores the uncompressed file name func NewZstdfile(fn string) (*Zstd, error) { base := filepath.Base(fn) pc := strings.Split(base, ".") unc := strings.Join(pc[0:len(pc)-1], ".") gfh, err := os.Open(fn) if err != nil { return &Zstd{}, errors.Wrap(err, "NewZstdFile") } return &Zstd{fn: fn, unc: unc, gfh: gfh}, nil } // Extract returns the content of the file func (a Zstd) Extract(t string) ([]byte, error) { zfh, err := zstd.NewReader(a.gfh) if err != nil { return []byte{}, errors.Wrap(err, "zstd uncompress") } content, err := ioutil.ReadAll(zfh) defer zfh.Close() return content, err } // Close is a no-op func (a Zstd) Close() error { return nil } // Type returns the archive type obviously. func (a Zstd) Type() int { return ArchiveZstd } // ------------------- GPG // Decrypter is the gpgme interface type Decrypter interface { Decrypt(r io.Reader) (*gpgme.Data, error) } // Gpgme is for real gpgme stuff type Gpgme struct{} // Decrypt does the obvious func (Gpgme) Decrypt(r io.Reader) (*gpgme.Data, error) { return gpgme.Decrypt(r) } // NullGPG is for testing type NullGPG struct{} // Decrypt does the obvious func (NullGPG) Decrypt(r io.Reader) (*gpgme.Data, error) { b, _ := ioutil.ReadAll(r) return gpgme.NewDataBytes(b) } // Gpg is how we use/mock decryption stuff type Gpg struct { fn string unc string gpg Decrypter } // NewGpgfile initializes the struct and check filename func NewGpgfile(fn string) (*Gpg, error) { // Strip .gpg or .asc from filename base := filepath.Base(fn) pc := strings.Split(base, ".") unc := strings.Join(pc[0:len(pc)-1], ".") return &Gpg{fn: fn, unc: unc, gpg: Gpgme{}}, nil } // Extract binds it to the Archiver interface func (a Gpg) Extract(t string) ([]byte, error) { // Carefully open the box fh, err := os.Open(a.fn) if err != nil { return []byte{}, errors.Wrap(err, "extract/open") } defer fh.Close() var buf bytes.Buffer // Do the decryption thing plain, err := a.gpg.Decrypt(fh) if err != nil { return []byte{}, errors.Wrap(err, "extract/decrypt") } defer plain.Close() // Save "plain" text verbose("Decrypting %s", a.fn) _, err = io.Copy(&buf, plain) if err != nil { return []byte{}, errors.Wrap(err, "extract/copy") } return buf.Bytes(), err } // Close is part of the Closer interface func (a Gpg) Close() error { return nil } // Type returns the archive type obviously. func (a *Gpg) Type() int { return ArchiveGpg } // ------------------- New/NewFromReader // New is the main creator func New(fn string) (ExtractCloser, error) { if fn == "" { return &Plain{}, fmt.Errorf("null string") } _, err := os.Stat(fn) if err != nil { return nil, errors.Wrap(err, "unknown file") } ext := filepath.Ext(fn) switch ext { case ".zip": return NewZipfile(fn) case ".gz": return NewGzipfile(fn) case ".zst": return NewZstdfile(fn) case ".asc": fallthrough case ".gpg": return NewGpgfile(fn) case ".tar": return NewTarfile(fn) } return NewPlainfile(fn) } // NewFromReader uses an io.Reader instead of a file func NewFromReader(r io.Reader, t int) (ExtractCloser, error) { if r == nil { return nil, fmt.Errorf("nil reader") } fn := "-" switch t { case ArchivePlain: return &Plain{Name: fn, r: r}, nil case ArchiveGzip: return &Gzip{fn: fn, unc: fn, gfh: r}, nil case ArchiveZstd: return &Zstd{fn: fn, unc: fn, gfh: r}, nil case ArchiveZip: return nil, fmt.Errorf("not supported") case ArchiveGpg: return NewGpgfile(fn) case ArchiveTar: return NewTarfile(fn) } return &Plain{Name: fn, r: r}, fmt.Errorf("unknown type") } // Ext2Type converts from string to archive type (int) func Ext2Type(typ string) int { switch typ { case ".zip": return ArchiveZip case ".gz": return ArchiveGzip case ".zst": return ArchiveZstd case ".asc": fallthrough case ".gpg": return ArchiveGpg case ".tar": return ArchiveTar default: return ArchivePlain } } // ------------------- Misc. // SetVerbose sets the mode func SetVerbose() { fVerbose = true } // SetDebug sets the mode too func SetDebug() { fDebug = true fVerbose = true } // Reset is for the two flags func Reset() { fDebug = false fVerbose = false } // Version reports it func Version() string { return myVersion } archive-0.8.0/archive_test.go000066400000000000000000000430511371424475600161670ustar00rootroot00000000000000package archive import ( "bytes" "fmt" "io" "io/ioutil" "os" "strings" "testing" "github.com/proglottis/gpgme" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // Misc. func TestVersion(t *testing.T) { require.Equal(t, myVersion, Version()) } func TestSetVerbose(t *testing.T) { assert.False(t, fVerbose) SetVerbose() assert.True(t, fVerbose) fVerbose = false } func TestSetDebug(t *testing.T) { assert.False(t, fDebug) SetDebug() assert.True(t, fDebug) assert.True(t, fVerbose) fDebug = false fVerbose = false } func TestReset(t *testing.T) { fVerbose = true Reset() require.False(t, fVerbose) } // NewArchive func TestNewArchive_Empty(t *testing.T) { a, err := New("") require.Error(t, err) assert.Empty(t, a) assert.IsType(t, (*Plain)(nil), a) } func TestNewArchive_None(t *testing.T) { _, err := New("foo.txt") require.Error(t, err) } func TestNewArchive_Plain(t *testing.T) { a, err := New("testdata/empty.txt") require.NoError(t, err) assert.NotEmpty(t, a) assert.IsType(t, (*Plain)(nil), a) } func TestNewArchive_Zip(t *testing.T) { a, err := New("testdata/notempty.zip") require.NoError(t, err) assert.NotEmpty(t, a) assert.IsType(t, (*Zip)(nil), a) } func TestNewArchive_ZipNone(t *testing.T) { a, err := New("testdata/foo.zip") require.Error(t, err) assert.Empty(t, a) } func TestNewArchive_Gzip(t *testing.T) { a, err := New("testdata/notempty.txt.gz") require.NoError(t, err) assert.NotEmpty(t, a) assert.IsType(t, (*Gzip)(nil), a) } func TestNewArchive_Gzip_Bad(t *testing.T) { a, err := New("/nonexistent") require.Error(t, err) assert.Empty(t, a) } func TestNewArchive_Zstd(t *testing.T) { a, err := New("testdata/notempty.txt.zst") require.NoError(t, err) assert.NotEmpty(t, a) assert.IsType(t, (*Zstd)(nil), a) } func TestNewArchive_Zstd_Bad(t *testing.T) { a, err := New("/nonexistent") require.Error(t, err) assert.Empty(t, a) } func TestNewArchive_Gpg(t *testing.T) { a, err := New("testdata/notempty.asc") require.NoError(t, err) assert.NotEmpty(t, a) assert.IsType(t, (*Gpg)(nil), a) } func TestNewArchive_Tar(t *testing.T) { a, err := New("testdata/notempty.tar") require.NoError(t, err) assert.NotEmpty(t, a) assert.IsType(t, (*Tar)(nil), a) } // Plain func TestNewPlainfile(t *testing.T) { fn := "testdata/notempty.txt" a, err := NewPlainfile(fn) require.NoError(t, err) require.NotNil(t, a) require.IsType(t, (*Plain)(nil), a) } func TestNewPlainfile2(t *testing.T) { fn := "/nonexistent" a, err := NewPlainfile(fn) require.Error(t, err) require.Nil(t, a) } func TestPlain_Extract(t *testing.T) { fn := "testdata/notempty.txt" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) txt, err := a.Extract("") assert.NoError(t, err) assert.Equal(t, "this is a file\n", string(txt)) } func TestPlain_Extract2(t *testing.T) { fn := "testdata/notempty.txt" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) txt, err := a.Extract(".doc") assert.Error(t, err) assert.Empty(t, txt) } func TestPlain_Extract3(t *testing.T) { fn := "testdata/notempty.txt" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) require.IsType(t, (*Plain)(nil), a) txt, err := a.Extract("-") assert.Error(t, err) assert.Empty(t, txt) } func TestPlain_Extract4(t *testing.T) { fn := "testdata/notempty.txt" tfn, err := ioutil.ReadFile(fn) require.NoError(t, err) fh, err := os.Open(fn) require.NoError(t, err) a, err := NewFromReader(fh, ArchivePlain) require.NoError(t, err) require.NotNil(t, a) require.IsType(t, (*Plain)(nil), a) txt, err := a.Extract("-") assert.NoError(t, err) assert.NotEmpty(t, txt) require.Equal(t, tfn, txt) } func TestPlain_Close(t *testing.T) { fn := "testdata/notempty.txt" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) require.NoError(t, a.Close()) } // Zip func TestNewZipfile_Garbage(t *testing.T) { fn := "testdata/garbage.zip" a, err := NewZipfile(fn) require.Error(t, err) assert.Empty(t, a) } func TestZip_Extract(t *testing.T) { fn := "testdata/notempty.zip" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) defer a.Close() rh, err := ioutil.ReadFile("testdata/notempty.txt") require.NoError(t, err) require.NotEmpty(t, rh) txt, err := a.Extract(".txt") assert.NoError(t, err) assert.Equal(t, string(rh), string(txt)) } func TestZip_Extract2(t *testing.T) { fn := "testdata/notempty.zip" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) txt, err := a.Extract(".xml") assert.Error(t, err) assert.Empty(t, txt) } func TestZip_Close(t *testing.T) { fn := "testdata/notempty.zip" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) require.NoError(t, a.Close()) } func TestZip_Type(t *testing.T) { fn := "testdata/notempty.zip" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) require.Equal(t, ArchiveZip, a.Type()) } // Gzip func TestNewGzipfile(t *testing.T) { a, err := NewGzipfile("testdata/notempty.txt.gz") require.NoError(t, err) assert.NotEmpty(t, a) assert.IsType(t, (*Gzip)(nil), a) } func TestGzipfile_Bad(t *testing.T) { a, err := NewGzipfile("/nonexistent") require.Error(t, err) assert.Empty(t, a) } func TestGzip_Extract(t *testing.T) { fn := "testdata/notempty.txt.gz" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) defer a.Close() rh, err := ioutil.ReadFile("testdata/notempty.txt") require.NoError(t, err) require.NotEmpty(t, rh) txt, err := a.Extract(".txt") t.Logf("err=%v", err) assert.NoError(t, err) assert.Equal(t, string(rh), string(txt)) } func TestGzip_Extract2(t *testing.T) { fn := "testdata/notempty.txt.gz" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) txt, err := a.Extract(".xml") assert.NoError(t, err) assert.NotEmpty(t, txt) } func TestGzip_Extract3(t *testing.T) { fn := "/nonexistent" a, err := New(fn) require.Error(t, err) require.Empty(t, a) } func TestGzip_Extract_Nil(t *testing.T) { fh, err := os.Open("/dev/null") require.NoError(t, err) require.NotNil(t, fh) a := &Gzip{gfh: fh} _, err = a.Extract(".txt") assert.Error(t, err) } func TestGzip_Extract_FromReader(t *testing.T) { fn := "testdata/notempty.txt.gz" fh, err := os.Open(fn) require.NoError(t, err) require.NotNil(t, fh) bn, err := ioutil.ReadFile("testdata/notempty.txt") require.NoError(t, err) require.NotEmpty(t, bn) a, err := NewFromReader(fh, ArchiveGzip) require.NoError(t, err) require.NotEmpty(t, a) content, err := a.Extract("") assert.NoError(t, err) assert.EqualValues(t, bn, content) } func TestGzip_Extract_FromReaderYes(t *testing.T) { fn := "testdata/notempty.txt.gz" fh, err := os.Open(fn) require.NoError(t, err) require.NotNil(t, fh) defer fh.Close() bn, err := ioutil.ReadFile("testdata/notempty.txt") require.NoError(t, err) require.NotEmpty(t, bn) a, err := NewFromReader(fh, ArchiveGzip) require.NoError(t, err) require.NotEmpty(t, a) content, err := a.Extract(".txt") assert.NoError(t, err) assert.EqualValues(t, bn, content) } func TestGzip_Extract_FromReader_Nope(t *testing.T) { fn := "testdata/notempty.txt.gz" fh, err := os.Open(fn) require.NoError(t, err) require.NotNil(t, fh) defer fh.Close() bn, err := ioutil.ReadFile("testdata/notempty.txt") require.NoError(t, err) require.NotEmpty(t, bn) a, err := NewFromReader(fh, ArchiveGzip) require.NoError(t, err) require.NotEmpty(t, a) content, err := a.Extract(".arc") assert.NoError(t, err) assert.EqualValues(t, bn, content) } func TestGzip_Close(t *testing.T) { fn := "testdata/notempty.txt.gz" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) require.NoError(t, a.Close()) } // Zstd func TestNewZstdfile(t *testing.T) { a, err := NewZstdfile("testdata/notempty.txt.zst") require.NoError(t, err) assert.NotEmpty(t, a) assert.IsType(t, (*Zstd)(nil), a) } func TestZstdfile_Bad(t *testing.T) { a, err := NewZstdfile("/nonexistent") require.Error(t, err) assert.Empty(t, a) } func TestZstd_Extract(t *testing.T) { fn := "testdata/notempty.txt.zst" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) defer a.Close() rh, err := ioutil.ReadFile("testdata/notempty.txt") require.NoError(t, err) require.NotEmpty(t, rh) txt, err := a.Extract(".txt") t.Logf("err=%v", err) assert.NoError(t, err) assert.Equal(t, string(rh), string(txt)) } func TestZstd_Extract2(t *testing.T) { fn := "testdata/notempty.txt.zst" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) txt, err := a.Extract(".xml") assert.NoError(t, err) assert.NotEmpty(t, txt) } func TestZstd_Extract3(t *testing.T) { fn := "/nonexistent" a, err := New(fn) require.Error(t, err) require.Empty(t, a) } func TestZstd_Extract_Nil(t *testing.T) { fh, err := os.Open("/dev/null") require.NoError(t, err) require.NotNil(t, fh) a := &Zstd{gfh: fh} _, err = a.Extract(".txt") assert.NoError(t, err) } func TestZstd_Extract_FromReader(t *testing.T) { fn := "testdata/notempty.txt.zst" fh, err := os.Open(fn) require.NoError(t, err) require.NotNil(t, fh) bn, err := ioutil.ReadFile("testdata/notempty.txt") require.NoError(t, err) require.NotEmpty(t, bn) a, err := NewFromReader(fh, ArchiveZstd) require.NoError(t, err) require.NotEmpty(t, a) content, err := a.Extract("") assert.NoError(t, err) assert.EqualValues(t, bn, content) } func TestZstd_Extract_FromReaderYes(t *testing.T) { fn := "testdata/notempty.txt.zst" fh, err := os.Open(fn) require.NoError(t, err) require.NotNil(t, fh) defer fh.Close() bn, err := ioutil.ReadFile("testdata/notempty.txt") require.NoError(t, err) require.NotEmpty(t, bn) a, err := NewFromReader(fh, ArchiveZstd) require.NoError(t, err) require.NotEmpty(t, a) content, err := a.Extract(".txt") assert.NoError(t, err) assert.EqualValues(t, bn, content) } func TestZstd_Extract_FromReader_Nope(t *testing.T) { fn := "testdata/notempty.txt.zst" fh, err := os.Open(fn) require.NoError(t, err) require.NotNil(t, fh) defer fh.Close() bn, err := ioutil.ReadFile("testdata/notempty.txt") require.NoError(t, err) require.NotEmpty(t, bn) a, err := NewFromReader(fh, ArchiveZstd) require.NoError(t, err) require.NotEmpty(t, a) content, err := a.Extract(".arc") assert.NoError(t, err) assert.EqualValues(t, bn, content) } func TestZstd_Close(t *testing.T) { fn := "testdata/notempty.txt.zst" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) require.NoError(t, a.Close()) } // gpg type NullGPGError struct{} var ErrFakeGPGError = fmt.Errorf("fake error") func (NullGPGError) Decrypt(r io.Reader) (*gpgme.Data, error) { b, _ := ioutil.ReadAll(r) gb, _ := gpgme.NewDataBytes(b) return gb, ErrFakeGPGError } func TestNullGPG_Decrypt(t *testing.T) { fVerbose = true gpg := NullGPG{} file := "testdata/notempty.asc" fh, err := os.Open(file) require.NoError(t, err) require.NotNil(t, fh) plain, err := gpg.Decrypt(fh) assert.NoError(t, err) assert.NotEmpty(t, plain) var buf strings.Builder _, err = io.Copy(&buf, plain) assert.NoError(t, err) assert.Equal(t, len("this is a file\n"), len(buf.String())) assert.Equal(t, "this is a file\n", buf.String()) } func TestNullGPGError_Decrypt(t *testing.T) { fVerbose = true gpg := NullGPGError{} file := "testdata/notempty.asc" fh, err := os.Open(file) require.NoError(t, err) require.NotNil(t, fh) plain, err := gpg.Decrypt(fh) assert.Error(t, err) assert.Equal(t, ErrFakeGPGError, err) var buf strings.Builder _, err = io.Copy(&buf, plain) assert.NoError(t, err) assert.Equal(t, len("this is a file\n"), len(buf.String())) assert.Equal(t, "this is a file\n", buf.String()) } func TestGpg_Decrypt(t *testing.T) { fVerbose = true gpg := Gpgme{} file := "testdata/notempty.asc" fh, err := os.Open(file) require.NoError(t, err) require.NotNil(t, fh) plain, err := gpg.Decrypt(fh) assert.Error(t, err) assert.NotEmpty(t, plain) } func TestGpg_Extract(t *testing.T) { fn := "testdata/notempty.asc" a := &Gpg{fn: fn, unc: "notempty.txt", gpg: NullGPG{}} defer a.Close() rh, err := ioutil.ReadFile("testdata/notempty.txt") require.NoError(t, err) require.NotEmpty(t, rh) txt, err := a.Extract(".txt") assert.NoError(t, err) assert.Equal(t, string(rh), string(txt)) } func TestGpg_Extract2(t *testing.T) { fn := "testdata/notempty.asc" a := &Gpg{fn: fn, unc: "notempty.txt", gpg: Gpgme{}} defer a.Close() _, err := a.Extract(".txt") assert.Error(t, err) } func TestGpg_Extract3(t *testing.T) { fn := "testdata/notempty.nowhere" a := &Gpg{fn: fn, unc: "notempty.txt", gpg: Gpgme{}} defer a.Close() _, err := a.Extract(".txt") assert.Error(t, err) } func TestGpg_Extract4(t *testing.T) { fn := "testdata/notempty.zip.asc" a := &Gpg{fn: fn, unc: "notempty.zip", gpg: NullGPG{}} defer a.Close() rh, err := ioutil.ReadFile("testdata/notempty.zip") require.NoError(t, err) require.NotEmpty(t, rh) zip, err := a.Extract(".zip") assert.NoError(t, err) assert.Equal(t, string(rh), string(zip)) } func TestGpg_Extract4_Debug(t *testing.T) { fn := "testdata/notempty.zip.asc" fDebug = true a := &Gpg{fn: fn, unc: "notempty.zip", gpg: NullGPG{}} defer a.Close() rh, err := ioutil.ReadFile("testdata/notempty.zip") require.NoError(t, err) require.NotEmpty(t, rh) zip, err := a.Extract(".zip") assert.NoError(t, err) assert.Equal(t, string(rh), string(zip)) fDebug = false } func TestGpg_Close(t *testing.T) { fn := "testdata/notempty.asc" a := &Gpg{fn: fn, unc: "notempty.txt", gpg: NullGPG{}} require.NoError(t, a.Close()) } // Tar func TestNewTarfile(t *testing.T) { fn := "/nonexistent" a, err := NewTarfile(fn) require.Error(t, err) assert.Empty(t, a) } func TestTar_Extract(t *testing.T) { fn := "testdata/notempty.tar" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) defer a.Close() rh, err := ioutil.ReadFile("testdata/notempty.txt") require.NoError(t, err) require.NotEmpty(t, rh) txt, err := a.Extract("notempty.txt") assert.NoError(t, err) assert.Equal(t, string(rh), string(txt)) } func TestTar_Extract_Debug(t *testing.T) { fn := "testdata/notempty.tar" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) defer a.Close() SetDebug() rh, err := ioutil.ReadFile("testdata/notempty.txt") require.NoError(t, err) require.NotEmpty(t, rh) txt, err := a.Extract("notempty.txt") assert.NoError(t, err) assert.Equal(t, string(rh), string(txt)) Reset() } func TestTar_Extract_Empty(t *testing.T) { fn := "testdata/empty.tar" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) defer a.Close() txt, err := a.Extract(".txt") assert.Error(t, err) assert.Empty(t, txt) } func TestTar_Extract2(t *testing.T) { fn := "testdata/notempty.tar" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) txt, err := a.Extract(".xml") assert.Error(t, err) assert.Empty(t, txt) } func TestTar_Close(t *testing.T) { fn := "testdata/notempty.tar" a, err := New(fn) require.NoError(t, err) require.NotNil(t, a) require.NoError(t, a.Close()) } // FromReader func TestNewFromReader_Nil(t *testing.T) { a, err := NewFromReader(nil, ArchivePlain) assert.Error(t, err) assert.Empty(t, a) } func TestNewFromReader_Plain(t *testing.T) { cipher, err := ioutil.ReadFile("testdata/notempty.txt") assert.NoError(t, err) assert.NotEmpty(t, cipher) var buf bytes.Buffer n, err := buf.Write(cipher) assert.NoError(t, err) assert.Equal(t, len(cipher), n) a, err := NewFromReader(&buf, ArchivePlain) assert.NoError(t, err) assert.NotEmpty(t, a) require.Equal(t, ArchivePlain, a.Type()) } func TestNewFromReader_Gpg(t *testing.T) { cipher, err := ioutil.ReadFile("testdata/notempty.asc") assert.NoError(t, err) assert.NotEmpty(t, cipher) var buf bytes.Buffer n, err := buf.Write(cipher) assert.NoError(t, err) assert.Equal(t, len(cipher), n) a, err := NewFromReader(&buf, ArchiveGpg) assert.NoError(t, err) assert.NotEmpty(t, a) require.Equal(t, ArchiveGpg, a.Type()) } func TestNewFromReader_Zip(t *testing.T) { file, err := ioutil.ReadFile("testdata/notempty.zip") assert.NoError(t, err) assert.NotEmpty(t, file) var buf bytes.Buffer n, err := buf.Write(file) assert.NoError(t, err) assert.Equal(t, len(file), n) a, err := NewFromReader(&buf, ArchiveZip) assert.Error(t, err) assert.Empty(t, a) } func TestNewFromReader_Gzip(t *testing.T) { file, err := ioutil.ReadFile("testdata/notempty.txt.gz") assert.NoError(t, err) assert.NotEmpty(t, file) var buf bytes.Buffer n, err := buf.Write(file) assert.NoError(t, err) assert.Equal(t, len(file), n) a, err := NewFromReader(&buf, ArchiveGzip) assert.NoError(t, err) assert.NotEmpty(t, a) require.Equal(t, ArchiveGzip, a.Type()) } func TestNewFromReader_Tar(t *testing.T) { file, err := ioutil.ReadFile("testdata/notempty.tar") assert.NoError(t, err) assert.NotEmpty(t, file) var buf bytes.Buffer n, err := buf.Write(file) assert.NoError(t, err) assert.Equal(t, len(file), n) a, err := NewFromReader(&buf, ArchiveTar) assert.NoError(t, err) assert.NotEmpty(t, a) require.Equal(t, ArchiveTar, a.Type()) } func TestNewFromReader_Invalid(t *testing.T) { file, err := ioutil.ReadFile("testdata/notempty.tar") assert.NoError(t, err) assert.NotEmpty(t, file) var buf bytes.Buffer n, err := buf.Write(file) assert.NoError(t, err) assert.Equal(t, len(file), n) a, err := NewFromReader(&buf, 666) assert.Error(t, err) assert.NotEmpty(t, a) assert.Equal(t, &Plain{"-", &buf}, a) } func TestExt2Type(t *testing.T) { td := []struct { ins string out int }{ {"", ArchivePlain}, {".zip", ArchiveZip}, {".gz", ArchiveGzip}, {".asc", ArchiveGpg}, {".gpg", ArchiveGpg}, {".tar", ArchiveTar}, {".txt", ArchivePlain}, } for _, d := range td { assert.Equal(t, d.out, Ext2Type(d.ins)) } } archive-0.8.0/go.mod000066400000000000000000000004201371424475600142570ustar00rootroot00000000000000module github.com/keltia/archive go 1.14 require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/klauspost/compress v1.10.10 github.com/pkg/errors v0.8.1 github.com/proglottis/gpgme v0.0.0-20181127053519-3b0be0916cd5 github.com/stretchr/testify v1.3.0 ) archive-0.8.0/go.sum000066400000000000000000000023641371424475600143150ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I= github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/proglottis/gpgme v0.0.0-20181127053519-3b0be0916cd5 h1:eRA7dSSk5ag/eeeHd8JzDhP5v9LYfzA4DnRSpKPl/Ak= github.com/proglottis/gpgme v0.0.0-20181127053519-3b0be0916cd5/go.mod h1:hbKCks+19s4oK5vcPKxliXTANhPsfz972l5GVM5+FYE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= archive-0.8.0/testdata/000077500000000000000000000000001371424475600147665ustar00rootroot00000000000000archive-0.8.0/testdata/empty.tar000066400000000000000000000000001371424475600166220ustar00rootroot00000000000000archive-0.8.0/testdata/empty.txt000066400000000000000000000000001371424475600166530ustar00rootroot00000000000000archive-0.8.0/testdata/garbage.zip000066400000000000000000000000071371424475600170770ustar00rootroot00000000000000foobar archive-0.8.0/testdata/notempty.asc000066400000000000000000000000171371424475600173330ustar00rootroot00000000000000this is a file archive-0.8.0/testdata/notempty.gpg000066400000000000000000000000171371424475600173420ustar00rootroot00000000000000this is a file archive-0.8.0/testdata/notempty.tar000066400000000000000000000050001371424475600173500ustar00rootroot00000000000000empty.txt000644 000765 000024 00000000000 13356420724 013570 0ustar00robertostaff000000 000000 notempty.txt000644 000765 000024 00000000017 13356720646 014327 0ustar00robertostaff000000 000000 this is a file archive-0.8.0/testdata/notempty.txt000066400000000000000000000000171371424475600174040ustar00rootroot00000000000000this is a file archive-0.8.0/testdata/notempty.txt.gz000066400000000000000000000000561371424475600200260ustar00rootroot00000000000000S[notempty.txt+,VD̜T.Pfarchive-0.8.0/testdata/notempty.txt.zst000066400000000000000000000000341371424475600202220ustar00rootroot00000000000000(/$ythis is a file karchive-0.8.0/testdata/notempty.zip000066400000000000000000000002751371424475600173750ustar00rootroot00000000000000PK ׉HMPf notempty.txtUT ft[z[ux this is a file PK ׉HMPf notempty.txtUTft[ux PKRUarchive-0.8.0/testdata/notempty.zip.asc000066400000000000000000000002751371424475600201420ustar00rootroot00000000000000PK ׉HMPf notempty.txtUT ft[z[ux this is a file PK ׉HMPf notempty.txtUTft[ux PKRUarchive-0.8.0/utils.go000066400000000000000000000004471371424475600146510ustar00rootroot00000000000000package archive import ( "log" ) // debug displays only if fDebug is set func debug(str string, a ...interface{}) { if fDebug { log.Printf(str, a...) } } // verbose displays only if fVerbose is set func verbose(str string, a ...interface{}) { if fVerbose { log.Printf(str, a...) } } archive-0.8.0/utils_test.go000066400000000000000000000004671371424475600157120ustar00rootroot00000000000000package archive import ( "testing" ) func TestVerbose_No(t *testing.T) { verbose("no") } func TestVerbose_Yes(t *testing.T) { SetVerbose() verbose("yes") fVerbose = false } func TestDebug_No(t *testing.T) { debug("no") } func TestDebug_Yes(t *testing.T) { SetDebug() debug("yes") fDebug = false }