2021-08-17 08:04:42 +00:00
|
|
|
package handler
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/xml"
|
2021-08-17 11:57:24 +00:00
|
|
|
"io"
|
2021-08-17 08:04:42 +00:00
|
|
|
"net/http"
|
2021-08-17 11:57:24 +00:00
|
|
|
"sort"
|
2021-08-17 08:04:42 +00:00
|
|
|
"strings"
|
|
|
|
"unicode"
|
|
|
|
|
2022-12-14 16:58:00 +00:00
|
|
|
"github.com/TrueCloudLab/frostfs-s3-gw/api"
|
|
|
|
"github.com/TrueCloudLab/frostfs-s3-gw/api/data"
|
|
|
|
"github.com/TrueCloudLab/frostfs-s3-gw/api/errors"
|
|
|
|
"github.com/TrueCloudLab/frostfs-s3-gw/api/layer"
|
2022-06-09 16:46:43 +00:00
|
|
|
"go.uber.org/zap"
|
2021-08-17 08:04:42 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
allowedTagChars = "+-=._:/@"
|
|
|
|
|
2021-08-17 11:57:24 +00:00
|
|
|
maxTags = 10
|
2021-08-17 08:04:42 +00:00
|
|
|
keyTagMaxLength = 128
|
|
|
|
valueTagMaxLength = 256
|
|
|
|
)
|
|
|
|
|
|
|
|
func (h *handler) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
reqInfo := api.GetReqInfo(r.Context())
|
|
|
|
|
2021-08-17 11:57:24 +00:00
|
|
|
tagSet, err := readTagSet(r.Body)
|
|
|
|
if err != nil {
|
|
|
|
h.logAndSendError(w, "could not read tag set", reqInfo, err)
|
2021-08-17 08:04:42 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-03-18 13:04:09 +00:00
|
|
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
|
|
|
if err != nil {
|
|
|
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
2021-08-19 12:33:02 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-10-14 14:36:43 +00:00
|
|
|
tagPrm := &layer.PutObjectTaggingParams{
|
|
|
|
ObjectVersion: &layer.ObjectVersion{
|
|
|
|
BktInfo: bktInfo,
|
|
|
|
ObjectName: reqInfo.ObjectName,
|
|
|
|
VersionID: reqInfo.URL.Query().Get(api.QueryVersionID),
|
|
|
|
},
|
|
|
|
TagSet: tagSet,
|
2021-08-17 08:04:42 +00:00
|
|
|
}
|
2022-10-14 14:36:43 +00:00
|
|
|
nodeVersion, err := h.obj.PutObjectTagging(r.Context(), tagPrm)
|
2022-07-18 14:51:34 +00:00
|
|
|
if err != nil {
|
2021-08-17 08:04:42 +00:00
|
|
|
h.logAndSendError(w, "could not put object tagging", reqInfo, err)
|
|
|
|
return
|
|
|
|
}
|
2022-06-09 16:46:43 +00:00
|
|
|
|
2022-12-09 13:30:51 +00:00
|
|
|
h.log.Debug("target details",
|
|
|
|
zap.String("reqId", reqInfo.RequestID),
|
|
|
|
zap.String("bucket", reqInfo.BucketName),
|
|
|
|
zap.Stringer("cid", bktInfo.CID),
|
|
|
|
zap.String("object", reqInfo.ObjectName),
|
|
|
|
zap.Stringer("oid", nodeVersion.OID))
|
|
|
|
|
2022-06-09 16:46:43 +00:00
|
|
|
s := &SendNotificationParams{
|
2022-05-24 06:58:33 +00:00
|
|
|
Event: EventObjectTaggingPut,
|
2022-07-18 14:51:34 +00:00
|
|
|
NotificationInfo: &data.NotificationInfo{
|
|
|
|
Name: nodeVersion.FilePath,
|
|
|
|
Size: nodeVersion.Size,
|
|
|
|
Version: nodeVersion.OID.EncodeToString(),
|
|
|
|
HashSum: nodeVersion.ETag,
|
2022-05-24 06:58:33 +00:00
|
|
|
},
|
2022-06-09 16:46:43 +00:00
|
|
|
BktInfo: bktInfo,
|
|
|
|
ReqInfo: reqInfo,
|
|
|
|
}
|
|
|
|
if err = h.sendNotifications(r.Context(), s); err != nil {
|
|
|
|
h.log.Error("couldn't send notification: %w", zap.Error(err))
|
|
|
|
}
|
|
|
|
|
2021-09-21 09:31:23 +00:00
|
|
|
w.WriteHeader(http.StatusOK)
|
2021-08-17 08:04:42 +00:00
|
|
|
}
|
|
|
|
|
2021-08-17 11:23:01 +00:00
|
|
|
func (h *handler) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
reqInfo := api.GetReqInfo(r.Context())
|
|
|
|
|
2022-03-18 13:04:09 +00:00
|
|
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
|
|
|
if err != nil {
|
|
|
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
2021-08-19 12:33:02 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-06-07 09:12:07 +00:00
|
|
|
settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
|
|
|
if err != nil {
|
|
|
|
h.logAndSendError(w, "could not get bucket settings", reqInfo, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-10-14 14:36:43 +00:00
|
|
|
tagPrm := &layer.GetObjectTaggingParams{
|
|
|
|
ObjectVersion: &layer.ObjectVersion{
|
|
|
|
BktInfo: bktInfo,
|
|
|
|
ObjectName: reqInfo.ObjectName,
|
|
|
|
VersionID: reqInfo.URL.Query().Get(api.QueryVersionID),
|
|
|
|
},
|
2021-08-17 11:23:01 +00:00
|
|
|
}
|
|
|
|
|
2022-10-14 14:36:43 +00:00
|
|
|
versionID, tagSet, err := h.obj.GetObjectTagging(r.Context(), tagPrm)
|
2021-08-17 11:23:01 +00:00
|
|
|
if err != nil {
|
|
|
|
h.logAndSendError(w, "could not get object tagging", reqInfo, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-12-09 13:30:51 +00:00
|
|
|
h.log.Debug("target details",
|
|
|
|
zap.String("reqId", reqInfo.RequestID),
|
|
|
|
zap.String("bucket", reqInfo.BucketName),
|
|
|
|
zap.Stringer("cid", bktInfo.CID),
|
|
|
|
zap.String("object", reqInfo.ObjectName),
|
|
|
|
zap.String("oid", versionID))
|
|
|
|
|
2022-07-19 14:58:18 +00:00
|
|
|
if settings.VersioningEnabled() {
|
2022-06-07 09:12:07 +00:00
|
|
|
w.Header().Set(api.AmzVersionID, versionID)
|
|
|
|
}
|
2021-08-17 11:57:24 +00:00
|
|
|
if err = api.EncodeToResponse(w, encodeTagging(tagSet)); err != nil {
|
2021-08-17 11:23:01 +00:00
|
|
|
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-17 11:23:49 +00:00
|
|
|
func (h *handler) DeleteObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
reqInfo := api.GetReqInfo(r.Context())
|
|
|
|
|
2022-03-18 13:04:09 +00:00
|
|
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
|
|
|
if err != nil {
|
|
|
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-05-26 13:11:14 +00:00
|
|
|
p := &layer.ObjectVersion{
|
|
|
|
BktInfo: bktInfo,
|
|
|
|
ObjectName: reqInfo.ObjectName,
|
2022-07-18 14:51:34 +00:00
|
|
|
VersionID: reqInfo.URL.Query().Get(api.QueryVersionID),
|
2021-08-17 11:23:49 +00:00
|
|
|
}
|
|
|
|
|
2022-07-18 14:51:34 +00:00
|
|
|
nodeVersion, err := h.obj.DeleteObjectTagging(r.Context(), p)
|
|
|
|
if err != nil {
|
2021-08-17 11:23:49 +00:00
|
|
|
h.logAndSendError(w, "could not delete object tagging", reqInfo, err)
|
|
|
|
return
|
|
|
|
}
|
2022-06-09 16:46:43 +00:00
|
|
|
|
2022-12-09 13:30:51 +00:00
|
|
|
h.log.Debug("target details",
|
|
|
|
zap.String("reqId", reqInfo.RequestID),
|
|
|
|
zap.String("bucket", reqInfo.BucketName),
|
|
|
|
zap.Stringer("cid", bktInfo.CID),
|
|
|
|
zap.String("object", reqInfo.ObjectName),
|
|
|
|
zap.Stringer("oid", nodeVersion.OID))
|
|
|
|
|
2022-06-09 16:46:43 +00:00
|
|
|
s := &SendNotificationParams{
|
2022-05-24 06:58:33 +00:00
|
|
|
Event: EventObjectTaggingDelete,
|
2022-07-18 14:51:34 +00:00
|
|
|
NotificationInfo: &data.NotificationInfo{
|
|
|
|
Name: nodeVersion.FilePath,
|
|
|
|
Size: nodeVersion.Size,
|
|
|
|
Version: nodeVersion.OID.EncodeToString(),
|
|
|
|
HashSum: nodeVersion.ETag,
|
2022-05-24 06:58:33 +00:00
|
|
|
},
|
2022-06-09 16:46:43 +00:00
|
|
|
BktInfo: bktInfo,
|
|
|
|
ReqInfo: reqInfo,
|
|
|
|
}
|
|
|
|
if err = h.sendNotifications(r.Context(), s); err != nil {
|
|
|
|
h.log.Error("couldn't send notification: %w", zap.Error(err))
|
|
|
|
}
|
|
|
|
|
2021-08-17 11:23:49 +00:00
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
}
|
|
|
|
|
2021-08-17 11:57:24 +00:00
|
|
|
func (h *handler) PutBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
reqInfo := api.GetReqInfo(r.Context())
|
|
|
|
|
|
|
|
tagSet, err := readTagSet(r.Body)
|
|
|
|
if err != nil {
|
|
|
|
h.logAndSendError(w, "could not read tag set", reqInfo, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-03-18 13:04:09 +00:00
|
|
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
|
|
|
if err != nil {
|
|
|
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
2021-08-19 12:33:02 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-09-13 09:44:18 +00:00
|
|
|
if err = h.obj.PutBucketTagging(r.Context(), bktInfo, tagSet); err != nil {
|
2021-08-17 11:57:24 +00:00
|
|
|
h.logAndSendError(w, "could not put object tagging", reqInfo, err)
|
2022-03-18 13:04:09 +00:00
|
|
|
return
|
2021-08-17 11:57:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *handler) GetBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
reqInfo := api.GetReqInfo(r.Context())
|
|
|
|
|
2022-03-18 13:04:09 +00:00
|
|
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
|
|
|
if err != nil {
|
|
|
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
2021-08-19 12:33:02 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-09-13 09:44:18 +00:00
|
|
|
tagSet, err := h.obj.GetBucketTagging(r.Context(), bktInfo)
|
2021-08-17 11:57:24 +00:00
|
|
|
if err != nil {
|
|
|
|
h.logAndSendError(w, "could not get object tagging", reqInfo, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = api.EncodeToResponse(w, encodeTagging(tagSet)); err != nil {
|
|
|
|
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
2022-03-18 13:04:09 +00:00
|
|
|
return
|
2021-08-17 11:57:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *handler) DeleteBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
reqInfo := api.GetReqInfo(r.Context())
|
2022-03-18 13:04:09 +00:00
|
|
|
|
|
|
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
|
|
|
if err != nil {
|
|
|
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
2021-08-19 12:33:02 +00:00
|
|
|
return
|
|
|
|
}
|
2022-03-18 13:04:09 +00:00
|
|
|
|
2022-09-13 09:44:18 +00:00
|
|
|
if err = h.obj.DeleteBucketTagging(r.Context(), bktInfo); err != nil {
|
2022-03-18 13:04:09 +00:00
|
|
|
h.logAndSendError(w, "could not delete bucket tagging", reqInfo, err)
|
|
|
|
return
|
2021-08-17 11:57:24 +00:00
|
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
}
|
|
|
|
|
|
|
|
func readTagSet(reader io.Reader) (map[string]string, error) {
|
|
|
|
tagging := new(Tagging)
|
|
|
|
if err := xml.NewDecoder(reader).Decode(tagging); err != nil {
|
|
|
|
return nil, errors.GetAPIError(errors.ErrMalformedXML)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := checkTagSet(tagging.TagSet); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
tagSet := make(map[string]string, len(tagging.TagSet))
|
|
|
|
for _, tag := range tagging.TagSet {
|
|
|
|
tagSet[tag.Key] = tag.Value
|
|
|
|
}
|
|
|
|
|
|
|
|
return tagSet, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeTagging(tagSet map[string]string) *Tagging {
|
|
|
|
tagging := &Tagging{}
|
|
|
|
for k, v := range tagSet {
|
|
|
|
tagging.TagSet = append(tagging.TagSet, Tag{Key: k, Value: v})
|
|
|
|
}
|
|
|
|
sort.Slice(tagging.TagSet, func(i, j int) bool {
|
|
|
|
return tagging.TagSet[i].Key < tagging.TagSet[j].Key
|
|
|
|
})
|
|
|
|
|
|
|
|
return tagging
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkTagSet(tagSet []Tag) error {
|
|
|
|
if len(tagSet) > maxTags {
|
|
|
|
return errors.GetAPIError(errors.ErrInvalidTagsSizeExceed)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tag := range tagSet {
|
|
|
|
if err := checkTag(tag); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-08-17 08:04:42 +00:00
|
|
|
func checkTag(tag Tag) error {
|
|
|
|
if len(tag.Key) < 1 || len(tag.Key) > keyTagMaxLength {
|
2021-08-17 11:57:24 +00:00
|
|
|
return errors.GetAPIError(errors.ErrInvalidTagKey)
|
2021-08-17 08:04:42 +00:00
|
|
|
}
|
2021-08-17 11:57:24 +00:00
|
|
|
if len(tag.Value) > valueTagMaxLength {
|
|
|
|
return errors.GetAPIError(errors.ErrInvalidTagValue)
|
2021-08-17 08:04:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if strings.HasPrefix(tag.Key, "aws:") {
|
2021-08-17 11:57:24 +00:00
|
|
|
return errors.GetAPIError(errors.ErrInvalidTagKey)
|
2021-08-17 08:04:42 +00:00
|
|
|
}
|
|
|
|
|
2021-08-17 11:57:24 +00:00
|
|
|
if !isValidTag(tag.Key) {
|
|
|
|
return errors.GetAPIError(errors.ErrInvalidTagKey)
|
2021-08-17 08:04:42 +00:00
|
|
|
}
|
2021-08-17 11:57:24 +00:00
|
|
|
if !isValidTag(tag.Value) {
|
|
|
|
return errors.GetAPIError(errors.ErrInvalidTagValue)
|
2021-08-17 08:04:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-08-17 11:57:24 +00:00
|
|
|
func isValidTag(str string) bool {
|
2021-08-17 08:04:42 +00:00
|
|
|
for _, r := range str {
|
|
|
|
if !unicode.IsLetter(r) && !unicode.IsDigit(r) &&
|
|
|
|
!unicode.IsSpace(r) && !strings.ContainsRune(allowedTagChars, r) {
|
2021-08-17 11:57:24 +00:00
|
|
|
return false
|
2021-08-17 08:04:42 +00:00
|
|
|
}
|
|
|
|
}
|
2021-08-17 11:57:24 +00:00
|
|
|
return true
|
2021-08-17 08:04:42 +00:00
|
|
|
}
|