2020-09-29 14:34:51 -07:00
// This file is part of MinIO Console Server
2021-01-19 17:04:13 -06:00
// Copyright (c) 2021 MinIO, Inc.
2020-09-29 14:34:51 -07:00
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2023-12-26 15:07:30 -06:00
package api
2020-09-29 14:34:51 -07:00
import (
"context"
2024-06-13 21:30:33 +02:00
"encoding/base64"
2022-05-05 13:44:10 -07:00
"errors"
2020-10-01 17:00:32 -07:00
"fmt"
2020-10-09 11:43:15 -07:00
"io"
"net/http"
2022-01-11 15:11:47 -08:00
"net/url"
2020-10-01 17:00:32 -07:00
"path/filepath"
"regexp"
2021-09-21 17:43:43 -07:00
"strconv"
2020-10-01 17:00:32 -07:00
"strings"
2020-09-29 14:34:51 -07:00
"time"
2023-08-16 14:18:08 -07:00
"github.com/minio/minio-go/v7"
2023-06-14 12:36:48 -07:00
"github.com/minio/console/pkg/utils"
2020-10-09 11:43:15 -07:00
"github.com/go-openapi/runtime"
2020-09-29 14:34:51 -07:00
"github.com/go-openapi/runtime/middleware"
2022-05-06 11:14:05 -07:00
"github.com/klauspost/compress/zip"
2023-12-26 15:07:30 -06:00
"github.com/minio/console/api/operations"
objectApi "github.com/minio/console/api/operations/object"
2020-09-29 14:34:51 -07:00
"github.com/minio/console/models"
2020-10-01 17:00:32 -07:00
mc "github.com/minio/mc/cmd"
2020-10-06 16:07:33 -07:00
"github.com/minio/mc/pkg/probe"
2020-10-28 16:08:26 -07:00
"github.com/minio/minio-go/v7/pkg/tags"
2024-05-24 10:44:55 -07:00
"github.com/minio/pkg/v3/mimedb"
2020-09-29 14:34:51 -07:00
)
2020-10-01 17:00:32 -07:00
// enum types
const (
objectStorage = iota // MinIO and S3 compatible cloud storage
fileSystem // POSIX compatible file systems
)
2020-09-29 14:34:51 -07:00
func registerObjectsHandlers ( api * operations . ConsoleAPI ) {
// list objects
2022-04-27 11:45:04 -07:00
api . ObjectListObjectsHandler = objectApi . ListObjectsHandlerFunc ( func ( params objectApi . ListObjectsParams , session * models . Principal ) middleware . Responder {
2020-09-29 14:34:51 -07:00
resp , err := getListObjectsResponse ( session , params )
if err != nil {
2023-08-16 14:18:08 -07:00
return objectApi . NewListObjectsDefault ( err . Code ) . WithPayload ( err . APIError )
2020-09-29 14:34:51 -07:00
}
2022-04-27 11:45:04 -07:00
return objectApi . NewListObjectsOK ( ) . WithPayload ( resp )
2020-09-29 14:34:51 -07:00
} )
2020-10-01 17:00:32 -07:00
// delete object
2022-04-27 11:45:04 -07:00
api . ObjectDeleteObjectHandler = objectApi . DeleteObjectHandlerFunc ( func ( params objectApi . DeleteObjectParams , session * models . Principal ) middleware . Responder {
2020-10-01 17:00:32 -07:00
if err := getDeleteObjectResponse ( session , params ) ; err != nil {
2023-08-16 14:18:08 -07:00
return objectApi . NewDeleteObjectDefault ( err . Code ) . WithPayload ( err . APIError )
2020-09-29 14:34:51 -07:00
}
2022-04-27 11:45:04 -07:00
return objectApi . NewDeleteObjectOK ( )
2020-10-01 17:00:32 -07:00
} )
2021-07-21 15:49:20 -07:00
// delete multiple objects
2022-04-27 11:45:04 -07:00
api . ObjectDeleteMultipleObjectsHandler = objectApi . DeleteMultipleObjectsHandlerFunc ( func ( params objectApi . DeleteMultipleObjectsParams , session * models . Principal ) middleware . Responder {
2021-07-21 15:49:20 -07:00
if err := getDeleteMultiplePathsResponse ( session , params ) ; err != nil {
2023-08-16 14:18:08 -07:00
return objectApi . NewDeleteMultipleObjectsDefault ( err . Code ) . WithPayload ( err . APIError )
2021-07-21 15:49:20 -07:00
}
2022-04-27 11:45:04 -07:00
return objectApi . NewDeleteMultipleObjectsOK ( )
2021-07-21 15:49:20 -07:00
} )
2020-10-09 11:43:15 -07:00
// download object
2022-04-27 11:45:04 -07:00
api . ObjectDownloadObjectHandler = objectApi . DownloadObjectHandlerFunc ( func ( params objectApi . DownloadObjectParams , session * models . Principal ) middleware . Responder {
2021-12-07 19:36:50 -08:00
isFolder := false
2021-09-01 12:39:17 -05:00
2024-06-05 23:48:27 +02:00
folders := strings . Split ( params . Prefix , "/" )
2021-12-07 19:36:50 -08:00
if folders [ len ( folders ) - 1 ] == "" {
isFolder = true
}
var resp middleware . Responder
2023-08-16 14:18:08 -07:00
var err * CodedAPIError
2021-09-01 12:39:17 -05:00
2021-12-07 19:36:50 -08:00
if isFolder {
resp , err = getDownloadFolderResponse ( session , params )
} else {
resp , err = getDownloadObjectResponse ( session , params )
}
2021-09-01 12:39:17 -05:00
2021-12-07 19:36:50 -08:00
if err != nil {
2023-08-16 14:18:08 -07:00
return objectApi . NewDownloadObjectDefault ( err . Code ) . WithPayload ( err . APIError )
2021-12-07 19:36:50 -08:00
}
return resp
2020-10-09 11:43:15 -07:00
} )
2023-08-03 05:58:25 +05:30
// download multiple objects
api . ObjectDownloadMultipleObjectsHandler = objectApi . DownloadMultipleObjectsHandlerFunc ( func ( params objectApi . DownloadMultipleObjectsParams , session * models . Principal ) middleware . Responder {
ctx := params . HTTPRequest . Context ( )
if len ( params . ObjectList ) < 1 {
2023-08-16 14:18:08 -07:00
errCode := ErrorWithContext ( ctx , errors . New ( "could not download, since object list is empty" ) )
return objectApi . NewDownloadMultipleObjectsDefault ( errCode . Code ) . WithPayload ( errCode . APIError )
2023-08-03 05:58:25 +05:30
}
var resp middleware . Responder
2023-08-16 14:18:08 -07:00
var err * CodedAPIError
2023-08-03 05:58:25 +05:30
resp , err = getMultipleFilesDownloadResponse ( session , params )
if err != nil {
2023-08-16 14:18:08 -07:00
return objectApi . NewDownloadMultipleObjectsDefault ( err . Code ) . WithPayload ( err . APIError )
2023-08-03 05:58:25 +05:30
}
return resp
} )
2020-10-14 23:09:33 -07:00
// upload object
2022-04-27 11:45:04 -07:00
api . ObjectPostBucketsBucketNameObjectsUploadHandler = objectApi . PostBucketsBucketNameObjectsUploadHandlerFunc ( func ( params objectApi . PostBucketsBucketNameObjectsUploadParams , session * models . Principal ) middleware . Responder {
2020-10-14 23:09:33 -07:00
if err := getUploadObjectResponse ( session , params ) ; err != nil {
2023-08-16 14:18:08 -07:00
if strings . Contains ( err . APIError . DetailedMessage , "413" ) {
return objectApi . NewPostBucketsBucketNameObjectsUploadDefault ( 413 ) . WithPayload ( err . APIError )
2022-01-11 19:45:14 -08:00
}
2023-08-16 14:18:08 -07:00
return objectApi . NewPostBucketsBucketNameObjectsUploadDefault ( err . Code ) . WithPayload ( err . APIError )
2020-10-14 23:09:33 -07:00
}
2022-04-27 11:45:04 -07:00
return objectApi . NewPostBucketsBucketNameObjectsUploadOK ( )
2020-10-14 23:09:33 -07:00
} )
2020-10-22 11:18:27 -07:00
// get share object url
2022-04-27 11:45:04 -07:00
api . ObjectShareObjectHandler = objectApi . ShareObjectHandlerFunc ( func ( params objectApi . ShareObjectParams , session * models . Principal ) middleware . Responder {
2023-06-12 14:02:38 -06:00
resp , err := getShareObjectResponse ( session , params )
2020-10-22 11:18:27 -07:00
if err != nil {
2023-08-16 14:18:08 -07:00
return objectApi . NewShareObjectDefault ( err . Code ) . WithPayload ( err . APIError )
2020-10-22 11:18:27 -07:00
}
2022-04-27 11:45:04 -07:00
return objectApi . NewShareObjectOK ( ) . WithPayload ( * resp )
2020-10-22 11:18:27 -07:00
} )
2020-12-03 16:21:44 -06:00
// set tags in object
2022-04-27 11:45:04 -07:00
api . ObjectPutObjectTagsHandler = objectApi . PutObjectTagsHandlerFunc ( func ( params objectApi . PutObjectTagsParams , session * models . Principal ) middleware . Responder {
2020-10-28 16:08:26 -07:00
if err := getPutObjectTagsResponse ( session , params ) ; err != nil {
2023-08-16 14:18:08 -07:00
return objectApi . NewPutObjectTagsDefault ( err . Code ) . WithPayload ( err . APIError )
2020-10-28 16:08:26 -07:00
}
2022-04-27 11:45:04 -07:00
return objectApi . NewPutObjectTagsOK ( )
2020-10-28 16:08:26 -07:00
} )
2022-05-05 13:44:10 -07:00
// Restore file version
2022-04-27 11:45:04 -07:00
api . ObjectPutObjectRestoreHandler = objectApi . PutObjectRestoreHandlerFunc ( func ( params objectApi . PutObjectRestoreParams , session * models . Principal ) middleware . Responder {
2021-11-02 17:59:52 -07:00
if err := getPutObjectRestoreResponse ( session , params ) ; err != nil {
2023-08-16 14:18:08 -07:00
return objectApi . NewPutObjectRestoreDefault ( err . Code ) . WithPayload ( err . APIError )
2021-11-02 17:59:52 -07:00
}
2022-04-27 11:45:04 -07:00
return objectApi . NewPutObjectRestoreOK ( )
2021-11-02 17:59:52 -07:00
} )
2021-12-15 10:50:34 -06:00
// Metadata in object
2022-04-27 11:45:04 -07:00
api . ObjectGetObjectMetadataHandler = objectApi . GetObjectMetadataHandlerFunc ( func ( params objectApi . GetObjectMetadataParams , session * models . Principal ) middleware . Responder {
2021-12-15 10:50:34 -06:00
resp , err := getObjectMetadataResponse ( session , params )
if err != nil {
2023-08-16 14:18:08 -07:00
return objectApi . NewGetObjectMetadataDefault ( err . Code ) . WithPayload ( err . APIError )
2021-12-15 10:50:34 -06:00
}
2022-04-27 11:45:04 -07:00
return objectApi . NewGetObjectMetadataOK ( ) . WithPayload ( resp )
2021-12-15 10:50:34 -06:00
} )
2020-09-29 14:34:51 -07:00
}
// getListObjectsResponse returns a list of objects
2023-08-16 14:18:08 -07:00
func getListObjectsResponse ( session * models . Principal , params objectApi . ListObjectsParams ) ( * models . ListObjectsResponse , * CodedAPIError ) {
2022-04-28 12:55:06 -07:00
ctx , cancel := context . WithCancel ( params . HTTPRequest . Context ( ) )
defer cancel ( )
2020-09-29 14:34:51 -07:00
var prefix string
var recursive bool
2020-10-06 16:07:33 -07:00
var withVersions bool
2021-08-02 12:20:29 -05:00
var withMetadata bool
2020-09-29 14:34:51 -07:00
if params . Prefix != nil {
2024-06-05 23:48:27 +02:00
prefix = * params . Prefix
2020-09-29 14:34:51 -07:00
}
if params . Recursive != nil {
recursive = * params . Recursive
}
2021-11-01 18:34:20 -07:00
if params . WithVersions != nil {
2020-10-06 16:07:33 -07:00
withVersions = * params . WithVersions
}
2021-08-02 12:20:29 -05:00
if params . WithMetadata != nil {
withMetadata = * params . WithMetadata
}
2020-09-29 14:34:51 -07:00
// bucket request needed to proceed
if params . BucketName == "" {
2022-04-28 12:55:06 -07:00
return nil , ErrorWithContext ( ctx , ErrBucketNameNotInRequest )
2020-09-29 14:34:51 -07:00
}
2023-06-14 12:36:48 -07:00
mClient , err := newMinioClient ( session , getClientIP ( params . HTTPRequest ) )
2020-09-29 14:34:51 -07:00
if err != nil {
2022-04-28 12:55:06 -07:00
return nil , ErrorWithContext ( ctx , err )
2020-09-29 14:34:51 -07:00
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient { client : mClient }
2023-01-27 12:23:30 -08:00
objs , err := listBucketObjects ( ListObjectsOpts {
ctx : ctx ,
client : minioClient ,
bucketName : params . BucketName ,
prefix : prefix ,
recursive : recursive ,
withVersions : withVersions ,
withMetadata : withMetadata ,
limit : params . Limit ,
} )
2020-09-29 14:34:51 -07:00
if err != nil {
2022-04-28 12:55:06 -07:00
return nil , ErrorWithContext ( ctx , err )
2020-09-29 14:34:51 -07:00
}
resp := & models . ListObjectsResponse {
Objects : objs ,
Total : int64 ( len ( objs ) ) ,
}
return resp , nil
}
2020-10-01 17:00:32 -07:00
2023-01-27 12:23:30 -08:00
type ListObjectsOpts struct {
ctx context . Context
client MinioClient
bucketName string
prefix string
recursive bool
withVersions bool
withMetadata bool
limit * int32
}
2020-10-01 17:00:32 -07:00
// listBucketObjects gets an array of objects in a bucket
2023-01-27 12:23:30 -08:00
func listBucketObjects ( listOpts ListObjectsOpts ) ( [ ] * models . BucketObject , error ) {
2020-10-01 17:00:32 -07:00
var objects [ ] * models . BucketObject
2021-11-01 18:34:20 -07:00
opts := minio . ListObjectsOptions {
2023-01-27 12:23:30 -08:00
Prefix : listOpts . prefix ,
Recursive : listOpts . recursive ,
WithVersions : listOpts . withVersions ,
WithMetadata : listOpts . withMetadata ,
2022-12-06 13:23:07 -06:00
MaxKeys : 100 ,
2021-11-01 18:34:20 -07:00
}
2023-01-27 12:23:30 -08:00
if listOpts . withMetadata {
2021-11-01 18:34:20 -07:00
opts . MaxKeys = 1
}
2023-01-27 12:23:30 -08:00
if listOpts . limit != nil {
opts . MaxKeys = int ( * listOpts . limit )
}
var totalObjs int32
for lsObj := range listOpts . client . listObjects ( listOpts . ctx , listOpts . bucketName , opts ) {
2020-10-01 17:00:32 -07:00
if lsObj . Err != nil {
return nil , lsObj . Err
}
2021-08-02 12:20:29 -05:00
2020-10-01 17:00:32 -07:00
obj := & models . BucketObject {
2020-10-06 16:07:33 -07:00
Name : lsObj . Key ,
Size : lsObj . Size ,
2021-01-13 14:08:32 -06:00
LastModified : lsObj . LastModified . Format ( time . RFC3339 ) ,
2020-10-06 16:07:33 -07:00
ContentType : lsObj . ContentType ,
VersionID : lsObj . VersionID ,
IsLatest : lsObj . IsLatest ,
IsDeleteMarker : lsObj . IsDeleteMarker ,
UserTags : lsObj . UserTags ,
2021-08-02 12:20:29 -05:00
UserMetadata : lsObj . UserMetadata ,
2022-02-28 23:30:13 -07:00
Etag : lsObj . ETag ,
2020-10-06 16:07:33 -07:00
}
2020-10-28 16:08:26 -07:00
// only if single object with or without versions; get legalhold, retention and tags
2023-01-27 12:23:30 -08:00
if ! lsObj . IsDeleteMarker && listOpts . prefix != "" && ! strings . HasSuffix ( listOpts . prefix , "/" ) {
2020-10-06 16:07:33 -07:00
// Add Legal Hold Status if available
2023-01-27 12:23:30 -08:00
legalHoldStatus , err := listOpts . client . getObjectLegalHold ( listOpts . ctx , listOpts . bucketName , lsObj . Key , minio . GetObjectLegalHoldOptions { VersionID : lsObj . VersionID } )
2020-10-06 16:07:33 -07:00
if err != nil {
errResp := minio . ToErrorResponse ( probe . NewError ( err ) . ToGoError ( ) )
2020-10-22 11:18:27 -07:00
if errResp . Code != "InvalidRequest" && errResp . Code != "NoSuchObjectLockConfiguration" {
2023-01-27 12:23:30 -08:00
ErrorWithContext ( listOpts . ctx , fmt . Errorf ( "error getting legal hold status for %s : %v" , lsObj . VersionID , err ) )
2020-10-06 16:07:33 -07:00
}
2022-05-05 13:44:10 -07:00
} else if legalHoldStatus != nil {
obj . LegalHoldStatus = string ( * legalHoldStatus )
2020-10-06 16:07:33 -07:00
}
// Add Retention Status if available
2023-01-27 12:23:30 -08:00
retention , retUntilDate , err := listOpts . client . getObjectRetention ( listOpts . ctx , listOpts . bucketName , lsObj . Key , lsObj . VersionID )
2020-10-06 16:07:33 -07:00
if err != nil {
errResp := minio . ToErrorResponse ( probe . NewError ( err ) . ToGoError ( ) )
2021-07-19 17:45:57 -07:00
if errResp . Code != "InvalidRequest" && errResp . Code != "NoSuchObjectLockConfiguration" {
2023-01-27 12:23:30 -08:00
ErrorWithContext ( listOpts . ctx , fmt . Errorf ( "error getting retention status for %s : %v" , lsObj . VersionID , err ) )
2020-10-06 16:07:33 -07:00
}
2022-05-05 13:44:10 -07:00
} else if retention != nil && retUntilDate != nil {
date := * retUntilDate
obj . RetentionMode = string ( * retention )
obj . RetentionUntilDate = date . Format ( time . RFC3339 )
2020-10-06 16:07:33 -07:00
}
2023-01-27 12:23:30 -08:00
objTags , err := listOpts . client . getObjectTagging ( listOpts . ctx , listOpts . bucketName , lsObj . Key , minio . GetObjectTaggingOptions { VersionID : lsObj . VersionID } )
2020-10-28 16:08:26 -07:00
if err != nil {
2023-01-27 12:23:30 -08:00
ErrorWithContext ( listOpts . ctx , fmt . Errorf ( "error getting object tags for %s : %v" , lsObj . VersionID , err ) )
2020-10-28 16:08:26 -07:00
} else {
2023-01-27 12:23:30 -08:00
obj . Tags = objTags . ToMap ( )
2020-10-28 16:08:26 -07:00
}
2020-10-01 17:00:32 -07:00
}
objects = append ( objects , obj )
2023-01-27 12:23:30 -08:00
totalObjs ++
if listOpts . limit != nil {
if totalObjs >= * listOpts . limit {
break
}
}
2020-10-01 17:00:32 -07:00
}
return objects , nil
}
2021-12-13 00:17:33 -08:00
type httpRange struct {
Start int64
Length int64
}
// Example:
2022-08-05 08:07:38 -07:00
//
// "Content-Range": "bytes 100-200/1000"
// "Content-Range": "bytes 100-200/*"
2021-12-13 00:17:33 -08:00
func getRange ( start , end , total int64 ) string {
// unknown total: -1
if total == - 1 {
return fmt . Sprintf ( "bytes %d-%d/*" , start , end )
}
return fmt . Sprintf ( "bytes %d-%d/%d" , start , end , total )
}
// Example:
2022-08-05 08:07:38 -07:00
//
// "Range": "bytes=100-200"
// "Range": "bytes=-50"
// "Range": "bytes=150-"
// "Range": "bytes=0-0,-1"
2021-12-13 00:17:33 -08:00
func parseRange ( s string , size int64 ) ( [ ] httpRange , error ) {
if s == "" {
return nil , nil // header not present
}
const b = "bytes="
if ! strings . HasPrefix ( s , b ) {
return nil , errors . New ( "invalid range" )
}
var ranges [ ] httpRange
for _ , ra := range strings . Split ( s [ len ( b ) : ] , "," ) {
ra = strings . TrimSpace ( ra )
if ra == "" {
continue
}
i := strings . Index ( ra , "-" )
if i < 0 {
return nil , errors . New ( "invalid range" )
}
start , end := strings . TrimSpace ( ra [ : i ] ) , strings . TrimSpace ( ra [ i + 1 : ] )
var r httpRange
if start == "" {
// If no start is specified, end specifies the
// range start relative to the end of the file.
i , err := strconv . ParseInt ( end , 10 , 64 )
if err != nil {
return nil , errors . New ( "invalid range" )
}
if i > size {
i = size
}
r . Start = size - i
r . Length = size - r . Start
} else {
i , err := strconv . ParseInt ( start , 10 , 64 )
if err != nil || i >= size || i < 0 {
return nil , errors . New ( "invalid range" )
}
r . Start = i
if end == "" {
// If no end is specified, range extends to end of the file.
r . Length = size - r . Start
} else {
i , err := strconv . ParseInt ( end , 10 , 64 )
if err != nil || r . Start > i {
return nil , errors . New ( "invalid range" )
}
if i >= size {
i = size - 1
}
r . Length = i - r . Start + 1
}
}
ranges = append ( ranges , r )
}
return ranges , nil
}
2023-08-16 14:18:08 -07:00
func getDownloadObjectResponse ( session * models . Principal , params objectApi . DownloadObjectParams ) ( middleware . Responder , * CodedAPIError ) {
2022-04-28 12:55:06 -07:00
ctx := params . HTTPRequest . Context ( )
2023-06-14 12:36:48 -07:00
mClient , err := newMinioClient ( session , getClientIP ( params . HTTPRequest ) )
2021-12-07 19:36:50 -08:00
if err != nil {
2022-04-28 12:55:06 -07:00
return nil , ErrorWithContext ( ctx , err )
2021-12-07 19:36:50 -08:00
}
2022-03-11 22:40:14 -08:00
opts := minio . GetObjectOptions { }
if params . VersionID != nil && * params . VersionID != "" {
opts . VersionID = * params . VersionID
}
2024-06-05 23:48:27 +02:00
resp , err := mClient . GetObject ( ctx , params . BucketName , params . Prefix , opts )
2021-12-07 19:36:50 -08:00
if err != nil {
2022-04-28 12:55:06 -07:00
return nil , ErrorWithContext ( ctx , err )
2020-10-09 11:43:15 -07:00
}
2021-12-07 19:36:50 -08:00
return middleware . ResponderFunc ( func ( rw http . ResponseWriter , _ runtime . Producer ) {
defer resp . Close ( )
isPreview := params . Preview != nil && * params . Preview
2024-06-05 23:48:27 +02:00
overrideName := * params . OverrideFileName
2022-06-22 12:43:57 -05:00
2021-12-07 19:36:50 -08:00
// indicate it's a download / inline content to the browser, and the size of the object
var filename string
2024-06-05 23:48:27 +02:00
prefixElements := strings . Split ( params . Prefix , "/" )
2022-06-22 12:43:57 -05:00
if len ( prefixElements ) > 0 && overrideName == "" {
2021-12-07 19:36:50 -08:00
if prefixElements [ len ( prefixElements ) - 1 ] == "" {
filename = prefixElements [ len ( prefixElements ) - 2 ]
} else {
filename = prefixElements [ len ( prefixElements ) - 1 ]
}
2022-06-22 12:43:57 -05:00
} else if overrideName != "" {
filename = overrideName
2021-11-24 20:08:25 -08:00
}
2022-06-22 12:43:57 -05:00
2022-01-11 15:11:47 -08:00
escapedName := url . PathEscape ( filename )
2021-12-07 19:36:50 -08:00
2021-12-13 00:17:33 -08:00
// indicate object size & content type
stat , err := resp . Stat ( )
if err != nil {
2022-04-18 12:03:06 -07:00
minErr := minio . ToErrorResponse ( err )
2024-06-05 23:48:27 +02:00
fmtError := ErrorWithContext ( ctx , fmt . Errorf ( "failed to get Stat() response from server for %s (version %s): %v" , params . Prefix , opts . VersionID , minErr . Error ( ) ) )
2024-01-26 13:06:25 -08:00
http . Error ( rw , fmtError . APIError . DetailedMessage , http . StatusInternalServerError )
2021-12-13 00:17:33 -08:00
return
}
2021-12-07 19:36:50 -08:00
2021-12-13 00:17:33 -08:00
// if we are getting a Range Request (video) handle that specially
ranges , err := parseRange ( params . HTTPRequest . Header . Get ( "Range" ) , stat . Size )
if err != nil {
2024-01-26 13:06:25 -08:00
fmtError := ErrorWithContext ( ctx , fmt . Errorf ( "unable to parse range header input %s: %v" , params . HTTPRequest . Header . Get ( "Range" ) , err ) )
http . Error ( rw , fmtError . APIError . DetailedMessage , http . StatusInternalServerError )
2021-12-13 00:17:33 -08:00
return
2021-12-07 19:36:50 -08:00
}
2022-03-02 12:18:43 -08:00
contentType := stat . ContentType
rw . Header ( ) . Set ( "X-XSS-Protection" , "1; mode=block" )
2021-12-07 19:36:50 -08:00
2022-03-02 12:18:43 -08:00
if isPreview && isSafeToPreview ( contentType ) {
2022-01-11 15:11:47 -08:00
rw . Header ( ) . Set ( "Content-Disposition" , fmt . Sprintf ( "inline; filename=\"%s\"" , escapedName ) )
2021-12-07 19:36:50 -08:00
rw . Header ( ) . Set ( "X-Frame-Options" , "SAMEORIGIN" )
} else {
2022-01-11 15:11:47 -08:00
rw . Header ( ) . Set ( "Content-Disposition" , fmt . Sprintf ( "attachment; filename=\"%s\"" , escapedName ) )
2021-12-07 19:36:50 -08:00
}
2021-12-13 00:17:33 -08:00
rw . Header ( ) . Set ( "Last-Modified" , stat . LastModified . UTC ( ) . Format ( http . TimeFormat ) )
2021-12-07 19:36:50 -08:00
2021-12-13 00:17:33 -08:00
if isPreview {
// In case content type was uploaded as octet-stream, we double verify content type
if stat . ContentType == "application/octet-stream" {
2022-01-11 15:11:47 -08:00
contentType = mimedb . TypeByExtension ( filepath . Ext ( escapedName ) )
2021-12-13 00:17:33 -08:00
}
}
rw . Header ( ) . Set ( "Content-Type" , contentType )
length := stat . Size
if len ( ranges ) > 0 {
start := ranges [ 0 ] . Start
length = ranges [ 0 ] . Length
2021-12-07 19:36:50 -08:00
2021-12-13 00:17:33 -08:00
_ , err = resp . Seek ( start , io . SeekStart )
if err != nil {
2024-01-26 13:06:25 -08:00
fmtError := ErrorWithContext ( ctx , fmt . Errorf ( "unable to seek at offset %d: %v" , start , err ) )
http . Error ( rw , fmtError . APIError . DetailedMessage , http . StatusInternalServerError )
2021-12-13 00:17:33 -08:00
return
2021-11-24 20:08:25 -08:00
}
2021-12-07 19:36:50 -08:00
2021-12-13 00:17:33 -08:00
rw . Header ( ) . Set ( "Accept-Ranges" , "bytes" )
rw . Header ( ) . Set ( "Access-Control-Allow-Origin" , "*" )
rw . Header ( ) . Set ( "Content-Range" , getRange ( start , start + length - 1 , stat . Size ) )
rw . WriteHeader ( http . StatusPartialContent )
2021-12-07 19:36:50 -08:00
}
2021-12-13 00:17:33 -08:00
rw . Header ( ) . Set ( "Content-Length" , fmt . Sprintf ( "%d" , length ) )
_ , err = io . Copy ( rw , io . LimitReader ( resp , length ) )
2021-12-07 19:36:50 -08:00
if err != nil {
2024-01-26 13:06:25 -08:00
ErrorWithContext ( ctx , fmt . Errorf ( "unable to write all data to client: %v" , err ) )
// You can't change headers after you already started writing the body.
// Handle incomplete write in client.
2021-12-13 00:17:33 -08:00
return
2021-12-07 19:36:50 -08:00
}
} ) , nil
}
2022-05-05 13:44:10 -07:00
2023-08-16 14:18:08 -07:00
func getDownloadFolderResponse ( session * models . Principal , params objectApi . DownloadObjectParams ) ( middleware . Responder , * CodedAPIError ) {
2022-05-06 11:14:05 -07:00
ctx := params . HTTPRequest . Context ( )
2023-06-14 12:36:48 -07:00
mClient , err := newMinioClient ( session , getClientIP ( params . HTTPRequest ) )
2021-12-07 19:36:50 -08:00
2024-06-05 23:48:27 +02:00
folders := strings . Split ( params . Prefix , "/" )
2021-12-07 19:36:50 -08:00
if err != nil {
2022-04-28 12:55:06 -07:00
return nil , ErrorWithContext ( ctx , err )
2021-11-24 20:08:25 -08:00
}
2021-12-07 19:36:50 -08:00
minioClient := minioClient { client : mClient }
2023-01-27 12:23:30 -08:00
objects , err := listBucketObjects ( ListObjectsOpts {
ctx : ctx ,
client : minioClient ,
bucketName : params . BucketName ,
2024-06-05 23:48:27 +02:00
prefix : params . Prefix ,
2023-01-27 12:23:30 -08:00
recursive : true ,
withVersions : false ,
withMetadata : false ,
} )
2020-10-09 11:43:15 -07:00
if err != nil {
2022-04-28 12:55:06 -07:00
return nil , ErrorWithContext ( ctx , err )
2020-10-09 11:43:15 -07:00
}
2022-05-06 11:14:05 -07:00
resp , pw := io . Pipe ( )
// Create file async
go func ( ) {
defer pw . Close ( )
zipw := zip . NewWriter ( pw )
var folder string
if len ( folders ) > 1 {
folder = folders [ len ( folders ) - 2 ]
2021-12-07 19:36:50 -08:00
}
2022-05-06 11:14:05 -07:00
defer zipw . Close ( )
for i , obj := range objects {
2024-06-05 23:48:27 +02:00
name := folder + objects [ i ] . Name [ len ( params . Prefix ) - 1 : ]
2022-05-06 11:14:05 -07:00
object , err := mClient . GetObject ( ctx , params . BucketName , obj . Name , minio . GetObjectOptions { } )
if err != nil {
// Ignore errors, move to next
continue
}
modified , _ := time . Parse ( time . RFC3339 , obj . LastModified )
f , err := zipw . CreateHeader ( & zip . FileHeader {
Name : name ,
NonUTF8 : false ,
Method : zip . Deflate ,
Modified : modified ,
} )
if err != nil {
2024-12-03 07:47:32 +05:30
object . Close ( )
2022-05-06 11:14:05 -07:00
// Ignore errors, move to next
continue
}
2024-12-03 07:47:32 +05:30
2022-05-06 11:14:05 -07:00
_ , err = io . Copy ( f , object )
2024-12-03 07:47:32 +05:30
object . Close ( )
2022-05-06 11:14:05 -07:00
if err != nil {
// We have a partial object, report error.
pw . CloseWithError ( err )
return
}
2021-12-07 19:36:50 -08:00
}
2022-05-06 11:14:05 -07:00
} ( )
2021-12-07 19:36:50 -08:00
return middleware . ResponderFunc ( func ( rw http . ResponseWriter , _ runtime . Producer ) {
defer resp . Close ( )
// indicate it's a download / inline content to the browser, and the size of the object
var filename string
2024-06-05 23:48:27 +02:00
prefixElements := strings . Split ( params . Prefix , "/" )
2021-12-07 19:36:50 -08:00
if len ( prefixElements ) > 0 {
if prefixElements [ len ( prefixElements ) - 1 ] == "" {
filename = prefixElements [ len ( prefixElements ) - 2 ]
} else {
filename = prefixElements [ len ( prefixElements ) - 1 ]
}
}
2022-01-11 15:11:47 -08:00
escapedName := url . PathEscape ( filename )
2021-12-07 19:36:50 -08:00
2022-01-11 15:11:47 -08:00
rw . Header ( ) . Set ( "Content-Disposition" , fmt . Sprintf ( "attachment; filename=\"%s.zip\"" , escapedName ) )
2021-12-07 19:36:50 -08:00
rw . Header ( ) . Set ( "Content-Type" , "application/zip" )
// Copy the stream
_ , err := io . Copy ( rw , resp )
if err != nil {
2024-01-26 13:06:25 -08:00
ErrorWithContext ( ctx , fmt . Errorf ( "unable to write all the requested data: %v" , err ) )
// You can't change headers after you already started writing the body.
// Handle incomplete write in client.
return
2021-12-07 19:36:50 -08:00
}
} ) , nil
2020-10-09 11:43:15 -07:00
}
2023-08-16 14:18:08 -07:00
func getMultipleFilesDownloadResponse ( session * models . Principal , params objectApi . DownloadMultipleObjectsParams ) ( middleware . Responder , * CodedAPIError ) {
2023-08-03 05:58:25 +05:30
ctx := params . HTTPRequest . Context ( )
mClient , err := newMinioClient ( session , getClientIP ( params . HTTPRequest ) )
if err != nil {
return nil , ErrorWithContext ( ctx , err )
}
minioClient := minioClient { client : mClient }
resp , pw := io . Pipe ( )
// Create file async
go func ( ) {
defer pw . Close ( )
zipw := zip . NewWriter ( pw )
defer zipw . Close ( )
addToZip := func ( name string , modified time . Time ) ( io . Writer , error ) {
f , err := zipw . CreateHeader ( & zip . FileHeader {
Name : name ,
NonUTF8 : false ,
Method : zip . Deflate ,
Modified : modified ,
} )
return f , err
}
for _ , dObj := range params . ObjectList {
// if a prefix is selected, list and add objects recursively
// the prefixes are not base64 encoded.
if strings . HasSuffix ( dObj , "/" ) {
prefix := dObj
folders := strings . Split ( prefix , "/" )
var folder string
if len ( folders ) > 1 {
folder = folders [ len ( folders ) - 2 ]
}
objects , err := listBucketObjects ( ListObjectsOpts {
ctx : ctx ,
client : minioClient ,
bucketName : params . BucketName ,
prefix : prefix ,
recursive : true ,
withVersions : false ,
withMetadata : false ,
} )
if err != nil {
pw . CloseWithError ( err )
}
for i , obj := range objects {
name := folder + objects [ i ] . Name [ len ( prefix ) - 1 : ]
object , err := mClient . GetObject ( ctx , params . BucketName , obj . Name , minio . GetObjectOptions { } )
if err != nil {
// Ignore errors, move to next
continue
}
2024-12-03 07:47:32 +05:30
modified , _ := time . Parse ( time . RFC3339 , obj . LastModified )
2023-08-03 05:58:25 +05:30
f , err := addToZip ( name , modified )
if err != nil {
2024-12-03 07:47:32 +05:30
object . Close ( )
2023-08-03 05:58:25 +05:30
// Ignore errors, move to next
continue
}
2024-12-03 07:47:32 +05:30
2023-08-03 05:58:25 +05:30
_ , err = io . Copy ( f , object )
2024-12-03 07:47:32 +05:30
object . Close ( )
2023-08-03 05:58:25 +05:30
if err != nil {
// We have a partial object, report error.
pw . CloseWithError ( err )
return
}
}
} else {
2024-12-03 07:47:32 +05:30
object , err := mClient . GetObject ( ctx , params . BucketName , dObj , minio . GetObjectOptions { } )
2023-08-03 05:58:25 +05:30
if err != nil {
// Ignore errors, move to next
continue
}
2024-12-03 07:47:32 +05:30
// add selected individual object
objectData , err := object . Stat ( )
2023-08-03 05:58:25 +05:30
if err != nil {
// Ignore errors, move to next
continue
}
2023-08-09 22:59:41 +05:30
prefixes := strings . Split ( dObj , "/" )
// truncate upper level prefixes to make the download as flat at the current level.
objectName := prefixes [ len ( prefixes ) - 1 ]
f , err := addToZip ( objectName , objectData . LastModified )
2023-08-03 05:58:25 +05:30
if err != nil {
2024-12-03 07:47:32 +05:30
object . Close ( )
2023-08-03 05:58:25 +05:30
// Ignore errors, move to next
continue
}
2024-12-03 07:47:32 +05:30
2023-08-03 05:58:25 +05:30
_ , err = io . Copy ( f , object )
2024-12-03 07:47:32 +05:30
object . Close ( )
2023-08-03 05:58:25 +05:30
if err != nil {
// We have a partial object, report error.
pw . CloseWithError ( err )
return
}
}
}
} ( )
return middleware . ResponderFunc ( func ( rw http . ResponseWriter , _ runtime . Producer ) {
defer resp . Close ( )
// indicate it's a download / inline content to the browser, and the size of the object
2023-08-03 20:21:44 -07:00
fileName := "selected_files_" + strings . ReplaceAll ( strings . ReplaceAll ( time . Now ( ) . UTC ( ) . Format ( time . RFC3339 ) , ":" , "" ) , "-" , "" )
2023-08-03 05:58:25 +05:30
rw . Header ( ) . Set ( "Content-Disposition" , fmt . Sprintf ( "attachment; filename=\"%s.zip\"" , fileName ) )
rw . Header ( ) . Set ( "Content-Type" , "application/zip" )
// Copy the stream
_ , err := io . Copy ( rw , resp )
if err != nil {
2024-01-26 13:06:25 -08:00
ErrorWithContext ( ctx , fmt . Errorf ( "unable to write all the requested data: %v" , err ) )
// You can't change headers after you already started writing the body.
// Handle incomplete write in client.
return
2023-08-03 05:58:25 +05:30
}
} ) , nil
}
2023-01-03 12:35:37 -06:00
// getDeleteObjectResponse returns whether there was an error on deletion of object
2023-08-16 14:18:08 -07:00
func getDeleteObjectResponse ( session * models . Principal , params objectApi . DeleteObjectParams ) * CodedAPIError {
2022-04-28 12:55:06 -07:00
ctx , cancel := context . WithCancel ( params . HTTPRequest . Context ( ) )
2022-04-11 16:20:30 -07:00
defer cancel ( )
2024-06-05 23:48:27 +02:00
s3Client , err := newS3BucketClient ( session , params . BucketName , params . Prefix , getClientIP ( params . HTTPRequest ) )
2020-10-01 17:00:32 -07:00
if err != nil {
2022-04-28 12:55:06 -07:00
return ErrorWithContext ( ctx , err )
2020-10-01 17:00:32 -07:00
}
// create a mc S3Client interface implementation
// defining the client to be used
mcClient := mcClient { client : s3Client }
var rec bool
var version string
2022-01-20 17:28:52 -08:00
var allVersions bool
2022-03-18 13:07:34 -07:00
var nonCurrentVersions bool
2023-01-03 12:35:37 -06:00
var bypass bool
2020-10-01 17:00:32 -07:00
if params . Recursive != nil {
rec = * params . Recursive
}
if params . VersionID != nil {
version = * params . VersionID
}
2022-01-20 17:28:52 -08:00
if params . AllVersions != nil {
allVersions = * params . AllVersions
}
2022-03-18 13:07:34 -07:00
if params . NonCurrentVersions != nil {
nonCurrentVersions = * params . NonCurrentVersions
}
2023-01-03 12:35:37 -06:00
if params . Bypass != nil {
bypass = * params . Bypass
}
2022-03-18 13:07:34 -07:00
if allVersions && nonCurrentVersions {
err := errors . New ( "cannot set delete all versions and delete non-current versions flags at the same time" )
2022-04-28 12:55:06 -07:00
return ErrorWithContext ( ctx , err )
2022-03-18 13:07:34 -07:00
}
2024-06-05 23:48:27 +02:00
err = deleteObjects ( ctx , mcClient , params . BucketName , params . Prefix , version , rec , allVersions , nonCurrentVersions , bypass )
2020-10-01 17:00:32 -07:00
if err != nil {
2022-04-28 12:55:06 -07:00
return ErrorWithContext ( ctx , err )
2020-10-01 17:00:32 -07:00
}
return nil
}
2023-01-03 12:35:37 -06:00
// getDeleteMultiplePathsResponse returns whether there was an error on deletion of any object
2023-08-16 14:18:08 -07:00
func getDeleteMultiplePathsResponse ( session * models . Principal , params objectApi . DeleteMultipleObjectsParams ) * CodedAPIError {
2022-04-28 12:55:06 -07:00
ctx , cancel := context . WithCancel ( params . HTTPRequest . Context ( ) )
2022-04-11 16:20:30 -07:00
defer cancel ( )
2021-07-21 15:49:20 -07:00
var version string
2022-01-20 17:28:52 -08:00
var allVersions bool
2023-01-03 12:35:37 -06:00
var bypass bool
2022-01-20 17:28:52 -08:00
if params . AllVersions != nil {
allVersions = * params . AllVersions
}
2023-01-03 12:35:37 -06:00
if params . Bypass != nil {
bypass = * params . Bypass
}
2021-07-21 15:49:20 -07:00
for i := 0 ; i < len ( params . Files ) ; i ++ {
if params . Files [ i ] . VersionID != "" {
version = params . Files [ i ] . VersionID
}
2021-07-29 12:18:50 -07:00
prefix := params . Files [ i ] . Path
2023-06-14 12:36:48 -07:00
s3Client , err := newS3BucketClient ( session , params . BucketName , prefix , getClientIP ( params . HTTPRequest ) )
2021-07-29 12:18:50 -07:00
if err != nil {
2022-04-28 12:55:06 -07:00
return ErrorWithContext ( ctx , err )
2021-07-29 12:18:50 -07:00
}
2021-07-21 15:49:20 -07:00
// create a mc S3Client interface implementation
// defining the client to be used
mcClient := mcClient { client : s3Client }
2023-01-03 12:35:37 -06:00
err = deleteObjects ( ctx , mcClient , params . BucketName , params . Files [ i ] . Path , version , params . Files [ i ] . Recursive , allVersions , false , bypass )
2021-07-21 15:49:20 -07:00
if err != nil {
2022-04-28 12:55:06 -07:00
return ErrorWithContext ( ctx , err )
2021-07-21 15:49:20 -07:00
}
}
return nil
}
2020-10-01 17:00:32 -07:00
// deleteObjects deletes either a single object or multiple objects based on recursive flag
2023-01-03 12:35:37 -06:00
func deleteObjects ( ctx context . Context , client MCClient , bucket string , path string , versionID string , recursive , allVersions , nonCurrentVersionsOnly , bypass bool ) error {
2022-03-18 13:07:34 -07:00
// Delete All non-Current versions only.
if nonCurrentVersionsOnly {
2023-03-22 15:06:04 -06:00
return deleteNonCurrentVersions ( ctx , client , bypass )
2022-03-18 13:07:34 -07:00
}
2022-12-16 11:07:40 -06:00
if recursive || allVersions {
2023-09-12 10:05:41 -06:00
return deleteMultipleObjects ( ctx , client , path , recursive , allVersions , bypass )
2020-10-01 17:00:32 -07:00
}
2022-06-28 20:25:50 -07:00
2023-01-03 12:35:37 -06:00
return deleteSingleObject ( ctx , client , bucket , path , versionID , bypass )
2020-10-01 17:00:32 -07:00
}
2023-09-12 10:05:41 -06:00
// Return standardized URL to be used to compare later.
func getStandardizedURL ( targetURL string ) string {
return filepath . FromSlash ( targetURL )
}
2020-10-01 17:00:32 -07:00
// deleteMultipleObjects uses listing before removal, it can list recursively or not,
2022-08-05 08:07:38 -07:00
//
// Use cases:
// * Remove objects recursively
2023-09-12 10:05:41 -06:00
func deleteMultipleObjects ( ctx context . Context , client MCClient , path string , recursive , allVersions , isBypass bool ) error {
2023-01-23 21:53:20 -06:00
// Constants defined to make this code more readable
const (
isIncomplete = false
isRemoveBucket = false
forceDelete = false // Force delete not meant to be used by console UI.
)
2022-12-16 11:07:40 -06:00
2022-06-28 20:25:50 -07:00
listOpts := mc . ListOptions {
Recursive : recursive ,
Incomplete : isIncomplete ,
ShowDir : mc . DirNone ,
WithOlderVersions : allVersions ,
WithDeleteMarkers : allVersions ,
}
2020-10-01 17:00:32 -07:00
2022-06-28 20:25:50 -07:00
lctx , cancel := context . WithCancel ( ctx )
defer cancel ( )
contentCh := make ( chan * mc . ClientContent )
go func ( ) {
defer close ( contentCh )
for content := range client . list ( lctx , listOpts ) {
if content . Err != nil {
2020-10-01 17:00:32 -07:00
continue
}
2023-09-12 10:05:41 -06:00
if ! strings . HasSuffix ( getStandardizedURL ( content . URL . Path ) , path ) && ! strings . HasSuffix ( path , "/" ) {
continue
}
2020-10-01 17:00:32 -07:00
select {
case contentCh <- content :
2022-06-28 20:25:50 -07:00
case <- lctx . Done ( ) :
return
2020-10-01 17:00:32 -07:00
}
}
2022-06-28 20:25:50 -07:00
} ( )
2022-12-16 11:07:40 -06:00
for result := range client . remove ( ctx , isIncomplete , isRemoveBucket , isBypass , forceDelete , contentCh ) {
2022-01-31 09:10:06 -08:00
if result . Err != nil {
return result . Err . Cause
2020-10-01 17:00:32 -07:00
}
}
2022-06-28 20:25:50 -07:00
2020-10-01 17:00:32 -07:00
return nil
}
2023-01-03 12:35:37 -06:00
func deleteSingleObject ( ctx context . Context , client MCClient , bucket , object string , versionID string , isBypass bool ) error {
2020-10-01 17:00:32 -07:00
targetURL := fmt . Sprintf ( "%s/%s" , bucket , object )
contentCh := make ( chan * mc . ClientContent , 1 )
contentCh <- & mc . ClientContent { URL : * newClientURL ( targetURL ) , VersionID : versionID }
close ( contentCh )
2022-06-28 20:25:50 -07:00
isIncomplete := false
isRemoveBucket := false
2020-10-01 17:00:32 -07:00
2022-12-16 11:07:40 -06:00
resultCh := client . remove ( ctx , isIncomplete , isRemoveBucket , isBypass , false , contentCh )
2022-01-31 09:10:06 -08:00
for result := range resultCh {
if result . Err != nil {
return result . Err . Cause
2020-10-01 17:00:32 -07:00
}
}
return nil
}
2023-03-22 15:06:04 -06:00
func deleteNonCurrentVersions ( ctx context . Context , client MCClient , isBypass bool ) error {
2022-06-28 20:25:50 -07:00
lctx , cancel := context . WithCancel ( ctx )
defer cancel ( )
2022-03-18 13:07:34 -07:00
2022-06-28 20:25:50 -07:00
contentCh := make ( chan * mc . ClientContent )
go func ( ) {
defer close ( contentCh )
// Get current object versions
for lsObj := range client . list ( lctx , mc . ListOptions {
WithDeleteMarkers : true ,
WithOlderVersions : true ,
Recursive : true ,
} ) {
if lsObj . Err != nil {
continue
}
if lsObj . IsLatest {
continue
}
// All non-current objects proceed to purge.
select {
case contentCh <- lsObj :
case <- lctx . Done ( ) :
return
2022-03-18 13:07:34 -07:00
}
}
2022-06-28 20:25:50 -07:00
} ( )
2023-01-03 12:35:37 -06:00
for result := range client . remove ( ctx , false , false , isBypass , false , contentCh ) {
2022-06-28 20:25:50 -07:00
if result . Err != nil {
return result . Err . Cause
}
2022-03-18 13:07:34 -07:00
}
return nil
}
2023-08-16 14:18:08 -07:00
func getUploadObjectResponse ( session * models . Principal , params objectApi . PostBucketsBucketNameObjectsUploadParams ) * CodedAPIError {
2022-04-28 12:55:06 -07:00
ctx := params . HTTPRequest . Context ( )
2023-06-14 12:36:48 -07:00
mClient , err := newMinioClient ( session , getClientIP ( params . HTTPRequest ) )
2020-10-14 23:09:33 -07:00
if err != nil {
2022-04-28 12:55:06 -07:00
return ErrorWithContext ( ctx , err )
2020-10-14 23:09:33 -07:00
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient { client : mClient }
2021-01-15 17:46:07 -06:00
if err := uploadFiles ( ctx , minioClient , params ) ; err != nil {
2022-04-28 12:55:06 -07:00
return ErrorWithContext ( ctx , err , ErrDefault )
2020-10-14 23:09:33 -07:00
}
return nil
}
2021-01-15 17:46:07 -06:00
// uploadFiles gets files from http.Request form and uploads them to MinIO
2022-04-27 11:45:04 -07:00
func uploadFiles ( ctx context . Context , client MinioClient , params objectApi . PostBucketsBucketNameObjectsUploadParams ) error {
2021-01-15 17:46:07 -06:00
var prefix string
if params . Prefix != nil {
2024-06-05 23:48:27 +02:00
prefix = * params . Prefix
2022-09-09 13:33:58 -07:00
// trim any leading '/', since that is not expected
// for any object.
prefix = strings . TrimPrefix ( prefix , "/" )
2021-01-15 17:46:07 -06:00
}
2021-09-21 17:43:43 -07:00
// parse a request body as multipart/form-data.
// 32 << 20 is default max memory
mr , err := params . HTTPRequest . MultipartReader ( )
2021-01-15 17:46:07 -06:00
if err != nil {
return err
}
2021-09-21 17:43:43 -07:00
for {
p , err := mr . NextPart ( )
if err == io . EOF {
break
2021-01-15 17:46:07 -06:00
}
2021-09-10 14:36:58 -07:00
2021-09-21 17:43:43 -07:00
size , err := strconv . ParseInt ( p . FormName ( ) , 10 , 64 )
if err != nil {
return err
}
2021-01-15 17:46:07 -06:00
2021-09-21 17:43:43 -07:00
contentType := p . Header . Get ( "content-type" )
if contentType == "" {
contentType = mimedb . TypeByExtension ( filepath . Ext ( p . FileName ( ) ) )
}
2023-12-07 03:44:25 +05:30
objectName := prefix // prefix will have complete object path e.g: /test-prefix/test-object.txt
_ , err = client . putObject ( ctx , params . BucketName , objectName , p , size , minio . PutObjectOptions {
2021-09-21 17:43:43 -07:00
ContentType : contentType ,
DisableMultipart : true , // Do not upload as multipart stream for console uploader.
} )
if err != nil {
return err
2021-01-15 17:46:07 -06:00
}
}
2021-09-10 14:36:58 -07:00
2021-09-21 17:43:43 -07:00
return nil
2020-10-14 23:09:33 -07:00
}
2023-06-12 14:02:38 -06:00
// getShareObjectResponse returns a share object url
2023-08-16 14:18:08 -07:00
func getShareObjectResponse ( session * models . Principal , params objectApi . ShareObjectParams ) ( * string , * CodedAPIError ) {
2022-04-28 12:55:06 -07:00
ctx := params . HTTPRequest . Context ( )
2023-06-14 12:36:48 -07:00
clientIP := utils . ClientIPFromContext ( ctx )
2024-06-05 23:48:27 +02:00
s3Client , err := newS3BucketClient ( session , params . BucketName , params . Prefix , clientIP )
2023-06-12 14:02:38 -06:00
if err != nil {
return nil , ErrorWithContext ( ctx , err )
2020-10-22 11:18:27 -07:00
}
2023-06-12 14:02:38 -06:00
// create a mc S3Client interface implementation
// defining the client to be used
mcClient := mcClient { client : s3Client }
var expireDuration string
if params . Expires != nil {
expireDuration = * params . Expires
2020-10-22 11:18:27 -07:00
}
2024-04-10 10:16:17 -07:00
url , err := getShareObjectURL ( ctx , mcClient , params . HTTPRequest , params . VersionID , expireDuration )
2020-10-22 11:18:27 -07:00
if err != nil {
2022-04-28 12:55:06 -07:00
return nil , ErrorWithContext ( ctx , err )
2020-10-22 11:18:27 -07:00
}
2024-04-10 10:16:17 -07:00
2023-06-12 14:02:38 -06:00
return url , nil
2020-10-22 11:18:27 -07:00
}
2024-06-05 23:48:27 +02:00
func getShareObjectURL ( ctx context . Context , client MCClient , r * http . Request , versionID string , duration string ) ( * string , error ) {
2020-10-22 11:18:27 -07:00
// default duration 7d if not defined
if strings . TrimSpace ( duration ) == "" {
duration = "168h"
}
expiresDuration , err := time . ParseDuration ( duration )
if err != nil {
return nil , err
}
2024-04-10 10:16:17 -07:00
minioURL , pErr := client . shareDownload ( ctx , versionID , expiresDuration )
2020-10-22 11:18:27 -07:00
if pErr != nil {
return nil , pErr . Cause
}
2024-04-10 10:16:17 -07:00
requestURL := getRequestURLWithScheme ( r )
2024-06-13 21:30:33 +02:00
encodedURL := base64 . RawURLEncoding . EncodeToString ( [ ] byte ( minioURL ) )
objURL := fmt . Sprintf ( "%s/api/v1/download-shared-object/%s" , requestURL , url . PathEscape ( encodedURL ) )
2020-10-22 11:18:27 -07:00
return & objURL , nil
}
2024-04-10 10:16:17 -07:00
func getRequestURLWithScheme ( r * http . Request ) string {
scheme := "http"
if r . TLS != nil {
scheme = "https"
}
2024-05-08 09:46:41 -07:00
redirectURL := getConsoleBrowserRedirectURL ( )
if redirectURL != "" {
return strings . TrimSuffix ( redirectURL , "/" )
}
2024-04-10 10:16:17 -07:00
return fmt . Sprintf ( "%s://%s" , scheme , r . Host )
}
2020-10-22 11:18:27 -07:00
2020-10-22 16:23:29 -07:00
func setObjectLegalHold ( ctx context . Context , client MinioClient , bucketName , prefix , versionID string , status models . ObjectLegalHoldStatus ) error {
var lstatus minio . LegalHoldStatus
if status == models . ObjectLegalHoldStatusEnabled {
lstatus = minio . LegalHoldEnabled
} else {
lstatus = minio . LegalHoldDisabled
}
return client . putObjectLegalHold ( ctx , bucketName , prefix , minio . PutObjectLegalHoldOptions { VersionID : versionID , Status : & lstatus } )
}
2020-12-03 13:45:45 -06:00
func setObjectRetention ( ctx context . Context , client MinioClient , bucketName , versionID , prefix string , retentionOps * models . PutObjectRetentionRequest ) error {
2020-10-23 15:04:02 -07:00
if retentionOps == nil {
return errors . New ( "object retention options can't be nil" )
}
if retentionOps . Expires == nil {
return errors . New ( "object retention expires can't be nil" )
}
2020-12-03 13:45:45 -06:00
2020-10-23 15:04:02 -07:00
var mode minio . RetentionMode
2021-06-08 12:35:39 -07:00
if * retentionOps . Mode == models . ObjectRetentionModeGovernance {
2020-10-23 15:04:02 -07:00
mode = minio . Governance
} else {
mode = minio . Compliance
}
retentionUntilDate , err := time . Parse ( time . RFC3339 , * retentionOps . Expires )
if err != nil {
return err
}
opts := minio . PutObjectRetentionOptions {
GovernanceBypass : retentionOps . GovernanceBypass ,
RetainUntilDate : & retentionUntilDate ,
Mode : & mode ,
VersionID : versionID ,
}
return client . putObjectRetention ( ctx , bucketName , prefix , opts )
2020-12-03 16:21:44 -06:00
}
func deleteObjectRetention ( ctx context . Context , client MinioClient , bucketName , prefix , versionID string ) error {
opts := minio . PutObjectRetentionOptions {
GovernanceBypass : true ,
VersionID : versionID ,
}
return client . putObjectRetention ( ctx , bucketName , prefix , opts )
2020-10-23 15:04:02 -07:00
}
2023-08-16 14:18:08 -07:00
func getPutObjectTagsResponse ( session * models . Principal , params objectApi . PutObjectTagsParams ) * CodedAPIError {
2022-04-28 12:55:06 -07:00
ctx := params . HTTPRequest . Context ( )
2023-06-14 12:36:48 -07:00
mClient , err := newMinioClient ( session , getClientIP ( params . HTTPRequest ) )
2020-10-28 16:08:26 -07:00
if err != nil {
2022-04-28 12:55:06 -07:00
return ErrorWithContext ( ctx , err )
2020-10-28 16:08:26 -07:00
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient { client : mClient }
2024-06-05 23:48:27 +02:00
err = putObjectTags ( ctx , minioClient , params . BucketName , params . Prefix , params . VersionID , params . Body . Tags )
2020-10-28 16:08:26 -07:00
if err != nil {
2022-04-28 12:55:06 -07:00
return ErrorWithContext ( ctx , err )
2020-10-28 16:08:26 -07:00
}
return nil
}
func putObjectTags ( ctx context . Context , client MinioClient , bucketName , prefix , versionID string , tagMap map [ string ] string ) error {
opt := minio . PutObjectTaggingOptions {
VersionID : versionID ,
}
otags , err := tags . MapToObjectTags ( tagMap )
if err != nil {
return err
}
return client . putObjectTagging ( ctx , bucketName , prefix , otags , opt )
}
2021-11-02 17:59:52 -07:00
// Restore Object Version
2023-08-16 14:18:08 -07:00
func getPutObjectRestoreResponse ( session * models . Principal , params objectApi . PutObjectRestoreParams ) * CodedAPIError {
2022-04-28 12:55:06 -07:00
ctx := params . HTTPRequest . Context ( )
2023-06-14 12:36:48 -07:00
mClient , err := newMinioClient ( session , getClientIP ( params . HTTPRequest ) )
2021-11-02 17:59:52 -07:00
if err != nil {
2022-04-28 12:55:06 -07:00
return ErrorWithContext ( ctx , err )
2021-11-02 17:59:52 -07:00
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient { client : mClient }
2024-06-05 23:48:27 +02:00
err = restoreObject ( ctx , minioClient , params . BucketName , params . Prefix , params . VersionID )
2021-11-02 17:59:52 -07:00
if err != nil {
2022-04-28 12:55:06 -07:00
return ErrorWithContext ( ctx , err )
2021-11-02 17:59:52 -07:00
}
return nil
}
func restoreObject ( ctx context . Context , client MinioClient , bucketName , prefix , versionID string ) error {
// Select required version
srcOpts := minio . CopySrcOptions {
Bucket : bucketName ,
Object : prefix ,
VersionID : versionID ,
}
// Destination object, same as current bucket
replaceMetadata := make ( map [ string ] string )
replaceMetadata [ "copy-source" ] = versionID
dstOpts := minio . CopyDestOptions {
Bucket : bucketName ,
Object : prefix ,
UserMetadata : replaceMetadata ,
}
// Copy object call
_ , err := client . copyObject ( ctx , dstOpts , srcOpts )
if err != nil {
return err
}
return nil
}
2021-12-15 10:50:34 -06:00
// Metadata Response from minio-go API
2023-08-16 14:18:08 -07:00
func getObjectMetadataResponse ( session * models . Principal , params objectApi . GetObjectMetadataParams ) ( * models . Metadata , * CodedAPIError ) {
2022-04-28 12:55:06 -07:00
ctx := params . HTTPRequest . Context ( )
2023-06-14 12:36:48 -07:00
mClient , err := newMinioClient ( session , getClientIP ( params . HTTPRequest ) )
2021-12-15 10:50:34 -06:00
if err != nil {
2022-04-28 12:55:06 -07:00
return nil , ErrorWithContext ( ctx , err )
2021-12-15 10:50:34 -06:00
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient { client : mClient }
2024-05-06 15:38:53 -06:00
var versionID string
if params . VersionID != nil {
versionID = * params . VersionID
}
2024-06-05 23:48:27 +02:00
objectInfo , err := getObjectInfo ( ctx , minioClient , params . BucketName , params . Prefix , versionID )
2021-12-15 10:50:34 -06:00
if err != nil {
2022-04-28 12:55:06 -07:00
return nil , ErrorWithContext ( ctx , err )
2021-12-15 10:50:34 -06:00
}
metadata := & models . Metadata { ObjectMetadata : objectInfo . Metadata }
return metadata , nil
}
2024-05-06 15:38:53 -06:00
func getObjectInfo ( ctx context . Context , client MinioClient , bucketName , prefix , versionID string ) ( minio . ObjectInfo , error ) {
objectData , err := client . statObject ( ctx , bucketName , prefix , minio . GetObjectOptions { VersionID : versionID } )
2021-12-15 10:50:34 -06:00
if err != nil {
return minio . ObjectInfo { } , err
}
return objectData , nil
}
2020-10-01 17:00:32 -07:00
// newClientURL returns an abstracted URL for filesystems and object storage.
func newClientURL ( urlStr string ) * mc . ClientURL {
scheme , rest := getScheme ( urlStr )
if strings . HasPrefix ( rest , "//" ) {
// if rest has '//' prefix, skip them
var authority string
authority , rest = splitSpecial ( rest [ 2 : ] , "/" , false )
if rest == "" {
rest = "/"
}
host := getHost ( authority )
if host != "" && ( scheme == "http" || scheme == "https" ) {
return & mc . ClientURL {
Scheme : scheme ,
Type : objectStorage ,
Host : host ,
Path : rest ,
SchemeSeparator : "://" ,
Separator : '/' ,
}
}
}
return & mc . ClientURL {
Type : fileSystem ,
Path : rest ,
Separator : filepath . Separator ,
}
}
// Maybe rawurl is of the form scheme:path. (Scheme must be [a-zA-Z][a-zA-Z0-9+-.]*)
// If so, return scheme, path; else return "", rawurl.
func getScheme ( rawurl string ) ( scheme , path string ) {
urlSplits := strings . Split ( rawurl , "://" )
if len ( urlSplits ) == 2 {
scheme , uri := urlSplits [ 0 ] , "//" + urlSplits [ 1 ]
// ignore numbers in scheme
validScheme := regexp . MustCompile ( "^[a-zA-Z]+$" )
if uri != "" {
if validScheme . MatchString ( scheme ) {
return scheme , uri
}
}
}
return "" , rawurl
}
// Assuming s is of the form [s delimiter s].
// If so, return s, [delimiter]s or return s, s if cutdelimiter == true
// If no delimiter found return s, "".
func splitSpecial ( s string , delimiter string , cutdelimiter bool ) ( string , string ) {
i := strings . Index ( s , delimiter )
if i < 0 {
// if delimiter not found return as is.
return s , ""
}
// if delimiter should be removed, remove it.
if cutdelimiter {
return s [ 0 : i ] , s [ i + len ( delimiter ) : ]
}
// return split strings with delimiter
return s [ 0 : i ] , s [ i : ]
}
// getHost - extract host from authority string, we do not support ftp style username@ yet.
func getHost ( authority string ) ( host string ) {
i := strings . LastIndex ( authority , "@" )
if i >= 0 {
// TODO support, username@password style userinfo, useful for ftp support.
return
}
return authority
}