mirror of
https://github.com/pgsty/minio.git
synced 2026-08-14 02:33:16 +03:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c1a49be639 | |||
| 03172b89e2 | |||
| b517c791e9 | |||
| 14aef52004 | |||
| 67b20125e4 | |||
| d4b822d697 | |||
| 1b63291ee2 |
@@ -1,7 +1,6 @@
|
||||
.git
|
||||
.github
|
||||
docs
|
||||
CREDITS
|
||||
default.etcd
|
||||
browser
|
||||
*.gz
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ ARG TARGETARCH
|
||||
LABEL name="MinIO" \
|
||||
vendor="MinIO Inc <dev@min.io>" \
|
||||
maintainer="MinIO Inc <dev@min.io>" \
|
||||
version="RELEASE.2021-02-19T04-38-02Z" \
|
||||
release="RELEASE.2021-02-19T04-38-02Z" \
|
||||
version="RELEASE.2021-02-23T20-05-01Z" \
|
||||
release="RELEASE.2021-02-23T20-05-01Z" \
|
||||
summary="MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service." \
|
||||
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."
|
||||
|
||||
|
||||
+1
-2
@@ -459,8 +459,7 @@ func isReqAuthenticated(ctx context.Context, r *http.Request, region string, sty
|
||||
|
||||
// Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present.
|
||||
// The verification happens implicit during reading.
|
||||
reader, err := hash.NewReader(r.Body, -1, hex.EncodeToString(contentMD5),
|
||||
hex.EncodeToString(contentSHA256), -1, globalCLIContext.StrictS3Compat)
|
||||
reader, err := hash.NewReader(r.Body, -1, hex.EncodeToString(contentMD5), hex.EncodeToString(contentSHA256), -1)
|
||||
if err != nil {
|
||||
return toAPIErrorCode(ctx, err)
|
||||
}
|
||||
|
||||
+1
-1
@@ -17,13 +17,13 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
"github.com/minio/highwayhash"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
|
||||
+9
-10
@@ -20,7 +20,6 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
@@ -28,7 +27,7 @@ import (
|
||||
func testBitrotReaderWriterAlgo(t *testing.T, bitrotAlgo BitrotAlgorithm) {
|
||||
tmpDir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
@@ -46,35 +45,35 @@ func testBitrotReaderWriterAlgo(t *testing.T, bitrotAlgo BitrotAlgorithm) {
|
||||
|
||||
_, err = writer.Write([]byte("aaaaaaaaaa"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = writer.Write([]byte("aaaaaaaaaa"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = writer.Write([]byte("aaaaaaaaaa"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = writer.Write([]byte("aaaaa"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
writer.(io.Closer).Close()
|
||||
|
||||
reader := newBitrotReader(disk, nil, volume, filePath, 35, bitrotAlgo, bitrotWriterSum(writer), 10)
|
||||
b := make([]byte, 10)
|
||||
if _, err = reader.ReadAt(b, 0); err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = reader.ReadAt(b, 10); err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = reader.ReadAt(b, 20); err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = reader.ReadAt(b[:5], 30); err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -917,7 +917,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
hashReader, err := hash.NewReader(fileBody, fileSize, "", "", fileSize, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err := hash.NewReader(fileBody, fileSize, "", "", fileSize)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
@@ -964,7 +964,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
}
|
||||
info := ObjectInfo{Size: fileSize}
|
||||
// do not try to verify encrypted content
|
||||
hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", fileSize, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", fileSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
|
||||
@@ -692,7 +692,7 @@ func restoreTransitionedObject(ctx context.Context, bucket, object string, objAP
|
||||
return err
|
||||
}
|
||||
defer gr.Close()
|
||||
hashReader, err := hash.NewReader(gr, objInfo.Size, "", "", objInfo.Size, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err := hash.NewReader(gr, objInfo.Size, "", "", objInfo.Size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
@@ -32,7 +33,6 @@ import (
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
"github.com/minio/minio/pkg/bucket/versioning"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -64,7 +64,7 @@ func deleteConfig(ctx context.Context, objAPI objectDeleter, configFile string)
|
||||
}
|
||||
|
||||
func saveConfig(ctx context.Context, objAPI ObjectLayer, configFile string, data []byte) error {
|
||||
hashReader, err := hash.NewReader(bytes.NewReader(data), int64(len(data)), "", getSHA256Hash(data), int64(len(data)), globalCLIContext.StrictS3Compat)
|
||||
hashReader, err := hash.NewReader(bytes.NewReader(data), int64(len(data)), "", getSHA256Hash(data), int64(len(data)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+1
-1
@@ -19,13 +19,13 @@ import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"path"
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
"github.com/minio/sio"
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -19,13 +19,13 @@ import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
"github.com/minio/sio"
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -128,7 +128,7 @@ func runDataScanner(ctx context.Context, objAPI ObjectLayer) {
|
||||
nextBloomCycle++
|
||||
var tmp [8]byte
|
||||
binary.LittleEndian.PutUint64(tmp[:], nextBloomCycle)
|
||||
r, err := hash.NewReader(bytes.NewReader(tmp[:]), int64(len(tmp)), "", "", int64(len(tmp)), false)
|
||||
r, err := hash.NewReader(bytes.NewReader(tmp[:]), int64(len(tmp)), "", "", int64(len(tmp)))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
continue
|
||||
|
||||
@@ -513,7 +513,7 @@ func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string)
|
||||
}()
|
||||
defer pr.Close()
|
||||
|
||||
r, err := hash.NewReader(pr, -1, "", "", -1, false)
|
||||
r, err := hash.NewReader(pr, -1, "", "", -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan
|
||||
continue
|
||||
}
|
||||
size := int64(len(dataUsageJSON))
|
||||
r, err := hash.NewReader(bytes.NewReader(dataUsageJSON), size, "", "", size, false)
|
||||
r, err := hash.NewReader(bytes.NewReader(dataUsageJSON), size, "", "", size)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
continue
|
||||
|
||||
+1
-1
@@ -705,7 +705,7 @@ func (c *cacheObjects) uploadObject(ctx context.Context, oi ObjectInfo) {
|
||||
if st == CommitComplete || st.String() == "" {
|
||||
return
|
||||
}
|
||||
hashReader, err := hash.NewReader(cReader, oi.Size, "", "", oi.Size, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err := hash.NewReader(cReader, oi.Size, "", "", oi.Size)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"bufio"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
@@ -33,7 +34,6 @@ import (
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
"github.com/minio/sio"
|
||||
)
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -28,7 +29,6 @@ import (
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/bucket/replication"
|
||||
"github.com/minio/minio/pkg/sync/errgroup"
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
const erasureAlgorithm = "rs-vandermonde"
|
||||
|
||||
@@ -18,6 +18,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -31,8 +32,8 @@ import (
|
||||
"github.com/minio/minio/cmd/config/storageclass"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/color"
|
||||
xioutil "github.com/minio/minio/pkg/ioutil"
|
||||
"github.com/minio/minio/pkg/sync/errgroup"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -156,7 +157,7 @@ func newFormatErasureV3(numSets int, setLen int) *formatErasureV3 {
|
||||
// successfully the version only if the backend is Erasure.
|
||||
func formatGetBackendErasureVersion(formatPath string) (string, error) {
|
||||
meta := &formatMetaV1{}
|
||||
b, err := ioutil.ReadFile(formatPath)
|
||||
b, err := xioutil.ReadFile(formatPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -218,7 +219,7 @@ func formatErasureMigrateV1ToV2(export, version string) error {
|
||||
formatPath := pathJoin(export, minioMetaBucket, formatConfigFile)
|
||||
|
||||
formatV1 := &formatErasureV1{}
|
||||
b, err := ioutil.ReadFile(formatPath)
|
||||
b, err := xioutil.ReadFile(formatPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -251,7 +252,7 @@ func formatErasureMigrateV2ToV3(export, version string) error {
|
||||
|
||||
formatPath := pathJoin(export, minioMetaBucket, formatConfigFile)
|
||||
formatV2 := &formatErasureV2{}
|
||||
b, err := ioutil.ReadFile(formatPath)
|
||||
b, err := xioutil.ReadFile(formatPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
mioutil "github.com/minio/minio/pkg/ioutil"
|
||||
xioutil "github.com/minio/minio/pkg/ioutil"
|
||||
"github.com/minio/minio/pkg/trie"
|
||||
)
|
||||
|
||||
@@ -114,7 +114,7 @@ func (fs *FSObjects) backgroundAppend(ctx context.Context, bucket, object, uploa
|
||||
}
|
||||
|
||||
partPath := pathJoin(uploadIDDir, entry)
|
||||
err = mioutil.AppendFile(file.filePath, partPath, globalFSOSync)
|
||||
err = xioutil.AppendFile(file.filePath, partPath, globalFSOSync)
|
||||
if err != nil {
|
||||
reqInfo := logger.GetReqInfo(ctx).AppendTags("partPath", partPath)
|
||||
reqInfo.AppendTags("filepath", file.filePath)
|
||||
@@ -390,7 +390,7 @@ func (fs *FSObjects) GetMultipartInfo(ctx context.Context, bucket, object, uploa
|
||||
return minfo, toObjectErr(err, bucket, object)
|
||||
}
|
||||
|
||||
fsMetaBytes, err := ioutil.ReadFile(pathJoin(uploadIDDir, fs.metaJSONFile))
|
||||
fsMetaBytes, err := xioutil.ReadFile(pathJoin(uploadIDDir, fs.metaJSONFile))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return minfo, toObjectErr(err, bucket, object)
|
||||
@@ -700,7 +700,7 @@ func (fs *FSObjects) CompleteMultipartUpload(ctx context.Context, bucket string,
|
||||
GotETag: part.ETag,
|
||||
}
|
||||
}
|
||||
if err = mioutil.AppendFile(appendFilePath, pathJoin(uploadIDDir, partFile), globalFSOSync); err != nil {
|
||||
if err = xioutil.AppendFile(appendFilePath, pathJoin(uploadIDDir, partFile), globalFSOSync); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return oi, toObjectErr(err)
|
||||
}
|
||||
@@ -744,7 +744,7 @@ func (fs *FSObjects) CompleteMultipartUpload(ctx context.Context, bucket string,
|
||||
}()
|
||||
|
||||
// Read saved fs metadata for ongoing multipart.
|
||||
fsMetaBuf, err := ioutil.ReadFile(pathJoin(uploadIDDir, fs.metaJSONFile))
|
||||
fsMetaBuf, err := xioutil.ReadFile(pathJoin(uploadIDDir, fs.metaJSONFile))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return oi, toObjectErr(err, bucket, object)
|
||||
|
||||
+2
-1
@@ -40,6 +40,7 @@ import (
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
"github.com/minio/minio/pkg/color"
|
||||
xioutil "github.com/minio/minio/pkg/ioutil"
|
||||
"github.com/minio/minio/pkg/lock"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
"github.com/minio/minio/pkg/mimedb"
|
||||
@@ -323,7 +324,7 @@ func (fs *FSObjects) crawlBucket(ctx context.Context, bucket string, cache dataU
|
||||
// Load bucket info.
|
||||
cache, err = crawlDataFolder(ctx, fs.fsPath, cache, func(item crawlItem) (sizeSummary, error) {
|
||||
bucket, object := item.bucket, item.objectPath()
|
||||
fsMetaBytes, err := ioutil.ReadFile(pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fs.metaJSONFile))
|
||||
fsMetaBytes, err := xioutil.ReadFile(pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fs.metaJSONFile))
|
||||
if err != nil && !osIsNotExist(err) {
|
||||
if intDataUpdateTracker.debug {
|
||||
logger.Info(color.Green("crawlBucket:")+" object return unexpected error: %v/%v: %w", item.bucket, item.objectPath(), err)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@@ -46,7 +47,6 @@ import (
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
"github.com/minio/minio/pkg/bucket/policy/condition"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
|
||||
minio "github.com/minio/minio/cmd"
|
||||
)
|
||||
@@ -793,6 +793,10 @@ func (a *azureObjects) GetObjectNInfo(ctx context.Context, bucket, object string
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if startOffset != 0 || length != objInfo.Size {
|
||||
delete(objInfo.UserDefined, "Content-MD5")
|
||||
}
|
||||
|
||||
pr, pw := io.Pipe()
|
||||
go func() {
|
||||
err := a.GetObject(ctx, bucket, object, startOffset, length, pw, objInfo.InnerETag, opts)
|
||||
|
||||
@@ -168,7 +168,7 @@ func getGWMetadata(ctx context.Context, bucket, prefix string, gwMeta gwMetaV1)
|
||||
logger.LogIf(ctx, err)
|
||||
return nil, err
|
||||
}
|
||||
hashReader, err := hash.NewReader(bytes.NewReader(metadataBytes), int64(len(metadataBytes)), "", "", int64(len(metadataBytes)), false)
|
||||
hashReader, err := hash.NewReader(bytes.NewReader(metadataBytes), int64(len(metadataBytes)), "", "", int64(len(metadataBytes)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+1
-2
@@ -18,9 +18,8 @@ package cmd
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// getSHA256Hash returns SHA-256 hash in hex encoding of given data.
|
||||
|
||||
@@ -176,7 +176,7 @@ func (b *bucketMetacache) save(ctx context.Context) error {
|
||||
b.updated = false
|
||||
b.mu.Unlock()
|
||||
|
||||
hr, err := hash.NewReader(tmp, int64(tmp.Len()), "", "", int64(tmp.Len()), false)
|
||||
hr, err := hash.NewReader(tmp, int64(tmp.Len()), "", "", int64(tmp.Len()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -672,7 +672,7 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
|
||||
return nil
|
||||
}
|
||||
o.debugln(color.Green("listPath:")+" saving block", b.n, "to", o.objectPath(b.n))
|
||||
r, err := hash.NewReader(bytes.NewReader(b.data), int64(len(b.data)), "", "", int64(len(b.data)), false)
|
||||
r, err := hash.NewReader(bytes.NewReader(b.data), int64(len(b.data)), "", "", int64(len(b.data)))
|
||||
logger.LogIf(ctx, err)
|
||||
custom := b.headerKV()
|
||||
_, err = er.putObject(ctx, minioMetaBucket, o.objectPath(b.n), NewPutObjReader(r), ObjectOptions{
|
||||
|
||||
@@ -19,7 +19,6 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -30,6 +29,7 @@ import (
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
xioutil "github.com/minio/minio/pkg/ioutil"
|
||||
)
|
||||
|
||||
// WalkDirOptions provides options for WalkDir operations.
|
||||
@@ -91,7 +91,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
// Fast exit track to check if we are listing an object with
|
||||
// a trailing slash, this will avoid to list the object content.
|
||||
if HasSuffix(opts.BaseDir, SlashSeparator) {
|
||||
metadata, err := ioutil.ReadFile(pathJoin(volumeDir,
|
||||
metadata, err := xioutil.ReadFile(pathJoin(volumeDir,
|
||||
opts.BaseDir[:len(opts.BaseDir)-1]+globalDirSuffix,
|
||||
xlStorageFormatFile))
|
||||
if err == nil {
|
||||
@@ -151,7 +151,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
// If root was an object return it as such.
|
||||
if HasSuffix(entry, xlStorageFormatFile) {
|
||||
var meta metaCacheEntry
|
||||
meta.metadata, err = ioutil.ReadFile(pathJoin(volumeDir, current, entry))
|
||||
meta.metadata, err = xioutil.ReadFile(pathJoin(volumeDir, current, entry))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
continue
|
||||
@@ -166,7 +166,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
// Check legacy.
|
||||
if HasSuffix(entry, xlStorageFormatFileV1) {
|
||||
var meta metaCacheEntry
|
||||
meta.metadata, err = ioutil.ReadFile(pathJoin(volumeDir, current, entry))
|
||||
meta.metadata, err = xioutil.ReadFile(pathJoin(volumeDir, current, entry))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
continue
|
||||
@@ -213,7 +213,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
meta.name = meta.name[:len(meta.name)-1] + globalDirSuffixWithSlash
|
||||
}
|
||||
|
||||
meta.metadata, err = ioutil.ReadFile(pathJoin(volumeDir, meta.name, xlStorageFormatFile))
|
||||
meta.metadata, err = xioutil.ReadFile(pathJoin(volumeDir, meta.name, xlStorageFormatFile))
|
||||
switch {
|
||||
case err == nil:
|
||||
// It was an object
|
||||
@@ -222,7 +222,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
}
|
||||
out <- meta
|
||||
case osIsNotExist(err):
|
||||
meta.metadata, err = ioutil.ReadFile(pathJoin(volumeDir, meta.name, xlStorageFormatFileV1))
|
||||
meta.metadata, err = xioutil.ReadFile(pathJoin(volumeDir, meta.name, xlStorageFormatFileV1))
|
||||
if err == nil {
|
||||
// Maybe rename? Would make it inconsistent across disks though.
|
||||
// os.Rename(pathJoin(volumeDir, meta.name, xlStorageFormatFileV1), pathJoin(volumeDir, meta.name, xlStorageFormatFile))
|
||||
|
||||
+31
-33
@@ -48,6 +48,7 @@ import (
|
||||
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
"github.com/minio/minio/pkg/bucket/replication"
|
||||
"github.com/minio/minio/pkg/etag"
|
||||
"github.com/minio/minio/pkg/event"
|
||||
"github.com/minio/minio/pkg/handlers"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
@@ -1025,7 +1026,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
srcInfo.metadataOnly = false
|
||||
}
|
||||
|
||||
var reader io.Reader
|
||||
var reader io.Reader = gr
|
||||
|
||||
// Set the actual size to the compressed/decrypted size if encrypted.
|
||||
actualSize, err := srcInfo.GetActualSize()
|
||||
@@ -1057,15 +1058,15 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
// avoid copying them in target object.
|
||||
crypto.RemoveInternalEntries(srcInfo.UserDefined)
|
||||
|
||||
s2c := newS2CompressReader(gr, actualSize)
|
||||
s2c := newS2CompressReader(reader, actualSize)
|
||||
defer s2c.Close()
|
||||
reader = s2c
|
||||
reader = etag.Wrap(s2c, reader)
|
||||
length = -1
|
||||
} else {
|
||||
reader = gr
|
||||
}
|
||||
|
||||
srcInfo.Reader, err = hash.NewReader(reader, length, "", "", actualSize, globalCLIContext.StrictS3Compat)
|
||||
srcInfo.Reader, err = hash.NewReader(reader, length, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -1162,11 +1163,13 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
if isTargetEncrypted {
|
||||
reader, objEncKey, err = newEncryptReader(srcInfo.Reader, newKey, dstBucket, dstObject, encMetadata, sseS3)
|
||||
var encReader io.Reader
|
||||
encReader, objEncKey, err = newEncryptReader(srcInfo.Reader, newKey, dstBucket, dstObject, encMetadata, sseS3)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
reader = etag.Wrap(encReader, srcInfo.Reader)
|
||||
}
|
||||
|
||||
if isSourceEncrypted {
|
||||
@@ -1176,7 +1179,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
// do not try to verify encrypted content
|
||||
srcInfo.Reader, err = hash.NewReader(reader, targetSize, "", "", actualSize, globalCLIContext.StrictS3Compat)
|
||||
srcInfo.Reader, err = hash.NewReader(reader, targetSize, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -1457,13 +1460,12 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
var (
|
||||
md5hex = hex.EncodeToString(md5Bytes)
|
||||
sha256hex = ""
|
||||
reader io.Reader
|
||||
md5hex = hex.EncodeToString(md5Bytes)
|
||||
sha256hex = ""
|
||||
reader io.Reader = r.Body
|
||||
s3Err APIErrorCode
|
||||
putObject = objectAPI.PutObject
|
||||
)
|
||||
reader = r.Body
|
||||
|
||||
// Check if put is allowed
|
||||
if s3Err = isPutActionAllowed(ctx, rAuthType, bucket, object, r, iampolicy.PutObjectAction); s3Err != ErrNone {
|
||||
@@ -1509,13 +1511,12 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
actualSize := size
|
||||
|
||||
if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) && size > 0 {
|
||||
// Storing the compression metadata.
|
||||
metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV2
|
||||
metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(size, 10)
|
||||
|
||||
actualReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize, globalCLIContext.StrictS3Compat)
|
||||
actualReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -1524,13 +1525,13 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
// Set compression metrics.
|
||||
s2c := newS2CompressReader(actualReader, actualSize)
|
||||
defer s2c.Close()
|
||||
reader = s2c
|
||||
reader = etag.Wrap(s2c, actualReader)
|
||||
size = -1 // Since compressed size is un-predictable.
|
||||
md5hex = "" // Do not try to verify the content.
|
||||
sha256hex = ""
|
||||
}
|
||||
|
||||
hashReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -1601,7 +1602,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
// do not try to verify encrypted content
|
||||
hashReader, err = hash.NewReader(reader, wantSize, "", "", actualSize, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err = hash.NewReader(etag.Wrap(reader, hashReader), wantSize, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -1999,8 +2000,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||
return
|
||||
}
|
||||
|
||||
partInfo, err := core.PutObjectPart(ctx, dstBucket, dstObject, uploadID, partID,
|
||||
gr, length, "", "", dstOpts.ServerSideEncryption)
|
||||
partInfo, err := core.PutObjectPart(ctx, dstBucket, dstObject, uploadID, partID, gr, length, "", "", dstOpts.ServerSideEncryption)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -2015,7 +2015,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||
}
|
||||
|
||||
actualPartSize = length
|
||||
var reader io.Reader
|
||||
var reader io.Reader = etag.NewReader(gr, nil)
|
||||
|
||||
mi, err := objectAPI.GetMultipartInfo(ctx, dstBucket, dstObject, uploadID, dstOpts)
|
||||
if err != nil {
|
||||
@@ -2027,15 +2027,13 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||
_, isCompressed := mi.UserDefined[ReservedMetadataPrefix+"compression"]
|
||||
// Compress only if the compression is enabled during initial multipart.
|
||||
if isCompressed {
|
||||
s2c := newS2CompressReader(gr, actualPartSize)
|
||||
s2c := newS2CompressReader(reader, actualPartSize)
|
||||
defer s2c.Close()
|
||||
reader = s2c
|
||||
reader = etag.Wrap(s2c, reader)
|
||||
length = -1
|
||||
} else {
|
||||
reader = gr
|
||||
}
|
||||
|
||||
srcInfo.Reader, err = hash.NewReader(reader, length, "", "", actualPartSize, globalCLIContext.StrictS3Compat)
|
||||
srcInfo.Reader, err = hash.NewReader(reader, length, "", "", actualPartSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -2077,11 +2075,12 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||
copy(objectEncryptionKey[:], key)
|
||||
|
||||
partEncryptionKey := objectEncryptionKey.DerivePartKey(uint32(partID))
|
||||
reader, err = sio.EncryptReader(reader, sio.Config{Key: partEncryptionKey[:]})
|
||||
encReader, err := sio.EncryptReader(reader, sio.Config{Key: partEncryptionKey[:]})
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
reader = etag.Wrap(encReader, reader)
|
||||
|
||||
wantSize := int64(-1)
|
||||
if length >= 0 {
|
||||
@@ -2089,7 +2088,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||
wantSize = info.EncryptedSize()
|
||||
}
|
||||
|
||||
srcInfo.Reader, err = hash.NewReader(reader, wantSize, "", "", actualPartSize, globalCLIContext.StrictS3Compat)
|
||||
srcInfo.Reader, err = hash.NewReader(reader, wantSize, "", "", actualPartSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -2209,12 +2208,11 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
}
|
||||
|
||||
var (
|
||||
md5hex = hex.EncodeToString(md5Bytes)
|
||||
sha256hex = ""
|
||||
reader io.Reader
|
||||
md5hex = hex.EncodeToString(md5Bytes)
|
||||
sha256hex = ""
|
||||
reader io.Reader = r.Body
|
||||
s3Error APIErrorCode
|
||||
)
|
||||
reader = r.Body
|
||||
if s3Error = isPutActionAllowed(ctx, rAuthType, bucket, object, r, iampolicy.PutObjectAction); s3Error != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -2271,7 +2269,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
_, isCompressed := mi.UserDefined[ReservedMetadataPrefix+"compression"]
|
||||
|
||||
if objectAPI.IsCompressionSupported() && isCompressed {
|
||||
actualReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize, globalCLIContext.StrictS3Compat)
|
||||
actualReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -2280,13 +2278,13 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
// Set compression metrics.
|
||||
s2c := newS2CompressReader(actualReader, actualSize)
|
||||
defer s2c.Close()
|
||||
reader = s2c
|
||||
reader = etag.Wrap(s2c, actualReader)
|
||||
size = -1 // Since compressed size is un-predictable.
|
||||
md5hex = "" // Do not try to verify the content.
|
||||
sha256hex = ""
|
||||
}
|
||||
|
||||
hashReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -2343,7 +2341,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
wantSize = info.EncryptedSize()
|
||||
}
|
||||
// do not try to verify encrypted content
|
||||
hashReader, err = hash.NewReader(reader, wantSize, "", "", actualSize, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err = hash.NewReader(etag.Wrap(reader, hashReader), wantSize, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
|
||||
@@ -50,10 +50,11 @@ func readDirFn(dirPath string, filter func(name string, typ os.FileMode) error)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if osErrToFileErr(err) == errFileNotFound {
|
||||
err = osErrToFileErr(err)
|
||||
if err == errFileNotFound {
|
||||
return nil
|
||||
}
|
||||
return osErrToFileErr(err)
|
||||
return err
|
||||
}
|
||||
for _, fi := range fis {
|
||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
|
||||
@@ -109,6 +109,10 @@ func readDirFn(dirPath string, fn func(name string, typ os.FileMode) error) erro
|
||||
if isSysErrNotDir(err) {
|
||||
return nil
|
||||
}
|
||||
err = osErrToFileErr(err)
|
||||
if err == errFileNotFound {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if nbuf <= 0 {
|
||||
@@ -183,7 +187,7 @@ func readDirN(dirPath string, count int) (entries []string, err error) {
|
||||
if isSysErrNotDir(err) {
|
||||
return nil, errFileNotFound
|
||||
}
|
||||
return nil, err
|
||||
return nil, osErrToFileErr(err)
|
||||
}
|
||||
if nbuf <= 0 {
|
||||
break
|
||||
|
||||
@@ -61,11 +61,15 @@ func readDirFn(dirPath string, filter func(name string, typ os.FileMode) error)
|
||||
if isSysErrPathNotFound(e) {
|
||||
return nil
|
||||
}
|
||||
return osErrToFileErr(&os.PathError{
|
||||
err = osErrToFileErr(&os.PathError{
|
||||
Op: "FindNextFile",
|
||||
Path: dirPath,
|
||||
Err: e,
|
||||
})
|
||||
if err == errFileNotFound {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
name := syscall.UTF16ToString(data.FileName[0:])
|
||||
|
||||
@@ -19,6 +19,7 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -29,7 +30,6 @@ import (
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// http Header "x-amz-content-sha256" == "UNSIGNED-PAYLOAD" indicates that the
|
||||
@@ -65,10 +65,9 @@ func getContentSha256Cksum(r *http.Request, stype serviceType) string {
|
||||
if err != nil {
|
||||
logger.CriticalIf(GlobalContext, err)
|
||||
}
|
||||
sum256 := sha256.New()
|
||||
sum256.Write(payload)
|
||||
sum256 := sha256.Sum256(payload)
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(payload))
|
||||
return hex.EncodeToString(sum256.Sum(nil))
|
||||
return hex.EncodeToString(sum256[:])
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
+1
-1
@@ -26,6 +26,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
@@ -38,7 +39,6 @@ import (
|
||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// AWS Signature Version '4' constants.
|
||||
|
||||
@@ -186,14 +186,11 @@ func (client *storageRESTClient) CrawlAndGetDataUsage(ctx context.Context, cache
|
||||
var newCache dataUsageCache
|
||||
pr, pw = io.Pipe()
|
||||
go func() {
|
||||
pr.CloseWithError(newCache.deserialize(pr))
|
||||
pw.CloseWithError(waitForHTTPStream(respBody, pw))
|
||||
}()
|
||||
err = waitForHTTPStream(respBody, pw)
|
||||
pw.CloseWithError(err)
|
||||
if err != nil {
|
||||
return cache, err
|
||||
}
|
||||
return newCache, nil
|
||||
err = newCache.deserialize(pr)
|
||||
pr.CloseWithError(err)
|
||||
return newCache, err
|
||||
}
|
||||
|
||||
func (client *storageRESTClient) GetDiskID() (string, error) {
|
||||
|
||||
@@ -21,6 +21,7 @@ package cmd
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"hash"
|
||||
@@ -31,7 +32,6 @@ import (
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// Streaming AWS Signature Version '4' constants.
|
||||
|
||||
@@ -160,7 +160,7 @@ func calculateSignedChunkLength(chunkDataSize int64) int64 {
|
||||
}
|
||||
|
||||
func mustGetPutObjReader(t TestErrHandler, data io.Reader, size int64, md5hex, sha256hex string) *PutObjReader {
|
||||
hr, err := hash.NewReader(data, size, md5hex, sha256hex, size, globalCLIContext.StrictS3Compat)
|
||||
hr, err := hash.NewReader(data, size, md5hex, sha256hex, size)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ import (
|
||||
"github.com/minio/minio/pkg/env"
|
||||
xnet "github.com/minio/minio/pkg/net"
|
||||
"github.com/minio/selfupdate"
|
||||
_ "github.com/minio/sha256-simd" // Needed for sha256 hash verifier.
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+11
-7
@@ -50,6 +50,7 @@ import (
|
||||
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
"github.com/minio/minio/pkg/bucket/replication"
|
||||
"github.com/minio/minio/pkg/etag"
|
||||
"github.com/minio/minio/pkg/event"
|
||||
"github.com/minio/minio/pkg/handlers"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
@@ -1233,7 +1234,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
var reader io.Reader = r.Body
|
||||
actualSize := size
|
||||
|
||||
hashReader, err := hash.NewReader(reader, size, "", "", actualSize, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err := hash.NewReader(reader, size, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeWebErrorResponse(w, err)
|
||||
return
|
||||
@@ -1244,7 +1245,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV2
|
||||
metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(actualSize, 10)
|
||||
|
||||
actualReader, err := hash.NewReader(reader, actualSize, "", "", actualSize, globalCLIContext.StrictS3Compat)
|
||||
actualReader, err := hash.NewReader(reader, actualSize, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeWebErrorResponse(w, err)
|
||||
return
|
||||
@@ -1254,8 +1255,8 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
size = -1 // Since compressed size is un-predictable.
|
||||
s2c := newS2CompressReader(actualReader, actualSize)
|
||||
defer s2c.Close()
|
||||
reader = s2c
|
||||
hashReader, err = hash.NewReader(reader, size, "", "", actualSize, globalCLIContext.StrictS3Compat)
|
||||
reader = etag.Wrap(s2c, actualReader)
|
||||
hashReader, err = hash.NewReader(reader, size, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeWebErrorResponse(w, err)
|
||||
return
|
||||
@@ -1276,15 +1277,18 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, ok := crypto.IsRequested(r.Header); ok && !HasSuffix(object, SlashSeparator) { // handle SSE requests
|
||||
var objectEncryptionKey crypto.ObjectKey
|
||||
reader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata)
|
||||
var (
|
||||
objectEncryptionKey crypto.ObjectKey
|
||||
encReader io.Reader
|
||||
)
|
||||
encReader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
info := ObjectInfo{Size: size}
|
||||
// do not try to verify encrypted content
|
||||
hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", size, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err = hash.NewReader(etag.Wrap(encReader, hashReader), info.EncryptedSize(), "", "", size)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
|
||||
+55
-45
@@ -264,9 +264,10 @@ func newXLStorage(ep Endpoint) (*xlStorage, error) {
|
||||
return &b
|
||||
},
|
||||
},
|
||||
globalSync: env.Get(config.EnvFSOSync, config.EnableOff) == config.EnableOn,
|
||||
ctx: GlobalContext,
|
||||
rootDisk: rootDisk,
|
||||
globalSync: env.Get(config.EnvFSOSync, config.EnableOff) == config.EnableOn,
|
||||
ctx: GlobalContext,
|
||||
rootDisk: rootDisk,
|
||||
readODirectSupported: true,
|
||||
}
|
||||
|
||||
// Create all necessary bucket folders if possible.
|
||||
@@ -382,7 +383,7 @@ func (s *xlStorage) CrawlAndGetDataUsage(ctx context.Context, cache dataUsageCac
|
||||
return sizeSummary{}, errSkipFile
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadFile(item.Path)
|
||||
buf, err := xioutil.ReadFile(item.Path)
|
||||
if err != nil {
|
||||
if intDataUpdateTracker.debug {
|
||||
console.Debugf(color.Green("crawlBucket:")+" object path missing: %v: %w\n", item.Path, err)
|
||||
@@ -539,7 +540,7 @@ func (s *xlStorage) GetDiskID() (string, error) {
|
||||
return diskID, nil
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(formatFile)
|
||||
b, err := xioutil.ReadFile(formatFile)
|
||||
if err != nil {
|
||||
// If the disk is still not initialized.
|
||||
if osIsNotExist(err) {
|
||||
@@ -827,7 +828,7 @@ func (s *xlStorage) WalkVersions(ctx context.Context, volume, dirPath, marker st
|
||||
},
|
||||
}
|
||||
} else {
|
||||
xlMetaBuf, err := ioutil.ReadFile(pathJoin(volumeDir, walkResult.entry, xlStorageFormatFile))
|
||||
xlMetaBuf, err := xioutil.ReadFile(pathJoin(volumeDir, walkResult.entry, xlStorageFormatFile))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -1150,9 +1151,9 @@ func (s *xlStorage) ReadVersion(ctx context.Context, volume, path, versionID str
|
||||
func (s *xlStorage) readAllData(volumeDir string, filePath string, requireDirectIO bool) (buf []byte, err error) {
|
||||
var f *os.File
|
||||
if requireDirectIO {
|
||||
f, err = disk.OpenFileDirectIO(filePath, os.O_RDONLY, 0666)
|
||||
f, err = disk.OpenFileDirectIO(filePath, readMode, 0666)
|
||||
} else {
|
||||
f, err = os.Open(filePath)
|
||||
f, err = os.OpenFile(filePath, readMode, 0)
|
||||
}
|
||||
if err != nil {
|
||||
if osIsNotExist(err) {
|
||||
@@ -1175,6 +1176,13 @@ func (s *xlStorage) readAllData(volumeDir string, filePath string, requireDirect
|
||||
} else if isSysErrTooManyFiles(err) {
|
||||
return nil, errTooManyOpenFiles
|
||||
} else if isSysErrInvalidArg(err) {
|
||||
st, _ := os.Lstat(filePath)
|
||||
if st != nil && st.IsDir() {
|
||||
// Linux returns InvalidArg for directory O_DIRECT
|
||||
// we need to keep this fallback code to return correct
|
||||
// errors upwards.
|
||||
return nil, errFileNotFound
|
||||
}
|
||||
return nil, errUnsupportedDisk
|
||||
}
|
||||
return nil, err
|
||||
@@ -1328,42 +1336,23 @@ func (s *xlStorage) openFile(volume, path string, mode int) (f *os.File, err err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Stat a volume entry.
|
||||
_, err = os.Lstat(volumeDir)
|
||||
if err != nil {
|
||||
if osIsNotExist(err) {
|
||||
return nil, errVolumeNotFound
|
||||
} else if isSysErrIO(err) {
|
||||
return nil, errFaultyDisk
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filePath := pathJoin(volumeDir, path)
|
||||
if err = checkPathLength(filePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Verify if the file already exists and is not of regular type.
|
||||
var st os.FileInfo
|
||||
if st, err = os.Lstat(filePath); err == nil {
|
||||
if !st.Mode().IsRegular() {
|
||||
return nil, errIsNotRegular
|
||||
}
|
||||
} else {
|
||||
// Create top level directories if they don't exist.
|
||||
// with mode 0777 mkdir honors system umask.
|
||||
if err = mkdirAll(pathutil.Dir(filePath), 0777); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create top level directories if they don't exist.
|
||||
// with mode 0777 mkdir honors system umask.
|
||||
if err = mkdirAll(pathutil.Dir(filePath), 0777); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w, err := os.OpenFile(filePath, mode, 0666)
|
||||
w, err := os.OpenFile(filePath, mode|writeMode, 0666)
|
||||
if err != nil {
|
||||
// File path cannot be verified since one of the parents is a file.
|
||||
switch {
|
||||
case isSysErrNotDir(err):
|
||||
return nil, errFileAccessDenied
|
||||
case isSysErrIsDir(err):
|
||||
return nil, errIsNotRegular
|
||||
case osIsPermission(err):
|
||||
return nil, errFileAccessDenied
|
||||
case isSysErrIO(err):
|
||||
@@ -1668,19 +1657,21 @@ func (s *xlStorage) WriteAll(ctx context.Context, volume string, path string, b
|
||||
atomic.AddInt32(&s.activeIOCount, -1)
|
||||
}()
|
||||
|
||||
w, err := s.openFile(volume, path, os.O_CREATE|os.O_SYNC|os.O_WRONLY)
|
||||
w, err := s.openFile(volume, path, os.O_CREATE|os.O_WRONLY)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer w.Close()
|
||||
|
||||
n, err := w.Write(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n != len(b) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1692,19 +1683,38 @@ func (s *xlStorage) AppendFile(ctx context.Context, volume string, path string,
|
||||
atomic.AddInt32(&s.activeIOCount, -1)
|
||||
}()
|
||||
|
||||
var w *os.File
|
||||
// Create file if not found. Not doing O_DIRECT here to avoid the code that does buffer aligned writes.
|
||||
// AppendFile() is only used by healing code to heal objects written in old format.
|
||||
w, err = s.openFile(volume, path, os.O_CREATE|os.O_SYNC|os.O_APPEND|os.O_WRONLY)
|
||||
volumeDir, err := s.getVolDir(volume)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = w.Write(buf); err != nil {
|
||||
// Stat a volume entry.
|
||||
if _, err = os.Lstat(volumeDir); err != nil {
|
||||
if osIsNotExist(err) {
|
||||
return errVolumeNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return w.Close()
|
||||
var w *os.File
|
||||
// Create file if not found. Not doing O_DIRECT here to avoid the code that does buffer aligned writes.
|
||||
// AppendFile() is only used by healing code to heal objects written in old format.
|
||||
w, err = s.openFile(volume, path, os.O_CREATE|os.O_APPEND|os.O_WRONLY)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
n, err := w.Write(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n != len(buf) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckParts check if path has necessary parts available.
|
||||
@@ -2000,7 +2010,7 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath, dataDir,
|
||||
return err
|
||||
}
|
||||
|
||||
srcBuf, err := ioutil.ReadFile(srcFilePath)
|
||||
srcBuf, err := xioutil.ReadFile(srcFilePath)
|
||||
if err != nil {
|
||||
return osErrToFileErr(err)
|
||||
}
|
||||
@@ -2010,7 +2020,7 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath, dataDir,
|
||||
return err
|
||||
}
|
||||
|
||||
dstBuf, err := ioutil.ReadFile(dstFilePath)
|
||||
dstBuf, err := xioutil.ReadFile(dstFilePath)
|
||||
if err != nil {
|
||||
if !osIsNotExist(err) {
|
||||
return osErrToFileErr(err)
|
||||
@@ -2021,7 +2031,7 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath, dataDir,
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
dstBuf, err = ioutil.ReadFile(dstFilePath)
|
||||
dstBuf, err = xioutil.ReadFile(dstFilePath)
|
||||
if err != nil && !osIsNotExist(err) {
|
||||
return osErrToFileErr(err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// +build windows darwin
|
||||
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2021 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
// No special option for reads on windows
|
||||
readMode = os.O_RDONLY
|
||||
|
||||
// Write with sync no buffering only used only for `xl.meta` writes
|
||||
writeMode = os.O_SYNC
|
||||
)
|
||||
@@ -0,0 +1,31 @@
|
||||
// +build !windows,!darwin
|
||||
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2021 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
// Disallow updating access times
|
||||
readMode = os.O_RDONLY | 0x40000 // O_NOATIME
|
||||
|
||||
// Write with data sync only used only for `xl.meta` writes
|
||||
writeMode = 0x1000 // O_DSYNC
|
||||
)
|
||||
@@ -5,7 +5,7 @@ version: '3.7'
|
||||
# it through port 9000.
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2021-02-19T04-38-02Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
volumes:
|
||||
- data1-1:/data1
|
||||
- data1-2:/data2
|
||||
@@ -22,7 +22,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2021-02-19T04-38-02Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
volumes:
|
||||
- data2-1:/data1
|
||||
- data2-2:/data2
|
||||
@@ -39,7 +39,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2021-02-19T04-38-02Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
volumes:
|
||||
- data3-1:/data1
|
||||
- data3-2:/data2
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2021-02-19T04-38-02Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
volumes:
|
||||
- data4-1:/data1
|
||||
- data4-2:/data2
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.7'
|
||||
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2021-02-19T04-38-02Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
hostname: minio1
|
||||
volumes:
|
||||
- minio1-data:/export
|
||||
@@ -29,7 +29,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2021-02-19T04-38-02Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
hostname: minio2
|
||||
volumes:
|
||||
- minio2-data:/export
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2021-02-19T04-38-02Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
hostname: minio3
|
||||
volumes:
|
||||
- minio3-data:/export
|
||||
@@ -83,7 +83,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2021-02-19T04-38-02Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
hostname: minio4
|
||||
volumes:
|
||||
- minio4-data:/export
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.7'
|
||||
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2021-02-19T04-38-02Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
hostname: minio1
|
||||
volumes:
|
||||
- minio1-data:/export
|
||||
@@ -33,7 +33,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2021-02-19T04-38-02Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
hostname: minio2
|
||||
volumes:
|
||||
- minio2-data:/export
|
||||
@@ -64,7 +64,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2021-02-19T04-38-02Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
hostname: minio3
|
||||
volumes:
|
||||
- minio3-data:/export
|
||||
@@ -95,7 +95,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2021-02-19T04-38-02Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
hostname: minio4
|
||||
volumes:
|
||||
- minio4-data:/export
|
||||
|
||||
@@ -70,6 +70,7 @@ require (
|
||||
github.com/prometheus/client_golang v1.8.0
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/prometheus/procfs v0.2.0
|
||||
github.com/quasilyte/go-ruleguard v0.2.1 // indirect
|
||||
github.com/rjeczalik/notify v0.9.2
|
||||
github.com/rs/cors v1.7.0
|
||||
github.com/secure-io/sio-go v0.3.1
|
||||
@@ -80,6 +81,7 @@ require (
|
||||
github.com/tidwall/gjson v1.6.7
|
||||
github.com/tidwall/sjson v1.0.4
|
||||
github.com/tinylib/msgp v1.1.3
|
||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 // indirect
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a
|
||||
github.com/willf/bitset v1.1.11 // indirect
|
||||
github.com/willf/bloom v2.0.3+incompatible
|
||||
|
||||
@@ -527,6 +527,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
|
||||
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/quasilyte/go-ruleguard v0.2.1 h1:56eRm0daAyny9UhJnmtJW/UyLZQusukBAB8oT8AHKHo=
|
||||
github.com/quasilyte/go-ruleguard v0.2.1/go.mod h1:hN2rVc/uS4bQhQKTio2XaSJSafJwqBUWWwtssT3cQmc=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
@@ -595,6 +597,8 @@ github.com/tinylib/msgp v1.1.3 h1:3giwAkmtaEDLSV0MdO1lDLuPgklgPzmk8H9+So2BVfA=
|
||||
github.com/tinylib/msgp v1.1.3/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 h1:OXcKh35JaYsGMRzpvFkLv/MEyPuL49CThT1pZ8aSml4=
|
||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
@@ -610,6 +614,7 @@ github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0=
|
||||
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
||||
@@ -701,6 +706,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -772,6 +778,7 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
// MinIO Cloud Storage, (C) 2021 MinIO, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package etag provides an implementation of S3 ETags.
|
||||
//
|
||||
// Each S3 object has an associated ETag that can be
|
||||
// used to e.g. quickly compare objects or check whether
|
||||
// the content of an object has changed.
|
||||
//
|
||||
// In general, an S3 ETag is an MD5 checksum of the object
|
||||
// content. However, there are many exceptions to this rule.
|
||||
//
|
||||
//
|
||||
// Single-part Upload
|
||||
//
|
||||
// In case of a basic single-part PUT operation - without server
|
||||
// side encryption or object compression - the ETag of an object
|
||||
// is its content MD5.
|
||||
//
|
||||
//
|
||||
// Multi-part Upload
|
||||
//
|
||||
// The ETag of an object does not correspond to its content MD5
|
||||
// when the object is uploaded in multiple parts via the S3
|
||||
// multipart API. Instead, S3 first computes a MD5 of each part:
|
||||
// e1 := MD5(part-1)
|
||||
// e2 := MD5(part-2)
|
||||
// ...
|
||||
// eN := MD5(part-N)
|
||||
//
|
||||
// Then, the ETag of the object is computed as MD5 of all individual
|
||||
// part checksums. S3 also encodes the number of parts into the ETag
|
||||
// by appending a -<number-of-parts> at the end:
|
||||
// ETag := MD5(e1 || e2 || e3 ... || eN) || -N
|
||||
//
|
||||
// For example: ceb8853ddc5086cc4ab9e149f8f09c88-5
|
||||
//
|
||||
// However, this scheme is only used for multipart objects that are
|
||||
// not encrypted.
|
||||
//
|
||||
// Server-side Encryption
|
||||
//
|
||||
// S3 specifies three types of server-side-encryption - SSE-C, SSE-S3
|
||||
// and SSE-KMS - with different semantics w.r.t. ETags.
|
||||
// In case of SSE-S3, the ETag of an object is computed the same as
|
||||
// for single resp. multipart plaintext objects. In particular,
|
||||
// the ETag of a singlepart SSE-S3 object is its content MD5.
|
||||
//
|
||||
// In case of SSE-C and SSE-KMS, the ETag of an object is computed
|
||||
// differently. For singlepart uploads the ETag is not the content
|
||||
// MD5 of the object. For multipart uploads the ETag is also not
|
||||
// the MD5 of the individual part checksums but it still contains
|
||||
// the number of parts as suffix.
|
||||
//
|
||||
// Instead, the ETag is kind of unpredictable for S3 clients when
|
||||
// an object is encrypted using SSE-C or SSE-KMS. Maybe AWS S3
|
||||
// computes the ETag as MD5 of the encrypted content but there is
|
||||
// no way to verify this assumption since the encryption happens
|
||||
// inside AWS S3.
|
||||
// Therefore, S3 clients must not make any assumption about ETags
|
||||
// in case of SSE-C or SSE-KMS except that the ETag is well-formed.
|
||||
//
|
||||
// To put all of this into a simple rule:
|
||||
// SSE-S3 : ETag == MD5
|
||||
// SSE-C : ETag != MD5
|
||||
// SSE-KMS: ETag != MD5
|
||||
//
|
||||
//
|
||||
// Encrypted ETags
|
||||
//
|
||||
// An S3 implementation has to remember the content MD5 of objects
|
||||
// in case of SSE-S3. However, storing the ETag of an encrypted
|
||||
// object in plaintext may reveal some information about the object.
|
||||
// For example, two objects with the same ETag are identical with
|
||||
// a very high probability.
|
||||
//
|
||||
// Therefore, an S3 implementation may encrypt an ETag before storing
|
||||
// it. In this case, the stored ETag may not be a well-formed S3 ETag.
|
||||
// For example, it can be larger due to a checksum added by authenticated
|
||||
// encryption schemes. Such an ETag must be decrypted before sent to an
|
||||
// S3 client.
|
||||
//
|
||||
//
|
||||
// S3 Clients
|
||||
//
|
||||
// There are many different S3 client implementations. Most of them
|
||||
// access the ETag by looking for the HTTP response header key "Etag".
|
||||
// However, some of them assume that the header key has to be "ETag"
|
||||
// (case-sensitive) and will fail otherwise.
|
||||
// Further, some clients require that the ETag value is a double-quoted
|
||||
// string. Therefore, this package provides dedicated functions for
|
||||
// adding and extracing the ETag to/from HTTP headers.
|
||||
package etag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ETag is a single S3 ETag.
|
||||
//
|
||||
// An S3 ETag sometimes corresponds to the MD5 of
|
||||
// the S3 object content. However, when an object
|
||||
// is encrypted, compressed or uploaded using
|
||||
// the S3 multipart API then its ETag is not
|
||||
// necessarily the MD5 of the object content.
|
||||
//
|
||||
// For a more detailed description of S3 ETags
|
||||
// take a look at the package documentation.
|
||||
type ETag []byte
|
||||
|
||||
// String returns the string representation of the ETag.
|
||||
//
|
||||
// The returned string is a hex representation of the
|
||||
// binary ETag with an optional '-<part-number>' suffix.
|
||||
func (e ETag) String() string {
|
||||
if e.IsMultipart() {
|
||||
return hex.EncodeToString(e[:16]) + string(e[16:])
|
||||
}
|
||||
return hex.EncodeToString(e)
|
||||
}
|
||||
|
||||
// IsEncrypted reports whether the ETag is encrypted.
|
||||
func (e ETag) IsEncrypted() bool {
|
||||
return len(e) > 16 && !bytes.ContainsRune(e, '-')
|
||||
}
|
||||
|
||||
// IsMultipart reports whether the ETag belongs to an
|
||||
// object that has been uploaded using the S3 multipart
|
||||
// API.
|
||||
// An S3 multipart ETag has a -<part-number> suffix.
|
||||
func (e ETag) IsMultipart() bool {
|
||||
return len(e) > 16 && bytes.ContainsRune(e, '-')
|
||||
}
|
||||
|
||||
// Parts returns the number of object parts that are
|
||||
// referenced by this ETag. It returns 1 if the object
|
||||
// has been uploaded using the S3 singlepart API.
|
||||
//
|
||||
// Parts may panic if the ETag is an invalid multipart
|
||||
// ETag.
|
||||
func (e ETag) Parts() int {
|
||||
if !e.IsMultipart() {
|
||||
return 1
|
||||
}
|
||||
|
||||
n := bytes.IndexRune(e, '-')
|
||||
parts, err := strconv.Atoi(string(e[n+1:]))
|
||||
if err != nil {
|
||||
panic(err) // malformed ETag
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
var _ Tagger = ETag{} // compiler check
|
||||
|
||||
// ETag returns the ETag itself.
|
||||
//
|
||||
// By providing this method ETag implements
|
||||
// the Tagger interface.
|
||||
func (e ETag) ETag() ETag { return e }
|
||||
|
||||
// Set adds the ETag to the HTTP headers. It overwrites any
|
||||
// existing ETag entry.
|
||||
//
|
||||
// Due to legacy S3 clients, that make incorrect assumptions
|
||||
// about HTTP headers, Set should be used instead of
|
||||
// http.Header.Set(...). Otherwise, some S3 clients will not
|
||||
// able to extract the ETag.
|
||||
func Set(etag ETag, h http.Header) {
|
||||
// Some (broken) S3 clients expect the ETag header to
|
||||
// literally "ETag" - not "Etag". Further, some clients
|
||||
// expect an ETag in double quotes. Therefore, we set the
|
||||
// ETag directly as map entry instead of using http.Header.Set
|
||||
h["ETag"] = []string{`"` + etag.String() + `"`}
|
||||
}
|
||||
|
||||
// Get extracts and parses an ETag from the given HTTP headers.
|
||||
// It returns an error when the HTTP headers do not contain
|
||||
// an ETag entry or when the ETag is malformed.
|
||||
//
|
||||
// Get only accepts AWS S3 compatible ETags - i.e. no
|
||||
// encrypted ETags - and therefore is stricter than Parse.
|
||||
func Get(h http.Header) (ETag, error) {
|
||||
const strict = true
|
||||
if v := h.Get("Etag"); v != "" {
|
||||
return parse(v, strict)
|
||||
}
|
||||
v, ok := h["ETag"]
|
||||
if !ok || len(v) == 0 {
|
||||
return nil, errors.New("etag: HTTP header does not contain an ETag")
|
||||
}
|
||||
return parse(v[0], strict)
|
||||
}
|
||||
|
||||
// Equal returns true if and only if the two ETags are
|
||||
// identical.
|
||||
func Equal(a, b ETag) bool { return bytes.Equal(a, b) }
|
||||
|
||||
// Parse parses s as an S3 ETag, returning the result.
|
||||
// The string can be an encrypted, singlepart
|
||||
// or multipart S3 ETag. It returns an error if s is
|
||||
// not a valid textual representation of an ETag.
|
||||
func Parse(s string) (ETag, error) {
|
||||
const strict = false
|
||||
return parse(s, strict)
|
||||
}
|
||||
|
||||
// parse parse s as an S3 ETag, returning the result.
|
||||
// It operates in one of two modes:
|
||||
// - strict
|
||||
// - non-strict
|
||||
//
|
||||
// In strict mode, parse only accepts ETags that
|
||||
// are AWS S3 compatible. In particular, an AWS
|
||||
// S3 ETag always consists of a 128 bit checksum
|
||||
// value and an optional -<part-number> suffix.
|
||||
// Therefore, s must have the following form in
|
||||
// strict mode: <32-hex-characters>[-<integer>]
|
||||
//
|
||||
// In non-strict mode, parse also accepts ETags
|
||||
// that are not AWS S3 compatible - e.g. encrypted
|
||||
// ETags.
|
||||
func parse(s string, strict bool) (ETag, error) {
|
||||
// An S3 ETag may be a double-quoted string.
|
||||
// Therefore, we remove double quotes at the
|
||||
// start and end, if any.
|
||||
if strings.HasPrefix(s, `"`) && strings.HasSuffix(s, `"`) {
|
||||
s = s[1 : len(s)-1]
|
||||
}
|
||||
|
||||
// An S3 ETag may be a multipart ETag that
|
||||
// contains a '-' followed by a number.
|
||||
// If the ETag does not a '-' is is either
|
||||
// a singlepart or encrypted ETag.
|
||||
n := strings.IndexRune(s, '-')
|
||||
if n == -1 {
|
||||
etag, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strict && len(etag) != 16 { // AWS S3 ETags are always 128 bit long
|
||||
return nil, fmt.Errorf("etag: invalid length %d", len(etag))
|
||||
}
|
||||
return ETag(etag), nil
|
||||
}
|
||||
|
||||
prefix, suffix := s[:n], s[n:]
|
||||
if len(prefix) != 32 {
|
||||
return nil, fmt.Errorf("etag: invalid prefix length %d", len(prefix))
|
||||
}
|
||||
if len(suffix) <= 1 {
|
||||
return nil, errors.New("etag: suffix is not a part number")
|
||||
}
|
||||
|
||||
etag, err := hex.DecodeString(prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
partNumber, err := strconv.Atoi(suffix[1:]) // suffix[0] == '-' Therefore, we start parsing at suffix[1]
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strict && (partNumber == 0 || partNumber > 10000) {
|
||||
return nil, fmt.Errorf("etag: invalid part number %d", partNumber)
|
||||
}
|
||||
return ETag(append(etag, suffix...)), nil
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
// MinIO Cloud Storage, (C) 2021 MinIO, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package etag
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var parseTests = []struct {
|
||||
String string
|
||||
ETag ETag
|
||||
ShouldFail bool
|
||||
}{
|
||||
{String: "3b83ef96387f1465", ETag: ETag{59, 131, 239, 150, 56, 127, 20, 101}}, // 0
|
||||
{String: "3b83ef96387f14655fc854ddc3c6bd57", ETag: ETag{59, 131, 239, 150, 56, 127, 20, 101, 95, 200, 84, 221, 195, 198, 189, 87}}, // 1
|
||||
{String: `"3b83ef96387f14655fc854ddc3c6bd57"`, ETag: ETag{59, 131, 239, 150, 56, 127, 20, 101, 95, 200, 84, 221, 195, 198, 189, 87}}, // 2
|
||||
{String: "ceb8853ddc5086cc4ab9e149f8f09c88-1", ETag: ETag{206, 184, 133, 61, 220, 80, 134, 204, 74, 185, 225, 73, 248, 240, 156, 136, 45, 49}}, // 3
|
||||
{String: `"ceb8853ddc5086cc4ab9e149f8f09c88-2"`, ETag: ETag{206, 184, 133, 61, 220, 80, 134, 204, 74, 185, 225, 73, 248, 240, 156, 136, 45, 50}}, // 4
|
||||
{ // 5
|
||||
String: "90402c78d2dccddee1e9e86222ce2c6361675f3529d26000ae2e900ff216b3cb59e130e092d8a2981e776f4d0bd60941",
|
||||
ETag: ETag{144, 64, 44, 120, 210, 220, 205, 222, 225, 233, 232, 98, 34, 206, 44, 99, 97, 103, 95, 53, 41, 210, 96, 0, 174, 46, 144, 15, 242, 22, 179, 203, 89, 225, 48, 224, 146, 216, 162, 152, 30, 119, 111, 77, 11, 214, 9, 65},
|
||||
},
|
||||
|
||||
{String: `"3b83ef96387f14655fc854ddc3c6bd57`, ShouldFail: true}, // 6
|
||||
{String: "ceb8853ddc5086cc4ab9e149f8f09c88-", ShouldFail: true}, // 7
|
||||
{String: "ceb8853ddc5086cc4ab9e149f8f09c88-2a", ShouldFail: true}, // 8
|
||||
{String: "ceb8853ddc5086cc4ab9e149f8f09c88-2-1", ShouldFail: true}, // 9
|
||||
{String: "90402c78d2dccddee1e9e86222ce2c-1", ShouldFail: true}, // 10
|
||||
{String: "90402c78d2dccddee1e9e86222ce2c6361675f3529d26000ae2e900ff216b3cb59e130e092d8a2981e776f4d0bd60941-1", ShouldFail: true}, // 11
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
for i, test := range parseTests {
|
||||
etag, err := Parse(test.String)
|
||||
if err == nil && test.ShouldFail {
|
||||
t.Fatalf("Test %d: parse should have failed but succeeded", i)
|
||||
}
|
||||
if err != nil && !test.ShouldFail {
|
||||
t.Fatalf("Test %d: failed to parse ETag %q: %v", i, test.String, err)
|
||||
}
|
||||
if !Equal(etag, test.ETag) {
|
||||
t.Log([]byte(etag))
|
||||
t.Fatalf("Test %d: ETags don't match", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var stringTests = []struct {
|
||||
ETag ETag
|
||||
String string
|
||||
}{
|
||||
{ETag: ETag{59, 131, 239, 150, 56, 127, 20, 101}, String: "3b83ef96387f1465"}, // 0
|
||||
{ETag: ETag{59, 131, 239, 150, 56, 127, 20, 101, 95, 200, 84, 221, 195, 198, 189, 87}, String: "3b83ef96387f14655fc854ddc3c6bd57"}, // 1
|
||||
{ETag: ETag{206, 184, 133, 61, 220, 80, 134, 204, 74, 185, 225, 73, 248, 240, 156, 136, 45, 49}, String: "ceb8853ddc5086cc4ab9e149f8f09c88-1"}, // 2
|
||||
{ETag: ETag{206, 184, 133, 61, 220, 80, 134, 204, 74, 185, 225, 73, 248, 240, 156, 136, 45, 50}, String: "ceb8853ddc5086cc4ab9e149f8f09c88-2"}, // 3
|
||||
{ // 4
|
||||
ETag: ETag{144, 64, 44, 120, 210, 220, 205, 222, 225, 233, 232, 98, 34, 206, 44, 99, 97, 103, 95, 53, 41, 210, 96, 0, 174, 46, 144, 15, 242, 22, 179, 203, 89, 225, 48, 224, 146, 216, 162, 152, 30, 119, 111, 77, 11, 214, 9, 65},
|
||||
String: "90402c78d2dccddee1e9e86222ce2c6361675f3529d26000ae2e900ff216b3cb59e130e092d8a2981e776f4d0bd60941",
|
||||
},
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
for i, test := range stringTests {
|
||||
s := test.ETag.String()
|
||||
if s != test.String {
|
||||
t.Fatalf("Test %d: got %s - want %s", i, s, test.String)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var equalTests = []struct {
|
||||
A string
|
||||
B string
|
||||
Equal bool
|
||||
}{
|
||||
{A: "3b83ef96387f14655fc854ddc3c6bd57", B: "3b83ef96387f14655fc854ddc3c6bd57", Equal: true}, // 0
|
||||
{A: "3b83ef96387f14655fc854ddc3c6bd57", B: `"3b83ef96387f14655fc854ddc3c6bd57"`, Equal: true}, // 1
|
||||
|
||||
{A: "3b83ef96387f14655fc854ddc3c6bd57", B: "3b83ef96387f14655fc854ddc3c6bd57-2", Equal: false}, // 2
|
||||
{A: "3b83ef96387f14655fc854ddc3c6bd57", B: "ceb8853ddc5086cc4ab9e149f8f09c88", Equal: false}, // 3
|
||||
}
|
||||
|
||||
func TestEqual(t *testing.T) {
|
||||
for i, test := range equalTests {
|
||||
A, err := Parse(test.A)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %v", i, err)
|
||||
}
|
||||
B, err := Parse(test.B)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %v", i, err)
|
||||
}
|
||||
if equal := Equal(A, B); equal != test.Equal {
|
||||
t.Fatalf("Test %d: got %v - want %v", i, equal, test.Equal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var readerTests = []struct { // Reference values computed by: echo <content> | md5sum
|
||||
Content string
|
||||
ETag ETag
|
||||
}{
|
||||
{
|
||||
Content: "", ETag: ETag{212, 29, 140, 217, 143, 0, 178, 4, 233, 128, 9, 152, 236, 248, 66, 126},
|
||||
},
|
||||
{
|
||||
Content: " ", ETag: ETag{114, 21, 238, 156, 125, 157, 194, 41, 210, 146, 26, 64, 232, 153, 236, 95},
|
||||
},
|
||||
{
|
||||
Content: "Hello World", ETag: ETag{177, 10, 141, 177, 100, 224, 117, 65, 5, 183, 169, 155, 231, 46, 63, 229},
|
||||
},
|
||||
}
|
||||
|
||||
func TestReader(t *testing.T) {
|
||||
for i, test := range readerTests {
|
||||
reader := NewReader(strings.NewReader(test.Content), test.ETag)
|
||||
if _, err := io.Copy(ioutil.Discard, reader); err != nil {
|
||||
t.Fatalf("Test %d: read failed: %v", i, err)
|
||||
}
|
||||
if ETag := reader.ETag(); !Equal(ETag, test.ETag) {
|
||||
t.Fatalf("Test %d: ETag mismatch: got %q - want %q", i, ETag, test.ETag)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// MinIO Cloud Storage, (C) 2021 MinIO, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package etag
|
||||
|
||||
import (
|
||||
"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
|
||||
}
|
||||
|
||||
// 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 implemetations
|
||||
// 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(r io.Reader, etag ETag) *Reader {
|
||||
if er, ok := r.(*Reader); ok {
|
||||
if er.readN == 0 && Equal(etag, er.checksum) {
|
||||
return er
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
@@ -17,20 +17,19 @@
|
||||
package target
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"hash"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
"github.com/xdg/scram"
|
||||
)
|
||||
|
||||
// KafkaSHA256 is a function that returns a crypto/sha256 hasher and should be used
|
||||
// to create Client objects configured for SHA-256 hashing.
|
||||
var KafkaSHA256 scram.HashGeneratorFcn = func() hash.Hash { return sha256.New() }
|
||||
var KafkaSHA256 scram.HashGeneratorFcn = sha256.New
|
||||
|
||||
// KafkaSHA512 is a function that returns a crypto/sha512 hasher and should be used
|
||||
// to create Client objects configured for SHA-512 hashing.
|
||||
var KafkaSHA512 scram.HashGeneratorFcn = func() hash.Hash { return sha512.New() }
|
||||
var KafkaSHA512 scram.HashGeneratorFcn = sha512.New
|
||||
|
||||
// XDGSCRAMClient implements the client-side of an authentication
|
||||
// conversation with a server. A new conversation must be created for
|
||||
|
||||
+147
-124
@@ -18,61 +18,140 @@ package hash
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
"github.com/minio/minio/pkg/etag"
|
||||
)
|
||||
|
||||
// Reader writes what it reads from an io.Reader to an MD5 and SHA256 hash.Hash.
|
||||
// Reader verifies that the content of the io.Reader matches the expected checksums.
|
||||
// A Reader wraps an io.Reader and computes the MD5 checksum
|
||||
// of the read content as ETag. Optionally, it also computes
|
||||
// the SHA256 checksum of the content.
|
||||
//
|
||||
// If the reference values for the ETag and content SHA26
|
||||
// are not empty then it will check whether the computed
|
||||
// match the reference values.
|
||||
type Reader struct {
|
||||
src io.Reader
|
||||
src io.Reader
|
||||
bytesRead int64
|
||||
|
||||
size int64
|
||||
actualSize int64
|
||||
bytesRead int64
|
||||
|
||||
md5sum, sha256sum []byte // Byte values of md5sum, sha256sum of client sent values.
|
||||
md5Hash, sha256Hash hash.Hash
|
||||
checksum etag.ETag
|
||||
contentSHA256 []byte
|
||||
|
||||
sha256 hash.Hash
|
||||
}
|
||||
|
||||
// NewReader returns a new hash Reader which computes the MD5 sum and
|
||||
// SHA256 sum (if set) of the provided io.Reader at EOF.
|
||||
func NewReader(src io.Reader, size int64, md5Hex, sha256Hex string, actualSize int64, strictCompat bool) (*Reader, error) {
|
||||
// NewReader returns a new Reader that wraps src and computes
|
||||
// MD5 checksum of everything it reads as ETag.
|
||||
//
|
||||
// It also computes the SHA256 checksum of everything it reads
|
||||
// if sha256Hex is not the empty string.
|
||||
//
|
||||
// If size resp. actualSize is unknown at the time of calling
|
||||
// NewReader then it should be set to -1.
|
||||
//
|
||||
// NewReader may try merge the given size, MD5 and SHA256 values
|
||||
// into src - if src is a Reader - to avoid computing the same
|
||||
// checksums multiple times.
|
||||
func NewReader(src io.Reader, size int64, md5Hex, sha256Hex string, actualSize int64) (*Reader, error) {
|
||||
MD5, err := hex.DecodeString(md5Hex)
|
||||
if err != nil {
|
||||
return nil, BadDigest{ // TODO(aead): Return an error that indicates that an invalid ETag has been specified
|
||||
ExpectedMD5: md5Hex,
|
||||
CalculatedMD5: "",
|
||||
}
|
||||
}
|
||||
SHA256, err := hex.DecodeString(sha256Hex)
|
||||
if err != nil {
|
||||
return nil, SHA256Mismatch{ // TODO(aead): Return an error that indicates that an invalid Content-SHA256 has been specified
|
||||
ExpectedSHA256: sha256Hex,
|
||||
CalculatedSHA256: "",
|
||||
}
|
||||
}
|
||||
|
||||
// Merge the size, MD5 and SHA256 values if src is a Reader.
|
||||
// The size may be set to -1 by callers if unknown.
|
||||
if r, ok := src.(*Reader); ok {
|
||||
// Merge expectations and return parent.
|
||||
return r.merge(size, md5Hex, sha256Hex, actualSize, strictCompat)
|
||||
if r.bytesRead > 0 {
|
||||
return nil, errors.New("hash: already read from hash reader")
|
||||
}
|
||||
if len(r.checksum) != 0 && len(MD5) != 0 && !etag.Equal(r.checksum, etag.ETag(MD5)) {
|
||||
return nil, BadDigest{
|
||||
ExpectedMD5: r.checksum.String(),
|
||||
CalculatedMD5: md5Hex,
|
||||
}
|
||||
}
|
||||
if len(r.contentSHA256) != 0 && len(SHA256) != 0 && !bytes.Equal(r.contentSHA256, SHA256) {
|
||||
return nil, SHA256Mismatch{
|
||||
ExpectedSHA256: hex.EncodeToString(r.contentSHA256),
|
||||
CalculatedSHA256: sha256Hex,
|
||||
}
|
||||
}
|
||||
if r.size >= 0 && size >= 0 && r.size != size {
|
||||
return nil, ErrSizeMismatch{Want: r.size, Got: size}
|
||||
}
|
||||
|
||||
r.checksum = etag.ETag(MD5)
|
||||
r.contentSHA256 = SHA256
|
||||
if r.size < 0 && size >= 0 {
|
||||
r.src = etag.Wrap(io.LimitReader(r.src, size), r.src)
|
||||
r.size = size
|
||||
}
|
||||
if r.actualSize <= 0 && actualSize >= 0 {
|
||||
r.actualSize = actualSize
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Create empty reader and merge into that.
|
||||
r := Reader{src: src, size: -1, actualSize: -1}
|
||||
return r.merge(size, md5Hex, sha256Hex, actualSize, strictCompat)
|
||||
var hash hash.Hash
|
||||
if size >= 0 {
|
||||
src = io.LimitReader(src, size)
|
||||
}
|
||||
if len(SHA256) != 0 {
|
||||
hash = newSHA256()
|
||||
}
|
||||
return &Reader{
|
||||
src: etag.NewReader(src, etag.ETag(MD5)),
|
||||
size: size,
|
||||
actualSize: actualSize,
|
||||
checksum: etag.ETag(MD5),
|
||||
contentSHA256: SHA256,
|
||||
sha256: hash,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||
n, err = r.src.Read(p)
|
||||
if n > 0 {
|
||||
if r.md5Hash != nil {
|
||||
r.md5Hash.Write(p[:n])
|
||||
}
|
||||
if r.sha256Hash != nil {
|
||||
r.sha256Hash.Write(p[:n])
|
||||
}
|
||||
}
|
||||
func (r *Reader) Read(p []byte) (int, error) {
|
||||
n, err := r.src.Read(p)
|
||||
r.bytesRead += int64(n)
|
||||
|
||||
// At io.EOF verify if the checksums are right.
|
||||
if err == io.EOF {
|
||||
if cerr := r.verify(); cerr != nil {
|
||||
return 0, cerr
|
||||
}
|
||||
if r.sha256 != nil {
|
||||
r.sha256.Write(p[:n])
|
||||
}
|
||||
|
||||
return
|
||||
if err == io.EOF { // Verify content SHA256, if set.
|
||||
if r.sha256 != nil {
|
||||
if sum := r.sha256.Sum(nil); !bytes.Equal(r.contentSHA256, sum) {
|
||||
return n, SHA256Mismatch{
|
||||
ExpectedSHA256: hex.EncodeToString(r.contentSHA256),
|
||||
CalculatedSHA256: hex.EncodeToString(sum),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil && err != io.EOF {
|
||||
if v, ok := err.(etag.VerifyError); ok {
|
||||
return n, BadDigest{
|
||||
ExpectedMD5: v.Expected.String(),
|
||||
CalculatedMD5: v.Computed.String(),
|
||||
}
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Size returns the absolute number of bytes the Reader
|
||||
@@ -84,114 +163,58 @@ func (r *Reader) Size() int64 { return r.size }
|
||||
// DecompressedSize - For compressed objects.
|
||||
func (r *Reader) ActualSize() int64 { return r.actualSize }
|
||||
|
||||
// MD5 - returns byte md5 value
|
||||
// ETag returns the ETag computed by an underlying etag.Tagger.
|
||||
// If the underlying io.Reader does not implement etag.Tagger
|
||||
// it returns nil.
|
||||
func (r *Reader) ETag() etag.ETag {
|
||||
if t, ok := r.src.(etag.Tagger); ok {
|
||||
return t.ETag()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MD5 returns the MD5 checksum set as reference value.
|
||||
//
|
||||
// It corresponds to the checksum that is expected and
|
||||
// not the actual MD5 checksum of the content.
|
||||
// Therefore, refer to MD5Current.
|
||||
func (r *Reader) MD5() []byte {
|
||||
return r.md5sum
|
||||
return r.checksum
|
||||
}
|
||||
|
||||
// MD5Current - returns byte md5 value of the current state
|
||||
// of the md5 hash after reading the incoming content.
|
||||
// NOTE: Calling this function multiple times might yield
|
||||
// different results if they are intermixed with Reader.
|
||||
// MD5Current returns the MD5 checksum of the content
|
||||
// that has been read so far.
|
||||
//
|
||||
// Calling MD5Current again after reading more data may
|
||||
// result in a different checksum.
|
||||
func (r *Reader) MD5Current() []byte {
|
||||
if r.md5Hash != nil {
|
||||
return r.md5Hash.Sum(nil)
|
||||
}
|
||||
return nil
|
||||
return r.ETag()[:]
|
||||
}
|
||||
|
||||
// SHA256 - returns byte sha256 value
|
||||
// SHA256 returns the SHA256 checksum set as reference value.
|
||||
//
|
||||
// It corresponds to the checksum that is expected and
|
||||
// not the actual SHA256 checksum of the content.
|
||||
func (r *Reader) SHA256() []byte {
|
||||
return r.sha256sum
|
||||
return r.contentSHA256
|
||||
}
|
||||
|
||||
// MD5HexString returns hex md5 value.
|
||||
// MD5HexString returns a hex representation of the MD5.
|
||||
func (r *Reader) MD5HexString() string {
|
||||
return hex.EncodeToString(r.md5sum)
|
||||
return hex.EncodeToString(r.checksum)
|
||||
}
|
||||
|
||||
// MD5Base64String returns base64 encoded MD5sum value.
|
||||
// MD5Base64String returns a hex representation of the MD5.
|
||||
func (r *Reader) MD5Base64String() string {
|
||||
return base64.StdEncoding.EncodeToString(r.md5sum)
|
||||
return base64.StdEncoding.EncodeToString(r.checksum)
|
||||
}
|
||||
|
||||
// SHA256HexString returns hex sha256 value.
|
||||
// SHA256HexString returns a hex representation of the SHA256.
|
||||
func (r *Reader) SHA256HexString() string {
|
||||
return hex.EncodeToString(r.sha256sum)
|
||||
return hex.EncodeToString(r.contentSHA256)
|
||||
}
|
||||
|
||||
// verify verifies if the computed MD5 sum and SHA256 sum are
|
||||
// equal to the ones specified when creating the Reader.
|
||||
func (r *Reader) verify() error {
|
||||
if r.sha256Hash != nil && len(r.sha256sum) > 0 {
|
||||
if sum := r.sha256Hash.Sum(nil); !bytes.Equal(r.sha256sum, sum) {
|
||||
return SHA256Mismatch{hex.EncodeToString(r.sha256sum), hex.EncodeToString(sum)}
|
||||
}
|
||||
}
|
||||
if r.md5Hash != nil && len(r.md5sum) > 0 {
|
||||
if sum := r.md5Hash.Sum(nil); !bytes.Equal(r.md5sum, sum) {
|
||||
return BadDigest{hex.EncodeToString(r.md5sum), hex.EncodeToString(sum)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// merge another hash into this one.
|
||||
// There cannot be conflicting information given.
|
||||
func (r *Reader) merge(size int64, md5Hex, sha256Hex string, actualSize int64, strictCompat bool) (*Reader, error) {
|
||||
if r.bytesRead > 0 {
|
||||
return nil, errors.New("internal error: Already read from hash reader")
|
||||
}
|
||||
// Merge sizes.
|
||||
// If not set before, just add it.
|
||||
if r.size < 0 && size >= 0 {
|
||||
r.src = io.LimitReader(r.src, size)
|
||||
r.size = size
|
||||
}
|
||||
// If set before and set now they must match.
|
||||
if r.size >= 0 && size >= 0 && r.size != size {
|
||||
return nil, ErrSizeMismatch{Want: r.size, Got: size}
|
||||
}
|
||||
|
||||
if r.actualSize <= 0 && actualSize >= 0 {
|
||||
r.actualSize = actualSize
|
||||
}
|
||||
|
||||
// Merge SHA256.
|
||||
sha256sum, err := hex.DecodeString(sha256Hex)
|
||||
if err != nil {
|
||||
return nil, SHA256Mismatch{}
|
||||
}
|
||||
|
||||
// If both are set, they must be the same.
|
||||
if r.sha256Hash != nil && len(sha256sum) > 0 {
|
||||
if !bytes.Equal(r.sha256sum, sha256sum) {
|
||||
return nil, SHA256Mismatch{}
|
||||
}
|
||||
} else if len(sha256sum) > 0 {
|
||||
r.sha256Hash = sha256.New()
|
||||
r.sha256sum = sha256sum
|
||||
}
|
||||
|
||||
// Merge MD5 Sum.
|
||||
md5sum, err := hex.DecodeString(md5Hex)
|
||||
if err != nil {
|
||||
return nil, BadDigest{}
|
||||
}
|
||||
// If both are set, they must expect the same.
|
||||
if r.md5Hash != nil && len(md5sum) > 0 {
|
||||
if !bytes.Equal(r.md5sum, md5sum) {
|
||||
return nil, BadDigest{}
|
||||
}
|
||||
} else if len(md5sum) > 0 || (r.md5Hash == nil && strictCompat) {
|
||||
r.md5Hash = md5.New()
|
||||
r.md5sum = md5sum
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
var _ io.Closer = (*Reader)(nil) // compiler check
|
||||
|
||||
// Close and release resources.
|
||||
func (r *Reader) Close() error {
|
||||
// Support the io.Closer interface.
|
||||
return nil
|
||||
}
|
||||
func (r *Reader) Close() error { return nil }
|
||||
|
||||
+45
-57
@@ -27,7 +27,7 @@ import (
|
||||
|
||||
// Tests functions like Size(), MD5*(), SHA256*()
|
||||
func TestHashReaderHelperMethods(t *testing.T) {
|
||||
r, err := NewReader(bytes.NewReader([]byte("abcd")), 4, "e2fc714c4727ee9395f324cd2e7f331f", "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589", 4, false)
|
||||
r, err := NewReader(bytes.NewReader([]byte("abcd")), 4, "e2fc714c4727ee9395f324cd2e7f331f", "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589", 4)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -109,13 +109,13 @@ func TestHashReaderVerification(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "Nested hash reader NewReader() should merge.",
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "", "", 4, false),
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "", "", 4),
|
||||
size: 4,
|
||||
actualSize: 4,
|
||||
},
|
||||
{
|
||||
desc: "Incorrect sha256, nested",
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "", "", 4, false),
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "", "", 4),
|
||||
size: 4,
|
||||
actualSize: 4,
|
||||
sha256hex: "50d858e0985ecc7f60418aaf0cc5ab587f42c2570a884095a9e8ccacd0f6545c",
|
||||
@@ -126,28 +126,28 @@ func TestHashReaderVerification(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "Correct sha256, nested",
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "", "", 4, false),
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "", "", 4),
|
||||
size: 4,
|
||||
actualSize: 4,
|
||||
sha256hex: "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589",
|
||||
},
|
||||
{
|
||||
desc: "Correct sha256, nested, truncated",
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd-more-stuff-to-be ignored")), 4, "", "", 4, false),
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd-more-stuff-to-be ignored")), 4, "", "", 4),
|
||||
size: 4,
|
||||
actualSize: -1,
|
||||
sha256hex: "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589",
|
||||
},
|
||||
{
|
||||
desc: "Correct sha256, nested, truncated, swapped",
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd-more-stuff-to-be ignored")), 4, "", "", -1, false),
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd-more-stuff-to-be ignored")), 4, "", "", -1),
|
||||
size: 4,
|
||||
actualSize: -1,
|
||||
sha256hex: "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589",
|
||||
},
|
||||
{
|
||||
desc: "Incorrect MD5, nested",
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "", "", 4, false),
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "", "", 4),
|
||||
size: 4,
|
||||
actualSize: 4,
|
||||
md5hex: "0773da587b322af3a8718cb418a715ce",
|
||||
@@ -165,7 +165,7 @@ func TestHashReaderVerification(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "Correct MD5, nested",
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "", "", 4, false),
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "", "", 4),
|
||||
size: 4,
|
||||
actualSize: 4,
|
||||
md5hex: "e2fc714c4727ee9395f324cd2e7f331f",
|
||||
@@ -180,7 +180,7 @@ func TestHashReaderVerification(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "Correct MD5, nested, truncated",
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd-morestuff")), -1, "", "", -1, false),
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd-morestuff")), -1, "", "", -1),
|
||||
size: 4,
|
||||
actualSize: 4,
|
||||
md5hex: "e2fc714c4727ee9395f324cd2e7f331f",
|
||||
@@ -188,7 +188,7 @@ func TestHashReaderVerification(t *testing.T) {
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
t.Run(fmt.Sprintf("case-%d", i+1), func(t *testing.T) {
|
||||
r, err := NewReader(testCase.src, testCase.size, testCase.md5hex, testCase.sha256hex, testCase.actualSize, false)
|
||||
r, err := NewReader(testCase.src, testCase.size, testCase.md5hex, testCase.sha256hex, testCase.actualSize)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %q: Initializing reader failed %s", testCase.desc, err)
|
||||
}
|
||||
@@ -202,8 +202,8 @@ func TestHashReaderVerification(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func mustReader(t *testing.T, src io.Reader, size int64, md5Hex, sha256Hex string, actualSize int64, strictCompat bool) *Reader {
|
||||
r, err := NewReader(src, size, md5Hex, sha256Hex, actualSize, strictCompat)
|
||||
func mustReader(t *testing.T, src io.Reader, size int64, md5Hex, sha256Hex string, actualSize int64) *Reader {
|
||||
r, err := NewReader(src, size, md5Hex, sha256Hex, actualSize)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -219,63 +219,57 @@ func TestHashReaderInvalidArguments(t *testing.T) {
|
||||
actualSize int64
|
||||
md5hex, sha256hex string
|
||||
success bool
|
||||
expectedErr error
|
||||
strict bool
|
||||
}{
|
||||
{
|
||||
desc: "Invalid md5sum NewReader() will fail.",
|
||||
src: bytes.NewReader([]byte("abcd")),
|
||||
size: 4,
|
||||
actualSize: 4,
|
||||
md5hex: "invalid-md5",
|
||||
success: false,
|
||||
expectedErr: BadDigest{},
|
||||
desc: "Invalid md5sum NewReader() will fail.",
|
||||
src: bytes.NewReader([]byte("abcd")),
|
||||
size: 4,
|
||||
actualSize: 4,
|
||||
md5hex: "invalid-md5",
|
||||
success: false,
|
||||
},
|
||||
{
|
||||
desc: "Invalid sha256 NewReader() will fail.",
|
||||
src: bytes.NewReader([]byte("abcd")),
|
||||
size: 4,
|
||||
actualSize: 4,
|
||||
sha256hex: "invalid-sha256",
|
||||
success: false,
|
||||
expectedErr: SHA256Mismatch{},
|
||||
desc: "Invalid sha256 NewReader() will fail.",
|
||||
src: bytes.NewReader([]byte("abcd")),
|
||||
size: 4,
|
||||
actualSize: 4,
|
||||
sha256hex: "invalid-sha256",
|
||||
success: false,
|
||||
},
|
||||
{
|
||||
desc: "Nested hash reader NewReader() should merge.",
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "", "", 4, false),
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "", "", 4),
|
||||
size: 4,
|
||||
actualSize: 4,
|
||||
success: true,
|
||||
},
|
||||
{
|
||||
desc: "Mismatching sha256",
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "", "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589", 4, false),
|
||||
size: 4,
|
||||
actualSize: 4,
|
||||
sha256hex: "50d858e0985ecc7f60418aaf0cc5ab587f42c2570a884095a9e8ccacd0f6545c",
|
||||
success: false,
|
||||
expectedErr: SHA256Mismatch{},
|
||||
desc: "Mismatching sha256",
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "", "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589", 4),
|
||||
size: 4,
|
||||
actualSize: 4,
|
||||
sha256hex: "50d858e0985ecc7f60418aaf0cc5ab587f42c2570a884095a9e8ccacd0f6545c",
|
||||
success: false,
|
||||
},
|
||||
{
|
||||
desc: "Correct sha256",
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "", "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589", 4, false),
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "", "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589", 4),
|
||||
size: 4,
|
||||
actualSize: 4,
|
||||
sha256hex: "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589",
|
||||
success: true,
|
||||
},
|
||||
{
|
||||
desc: "Mismatching MD5",
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "e2fc714c4727ee9395f324cd2e7f331f", "", 4, false),
|
||||
size: 4,
|
||||
actualSize: 4,
|
||||
md5hex: "0773da587b322af3a8718cb418a715ce",
|
||||
success: false,
|
||||
expectedErr: BadDigest{},
|
||||
desc: "Mismatching MD5",
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "e2fc714c4727ee9395f324cd2e7f331f", "", 4),
|
||||
size: 4,
|
||||
actualSize: 4,
|
||||
md5hex: "0773da587b322af3a8718cb418a715ce",
|
||||
success: false,
|
||||
},
|
||||
{
|
||||
desc: "Correct MD5",
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "e2fc714c4727ee9395f324cd2e7f331f", "", 4, false),
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd")), 4, "e2fc714c4727ee9395f324cd2e7f331f", "", 4),
|
||||
size: 4,
|
||||
actualSize: 4,
|
||||
md5hex: "e2fc714c4727ee9395f324cd2e7f331f",
|
||||
@@ -289,29 +283,23 @@ func TestHashReaderInvalidArguments(t *testing.T) {
|
||||
success: true,
|
||||
},
|
||||
{
|
||||
desc: "Nested, size mismatch",
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd-morestuff")), 4, "", "", -1, false),
|
||||
size: 2,
|
||||
actualSize: -1,
|
||||
success: false,
|
||||
expectedErr: ErrSizeMismatch{Want: 4, Got: 2},
|
||||
desc: "Nested, size mismatch",
|
||||
src: mustReader(t, bytes.NewReader([]byte("abcd-morestuff")), 4, "", "", -1),
|
||||
size: 2,
|
||||
actualSize: -1,
|
||||
success: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
t.Run(fmt.Sprintf("case-%d", i+1), func(t *testing.T) {
|
||||
_, err := NewReader(testCase.src, testCase.size, testCase.md5hex, testCase.sha256hex, testCase.actualSize, testCase.strict)
|
||||
_, err := NewReader(testCase.src, testCase.size, testCase.md5hex, testCase.sha256hex, testCase.actualSize)
|
||||
if err != nil && testCase.success {
|
||||
t.Errorf("Test %q: Expected success, but got error %s instead", testCase.desc, err)
|
||||
}
|
||||
if err == nil && !testCase.success {
|
||||
t.Errorf("Test %q: Expected error, but got success", testCase.desc)
|
||||
}
|
||||
if !testCase.success {
|
||||
if err != testCase.expectedErr {
|
||||
t.Errorf("Test %q: Expected error %v, but got %v", testCase.desc, testCase.expectedErr, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// MinIO Cloud Storage, (C) 2021 MinIO, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build fips
|
||||
|
||||
package hash
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// newSHA256 returns a new hash.Hash computing the SHA256 checksum.
|
||||
// The SHA256 implementation is FIPS 140-2 compliant when the
|
||||
// boringcrypto branch of Go is used.
|
||||
// Ref: https://github.com/golang/go/tree/dev.boringcrypto
|
||||
func newSHA256() hash.Hash { return sha256.New() }
|
||||
@@ -0,0 +1,27 @@
|
||||
// MinIO Cloud Storage, (C) 2021 MinIO, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !fips
|
||||
|
||||
package hash
|
||||
|
||||
import (
|
||||
"hash"
|
||||
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// newSHA256 returns a new hash.Hash computing the SHA256 checksum.
|
||||
// The SHA256 implementation is not FIPS 140-2 compliant.
|
||||
func newSHA256() hash.Hash { return sha256.New() }
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2021 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Forked from golang.org/pkg/os.ReadFile with NOATIME support.
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ioutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ReadFile reads the named file and returns the contents.
|
||||
// A successful call returns err == nil, not err == EOF.
|
||||
// Because ReadFile reads the whole file, it does not treat an EOF from Read
|
||||
// as an error to be reported.
|
||||
//
|
||||
// passes NOATIME flag for reads on Unix systems to avoid atime updates.
|
||||
func ReadFile(name string) ([]byte, error) {
|
||||
f, err := os.OpenFile(name, readMode, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var size int
|
||||
if info, err := f.Stat(); err == nil {
|
||||
size64 := info.Size()
|
||||
if int64(int(size64)) == size64 {
|
||||
size = int(size64)
|
||||
}
|
||||
}
|
||||
size++ // one byte for final read at EOF
|
||||
|
||||
// If a file claims a small size, read at least 512 bytes.
|
||||
// In particular, files in Linux's /proc claim size 0 but
|
||||
// then do not work right if read in small pieces,
|
||||
// so an initial read of 1 byte would not work correctly.
|
||||
if size < 512 {
|
||||
size = 512
|
||||
}
|
||||
|
||||
data := make([]byte, 0, size)
|
||||
for {
|
||||
if len(data) >= cap(data) {
|
||||
d := append(data[:cap(data)], 0)
|
||||
data = d[:len(data)]
|
||||
}
|
||||
n, err := f.Read(data[len(data):cap(data)])
|
||||
data = data[:len(data)+n]
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// +build windows darwin
|
||||
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2021 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ioutil
|
||||
|
||||
import "os"
|
||||
|
||||
var readMode = os.O_RDONLY
|
||||
@@ -0,0 +1,25 @@
|
||||
// +build !windows,!darwin
|
||||
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2021 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ioutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
var readMode = os.O_RDONLY | 0x40000 // read with O_NOATIME
|
||||
+3
-1
@@ -19,6 +19,7 @@ package madmin
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -476,7 +477,8 @@ func (adm AdminClient) newRequest(ctx context.Context, method string, reqData re
|
||||
if length := len(reqData.content); length > 0 {
|
||||
req.ContentLength = int64(length)
|
||||
}
|
||||
req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum256(reqData.content)))
|
||||
sum := sha256.Sum256(reqData.content)
|
||||
req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum[:]))
|
||||
req.Body = ioutil.NopCloser(bytes.NewReader(reqData.content))
|
||||
|
||||
req = signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, location)
|
||||
|
||||
@@ -25,8 +25,6 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
|
||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||
)
|
||||
|
||||
@@ -37,13 +35,6 @@ const (
|
||||
adminAPIPrefix = "/" + AdminAPIVersion
|
||||
)
|
||||
|
||||
// sum256 calculate sha256 sum for an input byte array.
|
||||
func sum256(data []byte) []byte {
|
||||
hash := sha256.New()
|
||||
hash.Write(data)
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
// jsonDecoder decode json to go type.
|
||||
func jsonDecoder(body io.Reader, v interface{}) error {
|
||||
d := json.NewDecoder(body)
|
||||
|
||||
Reference in New Issue
Block a user