storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/hash/reader.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2017 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package hash 18 19 import ( 20 "bytes" 21 "encoding/base64" 22 "encoding/hex" 23 "errors" 24 "hash" 25 "io" 26 27 "storj.io/minio/pkg/etag" 28 ) 29 30 // A Reader wraps an io.Reader and computes the MD5 checksum 31 // of the read content as ETag. Optionally, it also computes 32 // the SHA256 checksum of the content. 33 // 34 // If the reference values for the ETag and content SHA26 35 // are not empty then it will check whether the computed 36 // match the reference values. 37 type Reader struct { 38 src io.Reader 39 bytesRead int64 40 41 size int64 42 actualSize int64 43 44 checksum etag.ETag 45 contentSHA256 []byte 46 47 sha256 hash.Hash 48 } 49 50 // NewReader returns a new Reader that wraps src and computes 51 // MD5 checksum of everything it reads as ETag. 52 // 53 // It also computes the SHA256 checksum of everything it reads 54 // if sha256Hex is not the empty string. 55 // 56 // If size resp. actualSize is unknown at the time of calling 57 // NewReader then it should be set to -1. 58 // 59 // NewReader may try merge the given size, MD5 and SHA256 values 60 // into src - if src is a Reader - to avoid computing the same 61 // checksums multiple times. 62 func NewReader(src io.Reader, size int64, md5Hex, sha256Hex string, actualSize int64) (*Reader, error) { 63 MD5, err := hex.DecodeString(md5Hex) 64 if err != nil { 65 return nil, BadDigest{ // TODO(aead): Return an error that indicates that an invalid ETag has been specified 66 ExpectedMD5: md5Hex, 67 CalculatedMD5: "", 68 } 69 } 70 SHA256, err := hex.DecodeString(sha256Hex) 71 if err != nil { 72 return nil, SHA256Mismatch{ // TODO(aead): Return an error that indicates that an invalid Content-SHA256 has been specified 73 ExpectedSHA256: sha256Hex, 74 CalculatedSHA256: "", 75 } 76 } 77 78 // Merge the size, MD5 and SHA256 values if src is a Reader. 79 // The size may be set to -1 by callers if unknown. 80 if r, ok := src.(*Reader); ok { 81 if r.bytesRead > 0 { 82 return nil, errors.New("hash: already read from hash reader") 83 } 84 if len(r.checksum) != 0 && len(MD5) != 0 && !etag.Equal(r.checksum, etag.ETag(MD5)) { 85 return nil, BadDigest{ 86 ExpectedMD5: r.checksum.String(), 87 CalculatedMD5: md5Hex, 88 } 89 } 90 if len(r.contentSHA256) != 0 && len(SHA256) != 0 && !bytes.Equal(r.contentSHA256, SHA256) { 91 return nil, SHA256Mismatch{ 92 ExpectedSHA256: hex.EncodeToString(r.contentSHA256), 93 CalculatedSHA256: sha256Hex, 94 } 95 } 96 if r.size >= 0 && size >= 0 && r.size != size { 97 return nil, ErrSizeMismatch{Want: r.size, Got: size} 98 } 99 100 r.checksum = etag.ETag(MD5) 101 r.contentSHA256 = SHA256 102 if r.size < 0 && size >= 0 { 103 r.src = etag.Wrap(io.LimitReader(r.src, size), r.src) 104 r.size = size 105 } 106 if r.actualSize <= 0 && actualSize >= 0 { 107 r.actualSize = actualSize 108 } 109 return r, nil 110 } 111 112 var hash hash.Hash 113 if size >= 0 { 114 src = io.LimitReader(src, size) 115 } 116 if len(SHA256) != 0 { 117 hash = newSHA256() 118 } 119 return &Reader{ 120 src: etag.NewReader(src, etag.ETag(MD5)), 121 size: size, 122 actualSize: actualSize, 123 checksum: etag.ETag(MD5), 124 contentSHA256: SHA256, 125 sha256: hash, 126 }, nil 127 } 128 129 func (r *Reader) Read(p []byte) (int, error) { 130 n, err := r.src.Read(p) 131 r.bytesRead += int64(n) 132 if r.sha256 != nil { 133 r.sha256.Write(p[:n]) 134 } 135 136 if err == io.EOF { // Verify content SHA256, if set. 137 if r.sha256 != nil { 138 if sum := r.sha256.Sum(nil); !bytes.Equal(r.contentSHA256, sum) { 139 return n, SHA256Mismatch{ 140 ExpectedSHA256: hex.EncodeToString(r.contentSHA256), 141 CalculatedSHA256: hex.EncodeToString(sum), 142 } 143 } 144 } 145 } 146 if err != nil && err != io.EOF { 147 if v, ok := err.(etag.VerifyError); ok { 148 return n, BadDigest{ 149 ExpectedMD5: v.Expected.String(), 150 CalculatedMD5: v.Computed.String(), 151 } 152 } 153 } 154 return n, err 155 } 156 157 // Size returns the absolute number of bytes the Reader 158 // will return during reading. It returns -1 for unlimited 159 // data. 160 func (r *Reader) Size() int64 { return r.size } 161 162 // ActualSize returns the pre-modified size of the object. 163 // DecompressedSize - For compressed objects. 164 func (r *Reader) ActualSize() int64 { return r.actualSize } 165 166 // ETag returns the ETag computed by an underlying etag.Tagger. 167 // If the underlying io.Reader does not implement etag.Tagger 168 // it returns nil. 169 func (r *Reader) ETag() etag.ETag { 170 if t, ok := r.src.(etag.Tagger); ok { 171 return t.ETag() 172 } 173 return nil 174 } 175 176 // MD5 returns the MD5 checksum set as reference value. 177 // 178 // It corresponds to the checksum that is expected and 179 // not the actual MD5 checksum of the content. 180 // Therefore, refer to MD5Current. 181 func (r *Reader) MD5() []byte { 182 return r.checksum 183 } 184 185 // MD5Current returns the MD5 checksum of the content 186 // that has been read so far. 187 // 188 // Calling MD5Current again after reading more data may 189 // result in a different checksum. 190 func (r *Reader) MD5Current() []byte { 191 return r.ETag()[:] 192 } 193 194 // SHA256 returns the SHA256 checksum set as reference value. 195 // 196 // It corresponds to the checksum that is expected and 197 // not the actual SHA256 checksum of the content. 198 func (r *Reader) SHA256() []byte { 199 return r.contentSHA256 200 } 201 202 // MD5HexString returns a hex representation of the MD5. 203 func (r *Reader) MD5HexString() string { 204 return hex.EncodeToString(r.checksum) 205 } 206 207 // MD5Base64String returns a hex representation of the MD5. 208 func (r *Reader) MD5Base64String() string { 209 return base64.StdEncoding.EncodeToString(r.checksum) 210 } 211 212 // SHA256HexString returns a hex representation of the SHA256. 213 func (r *Reader) SHA256HexString() string { 214 return hex.EncodeToString(r.contentSHA256) 215 } 216 217 var _ io.Closer = (*Reader)(nil) // compiler check 218 219 // Close and release resources. 220 func (r *Reader) Close() error { return nil }