mirror of
				https://github.com/minio/minio.git
				synced 2025-11-04 10:11:09 +01:00 
			
		
		
		
	This refactor brings a change which allows targets to be added in a cleaner way and also audit is now moved out. This PR also simplifies logger dependency for auditing
		
			
				
	
	
		
			1672 lines
		
	
	
		
			47 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1672 lines
		
	
	
		
			47 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
 * Minio Cloud Storage, (C) 2016, 2017, 2018 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 (
 | 
						|
	"archive/zip"
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"net"
 | 
						|
	"net/http"
 | 
						|
	"net/url"
 | 
						|
	"os"
 | 
						|
	"path"
 | 
						|
	"runtime"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
 | 
						|
	humanize "github.com/dustin/go-humanize"
 | 
						|
	snappy "github.com/golang/snappy"
 | 
						|
	"github.com/gorilla/mux"
 | 
						|
	"github.com/gorilla/rpc/v2/json2"
 | 
						|
	miniogopolicy "github.com/minio/minio-go/pkg/policy"
 | 
						|
	"github.com/minio/minio-go/pkg/s3utils"
 | 
						|
	"github.com/minio/minio/browser"
 | 
						|
	"github.com/minio/minio/cmd/crypto"
 | 
						|
	"github.com/minio/minio/cmd/logger"
 | 
						|
	"github.com/minio/minio/pkg/auth"
 | 
						|
	"github.com/minio/minio/pkg/dns"
 | 
						|
	"github.com/minio/minio/pkg/event"
 | 
						|
	"github.com/minio/minio/pkg/handlers"
 | 
						|
	"github.com/minio/minio/pkg/hash"
 | 
						|
	"github.com/minio/minio/pkg/iam/policy"
 | 
						|
	"github.com/minio/minio/pkg/ioutil"
 | 
						|
	"github.com/minio/minio/pkg/policy"
 | 
						|
)
 | 
						|
 | 
						|
// WebGenericArgs - empty struct for calls that don't accept arguments
 | 
						|
// for ex. ServerInfo, GenerateAuth
 | 
						|
type WebGenericArgs struct{}
 | 
						|
 | 
						|
// WebGenericRep - reply structure for calls for which reply is success/failure
 | 
						|
// for ex. RemoveObject MakeBucket
 | 
						|
type WebGenericRep struct {
 | 
						|
	UIVersion string `json:"uiVersion"`
 | 
						|
}
 | 
						|
 | 
						|
// ServerInfoRep - server info reply.
 | 
						|
type ServerInfoRep struct {
 | 
						|
	MinioVersion    string
 | 
						|
	MinioMemory     string
 | 
						|
	MinioPlatform   string
 | 
						|
	MinioRuntime    string
 | 
						|
	MinioGlobalInfo map[string]interface{}
 | 
						|
	UIVersion       string `json:"uiVersion"`
 | 
						|
}
 | 
						|
 | 
						|
// ServerInfo - get server info.
 | 
						|
