2022-05-12 21:15:08 +00:00
|
|
|
package handler
|
|
|
|
|
|
|
|
import (
|
2022-06-22 19:40:52 +00:00
|
|
|
"fmt"
|
2022-05-12 21:15:08 +00:00
|
|
|
"net/http"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/nspcc-dev/neofs-s3-gw/api"
|
|
|
|
"github.com/nspcc-dev/neofs-s3-gw/api/data"
|
|
|
|
"github.com/nspcc-dev/neofs-s3-gw/api/errors"
|
|
|
|
"github.com/nspcc-dev/neofs-s3-gw/api/layer"
|
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
GetObjectAttributesResponse struct {
|
|
|
|
ETag string `xml:"ETag,omitempty"`
|
2022-06-02 15:26:26 +00:00
|
|
|
Checksum *Checksum `xml:"Checksum,omitempty"`
|
2022-05-12 21:15:08 +00:00
|
|
|
ObjectSize int64 `xml:"ObjectSize,omitempty"`
|
|
|
|
StorageClass string `xml:"StorageClass,omitempty"`
|
|
|
|
ObjectParts *ObjectParts `xml:"ObjectParts,omitempty"`
|
|
|
|
}
|
|
|
|
|
2022-06-02 15:26:26 +00:00
|
|
|
Checksum struct {
|
|
|
|
ChecksumSHA256 string `xml:"ChecksumSHA256,omitempty"`
|
|
|
|
}
|
|
|
|
|
2022-05-12 21:15:08 +00:00
|
|
|
ObjectParts struct {
|
|
|
|
IsTruncated bool `xml:"IsTruncated,omitempty"`
|
|
|
|
MaxParts int `xml:"MaxParts,omitempty"`
|
|
|
|
NextPartNumberMarker int `xml:"NextPartNumberMarker,omitempty"`
|
|
|
|
PartNumberMarker int `xml:"PartNumberMarker,omitempty"`
|
|
|
|
Parts []Part `xml:"Part,omitempty"`
|
2022-06-02 15:26:26 +00:00
|
|
|
PartsCount int `xml:"PartsCount,omitempty"`
|
2022-05-12 21:15:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Part struct {
|
2022-06-02 15:26:26 +00:00
|
|
|
ChecksumSHA256 string `xml:"ChecksumSHA256,omitempty"`
|
|
|
|
PartNumber int `xml:"PartNumber,omitempty"`
|
|
|
|
Size int `xml:"Size,omitempty"`
|
2022-05-12 21:15:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
GetObjectAttributesArgs struct {
|
2022-06-02 15:26:26 +00:00
|
|
|
MaxParts int
|
|
|
|
PartNumberMarker int
|
|
|
|
Attributes []string
|
|
|
|
VersionID string
|
|
|
|
Conditional *conditionalArgs
|
2022-05-12 21:15:08 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
eTag = "ETag"
|
|
|
|
checksum = "Checksum"
|
|
|
|
objectParts = "ObjectParts"
|
|
|
|
storageClass = "StorageClass"
|
|
|
|
objectSize = "ObjectSize"
|
|
|
|
)
|
|
|
|
|
|
|
|
var validAttributes = map[string]struct{}{
|
|
|
|
eTag: {},
|
|
|
|
checksum: {},
|
|
|
|
objectParts: {},
|
|
|
|
storageClass: {},
|
|
|
|
objectSize: {},
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *handler) GetObjectAttributesHandler(w http.ResponseWriter, r *http.Request) {
|
2022-06-28 13:35:05 +00:00
|
|
|
reqInfo := api.GetReqInfo(r.Context())
|
2022-05-12 21:15:08 +00:00
|
|
|
|
|
|
|
params, err := parseGetObjectAttributeArgs(r)
|
|
|
|
if err != nil {
|
|
|
|
h.logAndSendError(w, "invalid request", reqInfo, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
|
|
|
if err != nil {
|
|
|
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
p := &layer.HeadObjectParams{
|
|
|
|
BktInfo: bktInfo,
|
|
|
|
Object: reqInfo.ObjectName,
|
|
|
|
VersionID: params.VersionID,
|
|
|
|
}
|
|
|
|
|
2022-06-28 13:35:05 +00:00
|
|
|
extendedInfo, err := h.obj.GetObjectInfo(r.Context(), p)
|
|
|
|
if err != nil {
|
2022-05-12 21:15:08 +00:00
|
|
|
h.logAndSendError(w, "could not fetch object info", reqInfo, err)
|
|
|
|
return
|
|
|
|
}
|
2022-06-28 13:35:05 +00:00
|
|
|
info := extendedInfo.ObjectInfo
|
2022-05-12 21:15:08 +00:00
|
|
|
|
2022-06-01 14:09:28 +00:00
|
|
|
if err = checkPreconditions(info, params.Conditional); err != nil {
|
|
|
|
h.logAndSendError(w, "precondition failed", reqInfo, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-05-12 21:15:08 +00:00
|
|
|
response, err := encodeToObjectAttributesResponse(info, params)
|
|
|
|
if err != nil {
|
|
|
|
h.logAndSendError(w, "couldn't encode object info to response", reqInfo, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
writeAttributesHeaders(w.Header(), info, params)
|
|
|
|
if err = api.EncodeToResponse(w, response); err != nil {
|
|
|
|
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func writeAttributesHeaders(h http.Header, info *data.ObjectInfo, params *GetObjectAttributesArgs) {
|
|
|
|
h.Set(api.LastModified, info.Created.UTC().Format(http.TimeFormat))
|
|
|
|
if len(params.VersionID) != 0 {
|
|
|
|
h.Set(api.AmzVersionID, info.Version())
|
|
|
|
}
|
|
|
|
|
2022-05-31 09:10:59 +00:00
|
|
|
if info.IsDeleteMarker {
|
2022-05-12 21:15:08 +00:00
|
|
|
h.Set(api.AmzDeleteMarker, strconv.FormatBool(true))
|
|
|
|
}
|
|
|
|
|
|
|
|
// x-amz-request-charged
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseGetObjectAttributeArgs(r *http.Request) (*GetObjectAttributesArgs, error) {
|
2022-06-01 09:54:27 +00:00
|
|
|
res := &GetObjectAttributesArgs{
|
|
|
|
VersionID: r.URL.Query().Get(api.QueryVersionID),
|
|
|
|
}
|
2022-05-12 21:15:08 +00:00
|
|
|
|
2022-06-01 09:54:27 +00:00
|
|
|
attributesVal := r.Header.Get(api.AmzObjectAttributes)
|
2022-05-12 21:15:08 +00:00
|
|
|
if attributesVal == "" {
|
|
|
|
return nil, errors.GetAPIError(errors.ErrInvalidAttributeName)
|
|
|
|
}
|
|
|
|
|
|
|
|
attributes := strings.Split(attributesVal, ",")
|
|
|
|
for _, a := range attributes {
|
|
|
|
if _, ok := validAttributes[a]; !ok {
|
|
|
|
return nil, errors.GetAPIError(errors.ErrInvalidAttributeName)
|
|
|
|
}
|
|
|
|
res.Attributes = append(res.Attributes, a)
|
|
|
|
}
|
|
|
|
|
2022-06-02 15:26:26 +00:00
|
|
|
var err error
|
|
|
|
maxPartsVal := r.Header.Get(api.AmzMaxParts)
|
|
|
|
if maxPartsVal == "" {
|
|
|
|
res.MaxParts = layer.MaxSizePartsList
|
|
|
|
} else if res.MaxParts, err = strconv.Atoi(maxPartsVal); err != nil || res.MaxParts < 0 {
|
|
|
|
return nil, errors.GetAPIError(errors.ErrInvalidMaxKeys)
|
|
|
|
}
|
|
|
|
|
|
|
|
markerVal := r.Header.Get(api.AmzPartNumberMarker)
|
|
|
|
if markerVal != "" {
|
|
|
|
if res.PartNumberMarker, err = strconv.Atoi(markerVal); err != nil || res.PartNumberMarker < 0 {
|
|
|
|
return nil, errors.GetAPIError(errors.ErrInvalidPartNumberMarker)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
res.Conditional, err = parseConditionalHeaders(r.Header)
|
|
|
|
return res, err
|
2022-05-12 21:15:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func encodeToObjectAttributesResponse(info *data.ObjectInfo, p *GetObjectAttributesArgs) (*GetObjectAttributesResponse, error) {
|
|
|
|
resp := &GetObjectAttributesResponse{}
|
|
|
|
|
|
|
|
for _, attr := range p.Attributes {
|
|
|
|
switch attr {
|
|
|
|
case eTag:
|
|
|
|
resp.ETag = info.HashSum
|
|
|
|
case storageClass:
|
|
|
|
resp.StorageClass = "STANDARD"
|
|
|
|
case objectSize:
|
|
|
|
resp.ObjectSize = info.Size
|
2022-06-02 15:26:26 +00:00
|
|
|
case checksum:
|
|
|
|
resp.Checksum = &Checksum{ChecksumSHA256: info.HashSum}
|
2022-05-12 21:15:08 +00:00
|
|
|
case objectParts:
|
2022-06-02 15:26:26 +00:00
|
|
|
parts, err := formUploadAttributes(info, p.MaxParts, p.PartNumberMarker)
|
2022-05-12 21:15:08 +00:00
|
|
|
if err != nil {
|
2022-06-22 19:40:52 +00:00
|
|
|
return nil, fmt.Errorf("form upload attributes: %w", err)
|
2022-05-12 21:15:08 +00:00
|
|
|
}
|
|
|
|
if parts != nil {
|
|
|
|
resp.ObjectParts = parts
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
2022-06-02 15:26:26 +00:00
|
|
|
func formUploadAttributes(info *data.ObjectInfo, maxParts, marker int) (*ObjectParts, error) {
|
|
|
|
completedParts, ok := info.Headers[layer.UploadCompletedParts]
|
2022-06-01 09:54:27 +00:00
|
|
|
if !ok {
|
2022-05-12 21:15:08 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2022-06-02 15:26:26 +00:00
|
|
|
partInfos := strings.Split(completedParts, ",")
|
|
|
|
parts := make([]Part, len(partInfos))
|
|
|
|
for i, p := range partInfos {
|
|
|
|
// partInfo[0] -- part number, partInfo[1] -- part size, partInfo[2] -- checksum
|
|
|
|
partInfo := strings.Split(p, "-")
|
|
|
|
if len(partInfo) != 3 {
|
|
|
|
return nil, fmt.Errorf("invalid completed parts header")
|
|
|
|
}
|
|
|
|
num, err := strconv.Atoi(partInfo[0])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
size, err := strconv.Atoi(partInfo[1])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
parts[i] = Part{
|
|
|
|
PartNumber: num,
|
|
|
|
Size: size,
|
|
|
|
ChecksumSHA256: partInfo[2],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
res := &ObjectParts{
|
|
|
|
PartsCount: len(parts),
|
|
|
|
}
|
|
|
|
|
|
|
|
if marker != 0 {
|
|
|
|
res.PartNumberMarker = marker
|
|
|
|
var found bool
|
|
|
|
for i, n := range parts {
|
|
|
|
if n.PartNumber == marker {
|
|
|
|
parts = parts[i:]
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
return nil, errors.GetAPIError(errors.ErrInvalidPartNumberMarker)
|
|
|
|
}
|
2022-05-12 21:15:08 +00:00
|
|
|
}
|
|
|
|
|
2022-06-02 15:26:26 +00:00
|
|
|
res.MaxParts = maxParts
|
|
|
|
if len(parts) > maxParts {
|
|
|
|
res.IsTruncated = true
|
|
|
|
res.NextPartNumberMarker = parts[maxParts].PartNumber
|
|
|
|
parts = parts[:maxParts]
|
|
|
|
}
|
|
|
|
|
|
|
|
res.Parts = parts
|
|
|
|
|
|
|
|
return res, nil
|
2022-05-12 21:15:08 +00:00
|
|
|
}
|