Skip to content

Commit

Permalink
feat: add support for GetObjectAttributes API (minio#18732)
Browse files Browse the repository at this point in the history
  • Loading branch information
zveinn authored Jan 5, 2024
1 parent 7705605 commit 9b8ba97
Show file tree
Hide file tree
Showing 13 changed files with 432 additions and 7 deletions.
8 changes: 8 additions & 0 deletions cmd/api-errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,9 @@ const (
ErrLambdaARNInvalid
ErrLambdaARNNotFound

// New Codes for GetObjectAttributes and GetObjectVersionAttributes
ErrInvalidAttributeName

apiErrCodeEnd // This is used only for the testing code
)

Expand Down Expand Up @@ -2063,6 +2066,11 @@ var errorCodes = errorCodeMap{
Description: "The specified policy is not found.",
HTTPStatusCode: http.StatusNotFound,
},
ErrInvalidAttributeName: {
Code: "InvalidArgument",
Description: "Invalid attribute name specified.",
HTTPStatusCode: http.StatusBadRequest,
},
// Add your error structure here.
}

Expand Down
3 changes: 3 additions & 0 deletions cmd/api-router.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,9 @@ func registerAPIRouter(router *mux.Router) {
// HeadObject
router.Methods(http.MethodHead).Path("/{object:.+}").HandlerFunc(
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
router.Methods(http.MethodPut).Path("/{object:.+}").
HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").
Expand Down
7 changes: 4 additions & 3 deletions cmd/apierrorcode_string.go

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions cmd/encryption-v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
// Will return nil, nil if unencrypted.
func (o *ObjectInfo) metadataEncryptFn(headers http.Header) (objectMetaEncryptFn, error) {
Expand Down
39 changes: 39 additions & 0 deletions cmd/object-api-datatypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -612,3 +612,42 @@ type NewMultipartUploadResult struct {
UploadID 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
}
3 changes: 3 additions & 0 deletions cmd/object-api-interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.

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
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.
Expand Down
115 changes: 115 additions & 0 deletions cmd/object-api-options.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,121 @@ func getOpts(ctx context.Context, r *http.Request, bucket, object string) (Objec
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) {
value := strings.TrimSpace(h.Get(headerName))
if value != "" {
Expand Down
Loading

0 comments on commit 9b8ba97

Please sign in to comment.