|
|
|
/*
|
|
|
|
* MinIO Cloud Storage, (C) 2017 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 (
|
|
|
|
"context"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/minio/minio/cmd/logger"
|
|
|
|
"github.com/minio/minio/pkg/hash"
|
|
|
|
|
|
|
|
minio "github.com/minio/minio-go"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// CanonicalizeETag provides canonicalizeETag function alias.
|
|
|
|
CanonicalizeETag = canonicalizeETag
|
|
|
|
|
|
|
|
// MustGetUUID function alias.
|
|
|
|
MustGetUUID = mustGetUUID
|
|
|
|
|
|
|
|
// CleanMetadataKeys provides cleanMetadataKeys function alias.
|
|
|
|
CleanMetadataKeys = cleanMetadataKeys
|
|
|
|
|
|
|
|
// PathJoin function alias.
|
|
|
|
PathJoin = pathJoin
|
|
|
|
|
|
|
|
// ListObjects function alias.
|
|
|
|
ListObjects = listObjects
|
|
|
|
|
|
|
|
// FilterListEntries function alias.
|
|
|
|
FilterListEntries = filterListEntries
|
|
|
|
|
|
|
|
// IsStringEqual is string equal.
|
|
|
|
IsStringEqual = isStringEqual
|
|
|
|
|
|
|
|
// GetCompleteMultipartMD5 returns multipart MD5
|
|
|
|
GetCompleteMultipartMD5 = getCompleteMultipartMD5
|
|
|
|
)
|
|
|
|
|
|
|
|
// StatInfo - alias for statInfo
|
|
|
|
type StatInfo struct {
|
|
|
|
statInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
// AnonErrToObjectErr - converts standard http codes into meaningful object layer errors.
|
|
|
|
func AnonErrToObjectErr(statusCode int, params ...string) error {
|
|
|
|
bucket := ""
|
|
|
|
object := ""
|
|
|
|
if len(params) >= 1 {
|
|
|
|
bucket = params[0]
|
|
|
|
}
|
|
|
|
if len(params) == 2 {
|
|
|
|
object = params[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
switch statusCode {
|
|
|
|
case http.StatusNotFound:
|
|
|
|
if object != "" {
|
|
|
|
return ObjectNotFound{bucket, object}
|
|
|
|
}
|
|
|
|
return BucketNotFound{Bucket: bucket}
|
|
|
|
case http.StatusBadRequest:
|
|
|
|
if object != "" {
|
|
|
|
return ObjectNameInvalid{bucket, object}
|
|
|
|
}
|
|
|
|
return BucketNameInvalid{Bucket: bucket}
|
|
|
|
case http.StatusForbidden:
|
|
|
|
fallthrough
|
|
|
|
case http.StatusUnauthorized:
|
|
|
|
return AllAccessDisabled{bucket, object}
|
|
|
|
}
|
|
|
|
|
|
|
|
return errUnexpected
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromMinioClientMetadata converts minio metadata to map[string]string
|
|
|
|
func FromMinioClientMetadata(metadata map[string][]string) map[string]string {
|
|
|
|
mm := map[string]string{}
|
|
|
|
for k, v := range metadata {
|
|
|
|
mm[http.CanonicalHeaderKey(k)] = v[0]
|
|
|
|
}
|
|
|
|
return mm
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromMinioClientObjectPart converts minio ObjectPart to PartInfo
|
|
|
|
func FromMinioClientObjectPart(op minio.ObjectPart) PartInfo {
|
|
|
|
return PartInfo{
|
|
|
|
Size: op.Size,
|
|
|
|
ETag: canonicalizeETag(op.ETag),
|
|
|
|
LastModified: op.LastModified,
|
|
|
|
PartNumber: op.PartNumber,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromMinioClientListPartsInfo converts minio ListObjectPartsResult to ListPartsInfo
|
|
|
|
func FromMinioClientListPartsInfo(lopr minio.ListObjectPartsResult) ListPartsInfo {
|
|
|
|
// Convert minio ObjectPart to PartInfo
|
|
|
|
fromMinioClientObjectParts := func(parts []minio.ObjectPart) []PartInfo {
|
|
|
|
toParts := make([]PartInfo, len(parts))
|
|
|
|
for i, part := range parts {
|
|
|
|
toParts[i] = FromMinioClientObjectPart(part)
|
|
|
|
}
|
|
|
|
return toParts
|
|
|
|
}
|
|
|
|
|
|
|
|
return ListPartsInfo{
|
|
|
|
UploadID: lopr.UploadID,
|
|
|
|
Bucket: lopr.Bucket,
|
|
|
|
Object: lopr.Key,
|
|
|
|
StorageClass: "",
|
|
|
|
PartNumberMarker: lopr.PartNumberMarker,
|
|
|
|
NextPartNumberMarker: lopr.NextPartNumberMarker,
|
|
|
|
MaxParts: lopr.MaxParts,
|
|
|
|
IsTruncated: lopr.IsTruncated,
|
|
|
|
EncodingType: lopr.EncodingType,
|
|
|
|
Parts: fromMinioClientObjectParts(lopr.ObjectParts),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromMinioClientListMultipartsInfo converts minio ListMultipartUploadsResult to ListMultipartsInfo
|
|
|
|
func FromMinioClientListMultipartsInfo(lmur minio.ListMultipartUploadsResult) ListMultipartsInfo {
|
|
|
|
uploads := make([]MultipartInfo, len(lmur.Uploads))
|
|
|
|
|
|
|
|
for i, um := range lmur.Uploads {
|
|
|
|
uploads[i] = MultipartInfo{
|
|
|
|
Object: um.Key,
|
|
|
|
UploadID: um.UploadID,
|
|
|
|
Initiated: um.Initiated,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
commonPrefixes := make([]string, len(lmur.CommonPrefixes))
|
|
|
|
for i, cp := range lmur.CommonPrefixes {
|
|
|
|
commonPrefixes[i] = cp.Prefix
|
|
|
|
}
|
|
|
|
|
|
|
|
return ListMultipartsInfo{
|
|
|
|
KeyMarker: lmur.KeyMarker,
|
|
|
|
UploadIDMarker: lmur.UploadIDMarker,
|
|
|
|
NextKeyMarker: lmur.NextKeyMarker,
|
|
|
|
NextUploadIDMarker: lmur.NextUploadIDMarker,
|
|
|
|
MaxUploads: int(lmur.MaxUploads),
|
|
|
|
IsTruncated: lmur.IsTruncated,
|
|
|
|
Uploads: uploads,
|
|
|
|
Prefix: lmur.Prefix,
|
|
|
|
Delimiter: lmur.Delimiter,
|
|
|
|
CommonPrefixes: commonPrefixes,
|
|
|
|
EncodingType: lmur.EncodingType,
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromMinioClientObjectInfo converts minio ObjectInfo to gateway ObjectInfo
|
|
|
|
func FromMinioClientObjectInfo(bucket string, oi minio.ObjectInfo) ObjectInfo {
|
|
|
|
userDefined := FromMinioClientMetadata(oi.Metadata)
|
|
|
|
userDefined["Content-Type"] = oi.ContentType
|
|
|
|
|
|
|
|
return ObjectInfo{
|
|
|
|
Bucket: bucket,
|
|
|
|
Name: oi.Key,
|
|
|
|
ModTime: oi.LastModified,
|
|
|
|
Size: oi.Size,
|
|
|
|
ETag: canonicalizeETag(oi.ETag),
|
|
|
|
UserDefined: userDefined,
|
|
|
|
ContentType: oi.ContentType,
|
|
|
|
ContentEncoding: oi.Metadata.Get("Content-Encoding"),
|
|
|
|
StorageClass: oi.StorageClass,
|
|
|
|
Expires: oi.Expires,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromMinioClientListBucketV2Result converts minio ListBucketResult to ListObjectsInfo
|
|
|
|
func FromMinioClientListBucketV2Result(bucket string, result minio.ListBucketV2Result) ListObjectsV2Info {
|
|
|
|
objects := make([]ObjectInfo, len(result.Contents))
|
|
|
|
|
|
|
|
for i, oi := range result.Contents {
|
|
|
|
objects[i] = FromMinioClientObjectInfo(bucket, oi)
|
|
|
|
}
|
|
|
|
|
|
|
|
prefixes := make([]string, len(result.CommonPrefixes))
|
|
|
|
for i, p := range result.CommonPrefixes {
|
|
|
|
prefixes[i] = p.Prefix
|
|
|
|
}
|
|
|
|
|
|
|
|
return ListObjectsV2Info{
|
|
|
|
IsTruncated: result.IsTruncated,
|
|
|
|
Prefixes: prefixes,
|
|
|
|
Objects: objects,
|
|
|
|
|
|
|
|
ContinuationToken: result.ContinuationToken,
|
|
|
|
NextContinuationToken: result.NextContinuationToken,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromMinioClientListBucketResult converts minio ListBucketResult to ListObjectsInfo
|
|
|
|
func FromMinioClientListBucketResult(bucket string, result minio.ListBucketResult) ListObjectsInfo {
|
|
|
|
objects := make([]ObjectInfo, len(result.Contents))
|
|
|
|
|
|
|
|
for i, oi := range result.Contents {
|
|
|
|
objects[i] = FromMinioClientObjectInfo(bucket, oi)
|
|
|
|
}
|
|
|
|
|
|
|
|
prefixes := make([]string, len(result.CommonPrefixes))
|
|
|
|
for i, p := range result.CommonPrefixes {
|
|
|
|
prefixes[i] = p.Prefix
|
|
|
|
}
|
|
|
|
|
|
|
|
return ListObjectsInfo{
|
|
|
|
IsTruncated: result.IsTruncated,
|
|
|
|
NextMarker: result.NextMarker,
|
|
|
|
Prefixes: prefixes,
|
|
|
|
Objects: objects,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromMinioClientListBucketResultToV2Info converts minio ListBucketResult to ListObjectsV2Info
|
|
|
|
func FromMinioClientListBucketResultToV2Info(bucket string, result minio.ListBucketResult) ListObjectsV2Info {
|
|
|
|
objects := make([]ObjectInfo, len(result.Contents))
|
|
|
|
|
|
|
|
for i, oi := range result.Contents {
|
|
|
|
objects[i] = FromMinioClientObjectInfo(bucket, oi)
|
|
|
|
}
|
|
|
|
|
|
|
|
prefixes := make([]string, len(result.CommonPrefixes))
|
|
|
|
for i, p := range result.CommonPrefixes {
|
|
|
|
prefixes[i] = p.Prefix
|
|
|
|
}
|
|
|
|
|
|
|
|
return ListObjectsV2Info{
|
|
|
|
IsTruncated: result.IsTruncated,
|
|
|
|
Prefixes: prefixes,
|
|
|
|
Objects: objects,
|
|
|
|
ContinuationToken: result.Marker,
|
|
|
|
NextContinuationToken: result.NextMarker,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ToMinioClientObjectInfoMetadata convertes metadata to map[string][]string
|
|
|
|
func ToMinioClientObjectInfoMetadata(metadata map[string]string) map[string][]string {
|
|
|
|
mm := make(map[string][]string, len(metadata))
|
|
|
|
for k, v := range metadata {
|
|
|
|
mm[http.CanonicalHeaderKey(k)] = []string{v}
|
|
|
|
}
|
|
|
|
return mm
|
|
|
|
}
|
|
|
|
|
|
|
|
// ToMinioClientMetadata converts metadata to map[string]string
|
|
|
|
func ToMinioClientMetadata(metadata map[string]string) map[string]string {
|
|
|
|
mm := make(map[string]string)
|
|
|
|
for k, v := range metadata {
|
|
|
|
mm[http.CanonicalHeaderKey(k)] = v
|
|
|
|
}
|
|
|
|
return mm
|
|
|
|
}
|
|
|
|
|
|
|
|
// ToMinioClientCompletePart converts CompletePart to minio CompletePart
|
|
|
|
func ToMinioClientCompletePart(part CompletePart) minio.CompletePart {
|
|
|
|
return minio.CompletePart{
|
|
|
|
ETag: part.ETag,
|
|
|
|
PartNumber: part.PartNumber,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ToMinioClientCompleteParts converts []CompletePart to minio []CompletePart
|
|
|
|
func ToMinioClientCompleteParts(parts []CompletePart) []minio.CompletePart {
|
|
|
|
mparts := make([]minio.CompletePart, len(parts))
|
|
|
|
for i, part := range parts {
|
|
|
|
mparts[i] = ToMinioClientCompletePart(part)
|
|
|
|
}
|
|
|
|
return mparts
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrorRespToObjectError converts MinIO errors to minio object layer errors.
|
|
|
|
func ErrorRespToObjectError(err error, params ...string) error {
|
|
|
|
if err == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
bucket := ""
|
|
|
|
object := ""
|
|
|
|
if len(params) >= 1 {
|
|
|
|
bucket = params[0]
|
|
|
|
}
|
|
|
|
if len(params) == 2 {
|
|
|
|
object = params[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
if isNetworkOrHostDown(err) {
|
|
|
|
return BackendDown{}
|
|
|
|
}
|
|
|
|
|
|
|
|
minioErr, ok := err.(minio.ErrorResponse)
|
|
|
|
if !ok {
|
|
|
|
// We don't interpret non MinIO errors. As minio errors will
|
|
|
|
// have StatusCode to help to convert to object errors.
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch minioErr.Code {
|
|
|
|
case "BucketAlreadyOwnedByYou":
|
|
|
|
err = BucketAlreadyOwnedByYou{}
|
|
|
|
case "BucketNotEmpty":
|
|
|
|
err = BucketNotEmpty{}
|
|
|
|
case "NoSuchBucketPolicy":
|
|
|
|
err = BucketPolicyNotFound{}
|
|
|
|
case "InvalidBucketName":
|
|
|
|
err = BucketNameInvalid{Bucket: bucket}
|
|
|
|
case "InvalidPart":
|
|
|
|
err = InvalidPart{}
|
|
|
|
case "NoSuchBucket":
|
|
|
|
err = BucketNotFound{Bucket: bucket}
|
|
|
|
case "NoSuchKey":
|
|
|
|
if object != "" {
|
|
|
|
err = ObjectNotFound{Bucket: bucket, Object: object}
|
|
|
|
} else {
|
|
|
|
err = BucketNotFound{Bucket: bucket}
|
|
|
|
}
|
|
|
|
case "XMinioInvalidObjectName":
|
|
|
|
err = ObjectNameInvalid{}
|
|
|
|
case "AccessDenied":
|
|
|
|
err = PrefixAccessDenied{
|
|
|
|
Bucket: bucket,
|
|
|
|
Object: object,
|
|
|
|
}
|
|
|
|
case "XAmzContentSHA256Mismatch":
|
|
|
|
err = hash.SHA256Mismatch{}
|
|
|
|
case "NoSuchUpload":
|
|
|
|
err = InvalidUploadID{}
|
|
|
|
case "EntityTooSmall":
|
|
|
|
err = PartTooSmall{}
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// ComputeCompleteMultipartMD5 calculates MD5 ETag for complete multipart responses
|
|
|
|
func ComputeCompleteMultipartMD5(parts []CompletePart) (string, error) {
|
|
|
|
return getCompleteMultipartMD5(context.Background(), parts)
|
|
|
|
}
|
|
|
|
|
|
|
|
// parse gateway sse env variable
|
|
|
|
func parseGatewaySSE(s string) (gatewaySSE, error) {
|
|
|
|
l := strings.Split(s, ";")
|
|
|
|
var gwSlice = make([]string, 0)
|
|
|
|
for _, val := range l {
|
|
|
|
v := strings.ToUpper(val)
|
|
|
|
if v == gatewaySSES3 || v == gatewaySSEC {
|
|
|
|
gwSlice = append(gwSlice, v)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return nil, uiErrInvalidGWSSEValue(nil).Msg("gateway SSE cannot be (%s) ", v)
|
|
|
|
}
|
|
|
|
return gatewaySSE(gwSlice), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// handle gateway env vars
|
|
|
|
func handleGatewayEnvVars() {
|
|
|
|
gwsseVal, ok := os.LookupEnv("MINIO_GATEWAY_SSE")
|
|
|
|
if ok {
|
|
|
|
var err error
|
|
|
|
GlobalGatewaySSE, err = parseGatewaySSE(gwsseVal)
|
|
|
|
if err != nil {
|
|
|
|
logger.Fatal(err, "Unable to parse MINIO_GATEWAY_SSE value (`%s`)", gwsseVal)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|