Skip to content

Commit 6af2308

Browse files
committed
Initial commit
0 parents  commit 6af2308

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+3637
-0
lines changed

.gitattributes

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Auto detect text files and perform LF normalization
2+
* text=auto

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
go.mod
2+
go.sum

archive/archive.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package archive
2+
3+
import "os"
4+
5+
// File struct contains bytes body and the provided name field.
6+
type File struct {
7+
Name string
8+
Body []byte
9+
IsDir bool
10+
}
11+
12+
// Format represents the archive format.
13+
type Format int
14+
15+
const (
16+
// ZIP format
17+
ZIP Format = iota
18+
// TAR format
19+
TAR
20+
)
21+
22+
func readFiles(files ...string) (fs []File, err error) {
23+
for _, f := range files {
24+
var file File
25+
file.Name = f
26+
file.Body, err = os.ReadFile(f)
27+
if err != nil {
28+
return
29+
}
30+
fs = append(fs, file)
31+
}
32+
return
33+
}

archive/pack.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package archive
2+
3+
import (
4+
"errors"
5+
"io"
6+
)
7+
8+
// Pack creates an archive from File struct.
9+
func Pack(w io.Writer, format Format, files ...File) error {
10+
switch format {
11+
case ZIP:
12+
return packZip(w, files...)
13+
case TAR:
14+
return packTar(w, files...)
15+
default:
16+
return errors.New("unknow format")
17+
}
18+
}
19+
20+
// PackFromFiles creates an archive from files.
21+
func PackFromFiles(w io.Writer, format Format, files ...string) error {
22+
fs, err := readFiles(files...)
23+
if err != nil {
24+
return err
25+
}
26+
return Pack(w, format, fs...)
27+
}

archive/pack_test.go

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package archive
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
var files = []File{
9+
{Name: "testdata/1.txt", Body: []byte("1")},
10+
{Name: "testdata/2.txt", Body: []byte("2")},
11+
}
12+
13+
func TestPackZIP(t *testing.T) {
14+
var buf1, buf2 bytes.Buffer
15+
if err := Pack(&buf1, ZIP, files...); err != nil {
16+
t.Fatal(err)
17+
}
18+
if err := PackFromFiles(&buf2, ZIP, "testdata/1.txt", "testdata/2.txt"); err != nil {
19+
t.Fatal(err)
20+
}
21+
if !bytes.Equal(buf1.Bytes(), buf2.Bytes()) {
22+
t.Error("expected equal zip archive; got not equal")
23+
}
24+
if !match(zipMagic, buf1.Bytes()[:len(zipMagic)]) {
25+
t.Error("expected equal magic; got not equal")
26+
}
27+
}
28+
29+
func TestPackTAR(t *testing.T) {
30+
var buf1, buf2 bytes.Buffer
31+
if err := Pack(&buf1, TAR, files...); err != nil {
32+
t.Fatal(err)
33+
}
34+
if !match(tarMagic, buf1.Bytes()[:len(tarMagic)]) {
35+
t.Error("expected equal magic; got not equal")
36+
}
37+
if err := PackFromFiles(&buf2, TAR, "testdata/1.txt", "testdata/2.txt"); err != nil {
38+
t.Fatal(err)
39+
}
40+
if !match(tarMagic, buf2.Bytes()[:len(tarMagic)]) {
41+
t.Error("expected equal magic; got not equal")
42+
}
43+
}

archive/tar.go

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package archive
2+
3+
import (
4+
"archive/tar"
5+
"bytes"
6+
"compress/gzip"
7+
"io"
8+
"log"
9+
)
10+
11+
const tarMagic = "\x1f\x8b\x08\x00"
12+
13+
func packTar(w io.Writer, files ...File) error {
14+
gw := gzip.NewWriter(w)
15+
tw := tar.NewWriter(gw)
16+
17+
for _, file := range files {
18+
header := &tar.Header{
19+
Name: file.Name,
20+
Mode: 0600,
21+
Size: int64(len(file.Body)),
22+
}
23+
if err := tw.WriteHeader(header); err != nil {
24+
return err
25+
}
26+
if _, err := tw.Write(file.Body); err != nil {
27+
return err
28+
}
29+
}
30+
31+
if err := tw.Close(); err != nil {
32+
return err
33+
}
34+
35+
return gw.Close()
36+
}
37+
38+
func unpackTar(b []byte) ([]File, error) {
39+
gr, err := gzip.NewReader(bytes.NewReader(b))
40+
if err != nil {
41+
return nil, err
42+
}
43+
tr := tar.NewReader(gr)
44+
45+
var fs []File
46+
for {
47+
header, err := tr.Next()
48+
if err == io.EOF {
49+
break
50+
}
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
switch header.Typeflag {
56+
case tar.TypeDir:
57+
fs = append(fs, File{Name: header.Name, IsDir: true})
58+
case tar.TypeReg:
59+
var buf bytes.Buffer
60+
if _, err := io.Copy(&buf, tr); err != nil {
61+
return nil, err
62+
}
63+
fs = append(fs, File{Name: header.Name, Body: buf.Bytes()})
64+
default:
65+
log.Printf(
66+
"ExtractTarGz: uknown type: %v in %s",
67+
header.Typeflag,
68+
header.Name)
69+
}
70+
}
71+
72+
return fs, nil
73+
}

