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 }