github.com/dtroyer-salad/og2/v2@v2.0.0-20240412154159-c47231610877/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  	"crypto/sha256"
    20  	"encoding"
    21  	"encoding/hex"
    22  	"errors"
    23  	"fmt"
    24  	"hash"
    25  	"io"
    26  	"strconv"
    27  
    28  	"github.com/opencontainers/go-digest"
    29  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    30  	"oras.land/oras-go/v2/internal/spec"
    31  )
    32  
    33  var (
    34  	// ErrInvalidDescriptorSize is returned by ReadAll() when
    35  	// the descriptor has an invalid size.
    36  	ErrInvalidDescriptorSize = errors.New("invalid descriptor size")
    37  
    38  	// ErrMismatchedDigest is returned by ReadAll() when
    39  	// the descriptor has an invalid digest.
    40  	ErrMismatchedDigest = errors.New("mismatched digest")
    41  
    42  	// ErrTrailingData is returned by ReadAll() when
    43  	// there exists trailing data unread when the read terminates.
    44  	ErrTrailingData = errors.New("trailing data")
    45  )
    46  
    47  var (
    48  	// errEarlyVerify is returned by VerifyReader.Verify() when
    49  	// Verify() is called before completing reading the entire content blob.
    50  	errEarlyVerify = errors.New("early verify")
    51  )
    52  
    53  // VerifyReader reads the content described by its descriptor and verifies
    54  // against its size and digest.
    55  type VerifyReader struct {
    56  	base     *io.LimitedReader
    57  	verifier digest.Verifier
    58  	verified bool
    59  	err      error
    60  	resume   bool
    61  }
    62  
    63  // Read reads up to len(p) bytes into p. It returns the number of bytes
    64  // read (0 <= n <= len(p)) and any error encountered.
    65  func (vr *VerifyReader) Read(p []byte) (n int, err error) {
    66  	if vr.err != nil {
    67  		return 0, vr.err
    68  	}
    69  
    70  	n, err = vr.base.Read(p)
    71  	if err != nil {
    72  		if err == io.EOF && vr.base.N > 0 && !vr.resume {
    73  			err = io.ErrUnexpectedEOF
    74  		}
    75  		vr.err = err
    76  	}
    77  	return
    78  }
    79  
    80  // Verify checks for remaining unread content and verifies the read content against the digest
    81  func (vr *VerifyReader) Verify() error {
    82  	if vr.verified {
    83  		return nil
    84  	}
    85  	if vr.err == nil {
    86  		if vr.base.N > 0 {
    87  			return errEarlyVerify
    88  		}
    89  	} else if vr.err != io.EOF {
    90  		return vr.err
    91  	}
    92  
    93  	if err := ensureEOF(vr.base.R); err != nil {
    94  		vr.err = err
    95  		return vr.err
    96  	}
    97  	if !vr.verifier.Verified() {
    98  		vr.err = ErrMismatchedDigest
    99  		return vr.err
   100  	}
   101  
   102  	vr.verified = true
   103  	vr.err = io.EOF
   104  	return nil
   105  }
   106  
   107  // NewVerifyReader wraps r for reading content with verification against desc.
   108  func NewVerifyReader(r io.Reader, desc ocispec.Descriptor) *VerifyReader {
   109  	var verifier digest.Verifier
   110  
   111  	// Ignore error, if we can't parse it assume zero
   112  	offset, _ := strconv.ParseInt(desc.Annotations[spec.AnnotationResumeOffset], 10, 64)
   113  
   114  	// All error cases below fall through to create a digest.Verifier
   115  	if offset > 0 {
   116  		// Attempt to resume
   117  		newHash, err := DecodeHash(desc.Annotations[spec.AnnotationResumeHash], desc.Digest)
   118  		if err == nil {
   119  			// Create a verifier with our in-progress hash and the final digest
   120  			verifier = hashVerifier{
   121  				hash:   newHash,
   122  				digest: desc.Digest,
   123  			}
   124  		}
   125  	}
   126  	if verifier == nil {
   127  		// Did not get a verifier for resume, make a new empty one
   128  		verifier = desc.Digest.Verifier()
   129  	}
   130  
   131  	lr := &io.LimitedReader{
   132  		R: io.TeeReader(r, verifier),
   133  		N: desc.Size,
   134  	}
   135  	return &VerifyReader{
   136  		base:     lr,
   137  		verifier: verifier,
   138  		resume:   offset > 0,
   139  	}
   140  }
   141  
   142  // ReadAll safely reads the content described by the descriptor.
   143  // The read content is verified against the size and the digest
   144  // using a VerifyReader.
   145  func ReadAll(r io.Reader, desc ocispec.Descriptor) ([]byte, error) {
   146  	if desc.Size < 0 {
   147  		return nil, ErrInvalidDescriptorSize
   148  	}
   149  	buf := make([]byte, desc.Size)
   150  
   151  	vr := NewVerifyReader(r, desc)
   152  	if n, err := io.ReadFull(vr, buf); err != nil {
   153  		if errors.Is(err, io.ErrUnexpectedEOF) {
   154  			if err == io.ErrUnexpectedEOF && vr.base.N > 0 && vr.resume {
   155  				// In resume mode the buffers may not be exact
   156  				err = io.EOF
   157  			}
   158  			return nil, fmt.Errorf("read failed: expected content size of %d, got %d, for digest %s: %w", desc.Size, n, desc.Digest.String(), err)
   159  		}
   160  		return nil, fmt.Errorf("read failed: %w", err)
   161  	}
   162  	if err := vr.Verify(); err != nil {
   163  		return nil, err
   164  	}
   165  	return buf, nil
   166  }
   167  
   168  // ensureEOF ensures the read operation ends with an EOF and no
   169  // trailing data is present.
   170  func ensureEOF(r io.Reader) error {
   171  	var peek [1]byte
   172  	_, err := io.ReadFull(r, peek[:])
   173  	if err != io.EOF {
   174  		return ErrTrailingData
   175  	}
   176  	return nil
   177  }
   178  
   179  // Decode a Hash object
   180  func DecodeHash(encHash string, d digest.Digest) (hash.Hash, error) {
   181  	// Recover hash of existing data
   182  	state, err := hex.DecodeString(encHash)
   183  	if err == nil {
   184  		// Recover Hash object
   185  		newHash := d.Algorithm().Hash()
   186  		unmarshaler, ok := newHash.(encoding.BinaryUnmarshaler)
   187  		if ok {
   188  			if err := unmarshaler.UnmarshalBinary(state); err == nil {
   189  				return newHash, nil
   190  			}
   191  		}
   192  	}
   193  	// Return new empty Hash with error
   194  	return sha256.New(), err
   195  }
   196  
   197  // Encode a Hash object
   198  func EncodeHash(h hash.Hash) (string, error) {
   199  	// Yay! Serialize the hash to pass to the Verifier
   200  	marshaler, ok := h.(encoding.BinaryMarshaler)
   201  	if ok {
   202  		state, err := marshaler.MarshalBinary()
   203  		if err == nil {
   204  			// Save the new Hash as an Annotation to pass to the Verifier
   205  			buf := make([]byte, hex.EncodedLen(len(state)))
   206  			hex.Encode(buf, state)
   207  			return string(buf), nil
   208  		}
   209  	}
   210  	return "", fmt.Errorf("error encoding Hash")
   211  }