Skip to content

Commit d7bda79

Browse files
author
Zhou Hao
authored
Merge pull request #135 from erikh/fix-tar-files
image/manifest.go: implement compression detection / mediatype validation
2 parents dcad01c + aa191db commit d7bda79

File tree

3 files changed

+125
-22
lines changed

3 files changed

+125
-22
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ lint:
6060
@./.tool/lint
6161

6262
test:
63-
go test -race -cover $(shell go list ./... | grep -v /vendor/)
63+
go test -v -race -cover $(shell go list ./... | grep -v /vendor/)
6464

6565

6666
## this uses https://github.com/Masterminds/glide and https://github.com/sgotti/glide-vc

image/manifest.go

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ package image
1616

1717
import (
1818
"archive/tar"
19+
"bufio"
1920
"bytes"
21+
"compress/bzip2"
2022
"compress/gzip"
2123
"encoding/json"
2224
"fmt"
@@ -27,6 +29,7 @@ import (
2729
"strings"
2830
"time"
2931

32+
"github.com/Sirupsen/logrus"
3033
"github.com/opencontainers/image-spec/schema"
3134
"github.com/opencontainers/image-spec/specs-go/v1"
3235
"github.com/pkg/errors"
@@ -120,8 +123,8 @@ func (m *manifest) unpack(w walker, dest string) (retErr error) {
120123
return nil
121124
}
122125

123-
if err := unpackLayer(dest, r); err != nil {
124-
return errors.Wrap(err, "unpack: error extracting layer")
126+
if err := unpackLayer(d.MediaType, path, dest, r); err != nil {
127+
return errors.Wrap(err, "error unpack: extracting layer")
125128
}
126129

127130
return errEOW
@@ -136,16 +139,72 @@ func (m *manifest) unpack(w walker, dest string) (retErr error) {
136139
return nil
137140
}
138141

139-
func unpackLayer(dest string, r io.Reader) error {
142+
func getReader(path, mediaType, comp string, buf io.Reader) (io.Reader, error) {
143+
switch comp {
144+
case "gzip":
145+
if !strings.HasSuffix(mediaType, "+gzip") {
146+
logrus.Debugf("%q: %s media type with non-%s file", path, comp, comp)
147+
}
148+
149+
return gzip.NewReader(buf)
150+
case "bzip2":
151+
if !strings.HasSuffix(mediaType, "+bzip2") {
152+
logrus.Debugf("%q: %s media type with non-%s file", path, comp, comp)
153+
}
154+
155+
return bzip2.NewReader(buf), nil
156+
case "xz":
157+
return nil, errors.New("xz layers are not supported")
158+
default:
159+
if strings.Contains(mediaType, "+") {
160+
logrus.Debugf("%q: %s media type with non-%s file", path, comp, comp)
161+
}
162+
163+
return buf, nil
164+
}
165+
}
166+
167+
// DetectCompression detects the compression algorithm of the source.
168+
func DetectCompression(r *bufio.Reader) (string, error) {
169+
source, err := r.Peek(10)
170+
if err != nil {
171+
return "", err
172+
}
173+
174+
for compression, m := range map[string][]byte{
175+
"bzip2": {0x42, 0x5A, 0x68},
176+
"gzip": {0x1F, 0x8B, 0x08},
177+
// FIXME needs decompression support
178+
// "xz": {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00},
179+
} {
180+
if len(source) < len(m) {
181+
logrus.Debug("Len too short")
182+
continue
183+
}
184+
if bytes.Equal(m, source[:len(m)]) {
185+
return compression, nil
186+
}
187+
}
188+
return "plain", nil
189+
}
190+
191+
func unpackLayer(mediaType, path, dest string, r io.Reader) error {
140192
entries := make(map[string]bool)
141-
gz, err := gzip.NewReader(r)
193+
194+
buf := bufio.NewReader(r)
195+
196+
comp, err := DetectCompression(buf)
197+
if err != nil {
198+
return err
199+
}
200+
201+
reader, err := getReader(path, mediaType, comp, buf)
142202
if err != nil {
143-
return errors.Wrap(err, "error creating gzip reader")
203+
return err
144204
}
145-
defer gz.Close()
146205

147206
var dirs []*tar.Header
148-
tr := tar.NewReader(gz)
207+
tr := tar.NewReader(reader)
149208

150209
loop:
151210
for {

image/manifest_test.go

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,17 @@ import (
2525
"strings"
2626
"testing"
2727

28+
"github.com/Sirupsen/logrus"
2829
"github.com/opencontainers/go-digest"
30+
"github.com/pkg/errors"
31+
32+
bz2 "github.com/dsnet/compress/bzip2"
2933
)
3034

35+
func init() {
36+
logrus.SetLevel(logrus.DebugLevel)
37+
}
38+
3139
func TestUnpackLayerDuplicateEntries(t *testing.T) {
3240
tmp1, err := ioutil.TempDir("", "test-dup")
3341
if err != nil {
@@ -60,12 +68,12 @@ func TestUnpackLayerDuplicateEntries(t *testing.T) {
6068
t.Fatal(err)
6169
}
6270
defer os.RemoveAll(tmp2)
63-
if err := unpackLayer(tmp2, r); err != nil && !strings.Contains(err.Error(), "duplicate entry for") {
71+
if err := unpackLayer("application/vnd.oci.image.layer.v1.tar+gzip", f.Name(), tmp2, r); err != nil && !strings.Contains(err.Error(), "duplicate entry for") {
6472
t.Fatalf("Expected to fail with duplicate entry, got %v", err)
6573
}
6674
}
6775

68-
func TestUnpackLayer(t *testing.T) {
76+
func testUnpackLayer(t *testing.T, compression string, invalid bool) {
6977
tmp1, err := ioutil.TempDir("", "test-layer")
7078
if err != nil {
7179
t.Fatal(err)
@@ -81,14 +89,34 @@ func TestUnpackLayer(t *testing.T) {
8189
t.Fatal(err)
8290
}
8391

84-
gw := gzip.NewWriter(f)
85-
tw := tar.NewWriter(gw)
92+
var writer io.WriteCloser = f
93+
94+
if !invalid {
95+
switch compression {
96+
case "gzip":
97+
writer = gzip.NewWriter(f)
98+
case "bzip2":
99+
writer, err = bz2.NewWriter(f, nil)
100+
if err != nil {
101+
t.Fatal(errors.Wrap(err, "compiling bzip compressor"))
102+
}
103+
}
104+
} else if invalid && compression == "" {
105+
writer = gzip.NewWriter(f)
106+
}
107+
108+
tw := tar.NewWriter(writer)
109+
110+
if headerErr := tw.WriteHeader(&tar.Header{Name: "test", Size: 4, Mode: 0600}); headerErr != nil {
111+
t.Fatal(headerErr)
112+
}
113+
114+
if _, copyErr := io.Copy(tw, bytes.NewReader([]byte("test"))); copyErr != nil {
115+
t.Fatal(copyErr)
116+
}
86117

87-
tw.WriteHeader(&tar.Header{Name: "test", Size: 4, Mode: 0600})
88-
io.Copy(tw, bytes.NewReader([]byte("test")))
89118
tw.Close()
90-
gw.Close()
91-
f.Close()
119+
writer.Close()
92120

93121
digester := digest.SHA256.Digester()
94122
file, err := os.Open(tarfile)
@@ -100,28 +128,44 @@ func TestUnpackLayer(t *testing.T) {
100128
if err != nil {
101129
t.Fatal(err)
102130
}
103-
err = os.Rename(tarfile, filepath.Join(tmp1, "blobs", "sha256", digester.Digest().Hex()))
104-
if err != nil {
105-
t.Fatal(err)
131+
132+
blobPath := filepath.Join(tmp1, "blobs", "sha256", digester.Digest().Hex())
133+
134+
if renameErr := os.Rename(tarfile, blobPath); renameErr != nil {
135+
t.Fatal(errors.Wrap(renameErr, blobPath))
136+
}
137+
138+
mediatype := "application/vnd.oci.image.layer.v1.tar"
139+
if compression != "" {
140+
mediatype += "+" + compression
106141
}
107142

108143
testManifest := manifest{
109144
Layers: []descriptor{descriptor{
110-
MediaType: "application/vnd.oci.image.layer.v1.tar+gzip",
145+
MediaType: mediatype,
111146
Digest: digester.Digest().String(),
112147
}},
113148
}
114149
err = testManifest.unpack(newPathWalker(tmp1), filepath.Join(tmp1, "rootfs"))
115150
if err != nil {
116-
t.Fatal(err)
151+
t.Fatal(errors.Wrapf(err, "%q / %s", blobPath, compression))
117152
}
118153

119154
_, err = os.Stat(filepath.Join(tmp1, "rootfs", "test"))
120155
if err != nil {
121-
t.Fatal(err)
156+
t.Fatal(errors.Wrapf(err, "%q / %s", blobPath, compression))
122157
}
123158
}
124159

160+
func TestUnpackLayer(t *testing.T) {
161+
testUnpackLayer(t, "gzip", true)
162+
testUnpackLayer(t, "gzip", false)
163+
testUnpackLayer(t, "", true)
164+
testUnpackLayer(t, "", false)
165+
testUnpackLayer(t, "bzip2", true)
166+
testUnpackLayer(t, "bzip2", false)
167+
}
168+
125169
func TestUnpackLayerRemovePartialyUnpackedFile(t *testing.T) {
126170
// generate a tar file has duplicate entry which will failed on unpacking
127171
tmp1, err := ioutil.TempDir("", "test-layer")

0 commit comments

Comments
 (0)