func (web *webAPIHandlers) ServerInfo(r *http.Request, args *WebGenericArgs, reply *ServerInfoRep) error {
 | 
						|
	_, owner, authErr := webRequestAuthenticate(r)
 | 
						|
	if authErr != nil {
 | 
						|
		return toJSONError(authErr)
 | 
						|
	}
 | 
						|
	host, err := os.Hostname()
 | 
						|
	if err != nil {
 | 
						|
		host = ""
 | 
						|
	}
 | 
						|
	memstats := &runtime.MemStats{}
 | 
						|
	runtime.ReadMemStats(memstats)
 | 
						|
	mem := fmt.Sprintf("Used: %s | Allocated: %s | Used-Heap: %s | Allocated-Heap: %s",
 | 
						|
		humanize.Bytes(memstats.Alloc),
 | 
						|
		humanize.Bytes(memstats.TotalAlloc),
 | 
						|
		humanize.Bytes(memstats.HeapAlloc),
 | 
						|
		humanize.Bytes(memstats.HeapSys))
 | 
						|
	platform := fmt.Sprintf("Host: %s | OS: %s | Arch: %s",
 | 
						|
		host,
 | 
						|
		runtime.GOOS,
 | 
						|
		runtime.GOARCH)
 | 
						|
	goruntime := fmt.Sprintf("Version: %s | CPUs: %s", runtime.Version(), strconv.Itoa(runtime.NumCPU()))
 | 
						|
 | 
						|
	reply.MinioVersion = Version
 | 
						|
	reply.MinioGlobalInfo = getGlobalInfo()
 | 
						|
	// If ENV creds are not set and incoming user is not owner
 | 
						|
	// disable changing credentials.
 | 
						|
	// TODO: fix this in future and allow changing user credentials.
 | 
						|
	v, ok := reply.MinioGlobalInfo["isEnvCreds"].(bool)
 | 
						|
	if ok && !v {
 | 
						|
		reply.MinioGlobalInfo["isEnvCreds"] = !owner
 | 
						|
	}
 | 
						|
 | 
						|
	reply.MinioMemory = mem
 | 
						|
	reply.MinioPlatform = platform
 | 
						|
	reply.MinioRuntime = goruntime
 | 
						|
	reply.UIVersion = browser.UIVersion
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// StorageInfoRep - contains storage usage statistics.
 | 
						|
type StorageInfoRep struct {
 | 
						|
	StorageInfo StorageInfo `json:"storageInfo"`
 | 
						|
	UIVersion   string      `json:"uiVersion"`
 | 
						|
}
 | 
						|
 | 
						|
// StorageInfo - web call to gather storage usage statistics.
 | 
						|
func (web *webAPIHandlers) StorageInfo(r *http.Request, args *AuthArgs, reply *StorageInfoRep) error {
 | 
						|
	objectAPI := web.ObjectAPI()
 | 
						|
	if objectAPI == nil {
 | 
						|
		return toJSONError(errServerNotInitialized)
 | 
						|
	}
 | 
						|
	_, _, authErr := webRequestAuthenticate(r)
 | 
						|
	if authErr != nil {
 | 
						|
		return toJSONError(authErr)
 | 
						|
	}
 | 
						|
	reply.StorageInfo = objectAPI.StorageInfo(context.Background())
 | 
						|
	reply.UIVersion = browser.UIVersion
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// MakeBucketArgs - make bucket args.
 | 
						|
type MakeBucketArgs struct {
 | 
						|
	BucketName string `json:"bucketName"`
 | 
						|
}
 | 
						|
 | 
						|
// MakeBucket - creates a new bucket.
 | 
						|
func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, reply *WebGenericRep) error {
 | 
						|
	objectAPI := web.ObjectAPI()
 | 
						|
	if objectAPI == nil {
 | 
						|
		return toJSONError(errServerNotInitialized)
 | 
						|
	}
 | 
						|
	_, owner, authErr := webRequestAuthenticate(r)
 | 
						|
	if authErr != nil {
 | 
						|
		return toJSONError(authErr)
 | 
						|
	}
 | 
						|
	// TODO: Allow MakeBucket in future.
 | 
						|
	if !owner {
 | 
						|
		return toJSONError(errAccessDenied)
 | 
						|
	}
 | 
						|
 | 
						|
	// Check if bucket is a reserved bucket name or invalid.
 | 
						|
	if isReservedOrInvalidBucket(args.BucketName) {
 | 
						|
		return toJSONError(errInvalidBucketName)
 | 
						|
	}
 | 
						|
 | 
						|
	if globalDNSConfig != nil {
 | 
						|
		if _, err := globalDNSConfig.Get(args.BucketName); err != nil {
 | 
						|
			if err == dns.ErrNoEntriesFound {
 | 
						|
				// Proceed to creating a bucket.
 | 
						|
				if err = objectAPI.MakeBucketWithLocation(context.Background(), args.BucketName, globalServerConfig.GetRegion()); err != nil {
 | 
						|
					return toJSONError(err)
 | 
						|
				}
 | 
						|
				if err = globalDNSConfig.Put(args.BucketName); err != nil {
 | 
						|
					objectAPI.DeleteBucket(context.Background(), args.BucketName)
 | 
						|
					return toJSONError(err)
 | 
						|
				}
 | 
						|
 | 
						|
				reply.UIVersion = browser.UIVersion
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
			return toJSONError(err)
 | 
						|
		}
 | 
						|
		return toJSONError(errBucketAlreadyExists)
 | 
						|
	}
 | 
						|
 | 
						|
	if err := objectAPI.MakeBucketWithLocation(context.Background(), args.BucketName, globalServerConfig.GetRegion()); err != nil {
 | 
						|
		return toJSONError(err, args.BucketName)
 | 
						|
	}
 | 
						|
 | 
						|
	reply.UIVersion = browser.UIVersion
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// RemoveBucketArgs - remove bucket args.
 | 
						|
type RemoveBucketArgs struct {
 | 
						|
	BucketName string `json:"bucketName"`
 | 
						|
}
 | 
						|
 | 
						|
// DeleteBucket - removes a bucket, must be empty.
 | 
						|
func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs, reply *WebGenericRep) error {
 | 
						|
	objectAPI := web.ObjectAPI()
 | 
						|
	if objectAPI == nil {
 | 
						|
		return toJSONError(errServerNotInitialized)
 | 
						|
	}
 | 
						|
	_, owner, authErr := webRequestAuthenticate(r)
 | 
						|
	if authErr != nil {
 | 
						|
		return toJSONError(authErr)
 | 
						|
	}
 | 
						|
	// TODO: Allow DeleteBucket in future.
 | 
						|
	if !owner {
 | 
						|
		return toJSONError(errAccessDenied)
 | 
						|
	}
 | 
						|
 | 
						|
	ctx := context.Background()
 | 
						|
 | 
						|
	deleteBucket := objectAPI.DeleteBucket
 | 
						|
	if web.CacheAPI() != nil {
 | 
						|
		deleteBucket = web.CacheAPI().DeleteBucket
 | 
						|
	}
 | 
						|
 | 
						|
	if err := deleteBucket(ctx, args.BucketName); err != nil {
 | 
						|
		return toJSONError(err, args.BucketName)
 | 
						|
	}
 | 
						|
 | 
						|
	globalNotificationSys.RemoveNotification(args.BucketName)
 | 
						|
	globalPolicySys.Remove(args.BucketName)
 | 
						|
	globalNotificationSys.DeleteBucket(ctx, args.BucketName)
 | 
						|
 | 
						|
	if globalDNSConfig != nil {
 | 
						|
		if err := globalDNSConfig.Delete(args.BucketName); err != nil {
 | 
						|
			// Deleting DNS entry failed, attempt to create the bucket again.
 | 
						|
			objectAPI.MakeBucketWithLocation(ctx, args.BucketName, "")
 | 
						|
			return toJSONError(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	reply.UIVersion = browser.UIVersion
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// ListBucketsRep - list buckets response
 | 
						|
type ListBucketsRep struct {
 | 
						|
	Buckets   []WebBucketInfo `json:"buckets"`
 | 
						|
	UIVersion string          `json:"uiVersion"`
 | 
						|
}
 | 
						|
 | 
						|
// WebBucketInfo container for list buckets metadata.
 | 
						|
type WebBucketInfo struct {
 | 
						|
	// The name of the bucket.
 | 
						|
	Name string `json:"name"`
 | 
						|
	// Date the bucket was created.
 | 
						|
	CreationDate time.Time `json:"creationDate"`
 | 
						|
}
 | 
						|
 | 
						|
// ListBuckets - list buckets api.
 | 
						|
func (web *webAPIHandlers) ListBuckets(r *http.Request, args *WebGenericArgs, reply *ListBucketsRep) error {
 | 
						|
	objectAPI := web.ObjectAPI()
 | 
						|
	if objectAPI == nil {
 | 
						|
		return toJSONError(errServerNotInitialized)
 | 
						|
	}
 | 
						|
	listBuckets := objectAPI.ListBuckets
 | 
						|
	if web.CacheAPI() != nil {
 | 
						|
		listBuckets = web.CacheAPI().ListBuckets
 | 
						|
	}
 | 
						|
 | 
						|
	if _, _, authErr := webRequestAuthenticate(r); authErr != nil {
 | 
						|
		return toJSONError(authErr)
 | 
						|
	}
 | 
						|
 | 
						|
	// If etcd, dns federation configured list buckets from etcd.
 | 
						|
	if globalDNSConfig != nil {
 | 
						|
		dnsBuckets, err := globalDNSConfig.List()
 | 
						|
		if err != nil {
 | 
						|
			return toJSONError(err)
 | 
						|
		}
 | 
						|
		for _, dnsRecord := range dnsBuckets {
 | 
						|
			bucketName := strings.Trim(dnsRecord.Key, "/")
 | 
						|
			reply.Buckets = append(reply.Buckets, WebBucketInfo{
 | 
						|
				Name:         bucketName,
 | 
						|
				CreationDate: dnsRecord.CreationDate,
 | 
						|
			})
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		buckets, err := listBuckets(context.Background())
 | 
						|
		if err != nil {
 | 
						|
			return toJSONError(err)
 | 
						|
		}
 | 
						|
		for _, bucket := range buckets {
 | 
						|
			reply.Buckets = append(reply.Buckets, WebBucketInfo{
 | 
						|
				Name:         bucket.Name,
 | 
						|
				CreationDate: bucket.Created,
 | 
						|
			})
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	reply.UIVersion = browser.UIVersion
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// ListObjectsArgs - list object args.
 | 
						|
type ListObjectsArgs struct {
 | 
						|
	BucketName string `json:"bucketName"`
 | 
						|
	Prefix     string `json:"prefix"`
 | 
						|
	Marker     string `json:"marker"`
 | 
						|
}
 | 
						|
 | 
						|
// ListObjectsRep - list objects response.
 | 
						|
type ListObjectsRep struct {
 | 
						|
	Objects     []WebObjectInfo `json:"objects"`
 | 
						|
	NextMarker  string          `json:"nextmarker"`
 | 
						|
	IsTruncated bool            `json:"istruncated"`
 | 
						|
	Writable    bool            `json:"writable"` // Used by client to show "upload file" button.
 | 
						|
	UIVersion   string          `json:"uiVersion"`
 | 
						|
}
 | 
						|
 | 
						|
// WebObjectInfo container for list objects metadata.
 | 
						|
type WebObjectInfo struct {
 | 
						|
	// Name of the object
 | 
						|
	Key string `json:"name"`
 | 
						|
	// Date and time the object was last modified.
 | 
						|
	LastModified time.Time `json:"lastModified"`
 | 
						|
	// Size in bytes of the object.
 | 
						|
	Size int64 `json:"size"`
 | 
						|
	// ContentType is mime type of the object.
 | 
						|
	ContentType string `json:"contentType"`
 | 
						|
}
 | 
						|
 | 
						|
// ListObjects - list objects api.
 | 
						|
func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, reply *ListObjectsRep) error {
 | 
						|
	reply.UIVersion = browser.UIVersion
 | 
						|
	objectAPI := web.ObjectAPI()
 | 
						|
	if objectAPI == nil {
 | 
						|
		return toJSONError(errServerNotInitialized)
 | 
						|
	}
 | 
						|
 | 
						|
	listObjects := objectAPI.ListObjects
 | 
						|
	if web.CacheAPI() != nil {
 | 
						|
		listObjects = web.CacheAPI().ListObjects
 | 
						|
	}
 | 
						|
 | 
						|
	claims, owner, authErr := webRequestAuthenticate(r)
 | 
						|
	if authErr != nil {
 | 
						|
		if authErr == errNoAuthToken {
 | 
						|
			// Check if anonymous (non-owner) has access to download objects.
 | 
						|
			readable := globalPolicySys.IsAllowed(policy.Args{
 | 
						|
				Action:          policy.GetObjectAction,
 | 
						|
				BucketName:      args.BucketName,
 | 
						|
				ConditionValues: getConditionValues(r, ""),
 | 
						|
				IsOwner:         false,
 | 
						|
				ObjectName:      args.Prefix + "/",
 | 
						|
			})
 | 
						|
 | 
						|
			// Check if anonymous (non-owner) has access to upload objects.
 | 
						|
			writable := globalPolicySys.IsAllowed(policy.Args{
 | 
						|
				Action:          policy.PutObjectAction,
 | 
						|
				BucketName:      args.BucketName,
 | 
						|
				ConditionValues: getConditionValues(r, ""),
 | 
						|
				IsOwner:         false,
 | 
						|
				ObjectName:      args.Prefix + "/",
 | 
						|
			})
 | 
						|
 | 
						|
			reply.Writable = writable
 | 
						|
			if !readable {
 | 
						|
				// Error out if anonymous user (non-owner) has no access to download or upload objects
 | 
						|
				if !writable {
 | 
						|
					return errAccessDenied
 | 
						|
				}
 | 
						|
				// return empty object list if access is write only
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			return toJSONError(authErr)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// For authenticated users apply IAM policy.
 | 
						|
	if authErr == nil {
 | 
						|
		readable := globalIAMSys.IsAllowed(iampolicy.Args{
 | 
						|
			AccountName:     claims.Subject,
 | 
						|
			Action:          iampolicy.Action(policy.GetObjectAction),
 | 
						|
			BucketName:      args.BucketName,
 | 
						|
			ConditionValues: getConditionValues(r, ""),
 | 
						|
			IsOwner:         owner,
 | 
						|
			ObjectName:      args.Prefix + "/",
 | 
						|
		})
 | 
						|
 | 
						|
		writable := globalIAMSys.IsAllowed(iampolicy.Args{
 | 
						|
			AccountName:     claims.Subject,
 | 
						|
			Action:          iampolicy.Action(policy.PutObjectAction),
 | 
						|
			BucketName:      args.BucketName,
 | 
						|
			ConditionValues: getConditionValues(r, ""),
 | 
						|
			IsOwner:         owner,
 | 
						|
			ObjectName:      args.Prefix + "/",
 | 
						|
		})
 | 
						|
 | 
						|
		reply.Writable = writable
 | 
						|
		if !readable {
 | 
						|
			// Error out if anonymous user (non-owner) has no access to download or upload objects
 | 
						|
			if !writable {
 | 
						|
				return errAccessDenied
 | 
						|
			}
 | 
						|
			// return empty object list if access is write only
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	lo, err := listObjects(context.Background(), args.BucketName, args.Prefix, args.Marker, slashSeparator, 1000)
 | 
						|
	if err != nil {
 | 
						|
		return &json2.Error{Message: err.Error()}
 | 
						|
	}
 | 
						|
	for i := range lo.Objects {
 | 
						|
		if crypto.IsEncrypted(lo.Objects[i].UserDefined) {
 | 
						|
			lo.Objects[i].Size, err = lo.Objects[i].DecryptedSize()
 | 
						|
			if err != nil {
 | 
						|
				return toJSONError(err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	reply.NextMarker = lo.NextMarker
 | 
						|
	reply.IsTruncated = lo.IsTruncated
 | 
						|
	for _, obj := range lo.Objects {
 | 
						|
		reply.Objects = append(reply.Objects, WebObjectInfo{
 | 
						|
			Key:          obj.Name,
 | 
						|
			LastModified: obj.ModTime,
 | 
						|
			Size:         obj.Size,
 | 
						|
			ContentType:  obj.ContentType,
 | 
						|
		})
 | 
						|
	}
 | 
						|
	for _, prefix := range lo.Prefixes {
 | 
						|
		reply.Objects = append(reply.Objects, WebObjectInfo{
 | 
						|
			Key: prefix,
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// RemoveObjectArgs - args to remove an object, JSON will look like.
 | 
						|
//
 | 
						|
// {
 | 
						|
//     "bucketname": "testbucket",
 | 
						|
//     "objects": [
 | 
						|
//         "photos/hawaii/",
 | 
						|
//         "photos/maldives/",
 | 
						|
//         "photos/sanjose.jpg"
 | 
						|
//     ]
 | 
						|
// }
 | 
						|
type RemoveObjectArgs struct {
 | 
						|
	Objects    []string `json:"objects"`    // Contains objects, prefixes.
 | 
						|
	BucketName string   `json:"bucketname"` // Contains bucket name.
 | 
						|
}
 | 
						|
 | 
						|
// RemoveObject - removes an object, or all the objects at a given prefix.
 | 
						|
func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs, reply *WebGenericRep) error {
 | 
						|
	objectAPI := web.ObjectAPI()
 | 
						|
	if objectAPI == nil {
 | 
						|
		return toJSONError(errServerNotInitialized)
 | 
						|
	}
 | 
						|
	listObjects := objectAPI.ListObjects
 | 
						|
	if web.CacheAPI() != nil {
 | 
						|
		listObjects = web.CacheAPI().ListObjects
 | 
						|
	}
 | 
						|
 | 
						|
	claims, owner, authErr := webRequestAuthenticate(r)
 | 
						|
	if authErr != nil {
 | 
						|
		return toJSONError(authErr)
 | 
						|
	}
 | 
						|
 | 
						|
	if args.BucketName == "" || len(args.Objects) == 0 {
 | 
						|
		return toJSONError(errInvalidArgument)
 | 
						|
	}
 | 
						|
 | 
						|
	var err error
 | 
						|
next:
 | 
						|
	for _, objectName := range args.Objects {
 | 
						|
		// If not a directory, remove the object.
 | 
						|
		if !hasSuffix(objectName, slashSeparator) && objectName != "" {
 | 
						|
			// Deny if WORM is enabled
 | 
						|
			if globalWORMEnabled {
 | 
						|
				if _, err = objectAPI.GetObjectInfo(context.Background(), args.BucketName, objectName, ObjectOptions{}); err == nil {
 | 
						|
					return toJSONError(errMethodNotAllowed)
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if !globalIAMSys.IsAllowed(iampolicy.Args{
 | 
						|
				AccountName:     claims.Subject,
 | 
						|
				Action:          iampolicy.Action(policy.DeleteObjectAction),
 | 
						|
				BucketName:      args.BucketName,
 | 
						|
				ConditionValues: getConditionValues(r, ""),
 | 
						|
				IsOwner:         owner,
 | 
						|
				ObjectName:      objectName,
 | 
						|
			}) {
 | 
						|
				return toJSONError(errAccessDenied)
 | 
						|
			}
 | 
						|
 | 
						|
			if err = deleteObject(nil, objectAPI, web.CacheAPI(), args.BucketName, objectName, r); err != nil {
 | 
						|
				break next
 | 
						|
			}
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if !globalIAMSys.IsAllowed(iampolicy.Args{
 | 
						|
			AccountName:     claims.Subject,
 | 
						|
			Action:          iampolicy.Action(policy.DeleteObjectAction),
 | 
						|
			BucketName:      args.BucketName,
 | 
						|
			ConditionValues: getConditionValues(r, ""),
 | 
						|
			IsOwner:         owner,
 | 
						|
			ObjectName:      objectName,
 | 
						|
		}) {
 | 
						|
			return toJSONError(errAccessDenied)
 | 
						|
		}
 | 
						|
 | 
						|
		// For directories, list the contents recursively and remove.
 | 
						|
		marker := ""
 | 
						|
		for {
 | 
						|
			var lo ListObjectsInfo
 | 
						|
			lo, err = listObjects(context.Background(), args.BucketName, objectName, marker, "", 1000)
 | 
						|
			if err != nil {
 | 
						|
				break next
 | 
						|
			}
 | 
						|
			marker = lo.NextMarker
 | 
						|
			for _, obj := range lo.Objects {
 | 
						|
				err = deleteObject(nil, objectAPI, web.CacheAPI(), args.BucketName, obj.Name, r)
 | 
						|
				if err != nil {
 | 
						|
					break next
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if !lo.IsTruncated {
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if err != nil && !isErrObjectNotFound(err) {
 | 
						|
		// Ignore object not found error.
 | 
						|
		return toJSONError(err, args.BucketName, "")
 | 
						|
	}
 | 
						|
 | 
						|
	reply.UIVersion = browser.UIVersion
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// LoginArgs - login arguments.
 | 
						|
type LoginArgs struct {
 | 
						|
	Username string `json:"username" form:"username"`
 | 
						|
	Password string `json:"password" form:"password"`
 | 
						|
}
 | 
						|
 | 
						|
// LoginRep - login reply.
 | 
						|
type LoginRep struct {
 | 
						|
	Token     string `json:"token"`
 | 
						|
	UIVersion string `json:"uiVersion"`
 | 
						|
}
 | 
						|
 | 
						|
// Login - user login handler.
 | 
						|
func (web *webAPIHandlers) Login(r *http.Request, args *LoginArgs, reply *LoginRep) error {
 | 
						|
	token, err := authenticateWeb(args.Username, args.Password)
 | 
						|
	if err != nil {
 | 
						|
		return toJSONError(err)
 | 
						|
	}
 | 
						|
 | 
						|
	reply.Token = token
 | 
						|
	reply.UIVersion = browser.UIVersion
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// GenerateAuthReply - reply for GenerateAuth
 | 
						|
type GenerateAuthReply struct {
 | 
						|
	AccessKey string `json:"accessKey"`
 | 
						|
	SecretKey string `json:"secretKey"`
 | 
						|
	UIVersion string `json:"uiVersion"`
 | 
						|
}
 | 
						|
 | 
						|
func (web webAPIHandlers) GenerateAuth(r *http.Request, args *WebGenericArgs, reply *GenerateAuthReply) error {
 | 
						|
	_, owner, authErr := webRequestAuthenticate(r)
 | 
						|
	if authErr != nil {
 | 
						|
		return toJSONError(authErr)
 | 
						|
	}
 | 
						|
	if !owner {
 | 
						|
		return toJSONError(errAccessDenied)
 | 
						|
	}
 | 
						|
	cred, err := auth.GetNewCredentials()
 | 
						|
	if err != nil {
 | 
						|
		return toJSONError(err)
 | 
						|
	}
 | 
						|
	reply.AccessKey = cred.AccessKey
 | 
						|
	reply.SecretKey = cred.SecretKey
 | 
						|
	reply.UIVersion = browser.UIVersion
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// SetAuthArgs - argument for SetAuth
 | 
						|
type SetAuthArgs struct {
 | 
						|
	AccessKey string `json:"accessKey"`
 | 
						|
	SecretKey string `json:"secretKey"`
 | 
						|
}
 | 
						|
 | 
						|
// SetAuthReply - reply for SetAuth
 | 
						|
type SetAuthReply struct {
 | 
						|
	Token       string            `json:"token"`
 | 
						|
	UIVersion   string            `json:"uiVersion"`
 | 
						|
	PeerErrMsgs map[string]string `json:"peerErrMsgs"`
 | 
						|
}
 | 
						|
 | 
						|
// SetAuth - Set accessKey and secretKey credentials.
 | 
						|
func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *SetAuthReply) error {
 | 
						|
	_, owner, authErr := webRequestAuthenticate(r)
 | 
						|
	if authErr != nil {
 | 
						|
		return toJSONError(authErr)
 | 
						|
	}
 | 
						|
 | 
						|
	// If creds are set through ENV disallow changing credentials.
 | 
						|
	// TODO: Multi-user credentials also cannot be changed from browser.
 | 
						|
	if globalIsEnvCreds || globalWORMEnabled || !owner {
 | 
						|
		return toJSONError(errChangeCredNotAllowed)
 | 
						|
	}
 | 
						|
 | 
						|
	creds, err := auth.CreateCredentials(args.AccessKey, args.SecretKey)
 | 
						|
	if err != nil {
 | 
						|
		return toJSONError(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Acquire lock before updating global configuration.
 | 
						|
	globalServerConfigMu.Lock()
 | 
						|
	defer globalServerConfigMu.Unlock()
 | 
						|
 | 
						|
	// Update credentials in memory
 | 
						|
	prevCred := globalServerConfig.SetCredential(creds)
 | 
						|
 | 
						|
	// Persist updated credentials.
 | 
						|
	if err = saveServerConfig(context.Background(), newObjectLayerFn(), globalServerConfig); err != nil {
 | 
						|
		// Save the current creds when failed to update.
 | 
						|
		globalServerConfig.SetCredential(prevCred)
 | 
						|
		logger.LogIf(context.Background(), err)
 | 
						|
		return toJSONError(err)
 | 
						|
	}
 | 
						|
 | 
						|
	if errs := globalNotificationSys.LoadCredentials(); len(errs) != 0 {
 | 
						|
		reply.PeerErrMsgs = make(map[string]string)
 | 
						|
		for host, err := range errs {
 | 
						|
			err = fmt.Errorf("Unable to update credentials on server %v: %v", host, err)
 | 
						|
			logger.LogIf(context.Background(), err)
 | 
						|
			reply.PeerErrMsgs[host.String()] = err.Error()
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		reply.Token, err = authenticateWeb(creds.AccessKey, creds.SecretKey)
 | 
						|
		if err != nil {
 | 
						|
			return toJSONError(err)
 | 
						|
		}
 | 
						|
		reply.UIVersion = browser.UIVersion
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// GetAuthReply - Reply current credentials.
 | 
						|
type GetAuthReply struct {
 | 
						|
	AccessKey string `json:"accessKey"`
 | 
						|
	SecretKey string `json:"secretKey"`
 | 
						|
	UIVersion string `json:"uiVersion"`
 | 
						|
}
 | 
						|
 | 
						|
// GetAuth - return accessKey and secretKey credentials.
 | 
						|
func (web *webAPIHandlers) GetAuth(r *http.Request, args *WebGenericArgs, reply *GetAuthReply) error {
 | 
						|
	_, owner, authErr := webRequestAuthenticate(r)
 | 
						|
	if authErr != nil {
 | 
						|
		return toJSONError(authErr)
 | 
						|
	}
 | 
						|
	if !owner {
 | 
						|
		return toJSONError(errAccessDenied)
 | 
						|
	}
 | 
						|
	creds := globalServerConfig.GetCredential()
 | 
						|
	reply.AccessKey = creds.AccessKey
 | 
						|
	reply.SecretKey = creds.SecretKey
 | 
						|
	reply.UIVersion = browser.UIVersion
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// URLTokenReply contains the reply for CreateURLToken.
 | 
						|
type URLTokenReply struct {
 | 
						|
	Token     string `json:"token"`
 | 
						|
	UIVersion string `json:"uiVersion"`
 | 
						|
}
 | 
						|
 | 
						|
// CreateURLToken creates a URL token (short-lived) for GET requests.
 | 
						|
func (web *webAPIHandlers) CreateURLToken(r *http.Request, args *WebGenericArgs, reply *URLTokenReply) error {
 | 
						|
	claims, owner, authErr := webRequestAuthenticate(r)
 | 
						|
	if authErr != nil {
 | 
						|
		return toJSONError(authErr)
 | 
						|
	}
 | 
						|
 | 
						|
	creds := globalServerConfig.GetCredential()
 | 
						|
	if !owner {
 | 
						|
		var ok bool
 | 
						|
		creds, ok = globalIAMSys.GetUser(claims.Subject)
 | 
						|
		if !ok {
 | 
						|
			return toJSONError(errInvalidAccessKeyID)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	token, err := authenticateURL(creds.AccessKey, creds.SecretKey)
 | 
						|
	if err != nil {
 | 
						|
		return toJSONError(err)
 | 
						|
	}
 | 
						|
 | 
						|
	reply.Token = token
 | 
						|
	reply.UIVersion = browser.UIVersion
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Upload - file upload handler.
 | 
						|
func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
 | 
						|
	ctx := newContext(r, w, "WebUpload")
 | 
						|
 | 
						|
	defer logger.AuditLog(w, r, "WebUpload")
 | 
						|
 | 
						|
	objectAPI := web.ObjectAPI()
 | 
						|
	if objectAPI == nil {
 | 
						|
		writeWebErrorResponse(w, errServerNotInitialized)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	putObject := objectAPI.PutObject
 | 
						|
	if web.CacheAPI() != nil {
 | 
						|
		putObject = web.CacheAPI().PutObject
 | 
						|
	}
 | 
						|
	vars := mux.Vars(r)
 | 
						|
	bucket := vars["bucket"]
 | 
						|
	object := vars["object"]
 | 
						|
 | 
						|
	claims, owner, authErr := webRequestAuthenticate(r)
 | 
						|
	if authErr != nil {
 | 
						|
		if authErr == errNoAuthToken {
 | 
						|
			// Check if anonymous (non-owner) has access to upload objects.
 | 
						|
			if !globalPolicySys.IsAllowed(policy.Args{
 | 
						|
				Action:          policy.PutObjectAction,
 | 
						|
				BucketName:      bucket,
 | 
						|
				ConditionValues: getConditionValues(r, ""),
 | 
						|
				IsOwner:         false,
 | 
						|
				ObjectName:      object,
 | 
						|
			}) {
 | 
						|
				writeWebErrorResponse(w, errAuthentication)
 | 
						|
				return
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			writeWebErrorResponse(w, authErr)
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// For authenticated users apply IAM policy.
 | 
						|
	if authErr == nil {
 | 
						|
		if !globalIAMSys.IsAllowed(iampolicy.Args{
 | 
						|
			AccountName:     claims.Subject,
 | 
						|
			Action:          iampolicy.Action(policy.PutObjectAction),
 | 
						|
			BucketName:      bucket,
 | 
						|
			ConditionValues: getConditionValues(r, ""),
 | 
						|
			IsOwner:         owner,
 | 
						|
			ObjectName:      object,
 | 
						|
		}) {
 | 
						|
			writeWebErrorResponse(w, errAuthentication)
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Require Content-Length to be set in the request
 | 
						|
	size := r.ContentLength
 | 
						|
	if size < 0 {
 | 
						|
		writeWebErrorResponse(w, errSizeUnspecified)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Extract incoming metadata if any.
 | 
						|
	metadata, err := extractMetadata(context.Background(), r)
 | 
						|
	if err != nil {
 | 
						|
		writeErrorResponse(w, ErrInternalError, r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	reader := r.Body
 | 
						|
	actualSize := size
 | 
						|
 | 
						|
	if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) && size > 0 {
 | 
						|
		// Storing the compression metadata.
 | 
						|
		metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV1
 | 
						|
		metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(size, 10)
 | 
						|
 | 
						|
		pipeReader, pipeWriter := io.Pipe()
 | 
						|
		snappyWriter := snappy.NewWriter(pipeWriter)
 | 
						|
 | 
						|
		var actualReader *hash.Reader
 | 
						|
		actualReader, err = hash.NewReader(reader, size, "", "", actualSize)
 | 
						|
		if err != nil {
 | 
						|
			writeWebErrorResponse(w, err)
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		go func() {
 | 
						|
			// Writing to the compressed writer.
 | 
						|
			_, cerr := io.CopyN(snappyWriter, actualReader, actualSize)
 | 
						|
			snappyWriter.Close()
 | 
						|
			pipeWriter.CloseWithError(cerr)
 | 
						|
		}()
 | 
						|
 | 
						|
		// Set compression metrics.
 | 
						|
		size = -1 // Since compressed size is un-predictable.
 | 
						|
		reader = pipeReader
 | 
						|
	}
 | 
						|
 | 
						|
	hashReader, err := hash.NewReader(reader, size, "", "", actualSize)
 | 
						|
	if err != nil {
 | 
						|
		writeWebErrorResponse(w, err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	var opts ObjectOptions
 | 
						|
	// Deny if WORM is enabled
 | 
						|
	if globalWORMEnabled {
 | 
						|
		if _, err = objectAPI.GetObjectInfo(ctx, bucket, object, opts); err == nil {
 | 
						|
			writeWebErrorResponse(w, errMethodNotAllowed)
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	objInfo, err := putObject(ctx, bucket, object, NewPutObjReader(hashReader, nil, nil), metadata, opts)
 | 
						|
	if err != nil {
 | 
						|
		writeWebErrorResponse(w, err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Get host and port from Request.RemoteAddr.
 | 
						|
	host, port, err := net.SplitHostPort(handlers.GetSourceIP(r))
 | 
						|
	if err != nil {
 | 
						|
		host, port = "", ""
 | 
						|
	}
 | 
						|
 | 
						|
	// Notify object created event.
 | 
						|
	sendEvent(eventArgs{
 | 
						|
		EventName:    event.ObjectCreatedPut,
 | 
						|
		BucketName:   bucket,
 | 
						|
		Object:       objInfo,
 | 
						|
		ReqParams:    extractReqParams(r),
 | 
						|
		RespElements: extractRespElements(w),
 | 
						|
		UserAgent:    r.UserAgent(),
 | 
						|
		Host:         host,
 | 
						|
		Port:         port,
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// Download - file download handler.
 | 
						|
func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
 | 
						|
	ctx := newContext(r, w, "WebDownload")
 | 
						|
 | 
						|
	defer logger.AuditLog(w, r, "WebDownload")
 | 
						|
 | 
						|
	var wg sync.WaitGroup
 | 
						|
	objectAPI := web.ObjectAPI()
 | 
						|
	if objectAPI == nil {
 | 
						|
		writeWebErrorResponse(w, errServerNotInitialized)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	vars := mux.Vars(r)
 | 
						|
	bucket := vars["bucket"]
 | 
						|
	object := vars["object"]
 | 
						|
	token := r.URL.Query().Get("token")
 | 
						|
 | 
						|
	claims, owner, authErr := webTokenAuthenticate(token)
 | 
						|
	if authErr != nil {
 | 
						|
		if authErr == errNoAuthToken {
 | 
						|
			// Check if anonymous (non-owner) has access to download objects.
 | 
						|
			if !globalPolicySys.IsAllowed(policy.Args{
 | 
						|
				Action:          policy.GetObjectAction,
 | 
						|
				BucketName:      bucket,
 | 
						|
				ConditionValues: getConditionValues(r, ""),
 | 
						|
				IsOwner:         false,
 | 
						|
				ObjectName:      object,
 | 
						|
			}) {
 | 
						|
				writeWebErrorResponse(w, errAuthentication)
 | 
						|
				return
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			writeWebErrorResponse(w, authErr)
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// For authenticated users apply IAM policy.
 | 
						|
	if authErr == nil {
 | 
						|
		if !globalIAMSys.IsAllowed(iampolicy.Args{
 | 
						|
			AccountName:     claims.Subject,
 | 
						|
			Action:          iampolicy.Action(policy.GetObjectAction),
 | 
						|
			BucketName:      bucket,
 | 
						|
			ConditionValues: getConditionValues(r, ""),
 | 
						|
			IsOwner:         owner,
 | 
						|
			ObjectName:      object,
 | 
						|
		}) {
 | 
						|
			writeWebErrorResponse(w, errAuthentication)
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	opts := ObjectOptions{}
 | 
						|
	getObjectInfo := objectAPI.GetObjectInfo
 | 
						|
	getObject := objectAPI.GetObject
 | 
						|
	if web.CacheAPI() != nil {
 | 
						|
		getObjectInfo = web.CacheAPI().GetObjectInfo
 | 
						|
		getObject = web.CacheAPI().GetObject
 | 
						|
	}
 | 
						|
	objInfo, err := getObjectInfo(ctx, bucket, object, opts)
 | 
						|
	if err != nil {
 | 
						|
		writeWebErrorResponse(w, err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	length := objInfo.Size
 | 
						|
	var actualSize int64
 | 
						|
	if objInfo.IsCompressed() {
 | 
						|
		// Read the decompressed size from the meta.json.
 | 
						|
		actualSize = objInfo.GetActualSize()
 | 
						|
		if actualSize < 0 {
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if objectAPI.IsEncryptionSupported() {
 | 
						|
		if _, err = DecryptObjectInfo(&objInfo, r.Header); err != nil {
 | 
						|
			writeWebErrorResponse(w, err)
 | 
						|
			return
 | 
						|
		}
 | 
						|
		if crypto.IsEncrypted(objInfo.UserDefined) {
 | 
						|
			length, _ = objInfo.DecryptedSize()
 | 
						|
		}
 | 
						|
	}
 | 
						|
	var startOffset int64
 | 
						|
	var writer io.Writer
 | 
						|
	if objInfo.IsCompressed() {
 | 
						|
		// The decompress metrics are set.
 | 
						|
		snappyStartOffset := 0
 | 
						|
		snappyLength := actualSize
 | 
						|
 | 
						|
		// Open a pipe for compression
 | 
						|
		// Where compressWriter is actually passed to the getObject
 | 
						|
		decompressReader, compressWriter := io.Pipe()
 | 
						|
		snappyReader := snappy.NewReader(decompressReader)
 | 
						|
 | 
						|
		// The limit is set to the actual size.
 | 
						|
		responseWriter := ioutil.LimitedWriter(w, int64(snappyStartOffset), snappyLength)
 | 
						|
		wg.Add(1) //For closures.
 | 
						|
		go func() {
 | 
						|
			defer wg.Done()
 | 
						|
 | 
						|
			// Finally, writes to the client.
 | 
						|
			_, perr := io.Copy(responseWriter, snappyReader)
 | 
						|
 | 
						|
			// Close the compressWriter if the data is read already.
 | 
						|
			// Closing the pipe, releases the writer passed to the getObject.
 | 
						|
			compressWriter.CloseWithError(perr)
 | 
						|
		}()
 | 
						|
		writer = compressWriter
 | 
						|
	} else {
 | 
						|
		writer = w
 | 
						|
	}
 | 
						|
	if objectAPI.IsEncryptionSupported() && crypto.S3.IsEncrypted(objInfo.UserDefined) {
 | 
						|
		// Response writer should be limited early on for decryption upto required length,
 | 
						|
		// additionally also skipping mod(offset)64KiB boundaries.
 | 
						|
		writer = ioutil.LimitedWriter(writer, startOffset%(64*1024), length)
 | 
						|
 | 
						|
		writer, startOffset, length, err = DecryptBlocksRequest(writer, r, bucket, object, startOffset, length, objInfo, false)
 | 
						|
		if err != nil {
 | 
						|
			writeWebErrorResponse(w, err)
 | 
						|
			return
 | 
						|
		}
 | 
						|
		w.Header().Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256)
 | 
						|
	}
 | 
						|
 | 
						|
	httpWriter := ioutil.WriteOnClose(writer)
 | 
						|
 | 
						|
	// Add content disposition.
 | 
						|
	w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", path.Base(object)))
 | 
						|
 | 
						|
	if err = getObject(ctx, bucket, object, 0, -1, httpWriter, "", opts); err != nil {
 | 
						|
		httpWriter.Close()
 | 
						|
		if objInfo.IsCompressed() {
 | 
						|
			wg.Wait()
 | 
						|
		}
 | 
						|
		/// No need to print error, response writer already written to.
 | 
						|
		return
 | 
						|
	}
 | 
						|
	if err = httpWriter.Close(); err != nil {
 | 
						|
		if !httpWriter.HasWritten() { // write error response only if no data has been written to client yet
 | 
						|
			writeWebErrorResponse(w, err)
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if objInfo.IsCompressed() {
 | 
						|
		// Wait for decompression go-routine to retire.
 | 
						|
		wg.Wait()
 | 
						|
	}
 | 
						|
 | 
						|
	// Get host and port from Request.RemoteAddr.
 | 
						|
	host, port, err := net.SplitHostPort(handlers.GetSourceIP(r))
 | 
						|
	if err != nil {
 | 
						|
		host, port = "", ""
 | 
						|
	}
 | 
						|
 | 
						|
	// Notify object accessed via a GET request.
 | 
						|
	sendEvent(eventArgs{
 | 
						|
		EventName:    event.ObjectAccessedGet,
 | 
						|
		BucketName:   bucket,
 | 
						|
		Object:       objInfo,
 | 
						|
		ReqParams:    extractReqParams(r),
 | 
						|
		RespElements: extractRespElements(w),
 | 
						|
		UserAgent:    r.UserAgent(),
 | 
						|
		Host:         host,
 | 
						|
		Port:         port,
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// DownloadZipArgs - Argument for downloading a bunch of files as a zip file.
 | 
						|
// JSON will look like:
 | 
						|
// '{"bucketname":"testbucket","prefix":"john/pics/","objects":["hawaii/","maldives/","sanjose.jpg"]}'
 | 
						|
type DownloadZipArgs struct {
 | 
						|
	Objects    []string `json:"objects"`    // can be files or sub-directories
 | 
						|
	Prefix     string   `json:"prefix"`     // current directory in the browser-ui
 | 
						|
	BucketName string   `json:"bucketname"` // bucket name.
 | 
						|
}
 | 
						|
 | 
						|
// Takes a list of objects and creates a zip file that sent as the response body.
 | 
						|
func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) {
 | 
						|
	// Get host and port from Request.RemoteAddr.
 | 
						|
	host, port, err := net.SplitHostPort(handlers.GetSourceIP(r))
 | 
						|
	if err != nil {
 | 
						|
		host, port = "", ""
 | 
						|
	}
 | 
						|
 | 
						|
	ctx := newContext(r, w, "WebDownloadZip")
 | 
						|
	defer logger.AuditLog(w, r, "WebDownloadZip")
 | 
						|
 | 
						|
	var wg sync.WaitGroup
 | 
						|
	objectAPI := web.ObjectAPI()
 | 
						|
	if objectAPI == nil {
 | 
						|
		writeWebErrorResponse(w, errServerNotInitialized)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Auth is done after reading the body to accommodate for anonymous requests
 | 
						|
	// when bucket policy is enabled.
 | 
						|
	var args DownloadZipArgs
 | 
						|
	tenKB := 10 * 1024 // To limit r.Body to take care of misbehaving anonymous client.
 | 
						|
	decodeErr := json.NewDecoder(io.LimitReader(r.Body, int64(tenKB))).Decode(&args)
 | 
						|
	if decodeErr != nil {
 | 
						|
		writeWebErrorResponse(w, decodeErr)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	token := r.URL.Query().Get("token")
 | 
						|
	claims, owner, authErr := webTokenAuthenticate(token)
 | 
						|
	if authErr != nil {
 | 
						|
		if authErr == errNoAuthToken {
 | 
						|
			for _, object := range args.Objects {
 | 
						|
				// Check if anonymous (non-owner) has access to download objects.
 | 
						|
				if !globalPolicySys.IsAllowed(policy.Args{
 | 
						|
					Action:          policy.GetObjectAction,
 | 
						|
					BucketName:      args.BucketName,
 | 
						|
					ConditionValues: getConditionValues(r, ""),
 | 
						|
					IsOwner:         false,
 | 
						|
					ObjectName:      pathJoin(args.Prefix, object),
 | 
						|
				}) {
 | 
						|
					writeWebErrorResponse(w, errAuthentication)
 | 
						|
					return
 | 
						|
				}
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			writeWebErrorResponse(w, authErr)
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// For authenticated users apply IAM policy.
 | 
						|
	if authErr == nil {
 | 
						|
		for _, object := range args.Objects {
 | 
						|
			if !globalIAMSys.IsAllowed(iampolicy.Args{
 | 
						|
				AccountName:     claims.Subject,
 | 
						|
				Action:          iampolicy.Action(policy.GetObjectAction),
 | 
						|
				BucketName:      args.BucketName,
 | 
						|
				ConditionValues: getConditionValues(r, ""),
 | 
						|
				IsOwner:         owner,
 | 
						|
				ObjectName:      pathJoin(args.Prefix, object),
 | 
						|
			}) {
 | 
						|
				writeWebErrorResponse(w, errAuthentication)
 | 
						|
				return
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	getObject := objectAPI.GetObject
 | 
						|
	if web.CacheAPI() != nil {
 | 
						|
		getObject = web.CacheAPI().GetObject
 | 
						|
	}
 | 
						|
	listObjects := objectAPI.ListObjects
 | 
						|
	if web.CacheAPI() != nil {
 | 
						|
		listObjects = web.CacheAPI().ListObjects
 | 
						|
	}
 | 
						|
 | 
						|
	archive := zip.NewWriter(w)
 | 
						|
	defer archive.Close()
 | 
						|
 | 
						|
	getObjectInfo := objectAPI.GetObjectInfo
 | 
						|
	if web.CacheAPI() != nil {
 | 
						|
		getObjectInfo = web.CacheAPI().GetObjectInfo
 | 
						|
	}
 | 
						|
	opts := ObjectOptions{}
 | 
						|
	var length int64
 | 
						|
	for _, object := range args.Objects {
 | 
						|
		// Writes compressed object file to the response.
 | 
						|
		zipit := func(objectName string) error {
 | 
						|
			info, err := getObjectInfo(ctx, args.BucketName, objectName, opts)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			length = info.Size
 | 
						|
			if objectAPI.IsEncryptionSupported() {
 | 
						|
				if _, err = DecryptObjectInfo(&info, r.Header); err != nil {
 | 
						|
					writeWebErrorResponse(w, err)
 | 
						|
					return err
 | 
						|
				}
 | 
						|
				if crypto.IsEncrypted(info.UserDefined) {
 | 
						|
					length, _ = info.DecryptedSize()
 | 
						|
				}
 | 
						|
			}
 | 
						|
			length = info.Size
 | 
						|
			var actualSize int64
 | 
						|
			if info.IsCompressed() {
 | 
						|
				// Read the decompressed size from the meta.json.
 | 
						|
				actualSize = info.GetActualSize()
 | 
						|
				// Set the info.Size to the actualSize.
 | 
						|
				info.Size = actualSize
 | 
						|
			}
 | 
						|
			header := &zip.FileHeader{
 | 
						|
				Name:               strings.TrimPrefix(objectName, args.Prefix),
 | 
						|
				Method:             zip.Deflate,
 | 
						|
				UncompressedSize64: uint64(length),
 | 
						|
				UncompressedSize:   uint32(length),
 | 
						|
			}
 | 
						|
			zipWriter, err := archive.CreateHeader(header)
 | 
						|
			if err != nil {
 | 
						|
				writeWebErrorResponse(w, errUnexpected)
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			var startOffset int64
 | 
						|
			var writer io.Writer
 | 
						|
 | 
						|
			if info.IsCompressed() {
 | 
						|
				// The decompress metrics are set.
 | 
						|
				snappyStartOffset := 0
 | 
						|
				snappyLength := actualSize
 | 
						|
 | 
						|
				// Open a pipe for compression
 | 
						|
				// Where compressWriter is actually passed to the getObject
 | 
						|
				decompressReader, compressWriter := io.Pipe()
 | 
						|
				snappyReader := snappy.NewReader(decompressReader)
 | 
						|
 | 
						|
				// The limit is set to the actual size.
 | 
						|
				responseWriter := ioutil.LimitedWriter(zipWriter, int64(snappyStartOffset), snappyLength)
 | 
						|
				wg.Add(1) //For closures.
 | 
						|
				go func() {
 | 
						|
					defer wg.Done()
 | 
						|
					// Finally, writes to the client.
 | 
						|
					_, perr := io.Copy(responseWriter, snappyReader)
 | 
						|
 | 
						|
					// Close the compressWriter if the data is read already.
 | 
						|
					// Closing the pipe, releases the writer passed to the getObject.
 | 
						|
					compressWriter.CloseWithError(perr)
 | 
						|
				}()
 | 
						|
				writer = compressWriter
 | 
						|
			} else {
 | 
						|
				writer = zipWriter
 | 
						|
			}
 | 
						|
			if objectAPI.IsEncryptionSupported() && crypto.S3.IsEncrypted(info.UserDefined) {
 | 
						|
				// Response writer should be limited early on for decryption upto required length,
 | 
						|
				// additionally also skipping mod(offset)64KiB boundaries.
 | 
						|
				writer = ioutil.LimitedWriter(writer, startOffset%(64*1024), length)
 | 
						|
				writer, startOffset, length, err = DecryptBlocksRequest(writer, r, args.BucketName, objectName, startOffset, length, info, false)
 | 
						|
				if err != nil {
 | 
						|
					writeWebErrorResponse(w, err)
 | 
						|
					return err
 | 
						|
				}
 | 
						|
			}
 | 
						|
			httpWriter := ioutil.WriteOnClose(writer)
 | 
						|
			if err = getObject(ctx, args.BucketName, objectName, 0, length, httpWriter, "", opts); err != nil {
 | 
						|
				httpWriter.Close()
 | 
						|
				if info.IsCompressed() {
 | 
						|
					// Wait for decompression go-routine to retire.
 | 
						|
					wg.Wait()
 | 
						|
				}
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			if err = httpWriter.Close(); err != nil {
 | 
						|
				if !httpWriter.HasWritten() { // write error response only if no data has been written to client yet
 | 
						|
					writeWebErrorResponse(w, err)
 | 
						|
					return err
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if info.IsCompressed() {
 | 
						|
				// Wait for decompression go-routine to retire.
 | 
						|
				wg.Wait()
 | 
						|
			}
 | 
						|
 | 
						|
			// Notify object accessed via a GET request.
 | 
						|
			sendEvent(eventArgs{
 | 
						|
				EventName:    event.ObjectAccessedGet,
 | 
						|
				BucketName:   args.BucketName,
 | 
						|
				Object:       info,
 | 
						|
				ReqParams:    extractReqParams(r),
 | 
						|
				RespElements: extractRespElements(w),
 | 
						|
				UserAgent:    r.UserAgent(),
 | 
						|
				Host:         host,
 | 
						|
				Port:         port,
 | 
						|
			})
 | 
						|
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		if !hasSuffix(object, slashSeparator) {
 | 
						|
			// If not a directory, compress the file and write it to response.
 | 
						|
			err := zipit(pathJoin(args.Prefix, object))
 | 
						|
			if err != nil {
 | 
						|
				return
 | 
						|
			}
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// For directories, list the contents recursively and write the objects as compressed
 | 
						|
		// date to the response writer.
 | 
						|
		marker := ""
 | 
						|
		for {
 | 
						|
			lo, err := listObjects(context.Background(), args.BucketName, pathJoin(args.Prefix, object), marker, "", 1000)
 | 
						|
			if err != nil {
 | 
						|
				return
 | 
						|
			}
 | 
						|
			marker = lo.NextMarker
 | 
						|
			for _, obj := range lo.Objects {
 | 
						|
				err = zipit(obj.Name)
 | 
						|
				if err != nil {
 | 
						|
					return
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if !lo.IsTruncated {
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// GetBucketPolicyArgs - get bucket policy args.
 | 
						|
type GetBucketPolicyArgs struct {
 | 
						|
	BucketName string `json:"bucketName"`
 | 
						|
	Prefix     string `json:"prefix"`
 | 
						|
}
 | 
						|
 | 
						|
// GetBucketPolicyRep - get bucket policy reply.
 | 
						|
type GetBucketPolicyRep struct {
 | 
						|
	UIVersion string                     `json:"uiVersion"`
 | 
						|
	Policy    miniogopolicy.BucketPolicy `json:"policy"`
 | 
						|
}
 | 
						|
 | 
						|
// GetBucketPolicy - get bucket policy for the requested prefix.
 | 
						|
func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolicyArgs, reply *GetBucketPolicyRep) error {
 | 
						|
	objectAPI := web.ObjectAPI()
 | 
						|
	if objectAPI == nil {
 | 
						|
		return toJSONError(errServerNotInitialized)
 | 
						|
	}
 | 
						|
 | 
						|
	_, owner, authErr := webRequestAuthenticate(r)
 | 
						|
	if authErr != nil {
 | 
						|
		return toJSONError(authErr)
 | 
						|
	}
 | 
						|
	if !owner {
 | 
						|
		return toJSONError(errAccessDenied)
 | 
						|
	}
 | 
						|
 | 
						|
	bucketPolicy, err := objectAPI.GetBucketPolicy(context.Background(), args.BucketName)
 | 
						|
	if err != nil {
 | 
						|
		if _, ok := err.(BucketPolicyNotFound); !ok {
 | 
						|
			return toJSONError(err, args.BucketName)
 | 
						|
		}
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	policyInfo, err := PolicyToBucketAccessPolicy(bucketPolicy)
 | 
						|
	if err != nil {
 | 
						|
		// This should not happen.
 | 
						|
		return toJSONError(err, args.BucketName)
 | 
						|
	}
 | 
						|
 | 
						|
	reply.UIVersion = browser.UIVersion
 | 
						|
	reply.Policy = miniogopolicy.GetPolicy(policyInfo.Statements, args.BucketName, args.Prefix)
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// ListAllBucketPoliciesArgs - get all bucket policies.
 | 
						|
type ListAllBucketPoliciesArgs struct {
 | 
						|
	BucketName string `json:"bucketName"`
 | 
						|
}
 | 
						|
 | 
						|
// BucketAccessPolicy - Collection of canned bucket policy at a given prefix.
 | 
						|
type BucketAccessPolicy struct {
 | 
						|
	Bucket string                     `json:"bucket"`
 | 
						|
	Prefix string                     `json:"prefix"`
 | 
						|
	Policy miniogopolicy.BucketPolicy `json:"policy"`
 | 
						|
}
 | 
						|
 | 
						|
// ListAllBucketPoliciesRep - get all bucket policy reply.
 | 
						|
type ListAllBucketPoliciesRep struct {
 | 
						|
	UIVersion string               `json:"uiVersion"`
 | 
						|
	Policies  []BucketAccessPolicy `json:"policies"`
 | 
						|
}
 | 
						|
 | 
						|
// ListAllBucketPolicies - get all bucket policy.
 | 
						|
func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllBucketPoliciesArgs, reply *ListAllBucketPoliciesRep) error {
 | 
						|
	objectAPI := web.ObjectAPI()
 | 
						|
	if objectAPI == nil {
 | 
						|
		return toJSONError(errServerNotInitialized)
 | 
						|
	}
 | 
						|
 | 
						|
	_, owner, authErr := webRequestAuthenticate(r)
 | 
						|
	if authErr != nil {
 | 
						|
		return toJSONError(authErr)
 | 
						|
	}
 | 
						|
	if !owner {
 | 
						|
		return toJSONError(errAccessDenied)
 | 
						|
	}
 | 
						|
 | 
						|
	bucketPolicy, err := objectAPI.GetBucketPolicy(context.Background(), args.BucketName)
 | 
						|
	if err != nil {
 | 
						|
		if _, ok := err.(BucketPolicyNotFound); !ok {
 | 
						|
			return toJSONError(err, args.BucketName)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	policyInfo, err := PolicyToBucketAccessPolicy(bucketPolicy)
 | 
						|
	if err != nil {
 | 
						|
		// This should not happen.
 | 
						|
		return toJSONError(err, args.BucketName)
 | 
						|
	}
 | 
						|
 | 
						|
	reply.UIVersion = browser.UIVersion
 | 
						|
	for prefix, policy := range miniogopolicy.GetPolicies(policyInfo.Statements, args.BucketName, "") {
 | 
						|
		bucketName, objectPrefix := urlPath2BucketObjectName(prefix)
 | 
						|
		objectPrefix = strings.TrimSuffix(objectPrefix, "*")
 | 
						|
		reply.Policies = append(reply.Policies, BucketAccessPolicy{
 | 
						|
			Bucket: bucketName,
 | 
						|
			Prefix: objectPrefix,
 | 
						|
			Policy: policy,
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// SetBucketPolicyWebArgs - set bucket policy args.
 | 
						|
type SetBucketPolicyWebArgs struct {
 | 
						|
	BucketName string `json:"bucketName"`
 | 
						|
	Prefix     string `json:"prefix"`
 | 
						|
	Policy     string `json:"policy"`
 | 
						|
}
 | 
						|
 | 
						|
// SetBucketPolicy - set bucket policy.
 | 
						|
func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolicyWebArgs, reply *WebGenericRep) error {
 | 
						|
	objectAPI := web.ObjectAPI()
 | 
						|
	reply.UIVersion = browser.UIVersion
 | 
						|
 | 
						|
	if objectAPI == nil {
 | 
						|
		return toJSONError(errServerNotInitialized)
 | 
						|
	}
 | 
						|
 | 
						|
	_, owner, authErr := webRequestAuthenticate(r)
 | 
						|
	if authErr != nil {
 | 
						|
		return toJSONError(authErr)
 | 
						|
	}
 | 
						|
	if !owner {
 | 
						|
		return toJSONError(errAccessDenied)
 | 
						|
	}
 | 
						|
 | 
						|
	policyType := miniogopolicy.BucketPolicy(args.Policy)
 | 
						|
	if !policyType.IsValidBucketPolicy() {
 | 
						|
		return &json2.Error{
 | 
						|
			Message: "Invalid policy type " + args.Policy,
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	ctx := context.Background()
 | 
						|
 | 
						|
	bucketPolicy, err := objectAPI.GetBucketPolicy(ctx, args.BucketName)
 | 
						|
	if err != nil {
 | 
						|
		if _, ok := err.(BucketPolicyNotFound); !ok {
 | 
						|
			return toJSONError(err, args.BucketName)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	policyInfo, err := PolicyToBucketAccessPolicy(bucketPolicy)
 | 
						|
	if err != nil {
 | 
						|
		// This should not happen.
 | 
						|
		return toJSONError(err, args.BucketName)
 | 
						|
	}
 | 
						|
 | 
						|
	policyInfo.Statements = miniogopolicy.SetPolicy(policyInfo.Statements, policyType, args.BucketName, args.Prefix)
 | 
						|
 | 
						|
	if len(policyInfo.Statements) == 0 {
 | 
						|
		if err = objectAPI.DeleteBucketPolicy(ctx, args.BucketName); err != nil {
 | 
						|
			return toJSONError(err, args.BucketName)
 | 
						|
		}
 | 
						|
 | 
						|
		globalPolicySys.Remove(args.BucketName)
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	bucketPolicy, err = BucketAccessPolicyToPolicy(policyInfo)
 | 
						|
	if err != nil {
 | 
						|
		// This should not happen.
 | 
						|
		return toJSONError(err, args.BucketName)
 | 
						|
	}
 | 
						|
 | 
						|
	// Parse validate and save bucket policy.
 | 
						|
	if err := objectAPI.SetBucketPolicy(ctx, args.BucketName, bucketPolicy); err != nil {
 | 
						|
		return toJSONError(err, args.BucketName)
 | 
						|
	}
 | 
						|
 | 
						|
	globalPolicySys.Set(args.BucketName, *bucketPolicy)
 | 
						|
	globalNotificationSys.SetBucketPolicy(ctx, args.BucketName, bucketPolicy)
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// PresignedGetArgs - presigned-get API args.
 | 
						|
type PresignedGetArgs struct {
 | 
						|
	// Host header required for signed headers.
 | 
						|
	HostName string `json:"host"`
 | 
						|
 | 
						|
	// Bucket name of the object to be presigned.
 | 
						|
	BucketName string `json:"bucket"`
 | 
						|
 | 
						|
	// Object name to be presigned.
 | 
						|
	ObjectName string `json:"object"`
 | 
						|
 | 
						|
	// Expiry in seconds.
 | 
						|
	Expiry int64 `json:"expiry"`
 | 
						|
}
 | 
						|
 | 
						|
// PresignedGetRep - presigned-get URL reply.
 | 
						|
type PresignedGetRep struct {
 | 
						|
	UIVersion string `json:"uiVersion"`
 | 
						|
	// Presigned URL of the object.
 | 
						|
	URL string `json:"url"`
 | 
						|
}
 | 
						|
 | 
						|
// PresignedGET - returns presigned-Get url.
 | 
						|
func (web *webAPIHandlers) PresignedGet(r *http.Request, args *PresignedGetArgs, reply *PresignedGetRep) error {
 | 
						|
	claims, owner, authErr := webRequestAuthenticate(r)
 | 
						|
	if authErr != nil {
 | 
						|
		return toJSONError(authErr)
 | 
						|
	}
 | 
						|
	var creds auth.Credentials
 | 
						|
	if !owner {
 | 
						|
		var ok bool
 | 
						|
		creds, ok = globalIAMSys.GetUser(claims.Subject)
 | 
						|
		if !ok {
 | 
						|
			return toJSONError(errInvalidAccessKeyID)
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		creds = globalServerConfig.GetCredential()
 | 
						|
	}
 | 
						|
 | 
						|
	region := globalServerConfig.GetRegion()
 | 
						|
	if args.BucketName == "" || args.ObjectName == "" {
 | 
						|
		return &json2.Error{
 | 
						|
			Message: "Bucket and Object are mandatory arguments.",
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	reply.UIVersion = browser.UIVersion
 | 
						|
	reply.URL = presignedGet(args.HostName, args.BucketName, args.ObjectName, args.Expiry, creds, region)
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Returns presigned url for GET method.
 | 
						|
func presignedGet(host, bucket, object string, expiry int64, creds auth.Credentials, region string) string {
 | 
						|
	accessKey := creds.AccessKey
 | 
						|
	secretKey := creds.SecretKey
 | 
						|
 | 
						|
	date := UTCNow()
 | 
						|
	dateStr := date.Format(iso8601Format)
 | 
						|
	credential := fmt.Sprintf("%s/%s", accessKey, getScope(date, region))
 | 
						|
 | 
						|
	var expiryStr = "604800" // Default set to be expire in 7days.
 | 
						|
	if expiry < 604800 && expiry > 0 {
 | 
						|
		expiryStr = strconv.FormatInt(expiry, 10)
 | 
						|
	}
 | 
						|
 | 
						|
	query := url.Values{}
 | 
						|
	query.Set("X-Amz-Algorithm", signV4Algorithm)
 | 
						|
	query.Set("X-Amz-Credential", credential)
 | 
						|
	query.Set("X-Amz-Date", dateStr)
 | 
						|
	query.Set("X-Amz-Expires", expiryStr)
 | 
						|
	query.Set("X-Amz-SignedHeaders", "host")
 | 
						|
	queryStr := s3utils.QueryEncode(query)
 | 
						|
 | 
						|
	path := "/" + path.Join(bucket, object)
 | 
						|
 | 
						|
	// "host" is the only header required to be signed for Presigned URLs.
 | 
						|
	extractedSignedHeaders := make(http.Header)
 | 
						|
	extractedSignedHeaders.Set("host", host)
 | 
						|
	canonicalRequest := getCanonicalRequest(extractedSignedHeaders, unsignedPayload, queryStr, path, "GET")
 | 
						|
	stringToSign := getStringToSign(canonicalRequest, date, getScope(date, region))
 | 
						|
	signingKey := getSigningKey(secretKey, date, region)
 | 
						|
	signature := getSignature(signingKey, stringToSign)
 | 
						|
 | 
						|
	// Construct the final presigned URL.
 | 
						|
	return host + s3utils.EncodePath(path) + "?" + queryStr + "&" + "X-Amz-Signature=" + signature
 | 
						|
}
 | 
						|
 | 
						|
// toJSONError converts regular errors into more user friendly
 | 
						|
// and consumable error message for the browser UI.
 | 
						|
func toJSONError(err error, params ...string) (jerr *json2.Error) {
 | 
						|
	apiErr := toWebAPIError(err)
 | 
						|
	jerr = &json2.Error{
 | 
						|
		Message: apiErr.Description,
 | 
						|
	}
 | 
						|
	switch apiErr.Code {
 | 
						|
	// Reserved bucket name provided.
 | 
						|
	case "AllAccessDisabled":
 | 
						|
		if len(params) > 0 {
 | 
						|
			jerr = &json2.Error{
 | 
						|
				Message: fmt.Sprintf("All access to this bucket %s has been disabled.", params[0]),
 | 
						|
			}
 | 
						|
		}
 | 
						|
	// Bucket name invalid with custom error message.
 | 
						|
	case "InvalidBucketName":
 | 
						|
		if len(params) > 0 {
 | 
						|
			jerr = &json2.Error{
 | 
						|
				Message: fmt.Sprintf("Bucket Name %s is invalid. Lowercase letters, period, hyphen, numerals are the only allowed characters and should be minimum 3 characters in length.", params[0]),
 | 
						|
			}
 | 
						|
		}
 | 
						|
	// Bucket not found custom error message.
 | 
						|
	case "NoSuchBucket":
 | 
						|
		if len(params) > 0 {
 | 
						|
			jerr = &json2.Error{
 | 
						|
				Message: fmt.Sprintf("The specified bucket %s does not exist.", params[0]),
 | 
						|
			}
 | 
						|
		}
 | 
						|
	// Object not found custom error message.
 | 
						|
	case "NoSuchKey":
 | 
						|
		if len(params) > 1 {
 | 
						|
			jerr = &json2.Error{
 | 
						|
				Message: fmt.Sprintf("The specified key %s does not exist", params[1]),
 | 
						|
			}
 | 
						|
		}
 | 
						|
		// Add more custom error messages here with more context.
 | 
						|
	}
 | 
						|
	return jerr
 | 
						|
}
 | 
						|
 | 
						|
// toWebAPIError - convert into error into APIError.
 | 
						|
func toWebAPIError(err error) APIError {
 | 
						|
	if err == errAuthentication {
 | 
						|
		return APIError{
 | 
						|
			Code:           "AccessDenied",
 | 
						|
			HTTPStatusCode: http.StatusForbidden,
 | 
						|
			Description:    err.Error(),
 | 
						|
		}
 | 
						|
	} else if err == errServerNotInitialized {
 | 
						|
		return APIError{
 | 
						|
			Code:           "XMinioServerNotInitialized",
 | 
						|
			HTTPStatusCode: http.StatusServiceUnavailable,
 | 
						|
			Description:    err.Error(),
 | 
						|
		}
 | 
						|
	} else if err == auth.ErrInvalidAccessKeyLength {
 | 
						|
		return APIError{
 | 
						|
			Code:           "AccessDenied",
 | 
						|
			HTTPStatusCode: http.StatusForbidden,
 | 
						|
			Description:    err.Error(),
 | 
						|
		}
 | 
						|
	} else if err == auth.ErrInvalidSecretKeyLength {
 | 
						|
		return APIError{
 | 
						|
			Code:           "AccessDenied",
 | 
						|
			HTTPStatusCode: http.StatusForbidden,
 | 
						|
			Description:    err.Error(),
 | 
						|
		}
 | 
						|
	} else if err == errInvalidAccessKeyID {
 | 
						|
		return APIError{
 | 
						|
			Code:           "AccessDenied",
 | 
						|
			HTTPStatusCode: http.StatusForbidden,
 | 
						|
			Description:    err.Error(),
 | 
						|
		}
 | 
						|
	} else if err == errSizeUnspecified {
 | 
						|
		return APIError{
 | 
						|
			Code:           "InvalidRequest",
 | 
						|
			HTTPStatusCode: http.StatusBadRequest,
 | 
						|
			Description:    err.Error(),
 | 
						|
		}
 | 
						|
	} else if err == errChangeCredNotAllowed {
 | 
						|
		return APIError{
 | 
						|
			Code:           "MethodNotAllowed",
 | 
						|
			HTTPStatusCode: http.StatusMethodNotAllowed,
 | 
						|
			Description:    err.Error(),
 | 
						|
		}
 | 
						|
	} else if err == errInvalidBucketName {
 | 
						|
		return APIError{
 | 
						|
			Code:           "InvalidBucketName",
 | 
						|
			HTTPStatusCode: http.StatusBadRequest,
 | 
						|
			Description:    err.Error(),
 | 
						|
		}
 | 
						|
	} else if err == errInvalidArgument {
 | 
						|
		return APIError{
 | 
						|
			Code:           "InvalidArgument",
 | 
						|
			HTTPStatusCode: http.StatusBadRequest,
 | 
						|
			Description:    err.Error(),
 | 
						|
		}
 | 
						|
	} else if err == errEncryptedObject {
 | 
						|
		return getAPIError(ErrSSEEncryptedObject)
 | 
						|
	} else if err == errInvalidEncryptionParameters {
 | 
						|
		return getAPIError(ErrInvalidEncryptionParameters)
 | 
						|
	} else if err == errObjectTampered {
 | 
						|
		return getAPIError(ErrObjectTampered)
 | 
						|
	} else if err == errMethodNotAllowed {
 | 
						|
		return getAPIError(ErrMethodNotAllowed)
 | 
						|
	}
 | 
						|
 | 
						|
	// Convert error type to api error code.
 | 
						|
	switch err.(type) {
 | 
						|
	case StorageFull:
 | 
						|
		return getAPIError(ErrStorageFull)
 | 
						|
	case BucketNotFound:
 | 
						|
		return getAPIError(ErrNoSuchBucket)
 | 
						|
	case BucketExists:
 | 
						|
		return getAPIError(ErrBucketAlreadyOwnedByYou)
 | 
						|
	case BucketNameInvalid:
 | 
						|
		return getAPIError(ErrInvalidBucketName)
 | 
						|
	case hash.BadDigest:
 | 
						|
		return getAPIError(ErrBadDigest)
 | 
						|
	case IncompleteBody:
 | 
						|
		return getAPIError(ErrIncompleteBody)
 | 
						|
	case ObjectExistsAsDirectory:
 | 
						|
		return getAPIError(ErrObjectExistsAsDirectory)
 | 
						|
	case ObjectNotFound:
 | 
						|
		return getAPIError(ErrNoSuchKey)
 | 
						|
	case ObjectNameInvalid:
 | 
						|
		return getAPIError(ErrNoSuchKey)
 | 
						|
	case InsufficientWriteQuorum:
 | 
						|
		return getAPIError(ErrWriteQuorum)
 | 
						|
	case InsufficientReadQuorum:
 | 
						|
		return getAPIError(ErrReadQuorum)
 | 
						|
	case PolicyNesting:
 | 
						|
		return getAPIError(ErrPolicyNesting)
 | 
						|
	case NotImplemented:
 | 
						|
		return APIError{
 | 
						|
			Code:           "NotImplemented",
 | 
						|
			HTTPStatusCode: http.StatusBadRequest,
 | 
						|
			Description:    "Functionality not implemented",
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Log unexpected and unhandled errors.
 | 
						|
	logger.LogIf(context.Background(), err)
 | 
						|
	return APIError{
 | 
						|
		Code:           "InternalError",
 | 
						|
		HTTPStatusCode: http.StatusInternalServerError,
 | 
						|
		Description:    err.Error(),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// writeWebErrorResponse - set HTTP status code and write error description to the body.
 | 
						|
func writeWebErrorResponse(w http.ResponseWriter, err error) {
 | 
						|
	apiErr := toWebAPIError(err)
 | 
						|
	w.WriteHeader(apiErr.HTTPStatusCode)
 | 
						|
	w.Write([]byte(apiErr.Description))
 | 
						|
}
 |