storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/etag/reader.go (about) 1 // MinIO Cloud Storage, (C) 2021 MinIO, Inc. 2 // 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 package etag 16 17 import ( 18 "crypto/md5" 19 "fmt" 20 "hash" 21 "io" 22 ) 23 24 // Tagger is the interface that wraps the basic ETag method. 25 type Tagger interface { 26 ETag() ETag 27 } 28 29 type wrapReader struct { 30 io.Reader 31 Tagger 32 } 33 34 // ETag returns the ETag of the underlying Tagger. 35 func (r *wrapReader) ETag() ETag { 36 if r.Tagger == nil { 37 return nil 38 } 39 return r.Tagger.ETag() 40 } 41 42 // Wrap returns an io.Reader that reads from the wrapped 43 // io.Reader and implements the Tagger interaface. 44 // 45 // If content implements Tagger then the returned Reader 46 // returns ETag of the content. Otherwise, it returns 47 // nil as ETag. 48 // 49 // Wrap provides an adapter for io.Reader implemetations 50 // that don't implement the Tagger interface. 51 // It is mainly used to provide a high-level io.Reader 52 // access to the ETag computed by a low-level io.Reader: 53 // 54 // content := etag.NewReader(r.Body, nil) 55 // 56 // compressedContent := Compress(content) 57 // encryptedContent := Encrypt(compressedContent) 58 // 59 // // Now, we need an io.Reader that can access 60 // // the ETag computed over the content. 61 // reader := etag.Wrap(encryptedContent, content) 62 // 63 func Wrap(wrapped, content io.Reader) io.Reader { 64 if t, ok := content.(Tagger); ok { 65 return wrapReader{ 66 Reader: wrapped, 67 Tagger: t, 68 } 69 } 70 return wrapReader{ 71 Reader: wrapped, 72 } 73 } 74 75 // A Reader wraps an io.Reader and computes the 76 // MD5 checksum of the read content as ETag. 77 // 78 // Optionally, a Reader can also verify that 79 // the computed ETag matches an expected value. 80 // Therefore, it compares both ETags once the 81 // underlying io.Reader returns io.EOF. 82 // If the computed ETag does not match the 83 // expected ETag then Read returns a VerifyError. 84 // 85 // Reader implements the Tagger interface. 86 type Reader struct { 87 src io.Reader 88 89 md5 hash.Hash 90 checksum ETag 91 92 readN int64 93 } 94 95 // NewReader returns a new Reader that computes the 96 // MD5 checksum of the content read from r as ETag. 97 // 98 // If the provided etag is not nil the returned 99 // Reader compares the etag with the computed 100 // MD5 sum once the r returns io.EOF. 101 func NewReader(r io.Reader, etag ETag) *Reader { 102 if er, ok := r.(*Reader); ok { 103 if er.readN == 0 && Equal(etag, er.checksum) { 104 return er 105 } 106 } 107 return &Reader{ 108 src: r, 109 md5: md5.New(), 110 checksum: etag, 111 } 112 } 113 114 // Read reads up to len(p) bytes from the underlying 115 // io.Reader as specified by the io.Reader interface. 116 func (r *Reader) Read(p []byte) (int, error) { 117 n, err := r.src.Read(p) 118 r.readN += int64(n) 119 r.md5.Write(p[:n]) 120 121 if err == io.EOF && len(r.checksum) != 0 { 122 if etag := r.ETag(); !Equal(etag, r.checksum) { 123 return n, VerifyError{ 124 Expected: r.checksum, 125 Computed: etag, 126 } 127 } 128 } 129 return n, err 130 } 131 132 // ETag returns the ETag of all the content read 133 // so far. Reading more content changes the MD5 134 // checksum. Therefore, calling ETag multiple 135 // times may return different results. 136 func (r *Reader) ETag() ETag { 137 sum := r.md5.Sum(nil) 138 return ETag(sum) 139 } 140 141 // VerifyError is an error signaling that a 142 // computed ETag does not match an expected 143 // ETag. 144 type VerifyError struct { 145 Expected ETag 146 Computed ETag 147 } 148 149 func (v VerifyError) Error() string { 150 return fmt.Sprintf("etag: expected ETag %q does not match computed ETag %q", v.Expected, v.Computed) 151 }