determine gzip archives from magic values

instead of guessing.
This commit is contained in:
Motiejus Jakštys 2021-08-13 15:27:44 +03:00
parent 8d298610f2
commit f1566184cf

View File

@ -5,9 +5,9 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"path/filepath" "path/filepath"
"strings" "strings"
) )
@ -19,6 +19,8 @@ const (
_whPrefix = ".wh." _whPrefix = ".wh."
) )
var _gzipMagic = []byte{0x1f, 0x8b}
type ( type (
dockerManifestJSON []struct { dockerManifestJSON []struct {
Layers []string `json:"Layers"` Layers []string `json:"Layers"`
@ -33,9 +35,10 @@ type (
// Flatten flattens a docker image to a tarball. The underlying io.Writer // Flatten flattens a docker image to a tarball. The underlying io.Writer
// should be an open file handle, which the caller is responsible for closing // should be an open file handle, which the caller is responsible for closing
// themselves // themselves
func Flatten(rd io.ReadSeeker, w io.Writer) (err error) { func Flatten(rd io.ReadSeeker, w io.Writer) (_err error) {
tr := tar.NewReader(rd) tr := tar.NewReader(rd)
var closer func() error var closer func() error
var err error
// layerOffsets maps a layer name (a9b123c0daa/layer.tar) to it's offset // layerOffsets maps a layer name (a9b123c0daa/layer.tar) to it's offset
layerOffsets := map[string]int64{} layerOffsets := map[string]int64{}
@ -95,7 +98,10 @@ func Flatten(rd io.ReadSeeker, w io.Writer) (err error) {
if _, err := rd.Seek(no.offset, io.SeekStart); err != nil { if _, err := rd.Seek(no.offset, io.SeekStart); err != nil {
return err return err
} }
tr, closer = openTargz(rd) tr, closer, err = openTargz(rd)
if err != nil {
return err
}
for { for {
hdr, err := tr.Next() hdr, err := tr.Next()
if err == io.EOF { if err == io.EOF {
@ -138,8 +144,8 @@ func Flatten(rd io.ReadSeeker, w io.Writer) (err error) {
// Avoiding use of multierr: if error is present, return // Avoiding use of multierr: if error is present, return
// that. Otherwise return whatever `Close` returns. // that. Otherwise return whatever `Close` returns.
err1 := tw.Close() err1 := tw.Close()
if err == nil { if _err == nil {
err = err1 _err = err1
} }
}() }()
// iterate through all layers, all files, and write files. // iterate through all layers, all files, and write files.
@ -147,7 +153,10 @@ func Flatten(rd io.ReadSeeker, w io.Writer) (err error) {
if _, err := rd.Seek(no.offset, io.SeekStart); err != nil { if _, err := rd.Seek(no.offset, io.SeekStart); err != nil {
return err return err
} }
tr, closer = openTargz(rd) tr, closer, err = openTargz(rd)
if err != nil {
return err
}
for { for {
hdr, err := tr.Next() hdr, err := tr.Next()
if err == io.EOF { if err == io.EOF {
@ -242,29 +251,31 @@ func validateManifest(
} }
// openTargz creates a tar reader from a targzip or tar. // openTargz creates a tar reader from a targzip or tar.
// func openTargz(rs io.ReadSeeker) (*tar.Reader, func() error, error) {
// It will try to open a gzip stream, and, if that fails, silently fall back to // find out whether the given file is targz or tar
// tar. I will accept a cleaner implementation looking at magic values. head := make([]byte, 2)
func openTargz(r io.Reader) (*tar.Reader, func() error) { _, err := io.ReadFull(rs, head)
hdrbuf := &bytes.Buffer{} switch {
hdrw := &proxyWriter{w: hdrbuf} case err == io.ErrUnexpectedEOF:
gz, err := gzip.NewReader(io.TeeReader(r, hdrw)) return nil, nil, errors.New("tarball or gzipfile too small")
if err == nil { case err != nil:
hdrw.w = ioutil.Discard return nil, nil, fmt.Errorf("read error: %w", err)
hdrbuf = nil
return tar.NewReader(gz), gz.Close
} }
return tar.NewReader(io.MultiReader(hdrbuf, r)), func() error { return nil }
}
// proxyWriter is a pass-through writer. Its underlying writer can be changed if _, err := rs.Seek(-2, io.SeekCurrent); err != nil {
// on-the-fly. Useful when there is a stream that needs to be discarded (change return nil, nil, fmt.Errorf("seek: %w", err)
// the underlying writer to, say, ioutil.Discard). }
type proxyWriter struct {
w io.Writer
}
// Write writes a slice to the underlying w. r := rs.(io.Reader)
func (pw *proxyWriter) Write(p []byte) (int, error) { closer := func() error { return nil }
return pw.w.Write(p) if bytes.Equal(head, _gzipMagic) {
gzipr, err := gzip.NewReader(r)
if err != nil {
return nil, nil, fmt.Errorf("gzip.NewReader: %w", err)
}
closer = gzipr.Close
r = gzipr
}
return tar.NewReader(r), closer, nil
} }