mirror of
https://github.com/minio/minio.git
synced 2025-04-09 14:10:10 -04:00
feat: add support for GetObjectAttributes API (#18732)
This commit is contained in:
parent
7705605b5a
commit
9b8ba97f9f
@ -430,6 +430,9 @@ const (
|
|||||||
ErrLambdaARNInvalid
|
ErrLambdaARNInvalid
|
||||||
ErrLambdaARNNotFound
|
ErrLambdaARNNotFound
|
||||||
|
|
||||||
|
// New Codes for GetObjectAttributes and GetObjectVersionAttributes
|
||||||
|
ErrInvalidAttributeName
|
||||||
|
|
||||||
apiErrCodeEnd // This is used only for the testing code
|
apiErrCodeEnd // This is used only for the testing code
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2063,6 +2066,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The specified policy is not found.",
|
Description: "The specified policy is not found.",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
|
ErrInvalidAttributeName: {
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
Description: "Invalid attribute name specified.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
// Add your error structure here.
|
// Add your error structure here.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,6 +227,9 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
// HeadObject
|
// HeadObject
|
||||||
router.Methods(http.MethodHead).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodHead).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("headobject", maxClients(gz(httpTraceAll(api.HeadObjectHandler)))))
|
collectAPIStats("headobject", maxClients(gz(httpTraceAll(api.HeadObjectHandler)))))
|
||||||
|
// GetObjectAttribytes
|
||||||
|
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
||||||
|
collectAPIStats("getobjectattributes", maxClients(gz(httpTraceHdrs(api.GetObjectAttributesHandler))))).Queries("attributes", "")
|
||||||
// CopyObjectPart
|
// CopyObjectPart
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").
|
router.Methods(http.MethodPut).Path("/{object:.+}").
|
||||||
HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").
|
HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1082,6 +1082,30 @@ func (o *ObjectInfo) metadataDecrypter() objectMetaDecryptFn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// decryptChecksums will attempt to decode checksums and return it/them if set.
|
||||||
|
// if part > 0, and we have the checksum for the part that will be returned.
|
||||||
|
func (o *ObjectInfo) decryptPartsChecksums() {
|
||||||
|
data := o.Checksum
|
||||||
|
if len(data) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, encrypted := crypto.IsEncrypted(o.UserDefined); encrypted {
|
||||||
|
decrypted, err := o.metadataDecrypter()("object-checksum", data)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(GlobalContext, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data = decrypted
|
||||||
|
}
|
||||||
|
cs := hash.ReadPartCheckSums(data)
|
||||||
|
if len(cs) == len(o.Parts) {
|
||||||
|
for i := range o.Parts {
|
||||||
|
o.Parts[i].Checksums = cs[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// metadataEncryptFn provides an encryption function for metadata.
|
// metadataEncryptFn provides an encryption function for metadata.
|
||||||
// Will return nil, nil if unencrypted.
|
// Will return nil, nil if unencrypted.
|
||||||
func (o *ObjectInfo) metadataEncryptFn(headers http.Header) (objectMetaEncryptFn, error) {
|
func (o *ObjectInfo) metadataEncryptFn(headers http.Header) (objectMetaEncryptFn, error) {
|
||||||
|
@ -612,3 +612,42 @@ type NewMultipartUploadResult struct {
|
|||||||
UploadID string
|
UploadID string
|
||||||
ChecksumAlgo string
|
ChecksumAlgo string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type getObjectAttributesResponse struct {
|
||||||
|
ETag string `xml:",omitempty"`
|
||||||
|
Checksum *objectAttributesChecksum `xml:",omitempty"`
|
||||||
|
ObjectParts *objectAttributesParts `xml:",omitempty"`
|
||||||
|
StorageClass string `xml:",omitempty"`
|
||||||
|
ObjectSize int64 `xml:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type objectAttributesChecksum struct {
|
||||||
|
ChecksumCRC32 string `xml:",omitempty"`
|
||||||
|
ChecksumCRC32C string `xml:",omitempty"`
|
||||||
|
ChecksumSHA1 string `xml:",omitempty"`
|
||||||
|
ChecksumSHA256 string `xml:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type objectAttributesParts struct {
|
||||||
|
IsTruncated bool
|
||||||
|
MaxParts int
|
||||||
|
NextPartNumberMarker int
|
||||||
|
PartNumberMarker int
|
||||||
|
PartsCount int
|
||||||
|
Parts []*objectAttributesPart `xml:"Part"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type objectAttributesPart struct {
|
||||||
|
PartNumber int
|
||||||
|
Size int64
|
||||||
|
ChecksumCRC32 string `xml:",omitempty"`
|
||||||
|
ChecksumCRC32C string `xml:",omitempty"`
|
||||||
|
ChecksumSHA1 string `xml:",omitempty"`
|
||||||
|
ChecksumSHA256 string `xml:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type objectAttributesErrorResponse struct {
|
||||||
|
ArgumentValue *string `xml:"ArgumentValue,omitempty"`
|
||||||
|
ArgumentName *string `xml:"ArgumentName"`
|
||||||
|
APIErrorResponse
|
||||||
|
}
|
||||||
|
@ -69,6 +69,9 @@ type ObjectOptions struct {
|
|||||||
Tagging bool // Is only in GET/HEAD operations to return tagging metadata along with regular metadata and body.
|
Tagging bool // Is only in GET/HEAD operations to return tagging metadata along with regular metadata and body.
|
||||||
|
|
||||||
UserDefined map[string]string // only set in case of POST/PUT operations
|
UserDefined map[string]string // only set in case of POST/PUT operations
|
||||||
|
ObjectAttributes map[string]struct{} // Attribute tags defined by the users for the GetObjectAttributes request
|
||||||
|
MaxParts int // used in GetObjectAttributes. Signals how many parts we should return
|
||||||
|
PartNumberMarker int // used in GetObjectAttributes. Signals the part number after which results should be returned
|
||||||
PartNumber int // only useful in case of GetObject/HeadObject
|
PartNumber int // only useful in case of GetObject/HeadObject
|
||||||
CheckPrecondFn CheckPreconditionFn // only set during GetObject/HeadObject/CopyObjectPart preconditional valuation
|
CheckPrecondFn CheckPreconditionFn // only set during GetObject/HeadObject/CopyObjectPart preconditional valuation
|
||||||
EvalMetadataFn EvalMetadataFn // only set for retention settings, meant to be used only when updating metadata in-place.
|
EvalMetadataFn EvalMetadataFn // only set for retention settings, meant to be used only when updating metadata in-place.
|
||||||
|
@ -133,6 +133,121 @@ func getOpts(ctx context.Context, r *http.Request, bucket, object string) (Objec
|
|||||||
return opts, nil
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getAndValidateAttributesOpts(ctx context.Context, w http.ResponseWriter, r *http.Request, bucket, object string) (opts ObjectOptions, valid bool) {
|
||||||
|
var argumentName string
|
||||||
|
var argumentValue string
|
||||||
|
var apiErr APIError
|
||||||
|
var err error
|
||||||
|
valid = true
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if valid {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
errResp := objectAttributesErrorResponse{
|
||||||
|
ArgumentName: &argumentName,
|
||||||
|
ArgumentValue: &argumentValue,
|
||||||
|
APIErrorResponse: getAPIErrorResponse(
|
||||||
|
ctx,
|
||||||
|
apiErr,
|
||||||
|
r.URL.Path,
|
||||||
|
w.Header().Get(xhttp.AmzRequestID),
|
||||||
|
w.Header().Get(xhttp.AmzRequestHostID),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
writeResponse(w, apiErr.HTTPStatusCode, encodeResponse(errResp), mimeXML)
|
||||||
|
}()
|
||||||
|
|
||||||
|
opts, err = getOpts(ctx, r, bucket, object)
|
||||||
|
if err != nil {
|
||||||
|
switch vErr := err.(type) {
|
||||||
|
case InvalidVersionID:
|
||||||
|
apiErr = toAPIError(ctx, vErr)
|
||||||
|
argumentName = strings.ToLower("versionId")
|
||||||
|
argumentValue = vErr.VersionID
|
||||||
|
default:
|
||||||
|
apiErr = toAPIError(ctx, vErr)
|
||||||
|
}
|
||||||
|
valid = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.MaxParts, err = parseIntHeader(bucket, object, r.Header, xhttp.AmzMaxParts)
|
||||||
|
if err != nil {
|
||||||
|
apiErr = toAPIError(ctx, err)
|
||||||
|
argumentName = strings.ToLower(xhttp.AmzMaxParts)
|
||||||
|
valid = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.MaxParts == 0 {
|
||||||
|
opts.MaxParts = maxPartsList
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.PartNumberMarker, err = parseIntHeader(bucket, object, r.Header, xhttp.AmzPartNumberMarker)
|
||||||
|
if err != nil {
|
||||||
|
apiErr = toAPIError(ctx, err)
|
||||||
|
argumentName = strings.ToLower(xhttp.AmzPartNumberMarker)
|
||||||
|
valid = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.ObjectAttributes = parseObjectAttributes(r.Header)
|
||||||
|
if len(opts.ObjectAttributes) < 1 {
|
||||||
|
apiErr = errorCodes.ToAPIErr(ErrInvalidAttributeName)
|
||||||
|
argumentName = strings.ToLower(xhttp.AmzObjectAttributes)
|
||||||
|
valid = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for tag := range opts.ObjectAttributes {
|
||||||
|
switch tag {
|
||||||
|
case xhttp.ETag:
|
||||||
|
case xhttp.Checksum:
|
||||||
|
case xhttp.StorageClass:
|
||||||
|
case xhttp.ObjectSize:
|
||||||
|
case xhttp.ObjectParts:
|
||||||
|
default:
|
||||||
|
apiErr = errorCodes.ToAPIErr(ErrInvalidAttributeName)
|
||||||
|
argumentName = strings.ToLower(xhttp.AmzObjectAttributes)
|
||||||
|
argumentValue = tag
|
||||||
|
valid = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseObjectAttributes(h http.Header) (attributes map[string]struct{}) {
|
||||||
|
attributes = make(map[string]struct{})
|
||||||
|
for _, v := range strings.Split(strings.TrimSpace(h.Get(xhttp.AmzObjectAttributes)), ",") {
|
||||||
|
if v != "" {
|
||||||
|
attributes[v] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseIntHeader(bucket, object string, h http.Header, headerName string) (value int, err error) {
|
||||||
|
stringInt := strings.TrimSpace(h.Get(headerName))
|
||||||
|
if stringInt == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value, err = strconv.Atoi(stringInt)
|
||||||
|
if err != nil {
|
||||||
|
return 0, InvalidArgument{
|
||||||
|
Bucket: bucket,
|
||||||
|
Object: object,
|
||||||
|
Err: fmt.Errorf("Unable to parse %s, value should be an integer", headerName),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func parseBoolHeader(bucket, object string, h http.Header, headerName string) (bool, error) {
|
func parseBoolHeader(bucket, object string, h http.Header, headerName string) (bool, error) {
|
||||||
value := strings.TrimSpace(h.Get(headerName))
|
value := strings.TrimSpace(h.Get(headerName))
|
||||||
if value != "" {
|
if value != "" {
|
||||||
|
@ -689,6 +689,155 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetObjectAttributes ...
|
||||||
|
func (api objectAPIHandlers) getObjectAttributesHandler(ctx context.Context, objectAPI ObjectLayer, bucket, object string, w http.ResponseWriter, r *http.Request) {
|
||||||
|
opts, valid := getAndValidateAttributesOpts(ctx, w, r, bucket, object)
|
||||||
|
if !valid {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var s3Error APIErrorCode
|
||||||
|
if opts.VersionID != "" {
|
||||||
|
s3Error = checkRequestAuthType(ctx, r, policy.GetObjectVersionAttributesAction, bucket, object)
|
||||||
|
if s3Error == ErrNone {
|
||||||
|
s3Error = checkRequestAuthType(ctx, r, policy.GetObjectVersionAction, bucket, object)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s3Error = checkRequestAuthType(ctx, r, policy.GetObjectAttributesAction, bucket, object)
|
||||||
|
if s3Error == ErrNone {
|
||||||
|
s3Error = checkRequestAuthType(ctx, r, policy.GetObjectAction, bucket, object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s3Error != ErrNone {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
objInfo, err := objectAPI.GetObjectInfo(ctx, bucket, object, opts)
|
||||||
|
if err != nil {
|
||||||
|
s3Error = checkRequestAuthType(ctx, r, policy.ListBucketAction, bucket, object)
|
||||||
|
if s3Error == ErrNone {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = DecryptObjectInfo(&objInfo, r); err != nil {
|
||||||
|
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkPreconditions(ctx, w, r, objInfo, opts) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
OA := new(getObjectAttributesResponse)
|
||||||
|
|
||||||
|
if opts.Versioned {
|
||||||
|
w.Header().Set(xhttp.AmzVersionID, objInfo.VersionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
lastModified := objInfo.ModTime.UTC().Format(http.TimeFormat)
|
||||||
|
w.Header().Set(xhttp.LastModified, lastModified)
|
||||||
|
w.Header().Del(xhttp.ContentType)
|
||||||
|
|
||||||
|
if _, ok := opts.ObjectAttributes[xhttp.Checksum]; ok {
|
||||||
|
chkSums := objInfo.decryptChecksums(0)
|
||||||
|
// AWS does not appear to append part number on this API call.
|
||||||
|
switch {
|
||||||
|
case chkSums["CRC32"] != "":
|
||||||
|
OA.Checksum = new(objectAttributesChecksum)
|
||||||
|
OA.Checksum.ChecksumCRC32 = strings.Split(chkSums["CRC32"], "-")[0]
|
||||||
|
case chkSums["CRC32C"] != "":
|
||||||
|
OA.Checksum = new(objectAttributesChecksum)
|
||||||
|
OA.Checksum.ChecksumCRC32C = strings.Split(chkSums["CRC32C"], "-")[0]
|
||||||
|
case chkSums["SHA256"] != "":
|
||||||
|
OA.Checksum = new(objectAttributesChecksum)
|
||||||
|
OA.Checksum.ChecksumSHA1 = strings.Split(chkSums["SHA1"], "-")[0]
|
||||||
|
case chkSums["SHA1"] != "":
|
||||||
|
OA.Checksum = new(objectAttributesChecksum)
|
||||||
|
OA.Checksum.ChecksumSHA256 = strings.Split(chkSums["SHA256"], "-")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := opts.ObjectAttributes[xhttp.ETag]; ok {
|
||||||
|
OA.ETag = objInfo.ETag
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := opts.ObjectAttributes[xhttp.ObjectSize]; ok {
|
||||||
|
OA.ObjectSize, _ = objInfo.GetActualSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := opts.ObjectAttributes[xhttp.StorageClass]; ok {
|
||||||
|
OA.StorageClass = objInfo.StorageClass
|
||||||
|
}
|
||||||
|
|
||||||
|
objInfo.decryptPartsChecksums()
|
||||||
|
|
||||||
|
if _, ok := opts.ObjectAttributes[xhttp.ObjectParts]; ok {
|
||||||
|
OA.ObjectParts = new(objectAttributesParts)
|
||||||
|
OA.ObjectParts.PartNumberMarker = opts.PartNumberMarker
|
||||||
|
|
||||||
|
OA.ObjectParts.MaxParts = opts.MaxParts
|
||||||
|
partsLength := len(objInfo.Parts)
|
||||||
|
OA.ObjectParts.PartsCount = partsLength
|
||||||
|
|
||||||
|
if opts.MaxParts > -1 {
|
||||||
|
for i, v := range objInfo.Parts {
|
||||||
|
if v.Number <= opts.PartNumberMarker {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(OA.ObjectParts.Parts) == opts.MaxParts {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
OA.ObjectParts.NextPartNumberMarker = v.Number
|
||||||
|
OA.ObjectParts.Parts = append(OA.ObjectParts.Parts, &objectAttributesPart{
|
||||||
|
ChecksumSHA1: objInfo.Parts[i].Checksums["SHA1"],
|
||||||
|
ChecksumSHA256: objInfo.Parts[i].Checksums["SHA256"],
|
||||||
|
ChecksumCRC32: objInfo.Parts[i].Checksums["CRC32"],
|
||||||
|
ChecksumCRC32C: objInfo.Parts[i].Checksums["CRC32C"],
|
||||||
|
PartNumber: objInfo.Parts[i].Number,
|
||||||
|
Size: objInfo.Parts[i].Size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if OA.ObjectParts.NextPartNumberMarker != partsLength {
|
||||||
|
OA.ObjectParts.IsTruncated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outBytes, err := xml.Marshal(OA)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
writeResponse(
|
||||||
|
w,
|
||||||
|
200,
|
||||||
|
append([]byte(`<?xml version="1.0" encoding="UTF-8"?>`), outBytes...),
|
||||||
|
mimeXML,
|
||||||
|
)
|
||||||
|
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
EventName: event.ObjectAccessedAttributes,
|
||||||
|
BucketName: bucket,
|
||||||
|
Object: objInfo,
|
||||||
|
ReqParams: extractReqParams(r),
|
||||||
|
RespElements: extractRespElements(w),
|
||||||
|
UserAgent: r.UserAgent(),
|
||||||
|
Host: handlers.GetSourceIP(r),
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// GetObjectHandler - GET Object
|
// GetObjectHandler - GET Object
|
||||||
// ----------
|
// ----------
|
||||||
// This implementation of the GET operation retrieves object. To use GET,
|
// This implementation of the GET operation retrieves object. To use GET,
|
||||||
@ -1043,6 +1192,30 @@ func (api objectAPIHandlers) headObjectHandler(ctx context.Context, objectAPI Ob
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetObjectAttributesHandles - GET Object
|
||||||
|
// -----------
|
||||||
|
// This operation retrieves metadata and part metadata from an object without returning the object itself.
|
||||||
|
func (api objectAPIHandlers) GetObjectAttributesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "GetObjectAttributes")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI := api.ObjectAPI()
|
||||||
|
if objectAPI == nil {
|
||||||
|
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrServerNotInitialized))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
object, err := unescapePath(vars["object"])
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
api.getObjectAttributesHandler(ctx, objectAPI, bucket, object, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
// HeadObjectHandler - HEAD Object
|
// HeadObjectHandler - HEAD Object
|
||||||
// -----------
|
// -----------
|
||||||
// The HEAD operation retrieves metadata from an object without returning the object itself.
|
// The HEAD operation retrieves metadata from an object without returning the object itself.
|
||||||
|
@ -36,6 +36,7 @@ const (
|
|||||||
ObjectAccessedGetRetention
|
ObjectAccessedGetRetention
|
||||||
ObjectAccessedGetLegalHold
|
ObjectAccessedGetLegalHold
|
||||||
ObjectAccessedHead
|
ObjectAccessedHead
|
||||||
|
ObjectAccessedAttributes
|
||||||
ObjectCreatedCompleteMultipartUpload
|
ObjectCreatedCompleteMultipartUpload
|
||||||
ObjectCreatedCopy
|
ObjectCreatedCopy
|
||||||
ObjectCreatedPost
|
ObjectCreatedPost
|
||||||
@ -84,7 +85,7 @@ func (name Name) Expand() []Name {
|
|||||||
case ObjectAccessedAll:
|
case ObjectAccessedAll:
|
||||||
return []Name{
|
return []Name{
|
||||||
ObjectAccessedGet, ObjectAccessedHead,
|
ObjectAccessedGet, ObjectAccessedHead,
|
||||||
ObjectAccessedGetRetention, ObjectAccessedGetLegalHold,
|
ObjectAccessedGetRetention, ObjectAccessedGetLegalHold, ObjectAccessedAttributes,
|
||||||
}
|
}
|
||||||
case ObjectCreatedAll:
|
case ObjectCreatedAll:
|
||||||
return []Name{
|
return []Name{
|
||||||
@ -162,6 +163,8 @@ func (name Name) String() string {
|
|||||||
return "s3:ObjectAccessed:GetLegalHold"
|
return "s3:ObjectAccessed:GetLegalHold"
|
||||||
case ObjectAccessedHead:
|
case ObjectAccessedHead:
|
||||||
return "s3:ObjectAccessed:Head"
|
return "s3:ObjectAccessed:Head"
|
||||||
|
case ObjectAccessedAttributes:
|
||||||
|
return "s3:ObjectAccessed:Attributes"
|
||||||
case ObjectCreatedAll:
|
case ObjectCreatedAll:
|
||||||
return "s3:ObjectCreated:*"
|
return "s3:ObjectCreated:*"
|
||||||
case ObjectCreatedCompleteMultipartUpload:
|
case ObjectCreatedCompleteMultipartUpload:
|
||||||
@ -278,6 +281,8 @@ func ParseName(s string) (Name, error) {
|
|||||||
return ObjectAccessedGetLegalHold, nil
|
return ObjectAccessedGetLegalHold, nil
|
||||||
case "s3:ObjectAccessed:Head":
|
case "s3:ObjectAccessed:Head":
|
||||||
return ObjectAccessedHead, nil
|
return ObjectAccessedHead, nil
|
||||||
|
case "s3:ObjectAccessed:Attributes":
|
||||||
|
return ObjectAccessedAttributes, nil
|
||||||
case "s3:ObjectCreated:*":
|
case "s3:ObjectCreated:*":
|
||||||
return ObjectCreatedAll, nil
|
return ObjectCreatedAll, nil
|
||||||
case "s3:ObjectCreated:CompleteMultipartUpload":
|
case "s3:ObjectCreated:CompleteMultipartUpload":
|
||||||
|
@ -31,7 +31,7 @@ func TestNameExpand(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{BucketCreated, []Name{BucketCreated}},
|
{BucketCreated, []Name{BucketCreated}},
|
||||||
{BucketRemoved, []Name{BucketRemoved}},
|
{BucketRemoved, []Name{BucketRemoved}},
|
||||||
{ObjectAccessedAll, []Name{ObjectAccessedGet, ObjectAccessedHead, ObjectAccessedGetRetention, ObjectAccessedGetLegalHold}},
|
{ObjectAccessedAll, []Name{ObjectAccessedGet, ObjectAccessedHead, ObjectAccessedGetRetention, ObjectAccessedGetLegalHold, ObjectAccessedAttributes}},
|
||||||
{ObjectCreatedAll, []Name{
|
{ObjectCreatedAll, []Name{
|
||||||
ObjectCreatedCompleteMultipartUpload, ObjectCreatedCopy, ObjectCreatedPost, ObjectCreatedPut,
|
ObjectCreatedCompleteMultipartUpload, ObjectCreatedCopy, ObjectCreatedPost, ObjectCreatedPut,
|
||||||
ObjectCreatedPutRetention, ObjectCreatedPutLegalHold, ObjectCreatedPutTagging, ObjectCreatedDeleteTagging,
|
ObjectCreatedPutRetention, ObjectCreatedPutLegalHold, ObjectCreatedPutTagging, ObjectCreatedDeleteTagging,
|
||||||
|
@ -154,12 +154,12 @@ func TestRulesMapMatch(t *testing.T) {
|
|||||||
|
|
||||||
func TestNewRulesMap(t *testing.T) {
|
func TestNewRulesMap(t *testing.T) {
|
||||||
rulesMapCase1 := make(RulesMap)
|
rulesMapCase1 := make(RulesMap)
|
||||||
rulesMapCase1.add([]Name{ObjectAccessedGet, ObjectAccessedHead, ObjectAccessedGetRetention, ObjectAccessedGetLegalHold},
|
rulesMapCase1.add([]Name{ObjectAccessedGet, ObjectAccessedHead, ObjectAccessedGetRetention, ObjectAccessedGetLegalHold, ObjectAccessedAttributes},
|
||||||
"*", TargetID{"1", "webhook"})
|
"*", TargetID{"1", "webhook"})
|
||||||
|
|
||||||
rulesMapCase2 := make(RulesMap)
|
rulesMapCase2 := make(RulesMap)
|
||||||
rulesMapCase2.add([]Name{
|
rulesMapCase2.add([]Name{
|
||||||
ObjectAccessedGet, ObjectAccessedHead,
|
ObjectAccessedGet, ObjectAccessedHead, ObjectAccessedAttributes,
|
||||||
ObjectCreatedPut, ObjectAccessedGetRetention, ObjectAccessedGetLegalHold,
|
ObjectCreatedPut, ObjectAccessedGetRetention, ObjectAccessedGetLegalHold,
|
||||||
}, "*", TargetID{"1", "webhook"})
|
}, "*", TargetID{"1", "webhook"})
|
||||||
|
|
||||||
|
@ -240,6 +240,49 @@ func ReadCheckSums(b []byte, part int) map[string]string {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadPartCheckSums will read all part checksums from b and return them.
|
||||||
|
func ReadPartCheckSums(b []byte) (res []map[string]string) {
|
||||||
|
for len(b) > 0 {
|
||||||
|
t, n := binary.Uvarint(b)
|
||||||
|
if n <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b = b[n:]
|
||||||
|
|
||||||
|
typ := ChecksumType(t)
|
||||||
|
length := typ.RawByteLen()
|
||||||
|
if length == 0 || len(b) < length {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Skip main checksum
|
||||||
|
b = b[length:]
|
||||||
|
if !typ.Is(ChecksumIncludesMultipart) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts, n := binary.Uvarint(b)
|
||||||
|
if n <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(res) == 0 {
|
||||||
|
res = make([]map[string]string, parts)
|
||||||
|
}
|
||||||
|
b = b[n:]
|
||||||
|
for part := 0; part < int(parts); part++ {
|
||||||
|
if len(b) < length {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Read part checksum
|
||||||
|
cs := base64.StdEncoding.EncodeToString(b[:length])
|
||||||
|
b = b[length:]
|
||||||
|
if res[part] == nil {
|
||||||
|
res[part] = make(map[string]string, 1)
|
||||||
|
}
|
||||||
|
res[part][typ.String()] = cs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
// NewChecksumWithType is similar to NewChecksumString but expects input algo of ChecksumType.
|
// NewChecksumWithType is similar to NewChecksumString but expects input algo of ChecksumType.
|
||||||
func NewChecksumWithType(alg ChecksumType, value string) *Checksum {
|
func NewChecksumWithType(alg ChecksumType, value string) *Checksum {
|
||||||
if !alg.IsSet() {
|
if !alg.IsSet() {
|
||||||
|
@ -55,6 +55,12 @@ const (
|
|||||||
IfMatch = "If-Match"
|
IfMatch = "If-Match"
|
||||||
IfNoneMatch = "If-None-Match"
|
IfNoneMatch = "If-None-Match"
|
||||||
|
|
||||||
|
// Request tags used in GetObjectAttributes
|
||||||
|
Checksum = "Checksum"
|
||||||
|
StorageClass = "StorageClass"
|
||||||
|
ObjectSize = "ObjectSize"
|
||||||
|
ObjectParts = "ObjectParts"
|
||||||
|
|
||||||
// S3 storage class
|
// S3 storage class
|
||||||
AmzStorageClass = "x-amz-storage-class"
|
AmzStorageClass = "x-amz-storage-class"
|
||||||
|
|
||||||
@ -123,6 +129,11 @@ const (
|
|||||||
AmzSecurityToken = "X-Amz-Security-Token"
|
AmzSecurityToken = "X-Amz-Security-Token"
|
||||||
AmzDecodedContentLength = "X-Amz-Decoded-Content-Length"
|
AmzDecodedContentLength = "X-Amz-Decoded-Content-Length"
|
||||||
AmzTrailer = "X-Amz-Trailer"
|
AmzTrailer = "X-Amz-Trailer"
|
||||||
|
AmzMaxParts = "X-Amz-Max-Parts"
|
||||||
|
AmzPartNumberMarker = "X-Amz-Part-Number-Marker"
|
||||||
|
|
||||||
|
// Constants used for GetObjectAttributes and GetObjectVersionAttributes
|
||||||
|
AmzObjectAttributes = "X-Amz-Object-Attributes"
|
||||||
|
|
||||||
AmzMetaUnencryptedContentLength = "X-Amz-Meta-X-Amz-Unencrypted-Content-Length"
|
AmzMetaUnencryptedContentLength = "X-Amz-Meta-X-Amz-Unencrypted-Content-Length"
|
||||||
AmzMetaUnencryptedContentMD5 = "X-Amz-Meta-X-Amz-Unencrypted-Content-Md5"
|
AmzMetaUnencryptedContentMD5 = "X-Amz-Meta-X-Amz-Unencrypted-Content-Md5"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user