github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/etag/reader.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package etag
    19  
    20  import (
    21  	"context"
    22  	"crypto/md5"
    23  	"fmt"
    24  	"hash"
    25  	"io"
    26  )
    27  
    28  // Tagger is the interface that wraps the basic ETag method.
    29  type Tagger interface {
    30  	ETag() ETag
    31  }
    32  
    33  type wrapReader struct {
    34  	io.Reader
    35  	Tagger
    36  }
    37  
    38  var _ Tagger = wrapReader{} // compiler check
    39  
    40  // ETag returns the ETag of the underlying Tagger.
    41  func (r wrapReader) ETag() ETag {
    42  	if r.Tagger == nil {
    43  		return nil
    44  	}
    45  	return r.Tagger.ETag()
    46  }
    47  
    48  // Wrap returns an io.Reader that reads from the wrapped
    49  // io.Reader and implements the Tagger interaface.
    50  //
    51  // If content implements Tagger then the returned Reader
    52  // returns ETag of the content. Otherwise, it returns
    53  // nil as ETag.
    54  //
    55  // Wrap provides an adapter for io.Reader implementations
    56  // that don't implement the Tagger interface.
    57  // It is mainly used to provide a high-level io.Reader
    58  // access to the ETag computed by a low-level io.Reader:
    59  //
    60  //	content := etag.NewReader(r.Body, nil)
    61  //
    62  //	compressedContent := Compress(content)
    63  //	encryptedContent := Encrypt(compressedContent)
    64  //
    65  //	// Now, we need an io.Reader that can access
    66  //	// the ETag computed over the content.
    67  //	reader := etag.Wrap(encryptedContent, content)
    68  func Wrap(wrapped, content io.Reader) io.Reader {
    69  	if t, ok := content.(Tagger); ok {
    70  		return wrapReader{
    71  			Reader: wrapped,
    72  			Tagger: t,
    73  		}
    74  	}
    75  	return wrapReader{
    76  		Reader: wrapped,
    77  	}
    78  }
    79  
    80  // A Reader wraps an io.Reader and computes the
    81  // MD5 checksum of the read content as ETag.
    82  //
    83  // Optionally, a Reader can also verify that
    84  // the computed ETag matches an expected value.
    85  // Therefore, it compares both ETags once the
    86  // underlying io.Reader returns io.EOF.
    87  // If the computed ETag does not match the
    88  // expected ETag then Read returns a VerifyError.
    89  //
    90  // Reader implements the Tagger interface.
    91  type Reader struct {
    92  	src io.Reader
    93  
    94  	md5      hash.Hash
    95  	checksum ETag
    96  
    97  	readN int64
    98  }
    99  
   100  // NewReader returns a new Reader that computes the
   101  // MD5 checksum of the content read from r as ETag.
   102  //
   103  // If the provided etag is not nil the returned
   104  // Reader compares the etag with the computed
   105  // MD5 sum once the r returns io.EOF.
   106  func NewReader(ctx context.Context, r io.Reader, etag ETag, forceMD5 []byte) *Reader {
   107  	if er, ok := r.(*Reader); ok {
   108  		if er.readN == 0 && Equal(etag, er.checksum) {
   109  			return er
   110  		}
   111  	}
   112  	if len(forceMD5) != 0 {
   113  		return &Reader{
   114  			src:      r,
   115  			md5:      NewUUIDHash(forceMD5),
   116  			checksum: etag,
   117  		}
   118  	}
   119  	return &Reader{
   120  		src:      r,
   121  		md5:      md5.New(),
   122  		checksum: etag,
   123  	}
   124  }
   125  
   126  // Read reads up to len(p) bytes from the underlying
   127  // io.Reader as specified by the io.Reader interface.
   128  func (r *Reader) Read(p []byte) (int, error) {
   129  	n, err := r.src.Read(p)
   130  	r.readN += int64(n)
   131  	r.md5.Write(p[:n])
   132  
   133  	if err == io.EOF && len(r.checksum) != 0 {
   134  		if etag := r.ETag(); !Equal(etag, r.checksum) {
   135  			return n, VerifyError{
   136  				Expected: r.checksum,
   137  				Computed: etag,
   138  			}
   139  		}
   140  	}
   141  	return n, err
   142  }
   143  
   144  // ETag returns the ETag of all the content read
   145  // so far. Reading more content changes the MD5
   146  // checksum. Therefore, calling ETag multiple
   147  // times may return different results.
   148  func (r *Reader) ETag() ETag {
   149  	sum := r.md5.Sum(nil)
   150  	return ETag(sum)
   151  }
   152  
   153  // VerifyError is an error signaling that a
   154  // computed ETag does not match an expected
   155  // ETag.
   156  type VerifyError struct {
   157  	Expected ETag
   158  	Computed ETag
   159  }
   160  
   161  func (v VerifyError) Error() string {
   162  	return fmt.Sprintf("etag: expected ETag %q does not match computed ETag %q", v.Expected, v.Computed)
   163  }
   164  
   165  // UUIDHash - use uuid to make md5sum
   166  type UUIDHash struct {
   167  	uuid []byte
   168  }
   169  
   170  // Write -  implement hash.Hash Write
   171  func (u UUIDHash) Write(p []byte) (n int, err error) {
   172  	return len(p), nil
   173  }
   174  
   175  // Sum -  implement md5.Sum
   176  func (u UUIDHash) Sum(b []byte) []byte {
   177  	return u.uuid
   178  }
   179  
   180  // Reset -  implement hash.Hash Reset
   181  func (u UUIDHash) Reset() {
   182  	return
   183  }
   184  
   185  // Size -  implement hash.Hash Size
   186  func (u UUIDHash) Size() int {
   187  	return len(u.uuid)
   188  }
   189  
   190  // BlockSize -  implement hash.Hash BlockSize
   191  func (u UUIDHash) BlockSize() int {
   192  	return md5.BlockSize
   193  }
   194  
   195  var _ hash.Hash = &UUIDHash{}
   196  
   197  // NewUUIDHash - new UUIDHash
   198  func NewUUIDHash(uuid []byte) *UUIDHash {
   199  	return &UUIDHash{uuid: uuid}
   200  }