mirror of
https://github.com/minio/minio.git
synced 2024-12-25 22:55:54 -05:00
CompleteMultipartUpload and CreateObjectPart now fully support signature v4
This commit is contained in:
parent
89c1215194
commit
00890c254e
@ -87,6 +87,24 @@ type PartMetadata struct {
|
|||||||
Size int64
|
Size int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CompletePart - completed part container
|
||||||
|
type CompletePart struct {
|
||||||
|
PartNumber int
|
||||||
|
ETag string
|
||||||
|
}
|
||||||
|
|
||||||
|
// completedParts is a sortable interface for Part slice
|
||||||
|
type completedParts []CompletePart
|
||||||
|
|
||||||
|
func (a completedParts) Len() int { return len(a) }
|
||||||
|
func (a completedParts) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a completedParts) Less(i, j int) bool { return a[i].PartNumber < a[j].PartNumber }
|
||||||
|
|
||||||
|
// CompleteMultipartUpload container for completing multipart upload
|
||||||
|
type CompleteMultipartUpload struct {
|
||||||
|
Part []CompletePart
|
||||||
|
}
|
||||||
|
|
||||||
// ObjectResourcesMetadata - various types of object resources
|
// ObjectResourcesMetadata - various types of object resources
|
||||||
type ObjectResourcesMetadata struct {
|
type ObjectResourcesMetadata struct {
|
||||||
Bucket string
|
Bucket string
|
||||||
|
@ -371,11 +371,6 @@ func (donut API) createObject(bucket, key, contentType, expectedMD5Sum string, s
|
|||||||
var length int
|
var length int
|
||||||
byteBuffer := make([]byte, 1024*1024)
|
byteBuffer := make([]byte, 1024*1024)
|
||||||
length, err = data.Read(byteBuffer)
|
length, err = data.Read(byteBuffer)
|
||||||
// While hash.Write() wouldn't mind a Nil byteBuffer
|
|
||||||
// It is necessary for us to verify this and break
|
|
||||||
if length == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
hash.Write(byteBuffer[0:length])
|
hash.Write(byteBuffer[0:length])
|
||||||
sha256hash.Write(byteBuffer[0:length])
|
sha256hash.Write(byteBuffer[0:length])
|
||||||
ok := donut.objects.Append(objectKey, byteBuffer[0:length])
|
ok := donut.objects.Append(objectKey, byteBuffer[0:length])
|
||||||
|
@ -334,3 +334,19 @@ type MissingDateHeader struct{}
|
|||||||
func (e MissingDateHeader) Error() string {
|
func (e MissingDateHeader) Error() string {
|
||||||
return "Missing date header"
|
return "Missing date header"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InvalidPartOrder parts are not ordered as Requested
|
||||||
|
type InvalidPartOrder struct {
|
||||||
|
UploadID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e InvalidPartOrder) Error() string {
|
||||||
|
return "Invalid part order sent for " + e.UploadID
|
||||||
|
}
|
||||||
|
|
||||||
|
// MalformedXML invalid xml format
|
||||||
|
type MalformedXML struct{}
|
||||||
|
|
||||||
|
func (e MalformedXML) Error() string {
|
||||||
|
return "Malformed XML"
|
||||||
|
}
|
||||||
|
@ -51,8 +51,8 @@ type ObjectStorage interface {
|
|||||||
type Multipart interface {
|
type Multipart interface {
|
||||||
NewMultipartUpload(bucket, key, contentType string) (string, error)
|
NewMultipartUpload(bucket, key, contentType string) (string, error)
|
||||||
AbortMultipartUpload(bucket, key, uploadID string) error
|
AbortMultipartUpload(bucket, key, uploadID string) error
|
||||||
CreateObjectPart(bucket, key, uploadID string, partID int, contentType, expectedMD5Sum string, size int64, data io.Reader) (string, error)
|
CreateObjectPart(string, string, string, int, string, string, int64, io.Reader, *Signature) (string, error)
|
||||||
CompleteMultipartUpload(bucket, key, uploadID string, parts map[int]string) (ObjectMetadata, error)
|
CompleteMultipartUpload(bucket, key, uploadID string, data io.Reader, signature *Signature) (ObjectMetadata, error)
|
||||||
ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, error)
|
ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, error)
|
||||||
ListObjectParts(bucket, key string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, error)
|
ListObjectParts(bucket, key string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, error)
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,10 @@ import (
|
|||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sort"
|
"sort"
|
||||||
@ -31,6 +33,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio/pkg/crypto/sha256"
|
||||||
"github.com/minio/minio/pkg/donut/cache/data"
|
"github.com/minio/minio/pkg/donut/cache/data"
|
||||||
"github.com/minio/minio/pkg/iodine"
|
"github.com/minio/minio/pkg/iodine"
|
||||||
)
|
)
|
||||||
@ -90,11 +93,11 @@ func (donut API) AbortMultipartUpload(bucket, key, uploadID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateObjectPart - create a part in a multipart session
|
// CreateObjectPart - create a part in a multipart session
|
||||||
func (donut API) CreateObjectPart(bucket, key, uploadID string, partID int, contentType, expectedMD5Sum string, size int64, data io.Reader) (string, error) {
|
func (donut API) CreateObjectPart(bucket, key, uploadID string, partID int, contentType, expectedMD5Sum string, size int64, data io.Reader, signature *Signature) (string, error) {
|
||||||
donut.lock.Lock()
|
donut.lock.Lock()
|
||||||
defer donut.lock.Unlock()
|
defer donut.lock.Unlock()
|
||||||
|
|
||||||
etag, err := donut.createObjectPart(bucket, key, uploadID, partID, "", expectedMD5Sum, size, data)
|
etag, err := donut.createObjectPart(bucket, key, uploadID, partID, "", expectedMD5Sum, size, data, signature)
|
||||||
// possible free
|
// possible free
|
||||||
debug.FreeOSMemory()
|
debug.FreeOSMemory()
|
||||||
|
|
||||||
@ -102,7 +105,7 @@ func (donut API) CreateObjectPart(bucket, key, uploadID string, partID int, cont
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createObject - internal wrapper function called by CreateObjectPart
|
// createObject - internal wrapper function called by CreateObjectPart
|
||||||
func (donut API) createObjectPart(bucket, key, uploadID string, partID int, contentType, expectedMD5Sum string, size int64, data io.Reader) (string, error) {
|
func (donut API) createObjectPart(bucket, key, uploadID string, partID int, contentType, expectedMD5Sum string, size int64, data io.Reader, signature *Signature) (string, error) {
|
||||||
if !IsValidBucket(bucket) {
|
if !IsValidBucket(bucket) {
|
||||||
return "", iodine.New(BucketNameInvalid{Bucket: bucket}, nil)
|
return "", iodine.New(BucketNameInvalid{Bucket: bucket}, nil)
|
||||||
}
|
}
|
||||||
@ -138,6 +141,7 @@ func (donut API) createObjectPart(bucket, key, uploadID string, partID int, cont
|
|||||||
|
|
||||||
// calculate md5
|
// calculate md5
|
||||||
hash := md5.New()
|
hash := md5.New()
|
||||||
|
sha256hash := sha256.New()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var totalLength int64
|
var totalLength int64
|
||||||
@ -145,12 +149,8 @@ func (donut API) createObjectPart(bucket, key, uploadID string, partID int, cont
|
|||||||
var length int
|
var length int
|
||||||
byteBuffer := make([]byte, 1024*1024)
|
byteBuffer := make([]byte, 1024*1024)
|
||||||
length, err = data.Read(byteBuffer)
|
length, err = data.Read(byteBuffer)
|
||||||
// While hash.Write() wouldn't mind a Nil byteBuffer
|
|
||||||
// It is necessary for us to verify this and break
|
|
||||||
if length == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
hash.Write(byteBuffer[0:length])
|
hash.Write(byteBuffer[0:length])
|
||||||
|
sha256hash.Write(byteBuffer[0:length])
|
||||||
ok := donut.multiPartObjects[uploadID].Append(partID, byteBuffer[0:length])
|
ok := donut.multiPartObjects[uploadID].Append(partID, byteBuffer[0:length])
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", iodine.New(InternalError{}, nil)
|
return "", iodine.New(InternalError{}, nil)
|
||||||
@ -174,6 +174,17 @@ func (donut API) createObjectPart(bucket, key, uploadID string, partID int, cont
|
|||||||
return "", iodine.New(BadDigest{}, nil)
|
return "", iodine.New(BadDigest{}, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if signature != nil {
|
||||||
|
ok, err := signature.DoesSignatureMatch(hex.EncodeToString(sha256hash.Sum(nil)))
|
||||||
|
if err != nil {
|
||||||
|
return "", iodine.New(err, nil)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return "", iodine.New(SignatureDoesNotMatch{}, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newPart := PartMetadata{
|
newPart := PartMetadata{
|
||||||
PartNumber: partID,
|
PartNumber: partID,
|
||||||
LastModified: time.Now().UTC(),
|
LastModified: time.Now().UTC(),
|
||||||
@ -200,7 +211,7 @@ func (donut API) cleanupMultipartSession(bucket, key, uploadID string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CompleteMultipartUpload - complete a multipart upload and persist the data
|
// CompleteMultipartUpload - complete a multipart upload and persist the data
|
||||||
func (donut API) CompleteMultipartUpload(bucket, key, uploadID string, parts map[int]string) (ObjectMetadata, error) {
|
func (donut API) CompleteMultipartUpload(bucket, key, uploadID string, data io.Reader, signature *Signature) (ObjectMetadata, error) {
|
||||||
donut.lock.Lock()
|
donut.lock.Lock()
|
||||||
|
|
||||||
if !IsValidBucket(bucket) {
|
if !IsValidBucket(bucket) {
|
||||||
@ -221,11 +232,36 @@ func (donut API) CompleteMultipartUpload(bucket, key, uploadID string, parts map
|
|||||||
donut.lock.Unlock()
|
donut.lock.Unlock()
|
||||||
return ObjectMetadata{}, iodine.New(InvalidUploadID{UploadID: uploadID}, nil)
|
return ObjectMetadata{}, iodine.New(InvalidUploadID{UploadID: uploadID}, nil)
|
||||||
}
|
}
|
||||||
|
partBytes, err := ioutil.ReadAll(data)
|
||||||
|
if err != nil {
|
||||||
|
donut.lock.Unlock()
|
||||||
|
return ObjectMetadata{}, iodine.New(err, nil)
|
||||||
|
}
|
||||||
|
if signature != nil {
|
||||||
|
ok, err := signature.DoesSignatureMatch(hex.EncodeToString(sha256.Sum256(partBytes)[:]))
|
||||||
|
if err != nil {
|
||||||
|
donut.lock.Unlock()
|
||||||
|
return ObjectMetadata{}, iodine.New(err, nil)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
donut.lock.Unlock()
|
||||||
|
return ObjectMetadata{}, iodine.New(SignatureDoesNotMatch{}, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parts := &CompleteMultipartUpload{}
|
||||||
|
if err := xml.Unmarshal(partBytes, parts); err != nil {
|
||||||
|
donut.lock.Unlock()
|
||||||
|
return ObjectMetadata{}, iodine.New(MalformedXML{}, nil)
|
||||||
|
}
|
||||||
|
if !sort.IsSorted(completedParts(parts.Part)) {
|
||||||
|
donut.lock.Unlock()
|
||||||
|
return ObjectMetadata{}, iodine.New(InvalidPartOrder{}, nil)
|
||||||
|
}
|
||||||
var size int64
|
var size int64
|
||||||
var fullObject bytes.Buffer
|
var fullObject bytes.Buffer
|
||||||
for i := 1; i <= len(parts); i++ {
|
for i := 1; i <= len(parts.Part); i++ {
|
||||||
recvMD5 := parts[i]
|
recvMD5 := parts.Part[i-1].ETag
|
||||||
object, ok := donut.multiPartObjects[uploadID].Get(i)
|
object, ok := donut.multiPartObjects[uploadID].Get(parts.Part[i-1].PartNumber)
|
||||||
if ok == false {
|
if ok == false {
|
||||||
donut.lock.Unlock()
|
donut.lock.Unlock()
|
||||||
return ObjectMetadata{}, iodine.New(errors.New("missing part: "+strconv.Itoa(i)), nil)
|
return ObjectMetadata{}, iodine.New(errors.New("missing part: "+strconv.Itoa(i)), nil)
|
||||||
|
@ -100,7 +100,7 @@ func (api Minio) ListMultipartUploadsHandler(w http.ResponseWriter, req *http.Re
|
|||||||
case nil: // success
|
case nil: // success
|
||||||
{
|
{
|
||||||
// generate response
|
// generate response
|
||||||
response := generateListMultipartUploadsResult(bucket, resources)
|
response := generateListMultipartUploadsResponse(bucket, resources)
|
||||||
encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
|
encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
|
||||||
// write headers
|
// write headers
|
||||||
setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse))
|
setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse))
|
||||||
|
@ -62,6 +62,14 @@ type ListObjectsResponse struct {
|
|||||||
Prefix string
|
Prefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Part container for part metadata
|
||||||
|
type Part struct {
|
||||||
|
PartNumber int
|
||||||
|
ETag string
|
||||||
|
LastModified string
|
||||||
|
Size int64
|
||||||
|
}
|
||||||
|
|
||||||
// ListPartsResponse - format for list parts response
|
// ListPartsResponse - format for list parts response
|
||||||
type ListPartsResponse struct {
|
type ListPartsResponse struct {
|
||||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListPartsResult" json:"-"`
|
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListPartsResult" json:"-"`
|
||||||
@ -134,14 +142,6 @@ type Bucket struct {
|
|||||||
CreationDate string
|
CreationDate string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Part container for part metadata
|
|
||||||
type Part struct {
|
|
||||||
PartNumber int
|
|
||||||
ETag string
|
|
||||||
LastModified string
|
|
||||||
Size int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Object container for object metadata
|
// Object container for object metadata
|
||||||
type Object struct {
|
type Object struct {
|
||||||
ETag string
|
ETag string
|
||||||
@ -164,8 +164,8 @@ type Owner struct {
|
|||||||
DisplayName string
|
DisplayName string
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitiateMultipartUploadResult container for InitiateMultiPartUpload response, provides uploadID to start MultiPart upload
|
// InitiateMultipartUploadResponse container for InitiateMultiPartUpload response, provides uploadID to start MultiPart upload
|
||||||
type InitiateMultipartUploadResult struct {
|
type InitiateMultipartUploadResponse struct {
|
||||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ InitiateMultipartUploadResult" json:"-"`
|
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ InitiateMultipartUploadResult" json:"-"`
|
||||||
|
|
||||||
Bucket string
|
Bucket string
|
||||||
@ -173,20 +173,8 @@ type InitiateMultipartUploadResult struct {
|
|||||||
UploadID string `xml:"UploadId"`
|
UploadID string `xml:"UploadId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// completedParts is a sortable interface for Part slice
|
// CompleteMultipartUploadResponse container for completed multipart upload response
|
||||||
type completedParts []Part
|
type CompleteMultipartUploadResponse struct {
|
||||||
|
|
||||||
func (a completedParts) Len() int { return len(a) }
|
|
||||||
func (a completedParts) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
func (a completedParts) Less(i, j int) bool { return a[i].PartNumber < a[j].PartNumber }
|
|
||||||
|
|
||||||
// CompleteMultipartUpload container for completing multipart upload
|
|
||||||
type CompleteMultipartUpload struct {
|
|
||||||
Part []Part
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompleteMultipartUploadResult container for completed multipart upload response
|
|
||||||
type CompleteMultipartUploadResult struct {
|
|
||||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CompleteMultipartUploadResult" json:"-"`
|
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CompleteMultipartUploadResult" json:"-"`
|
||||||
|
|
||||||
Location string
|
Location string
|
||||||
|
@ -18,11 +18,8 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"encoding/xml"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/minio/minio/pkg/donut"
|
"github.com/minio/minio/pkg/donut"
|
||||||
"github.com/minio/minio/pkg/iodine"
|
"github.com/minio/minio/pkg/iodine"
|
||||||
@ -277,7 +274,7 @@ func (api Minio) NewMultipartUploadHandler(w http.ResponseWriter, req *http.Requ
|
|||||||
switch iodine.ToError(err).(type) {
|
switch iodine.ToError(err).(type) {
|
||||||
case nil:
|
case nil:
|
||||||
{
|
{
|
||||||
response := generateInitiateMultipartUploadResult(bucket, object, uploadID)
|
response := generateInitiateMultipartUploadResponse(bucket, object, uploadID)
|
||||||
encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
|
encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
|
||||||
// write headers
|
// write headers
|
||||||
setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse))
|
setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse))
|
||||||
@ -346,7 +343,18 @@ func (api Minio) PutObjectPartHandler(w http.ResponseWriter, req *http.Request)
|
|||||||
writeErrorResponse(w, req, InvalidPart, acceptsContentType, req.URL.Path)
|
writeErrorResponse(w, req, InvalidPart, acceptsContentType, req.URL.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedMD5, err := api.Donut.CreateObjectPart(bucket, object, uploadID, partID, "", md5, sizeInt64, req.Body)
|
var signature *donut.Signature
|
||||||
|
if _, ok := req.Header["Authorization"]; ok {
|
||||||
|
// Init signature V4 verification
|
||||||
|
var err error
|
||||||
|
signature, err = InitSignatureV4(req)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
calculatedMD5, err := api.Donut.CreateObjectPart(bucket, object, uploadID, partID, "", md5, sizeInt64, req.Body, signature)
|
||||||
switch iodine.ToError(err).(type) {
|
switch iodine.ToError(err).(type) {
|
||||||
case nil:
|
case nil:
|
||||||
w.Header().Set("ETag", calculatedMD5)
|
w.Header().Set("ETag", calculatedMD5)
|
||||||
@ -357,6 +365,8 @@ func (api Minio) PutObjectPartHandler(w http.ResponseWriter, req *http.Request)
|
|||||||
writeErrorResponse(w, req, MethodNotAllowed, acceptsContentType, req.URL.Path)
|
writeErrorResponse(w, req, MethodNotAllowed, acceptsContentType, req.URL.Path)
|
||||||
case donut.BadDigest:
|
case donut.BadDigest:
|
||||||
writeErrorResponse(w, req, BadDigest, acceptsContentType, req.URL.Path)
|
writeErrorResponse(w, req, BadDigest, acceptsContentType, req.URL.Path)
|
||||||
|
case donut.SignatureDoesNotMatch:
|
||||||
|
writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path)
|
||||||
case donut.IncompleteBody:
|
case donut.IncompleteBody:
|
||||||
writeErrorResponse(w, req, IncompleteBody, acceptsContentType, req.URL.Path)
|
writeErrorResponse(w, req, IncompleteBody, acceptsContentType, req.URL.Path)
|
||||||
case donut.EntityTooLarge:
|
case donut.EntityTooLarge:
|
||||||
@ -433,7 +443,7 @@ func (api Minio) ListObjectPartsHandler(w http.ResponseWriter, req *http.Request
|
|||||||
switch iodine.ToError(err).(type) {
|
switch iodine.ToError(err).(type) {
|
||||||
case nil:
|
case nil:
|
||||||
{
|
{
|
||||||
response := generateListPartsResult(objectResourcesMetadata)
|
response := generateListPartsResponse(objectResourcesMetadata)
|
||||||
encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
|
encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
|
||||||
// write headers
|
// write headers
|
||||||
setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse))
|
setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse))
|
||||||
@ -464,35 +474,27 @@ func (api Minio) CompleteMultipartUploadHandler(w http.ResponseWriter, req *http
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
decoder := xml.NewDecoder(req.Body)
|
|
||||||
parts := &CompleteMultipartUpload{}
|
|
||||||
err := decoder.Decode(parts)
|
|
||||||
if err != nil {
|
|
||||||
log.Error.Println(iodine.New(err, nil))
|
|
||||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !sort.IsSorted(completedParts(parts.Part)) {
|
|
||||||
writeErrorResponse(w, req, InvalidPartOrder, acceptsContentType, req.URL.Path)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := mux.Vars(req)
|
vars := mux.Vars(req)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object := vars["object"]
|
object := vars["object"]
|
||||||
|
|
||||||
objectResourcesMetadata := getObjectResources(req.URL.Query())
|
objectResourcesMetadata := getObjectResources(req.URL.Query())
|
||||||
|
|
||||||
partMap := make(map[int]string)
|
var signature *donut.Signature
|
||||||
for _, part := range parts.Part {
|
if _, ok := req.Header["Authorization"]; ok {
|
||||||
partMap[part.PartNumber] = part.ETag
|
// Init signature V4 verification
|
||||||
|
var err error
|
||||||
|
signature, err = InitSignatureV4(req)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
metadata, err := api.Donut.CompleteMultipartUpload(bucket, object, objectResourcesMetadata.UploadID, req.Body, signature)
|
||||||
metadata, err := api.Donut.CompleteMultipartUpload(bucket, object, objectResourcesMetadata.UploadID, partMap)
|
|
||||||
switch iodine.ToError(err).(type) {
|
switch iodine.ToError(err).(type) {
|
||||||
case nil:
|
case nil:
|
||||||
{
|
{
|
||||||
response := generateCompleteMultpartUploadResult(bucket, object, "", metadata.MD5Sum)
|
response := generateCompleteMultpartUploadResponse(bucket, object, "", metadata.MD5Sum)
|
||||||
encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
|
encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
|
||||||
// write headers
|
// write headers
|
||||||
setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse))
|
setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse))
|
||||||
@ -501,6 +503,16 @@ func (api Minio) CompleteMultipartUploadHandler(w http.ResponseWriter, req *http
|
|||||||
}
|
}
|
||||||
case donut.InvalidUploadID:
|
case donut.InvalidUploadID:
|
||||||
writeErrorResponse(w, req, NoSuchUpload, acceptsContentType, req.URL.Path)
|
writeErrorResponse(w, req, NoSuchUpload, acceptsContentType, req.URL.Path)
|
||||||
|
case donut.InvalidPartOrder:
|
||||||
|
writeErrorResponse(w, req, InvalidPartOrder, acceptsContentType, req.URL.Path)
|
||||||
|
case donut.MissingDateHeader:
|
||||||
|
writeErrorResponse(w, req, RequestTimeTooSkewed, acceptsContentType, req.URL.Path)
|
||||||
|
case donut.SignatureDoesNotMatch:
|
||||||
|
writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path)
|
||||||
|
case donut.IncompleteBody:
|
||||||
|
writeErrorResponse(w, req, IncompleteBody, acceptsContentType, req.URL.Path)
|
||||||
|
case donut.MalformedXML:
|
||||||
|
writeErrorResponse(w, req, MalformedXML, acceptsContentType, req.URL.Path)
|
||||||
default:
|
default:
|
||||||
log.Error.Println(iodine.New(err, nil))
|
log.Error.Println(iodine.New(err, nil))
|
||||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||||
|
@ -111,18 +111,18 @@ func generateListObjectsResponse(bucket string, objects []donut.ObjectMetadata,
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateInitiateMultipartUploadResult
|
// generateInitiateMultipartUploadResponse
|
||||||
func generateInitiateMultipartUploadResult(bucket, key, uploadID string) InitiateMultipartUploadResult {
|
func generateInitiateMultipartUploadResponse(bucket, key, uploadID string) InitiateMultipartUploadResponse {
|
||||||
return InitiateMultipartUploadResult{
|
return InitiateMultipartUploadResponse{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Key: key,
|
Key: key,
|
||||||
UploadID: uploadID,
|
UploadID: uploadID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateCompleteMultipartUploadResult
|
// generateCompleteMultipartUploadResponse
|
||||||
func generateCompleteMultpartUploadResult(bucket, key, location, etag string) CompleteMultipartUploadResult {
|
func generateCompleteMultpartUploadResponse(bucket, key, location, etag string) CompleteMultipartUploadResponse {
|
||||||
return CompleteMultipartUploadResult{
|
return CompleteMultipartUploadResponse{
|
||||||
Location: location,
|
Location: location,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Key: key,
|
Key: key,
|
||||||
@ -131,7 +131,7 @@ func generateCompleteMultpartUploadResult(bucket, key, location, etag string) Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generateListPartsResult
|
// generateListPartsResult
|
||||||
func generateListPartsResult(objectMetadata donut.ObjectResourcesMetadata) ListPartsResponse {
|
func generateListPartsResponse(objectMetadata donut.ObjectResourcesMetadata) ListPartsResponse {
|
||||||
// TODO - support EncodingType in xml decoding
|
// TODO - support EncodingType in xml decoding
|
||||||
listPartsResponse := ListPartsResponse{}
|
listPartsResponse := ListPartsResponse{}
|
||||||
listPartsResponse.Bucket = objectMetadata.Bucket
|
listPartsResponse.Bucket = objectMetadata.Bucket
|
||||||
@ -160,8 +160,8 @@ func generateListPartsResult(objectMetadata donut.ObjectResourcesMetadata) ListP
|
|||||||
return listPartsResponse
|
return listPartsResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateListMultipartUploadsResult
|
// generateListMultipartUploadsResponse
|
||||||
func generateListMultipartUploadsResult(bucket string, metadata donut.BucketMultipartResourcesMetadata) ListMultipartUploadsResponse {
|
func generateListMultipartUploadsResponse(bucket string, metadata donut.BucketMultipartResourcesMetadata) ListMultipartUploadsResponse {
|
||||||
listMultipartUploadsResponse := ListMultipartUploadsResponse{}
|
listMultipartUploadsResponse := ListMultipartUploadsResponse{}
|
||||||
listMultipartUploadsResponse.Bucket = bucket
|
listMultipartUploadsResponse.Bucket = bucket
|
||||||
listMultipartUploadsResponse.Delimiter = metadata.Delimiter
|
listMultipartUploadsResponse.Delimiter = metadata.Delimiter
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
|
||||||
. "github.com/minio/check"
|
. "github.com/minio/check"
|
||||||
|
"github.com/minio/minio/pkg/donut"
|
||||||
"github.com/minio/minio/pkg/server/api"
|
"github.com/minio/minio/pkg/server/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -627,7 +628,7 @@ func (s *MyAPIDonutCacheSuite) TestObjectMultipartAbort(c *C) {
|
|||||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||||
|
|
||||||
decoder := xml.NewDecoder(response.Body)
|
decoder := xml.NewDecoder(response.Body)
|
||||||
newResponse := &api.InitiateMultipartUploadResult{}
|
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||||
|
|
||||||
err = decoder.Decode(newResponse)
|
err = decoder.Decode(newResponse)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
@ -672,7 +673,7 @@ func (s *MyAPIDonutCacheSuite) TestBucketMultipartList(c *C) {
|
|||||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||||
|
|
||||||
decoder := xml.NewDecoder(response.Body)
|
decoder := xml.NewDecoder(response.Body)
|
||||||
newResponse := &api.InitiateMultipartUploadResult{}
|
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||||
|
|
||||||
err = decoder.Decode(newResponse)
|
err = decoder.Decode(newResponse)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
@ -723,7 +724,7 @@ func (s *MyAPIDonutCacheSuite) TestObjectMultipartList(c *C) {
|
|||||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||||
|
|
||||||
decoder := xml.NewDecoder(response.Body)
|
decoder := xml.NewDecoder(response.Body)
|
||||||
newResponse := &api.InitiateMultipartUploadResult{}
|
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||||
|
|
||||||
err = decoder.Decode(newResponse)
|
err = decoder.Decode(newResponse)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
@ -771,7 +772,7 @@ func (s *MyAPIDonutCacheSuite) TestObjectMultipart(c *C) {
|
|||||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||||
|
|
||||||
decoder := xml.NewDecoder(response.Body)
|
decoder := xml.NewDecoder(response.Body)
|
||||||
newResponse := &api.InitiateMultipartUploadResult{}
|
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||||
|
|
||||||
err = decoder.Decode(newResponse)
|
err = decoder.Decode(newResponse)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
@ -795,8 +796,8 @@ func (s *MyAPIDonutCacheSuite) TestObjectMultipart(c *C) {
|
|||||||
c.Assert(response2.StatusCode, Equals, http.StatusOK)
|
c.Assert(response2.StatusCode, Equals, http.StatusOK)
|
||||||
|
|
||||||
// complete multipart upload
|
// complete multipart upload
|
||||||
completeUploads := &api.CompleteMultipartUpload{
|
completeUploads := &donut.CompleteMultipartUpload{
|
||||||
Part: []api.Part{
|
Part: []donut.CompletePart{
|
||||||
{
|
{
|
||||||
PartNumber: 1,
|
PartNumber: 1,
|
||||||
ETag: response1.Header.Get("ETag"),
|
ETag: response1.Header.Get("ETag"),
|
||||||
|
@ -664,7 +664,7 @@ func (s *MyAPIDonutSuite) TestObjectMultipartAbort(c *C) {
|
|||||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||||
|
|
||||||
decoder := xml.NewDecoder(response.Body)
|
decoder := xml.NewDecoder(response.Body)
|
||||||
newResponse := &api.InitiateMultipartUploadResult{}
|
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||||
|
|
||||||
err = decoder.Decode(newResponse)
|
err = decoder.Decode(newResponse)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
@ -709,7 +709,7 @@ func (s *MyAPIDonutSuite) TestBucketMultipartList(c *C) {
|
|||||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||||
|
|
||||||
decoder := xml.NewDecoder(response.Body)
|
decoder := xml.NewDecoder(response.Body)
|
||||||
newResponse := &api.InitiateMultipartUploadResult{}
|
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||||
|
|
||||||
err = decoder.Decode(newResponse)
|
err = decoder.Decode(newResponse)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
@ -760,7 +760,7 @@ func (s *MyAPIDonutSuite) TestObjectMultipartList(c *C) {
|
|||||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||||
|
|
||||||
decoder := xml.NewDecoder(response.Body)
|
decoder := xml.NewDecoder(response.Body)
|
||||||
newResponse := &api.InitiateMultipartUploadResult{}
|
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||||
|
|
||||||
err = decoder.Decode(newResponse)
|
err = decoder.Decode(newResponse)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
@ -808,7 +808,7 @@ func (s *MyAPIDonutSuite) TestObjectMultipart(c *C) {
|
|||||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||||
|
|
||||||
decoder := xml.NewDecoder(response.Body)
|
decoder := xml.NewDecoder(response.Body)
|
||||||
newResponse := &api.InitiateMultipartUploadResult{}
|
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||||
|
|
||||||
err = decoder.Decode(newResponse)
|
err = decoder.Decode(newResponse)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
@ -832,8 +832,8 @@ func (s *MyAPIDonutSuite) TestObjectMultipart(c *C) {
|
|||||||
c.Assert(response2.StatusCode, Equals, http.StatusOK)
|
c.Assert(response2.StatusCode, Equals, http.StatusOK)
|
||||||
|
|
||||||
// complete multipart upload
|
// complete multipart upload
|
||||||
completeUploads := &api.CompleteMultipartUpload{
|
completeUploads := &donut.CompleteMultipartUpload{
|
||||||
Part: []api.Part{
|
Part: []donut.CompletePart{
|
||||||
{
|
{
|
||||||
PartNumber: 1,
|
PartNumber: 1,
|
||||||
ETag: response1.Header.Get("ETag"),
|
ETag: response1.Header.Get("ETag"),
|
||||||
|
Loading…
Reference in New Issue
Block a user