frostfs-s3-gw/api/handler/tagging.go

162 lines
3.8 KiB
Go
Raw Normal View History

package handler
import (
"encoding/xml"
"net/http"
"strings"
"unicode"
"github.com/nspcc-dev/neofs-s3-gw/api"
"github.com/nspcc-dev/neofs-s3-gw/api/errors"
"github.com/nspcc-dev/neofs-s3-gw/api/layer"
)
const (
allowedTagChars = "+-=._:/@"
keyTagMaxLength = 128
valueTagMaxLength = 256
)
func (h *handler) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
reqInfo := api.GetReqInfo(r.Context())
tagging := new(Tagging)
if err := xml.NewDecoder(r.Body).Decode(tagging); err != nil {
h.logAndSendError(w, "could not decode body", reqInfo, errors.GetAPIError(errors.ErrMalformedXML))
return
}
if err := checkTagSet(tagging.TagSet); err != nil {
h.logAndSendError(w, "some tags are invalid", reqInfo, err)
return
}
p := &layer.HeadObjectParams{
Bucket: reqInfo.BucketName,
Object: reqInfo.ObjectName,
VersionID: reqInfo.URL.Query().Get("versionId"),
}
objInfo, err := h.obj.GetObjectInfo(r.Context(), p)
if err != nil {
h.logAndSendError(w, "could not get object info", reqInfo, err)
return
}
tagSet := make(map[string]string, len(tagging.TagSet))
for _, tag := range tagging.TagSet {
tagSet[tag.Key] = tag.Value
}
p2 := &layer.PutTaggingParams{
ObjectInfo: objInfo,
TagSet: tagSet,
}
if err = h.obj.PutObjectTagging(r.Context(), p2); err != nil {
h.logAndSendError(w, "could not put object tagging", reqInfo, err)
return
}
}
func (h *handler) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
reqInfo := api.GetReqInfo(r.Context())
p := &layer.HeadObjectParams{
Bucket: reqInfo.BucketName,
Object: reqInfo.ObjectName,
VersionID: reqInfo.URL.Query().Get("versionId"),
}
objInfo, err := h.obj.GetObjectInfo(r.Context(), p)
if err != nil {
h.logAndSendError(w, "could not get object info", reqInfo, err)
return
}
tagSet, err := h.obj.GetObjectTagging(r.Context(), objInfo)
if err != nil {
h.logAndSendError(w, "could not get object tagging", reqInfo, err)
return
}
tagging := &Tagging{}
for k, v := range tagSet {
tagging.TagSet = append(tagging.TagSet, Tag{Key: k, Value: v})
}
w.Header().Set(api.AmzVersionID, objInfo.Version())
if err = api.EncodeToResponse(w, tagging); err != nil {
h.logAndSendError(w, "something went wrong", reqInfo, err)
}
}
func checkTagSet(tagSet []Tag) error {
if len(tagSet) > 10 {
return errors.GetAPIError(errors.ErrInvalidTag)
}
for _, tag := range tagSet {
if err := checkTag(tag); err != nil {
return err
}
}
return nil
}
func (h *handler) DeleteObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
reqInfo := api.GetReqInfo(r.Context())
p := &layer.HeadObjectParams{
Bucket: reqInfo.BucketName,
Object: reqInfo.ObjectName,
VersionID: reqInfo.URL.Query().Get("versionId"),
}
objInfo, err := h.obj.GetObjectInfo(r.Context(), p)
if err != nil {
h.logAndSendError(w, "could not get object info", reqInfo, err)
return
}
if err = h.obj.DeleteObjectTagging(r.Context(), objInfo); err != nil {
h.logAndSendError(w, "could not delete object tagging", reqInfo, err)
return
}
w.WriteHeader(http.StatusNoContent)
}
func checkTag(tag Tag) error {
if len(tag.Key) < 1 || len(tag.Key) > keyTagMaxLength {
return errors.GetAPIError(errors.ErrInvalidTag)
}
if len(tag.Value) < 1 || len(tag.Value) > valueTagMaxLength {
return errors.GetAPIError(errors.ErrInvalidTag)
}
if strings.HasPrefix(tag.Key, "aws:") {
return errors.GetAPIError(errors.ErrInvalidTag)
}
if err := checkCharacters(tag.Key); err != nil {
return err
}
if err := checkCharacters(tag.Value); err != nil {
return err
}
return nil
}
func checkCharacters(str string) error {
for _, r := range str {
if !unicode.IsLetter(r) && !unicode.IsDigit(r) &&
!unicode.IsSpace(r) && !strings.ContainsRune(allowedTagChars, r) {
return errors.GetAPIError(errors.ErrInvalidTag)
}
}
return nil
}