mirror of
https://github.com/minio/minio
synced 2024-10-15 12:22:36 +00:00
dd2542e96c
Original work here, #18474, refixed and updated.
201 lines
4.8 KiB
Go
201 lines
4.8 KiB
Go
// Copyright (c) 2015-2021 MinIO, Inc.
|
|
//
|
|
// This file is part of MinIO Object Storage stack
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package etag
|
|
|
|
import (
|
|
"context"
|
|
"crypto/md5"
|
|
"fmt"
|
|
"hash"
|
|
"io"
|
|
)
|
|
|
|
// Tagger is the interface that wraps the basic ETag method.
|
|
type Tagger interface {
|
|
ETag() ETag
|
|
}
|
|
|
|
type wrapReader struct {
|
|
io.Reader
|
|
Tagger
|
|
}
|
|
|
|
var _ Tagger = wrapReader{} // compiler check
|
|
|
|
// ETag returns the ETag of the underlying Tagger.
|
|
func (r wrapReader) ETag() ETag {
|
|
if r.Tagger == nil {
|
|
return nil
|
|
}
|
|
return r.Tagger.ETag()
|
|
}
|
|
|
|
// Wrap returns an io.Reader that reads from the wrapped
|
|
// io.Reader and implements the Tagger interaface.
|
|
//
|
|
// If content implements Tagger then the returned Reader
|
|
// returns ETag of the content. Otherwise, it returns
|
|
// nil as ETag.
|
|
//
|
|
// Wrap provides an adapter for io.Reader implementations
|
|
// that don't implement the Tagger interface.
|
|
// It is mainly used to provide a high-level io.Reader
|
|
// access to the ETag computed by a low-level io.Reader:
|
|
//
|
|
// content := etag.NewReader(r.Body, nil)
|
|
//
|
|
// compressedContent := Compress(content)
|
|
// encryptedContent := Encrypt(compressedContent)
|
|
//
|
|
// // Now, we need an io.Reader that can access
|
|
// // the ETag computed over the content.
|
|
// reader := etag.Wrap(encryptedContent, content)
|
|
func Wrap(wrapped, content io.Reader) io.Reader {
|
|
if t, ok := content.(Tagger); ok {
|
|
return wrapReader{
|
|
Reader: wrapped,
|
|
Tagger: t,
|
|
}
|
|
}
|
|
return wrapReader{
|
|
Reader: wrapped,
|
|
}
|
|
}
|
|
|
|
// A Reader wraps an io.Reader and computes the
|
|
// MD5 checksum of the read content as ETag.
|
|
//
|
|
// Optionally, a Reader can also verify that
|
|
// the computed ETag matches an expected value.
|
|
// Therefore, it compares both ETags once the
|
|
// underlying io.Reader returns io.EOF.
|
|
// If the computed ETag does not match the
|
|
// expected ETag then Read returns a VerifyError.
|
|
//
|
|
// Reader implements the Tagger interface.
|
|
type Reader struct {
|
|
src io.Reader
|
|
|
|
md5 hash.Hash
|
|
checksum ETag
|
|
|
|
readN int64
|
|
}
|
|
|
|
// NewReader returns a new Reader that computes the
|
|
// MD5 checksum of the content read from r as ETag.
|
|
//
|
|
// If the provided etag is not nil the returned
|
|
// Reader compares the etag with the computed
|
|
// MD5 sum once the r returns io.EOF.
|
|
func NewReader(ctx context.Context, r io.Reader, etag ETag, forceMD5 []byte) *Reader {
|
|
if er, ok := r.(*Reader); ok {
|
|
if er.readN == 0 && Equal(etag, er.checksum) {
|
|
return er
|
|
}
|
|
}
|
|
if len(forceMD5) != 0 {
|
|
return &Reader{
|
|
src: r,
|
|
md5: NewUUIDHash(forceMD5),
|
|
checksum: etag,
|
|
}
|
|
}
|
|
return &Reader{
|
|
src: r,
|
|
md5: md5.New(),
|
|
checksum: etag,
|
|
}
|
|
}
|
|
|
|
// Read reads up to len(p) bytes from the underlying
|
|
// io.Reader as specified by the io.Reader interface.
|
|
func (r *Reader) Read(p []byte) (int, error) {
|
|
n, err := r.src.Read(p)
|
|
r.readN += int64(n)
|
|
r.md5.Write(p[:n])
|
|
|
|
if err == io.EOF && len(r.checksum) != 0 {
|
|
if etag := r.ETag(); !Equal(etag, r.checksum) {
|
|
return n, VerifyError{
|
|
Expected: r.checksum,
|
|
Computed: etag,
|
|
}
|
|
}
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
// ETag returns the ETag of all the content read
|
|
// so far. Reading more content changes the MD5
|
|
// checksum. Therefore, calling ETag multiple
|
|
// times may return different results.
|
|
func (r *Reader) ETag() ETag {
|
|
sum := r.md5.Sum(nil)
|
|
return ETag(sum)
|
|
}
|
|
|
|
// VerifyError is an error signaling that a
|
|
// computed ETag does not match an expected
|
|
// ETag.
|
|
type VerifyError struct {
|
|
Expected ETag
|
|
Computed ETag
|
|
}
|
|
|
|
func (v VerifyError) Error() string {
|
|
return fmt.Sprintf("etag: expected ETag %q does not match computed ETag %q", v.Expected, v.Computed)
|
|
}
|
|
|
|
// UUIDHash - use uuid to make md5sum
|
|
type UUIDHash struct {
|
|
uuid []byte
|
|
}
|
|
|
|
// Write - implement hash.Hash Write
|
|
func (u UUIDHash) Write(p []byte) (n int, err error) {
|
|
return len(p), nil
|
|
}
|
|
|
|
// Sum - implement md5.Sum
|
|
func (u UUIDHash) Sum(b []byte) []byte {
|
|
return u.uuid
|
|
}
|
|
|
|
// Reset - implement hash.Hash Reset
|
|
func (u UUIDHash) Reset() {
|
|
return
|
|
}
|
|
|
|
// Size - implement hash.Hash Size
|
|
func (u UUIDHash) Size() int {
|
|
return len(u.uuid)
|
|
}
|
|
|
|
// BlockSize - implement hash.Hash BlockSize
|
|
func (u UUIDHash) BlockSize() int {
|
|
return md5.BlockSize
|
|
}
|
|
|
|
var _ hash.Hash = &UUIDHash{}
|
|
|
|
// NewUUIDHash - new UUIDHash
|
|
func NewUUIDHash(uuid []byte) *UUIDHash {
|
|
return &UUIDHash{uuid: uuid}
|
|
}
|