Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Towards eliminating v1util #872

Merged
merged 9 commits into from
Dec 17, 2020
12 changes: 6 additions & 6 deletions pkg/v1/hash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ func TestGoodHashes(t *testing.T) {
for _, s := range good {
h, err := NewHash(s)
if err != nil {
t.Errorf("Unexpected error parsing hash: %v", err)
t.Error("Unexpected error parsing hash:", err)
}
if got, want := h.String(), s; got != want {
t.Errorf("String(); got %q, want %q", got, want)
}
bytes, err := json.Marshal(h)
if err != nil {
t.Errorf("Unexpected error json.Marshaling hash: %v", err)
t.Error("Unexpected error json.Marshaling hash:", err)
}
if got, want := string(bytes), strconv.Quote(h.String()); got != want {
t.Errorf("json.Marshal(); got %q, want %q", got, want)
Expand All @@ -62,7 +62,7 @@ func TestBadHashes(t *testing.T) {
for _, s := range bad {
h, err := NewHash(s)
if err == nil {
t.Errorf("Expected error, got: %v", h)
t.Error("Expected error, got:", h)
}
}
}
Expand All @@ -71,7 +71,7 @@ func TestSHA256(t *testing.T) {
input := "asdf"
h, n, err := SHA256(strings.NewReader(input))
if err != nil {
t.Errorf("SHA256(asdf) = %v", err)
t.Error("SHA256(asdf) =", err)
}
if got, want := h.Algorithm, "sha256"; got != want {
t.Errorf("Algorithm; got %v, want %v", got, want)
Expand All @@ -90,10 +90,10 @@ func TestTextMarshalling(t *testing.T) {
foo := make(map[Hash]string)
b, err := json.Marshal(foo)
if err != nil {
t.Fatalf("could not marshal: %v", err)
t.Fatal("could not marshal:", err)
}
if err := json.Unmarshal(b, &foo); err != nil {
t.Errorf("could not unmarshal: %v", err)
t.Error("could not unmarshal:", err)
}

h := &Hash{
Expand Down
20 changes: 10 additions & 10 deletions pkg/v1/v1util/and_closer.go → pkg/v1/internal/and/and_closer.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2018 Google LLC All Rights Reserved.
// Copyright 2020 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -12,36 +12,36 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package v1util
package and

import (
"io"
)

// readAndCloser implements io.ReadCloser by reading from a particular io.Reader
// ReadCloser implements io.ReadCloser by reading from a particular io.Reader
// and then calling the provided "Close()" method.
type readAndCloser struct {
type ReadCloser struct {
io.Reader
CloseFunc func() error
}

var _ io.ReadCloser = (*readAndCloser)(nil)
var _ io.ReadCloser = (*ReadCloser)(nil)

// Close implements io.ReadCloser
func (rac *readAndCloser) Close() error {
func (rac *ReadCloser) Close() error {
return rac.CloseFunc()
}

// writeAndCloser implements io.WriteCloser by reading from a particular io.Writer
// WriteCloser implements io.WriteCloser by reading from a particular io.Writer
// and then calling the provided "Close()" method.
type writeAndCloser struct {
type WriteCloser struct {
io.Writer
CloseFunc func() error
}

var _ io.WriteCloser = (*writeAndCloser)(nil)
var _ io.WriteCloser = (*WriteCloser)(nil)

// Close implements io.WriteCloser
func (wac *writeAndCloser) Close() error {
func (wac *WriteCloser) Close() error {
return wac.CloseFunc()
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2018 Google LLC All Rights Reserved.
// Copyright 2020 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package v1util
package and

import (
"bytes"
Expand All @@ -25,7 +25,7 @@ func TestRead(t *testing.T) {
r := bytes.NewBufferString(want)
called := false

rac := &readAndCloser{
rac := &ReadCloser{
Reader: r,
CloseFunc: func() error {
called = true
Expand Down Expand Up @@ -56,7 +56,7 @@ func TestWrite(t *testing.T) {
w := bytes.NewBuffer([]byte{})
called := false

wac := &writeAndCloser{
wac := &WriteCloser{
Writer: w,
CloseFunc: func() error {
called = true
Expand Down
96 changes: 96 additions & 0 deletions pkg/v1/internal/gzip/zip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright 2020 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gzip

import (
"bytes"
"compress/gzip"
"io"

"github.com/google/go-containerregistry/pkg/v1/internal/and"
)

var gzipMagicHeader = []byte{'\x1f', '\x8b'}

// ReadCloser reads uncompressed input data from the io.ReadCloser and
// returns an io.ReadCloser from which compressed data may be read.
// This uses gzip.BestSpeed for the compression level.
func ReadCloser(r io.ReadCloser) io.ReadCloser {
return ReadCloserLevel(r, gzip.BestSpeed)
}

// ReadCloserLevel reads uncompressed input data from the io.ReadCloser and
// returns an io.ReadCloser from which compressed data may be read.
// Refer to compress/gzip for the level:
// https://golang.org/pkg/compress/gzip/#pkg-constants
func ReadCloserLevel(r io.ReadCloser, level int) io.ReadCloser {
pr, pw := io.Pipe()

// Returns err so we can pw.CloseWithError(err)
go func() error {
// TODO(go1.14): Just defer {pw,gw,r}.Close like you'd expect.
// Context: https://golang.org/issue/24283
gw, err := gzip.NewWriterLevel(pw, level)
if err != nil {
return pw.CloseWithError(err)
}

if _, err := io.Copy(gw, r); err != nil {
defer r.Close()
defer gw.Close()
return pw.CloseWithError(err)
}
defer pw.Close()
defer r.Close()
defer gw.Close()

return nil
}()

return pr
}

// UnzipReadCloser reads compressed input data from the io.ReadCloser and
// returns an io.ReadCloser from which uncompessed data may be read.
func UnzipReadCloser(r io.ReadCloser) (io.ReadCloser, error) {
gr, err := gzip.NewReader(r)
if err != nil {
return nil, err
}
return &and.ReadCloser{
Reader: gr,
CloseFunc: func() error {
// If the unzip fails, then this seems to return the same
// error as the read. We don't want this to interfere with
// us closing the main ReadCloser, since this could leave
// an open file descriptor (fails on Windows).
gr.Close()
return r.Close()
},
}, nil
}

// Is detects whether the input stream is compressed.
func Is(r io.Reader) (bool, error) {
magicHeader := make([]byte, 2)
n, err := r.Read(magicHeader)
if n == 0 && err == io.EOF {
return false, nil
}
if err != nil {
return false, err
}
return bytes.Equal(magicHeader, gzipMagicHeader), nil
}
38 changes: 19 additions & 19 deletions pkg/v1/v1util/zip_test.go → pkg/v1/internal/gzip/zip_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2018 Google LLC All Rights Reserved.
// Copyright 2020 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package v1util
package gzip

import (
"bytes"
Expand All @@ -25,25 +25,25 @@ import (
func TestReader(t *testing.T) {
want := "This is the input string."
buf := bytes.NewBufferString(want)
zipped := GzipReadCloser(ioutil.NopCloser(buf))
unzipped, err := GunzipReadCloser(zipped)
zipped := ReadCloser(ioutil.NopCloser(buf))
unzipped, err := UnzipReadCloser(zipped)
if err != nil {
t.Errorf("GunzipReadCloser() = %v", err)
t.Error("UnzipReadCloser() =", err)
}

b, err := ioutil.ReadAll(unzipped)
if err != nil {
t.Errorf("ReadAll() = %v", err)
t.Error("ReadAll() =", err)
}
if got := string(b); got != want {
t.Errorf("ReadAll(); got %q, want %q", got, want)
}
if err := unzipped.Close(); err != nil {
t.Errorf("Close() = %v", err)
t.Error("Close() =", err)
}
}

func TestIsGzipped(t *testing.T) {
func TestIs(t *testing.T) {
tests := []struct {
in []byte
out bool
Expand All @@ -55,12 +55,12 @@ func TestIsGzipped(t *testing.T) {
}
for _, test := range tests {
reader := bytes.NewReader(test.in)
got, err := IsGzipped(reader)
got, err := Is(reader)
if got != test.out {
t.Errorf("IsGzipped; n: got %v, wanted %v\n", got, test.out)
t.Errorf("Is; n: got %v, wanted %v\n", got, test.out)
}
if err != test.err {
t.Errorf("IsGzipped; err: got %v, wanted %v\n", err, test.err)
t.Errorf("Is; err: got %v, wanted %v\n", err, test.err)
}
}
}
Expand All @@ -77,22 +77,22 @@ func (f failReader) Read(_ []byte) (int, error) {

func TestReadErrors(t *testing.T) {
fr := failReader{}
if _, err := IsGzipped(fr); err != errRead {
t.Errorf("IsGzipped: expected errRead, got %v", err)
if _, err := Is(fr); err != errRead {
t.Error("Is: expected errRead, got", err)
}

frc := ioutil.NopCloser(fr)
if _, err := GunzipReadCloser(frc); err != errRead {
t.Errorf("GunzipReadCloser: expected errRead, got %v", err)
if _, err := UnzipReadCloser(frc); err != errRead {
t.Error("UnzipReadCloser: expected errRead, got", err)
}

zr := GzipReadCloser(ioutil.NopCloser(fr))
zr := ReadCloser(ioutil.NopCloser(fr))
if _, err := zr.Read(nil); err != errRead {
t.Errorf("GzipReadCloser: expected errRead, got %v", err)
t.Error("ReadCloser: expected errRead, got", err)
}

zr = GzipReadCloserLevel(ioutil.NopCloser(strings.NewReader("zip me")), -10)
zr = ReadCloserLevel(ioutil.NopCloser(strings.NewReader("zip me")), -10)
if _, err := zr.Read(nil); err == nil {
t.Errorf("Expected invalid level error, got: %v", err)
t.Error("Expected invalid level error, got:", err)
}
}
62 changes: 62 additions & 0 deletions pkg/v1/internal/verify/verify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2020 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package verify

import (
"encoding/hex"
"fmt"
"hash"
"io"

v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/internal/and"
)

type verifyReader struct {
inner io.Reader
hasher hash.Hash
expected v1.Hash
}

// Read implements io.Reader
func (vc *verifyReader) Read(b []byte) (int, error) {
n, err := vc.inner.Read(b)
if err == io.EOF {
got := hex.EncodeToString(vc.hasher.Sum(make([]byte, 0, vc.hasher.Size())))
if want := vc.expected.Hex; got != want {
return n, fmt.Errorf("error verifying %s checksum; got %q, want %q",
vc.expected.Algorithm, got, want)
}
}
return n, err
}

// ReadCloser wraps the given io.ReadCloser to verify that its contents match
// the provided v1.Hash before io.EOF is returned.
func ReadCloser(r io.ReadCloser, h v1.Hash) (io.ReadCloser, error) {
w, err := v1.Hasher(h.Algorithm)
if err != nil {
return nil, err
}
r2 := io.TeeReader(r, w)
return &and.ReadCloser{
Reader: &verifyReader{
inner: r2,
hasher: w,
expected: h,
},
CloseFunc: r.Close,
}, nil
}