archive/testdata/1.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1

archive/testdata/2.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2

archive/testdata/folder.zip

144 Bytes
Binary file not shown.

archive/testdata/test

Whitespace-only changes.

archive/testdata/test.tar.gz

106 Bytes
Binary file not shown.

archive/testdata/test.zip

268 Bytes
Binary file not shown.

archive/unpack.go

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package archive
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"os"
8+
"path/filepath"
9+
)
10+
11+
func match(magic string, b []byte) bool {
12+
if len(magic) != len(b) {
13+
return false
14+
}
15+
for i, c := range b {
16+
if magic[i] != c && magic[i] != '?' {
17+
return false
18+
}
19+
}
20+
return true
21+
}
22+
23+
// Unpack decompresses an archive to File struct.
24+
func Unpack(r io.Reader) ([]File, error) {
25+
b, err := io.ReadAll(r)
26+
if err != nil {
27+
return nil, err
28+
}
29+
switch {
30+
case match(zipMagic, b[:len(zipMagic)]):
31+
return unpackZip(b)
32+
case match(tarMagic, b[:len(tarMagic)]):
33+
return unpackTar(b)
34+
default:
35+
return nil, errors.New("unsupport file format")
36+
}
37+
}
38+
39+
// UnpackToFiles decompresses an archive to files.
40+
func UnpackToFiles(r io.Reader, dest string) error {
41+
files, err := Unpack(r)
42+
if err != nil {
43+
return err
44+
}
45+
46+
for _, file := range files {
47+
fpath := filepath.Join(dest, file.Name)
48+
if file.IsDir {
49+
dir, err := os.Stat(fpath)
50+
if err != nil {
51+
if os.IsNotExist(err) {
52+
if err := os.MkdirAll(fpath, 0755); err != nil {
53+
return err
54+
}
55+
} else {
56+
return err
57+
}
58+
} else if !dir.IsDir() {
59+
return fmt.Errorf("cannot create directory %q: File exists", fpath)
60+
}
61+
} else {
62+
if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil {
63+
return err
64+
}
65+
66+
f, err := os.Create(fpath)
67+
if err != nil {
68+
return err
69+
}
70+
if _, err := f.Write(file.Body); err != nil {
71+
return err
72+
}
73+
if err := f.Close(); err != nil {
74+
return err
75+
}
76+
}
77+
}
78+
79+
return nil
80+
}

archive/unpack_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package archive
2+
3+
import (
4+
"os"
5+
"reflect"
6+
"testing"
7+
)
8+
9+
func TestUnPack(t *testing.T) {
10+
tc := []string{"testdata/test.zip", "testdata/test.tar.gz"}
11+
result := []File{
12+
{Name: "1.txt", Body: []byte("1")},
13+
{Name: "2.txt", Body: []byte("2")},
14+
}
15+
for _, i := range tc {
16+
f, err := os.Open(i)
17+
if err != nil {
18+
t.Fatal(err)
19+
}
20+
fs, err := Unpack(f)
21+
if err != nil {
22+
t.Fatalf("Unpack %q failed: %v", i, err)
23+
}
24+
if !reflect.DeepEqual(fs, result) {
25+
t.Errorf("expected %#v; got %#v", result, fs)
26+
}
27+
}
28+
29+
f, err := os.Open("testdata/folder.zip")
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
if err := UnpackToFiles(f, "testdata"); err == nil {
34+
t.Error("expected error; got nil")
35+
}
36+
}

archive/zip.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package archive
2+
3+
import (
4+
"archive/zip"
5+
"bytes"
6+
"io"
7+
"log"
8+
)
9+
10+
const zipMagic = "PK\x03\x04"
11+
12+
func packZip(w io.Writer, files ...File) error {
13+
zw := zip.NewWriter(w)
14+
15+
for _, file := range files {
16+
f, err := zw.Create(file.Name)
17+
if err != nil {
18+
return err
19+
}
20+
if _, err := f.Write(file.Body); err != nil {
21+
return err
22+
}
23+
}
24+
25+
return zw.Close()
26+
}
27+
28+
func unpackZip(b []byte) ([]File, error) {
29+
r, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
30+
if err != nil {
31+
return nil, err
32+
}
33+
34+
var fs []File
35+
for _, f := range r.File {
36+
switch {
37+
case f.FileInfo().IsDir():
38+
fs = append(fs, File{Name: f.Name, IsDir: true})
39+
case f.FileInfo().Mode().IsRegular():
40+
rc, err := f.Open()
41+
if err != nil {
42+
return nil, err
43+
}
44+
var buf bytes.Buffer
45+
if _, err := io.Copy(&buf, rc); err != nil {
46+
return nil, err
47+
}
48+
fs = append(fs, File{Name: f.Name, Body: buf.Bytes()})
49+
if err := rc.Close(); err != nil {
50+
return nil, err
51+
}
52+
default:
53+
log.Printf(
54+
"ExtractZip: uknown type: %d in %s",
55+
f.FileInfo().Mode(),
56+
f.Name)
57+
}
58+
}
59+
60+
return fs, nil
61+
}

0 commit comments

Comments
 (0)