oras.land/oras-go/v2@v2.5.1-0.20240520045656-aef90e4d04c4/content/reader.go (about) 1 /* 2 Copyright The ORAS Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package content 17 18 import ( 19 "errors" 20 "fmt" 21 "io" 22 23 "github.com/opencontainers/go-digest" 24 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 25 ) 26 27 var ( 28 // ErrInvalidDescriptorSize is returned by ReadAll() when 29 // the descriptor has an invalid size. 30 ErrInvalidDescriptorSize = errors.New("invalid descriptor size") 31 32 // ErrMismatchedDigest is returned by ReadAll() when 33 // the descriptor has an invalid digest. 34 ErrMismatchedDigest = errors.New("mismatched digest") 35 36 // ErrTrailingData is returned by ReadAll() when 37 // there exists trailing data unread when the read terminates. 38 ErrTrailingData = errors.New("trailing data") 39 ) 40 41 var ( 42 // errEarlyVerify is returned by VerifyReader.Verify() when 43 // Verify() is called before completing reading the entire content blob. 44 errEarlyVerify = errors.New("early verify") 45 ) 46 47 // VerifyReader reads the content described by its descriptor and verifies 48 // against its size and digest. 49 type VerifyReader struct { 50 base *io.LimitedReader 51 verifier digest.Verifier 52 verified bool 53 err error 54 } 55 56 // Read reads up to len(p) bytes into p. It returns the number of bytes 57 // read (0 <= n <= len(p)) and any error encountered. 58 func (vr *VerifyReader) Read(p []byte) (n int, err error) { 59 if vr.err != nil { 60 return 0, vr.err 61 } 62 63 n, err = vr.base.Read(p) 64 if err != nil { 65 if err == io.EOF && vr.base.N > 0 { 66 err = io.ErrUnexpectedEOF 67 } 68 vr.err = err 69 } 70 return 71 } 72 73 // Verify checks for remaining unread content and verifies the read content against the digest 74 func (vr *VerifyReader) Verify() error { 75 if vr.verified { 76 return nil 77 } 78 if vr.err == nil { 79 if vr.base.N > 0 { 80 return errEarlyVerify 81 } 82 } else if vr.err != io.EOF { 83 return vr.err 84 } 85 86 if err := ensureEOF(vr.base.R); err != nil { 87 vr.err = err 88 return vr.err 89 } 90 if !vr.verifier.Verified() { 91 vr.err = ErrMismatchedDigest 92 return vr.err 93 } 94 95 vr.verified = true 96 vr.err = io.EOF 97 return nil 98 } 99 100 // NewVerifyReader wraps r for reading content with verification against desc. 101 func NewVerifyReader(r io.Reader, desc ocispec.Descriptor) *VerifyReader { 102 verifier := desc.Digest.Verifier() 103 lr := &io.LimitedReader{ 104 R: io.TeeReader(r, verifier), 105 N: desc.Size, 106 } 107 return &VerifyReader{ 108 base: lr, 109 verifier: verifier, 110 } 111 } 112 113 // ReadAll safely reads the content described by the descriptor. 114 // The read content is verified against the size and the digest 115 // using a VerifyReader. 116 func ReadAll(r io.Reader, desc ocispec.Descriptor) ([]byte, error) { 117 if desc.Size < 0 { 118 return nil, ErrInvalidDescriptorSize 119 } 120 buf := make([]byte, desc.Size) 121 122 vr := NewVerifyReader(r, desc) 123 if n, err := io.ReadFull(vr, buf); err != nil { 124 if errors.Is(err, io.ErrUnexpectedEOF) { 125 return nil, fmt.Errorf("read failed: expected content size of %d, got %d, for digest %s: %w", desc.Size, n, desc.Digest.String(), err) 126 } 127 return nil, fmt.Errorf("read failed: %w", err) 128 } 129 if err := vr.Verify(); err != nil { 130 return nil, err 131 } 132 return buf, nil 133 } 134 135 // ensureEOF ensures the read operation ends with an EOF and no 136 // trailing data is present. 137 func ensureEOF(r io.Reader) error { 138 var peek [1]byte 139 _, err := io.ReadFull(r, peek[:]) 140 if err != io.EOF { 141 return ErrTrailingData 142 } 143 return nil 144 }