250 lines
6.7 KiB
Go
250 lines
6.7 KiB
Go
package utils
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
|
)
|
|
|
|
const (
|
|
UserAttributeHeaderPrefix = "X-Attribute-"
|
|
)
|
|
|
|
const (
|
|
systemAttributePrefix = "__SYSTEM__"
|
|
|
|
// deprecated: use systemAttributePrefix
|
|
systemAttributePrefixNeoFS = "__NEOFS__"
|
|
)
|
|
|
|
type systemTransformer struct {
|
|
prefix string
|
|
backwardPrefix string
|
|
xAttrPrefixes [][]byte
|
|
}
|
|
|
|
var transformers = []systemTransformer{
|
|
{
|
|
prefix: systemAttributePrefix,
|
|
backwardPrefix: "System-",
|
|
xAttrPrefixes: [][]byte{[]byte("System-"), []byte("SYSTEM-"), []byte("system-")},
|
|
},
|
|
{
|
|
prefix: systemAttributePrefixNeoFS,
|
|
backwardPrefix: "Neofs-",
|
|
xAttrPrefixes: [][]byte{[]byte("Neofs-"), []byte("NEOFS-"), []byte("neofs-")},
|
|
},
|
|
}
|
|
|
|
func (t systemTransformer) existsExpirationAttributes(headers map[string]string) bool {
|
|
_, ok0 := headers[t.expirationEpochAttr()]
|
|
_, ok1 := headers[t.expirationDurationAttr()]
|
|
_, ok2 := headers[t.expirationTimestampAttr()]
|
|
_, ok3 := headers[t.expirationRFC3339Attr()]
|
|
return ok0 || ok1 || ok2 || ok3
|
|
}
|
|
|
|
func (t systemTransformer) expirationEpochAttr() string {
|
|
return t.prefix + "EXPIRATION_EPOCH"
|
|
}
|
|
|
|
func (t systemTransformer) expirationDurationAttr() string {
|
|
return t.prefix + "EXPIRATION_DURATION"
|
|
}
|
|
|
|
func (t systemTransformer) expirationTimestampAttr() string {
|
|
return t.prefix + "EXPIRATION_TIMESTAMP"
|
|
}
|
|
|
|
func (t systemTransformer) expirationRFC3339Attr() string {
|
|
return t.prefix + "EXPIRATION_RFC3339"
|
|
}
|
|
|
|
func (t systemTransformer) systemTranslator(key, prefix []byte) []byte {
|
|
// replace the specified prefix with system prefix
|
|
key = bytes.Replace(key, prefix, []byte(t.prefix), 1)
|
|
|
|
// replace `-` with `_`
|
|
key = bytes.ReplaceAll(key, []byte("-"), []byte("_"))
|
|
|
|
// replace with uppercase
|
|
return bytes.ToUpper(key)
|
|
}
|
|
|
|
func (t systemTransformer) transformIfSystem(key []byte) ([]byte, bool) {
|
|
// checks that it's a system FrostFS header
|
|
for _, system := range t.xAttrPrefixes {
|
|
if bytes.HasPrefix(key, system) {
|
|
return t.systemTranslator(key, system), true
|
|
}
|
|
}
|
|
|
|
return key, false
|
|
}
|
|
|
|
// systemBackwardTranslator is used to convert headers looking like '__PREFIX__ATTR_NAME' to 'Prefix-Attr-Name'.
|
|
func (t systemTransformer) systemBackwardTranslator(key string) string {
|
|
// trim specified prefix '__PREFIX__'
|
|
key = strings.TrimPrefix(key, t.prefix)
|
|
|
|
var res strings.Builder
|
|
res.WriteString(t.backwardPrefix)
|
|
|
|
strs := strings.Split(key, "_")
|
|
for i, s := range strs {
|
|
s = title(strings.ToLower(s))
|
|
res.WriteString(s)
|
|
if i != len(strs)-1 {
|
|
res.WriteString("-")
|
|
}
|
|
}
|
|
|
|
return res.String()
|
|
}
|
|
|
|
func (t systemTransformer) backwardTransformIfSystem(key string) (string, bool) {
|
|
if strings.HasPrefix(key, t.prefix) {
|
|
return t.systemBackwardTranslator(key), true
|
|
}
|
|
|
|
return key, false
|
|
}
|
|
|
|
func TransformIfSystem(key []byte) []byte {
|
|
for _, transformer := range transformers {
|
|
key, transformed := transformer.transformIfSystem(key)
|
|
if transformed {
|
|
return key
|
|
}
|
|
}
|
|
|
|
return key
|
|
}
|
|
|
|
func BackwardTransformIfSystem(key string) string {
|
|
for _, transformer := range transformers {
|
|
key, transformed := transformer.backwardTransformIfSystem(key)
|
|
if transformed {
|
|
return key
|
|
}
|
|
}
|
|
|
|
return key
|
|
}
|
|
|
|
func title(str string) string {
|
|
if str == "" {
|
|
return ""
|
|
}
|
|
|
|
r, size := utf8.DecodeRuneInString(str)
|
|
r0 := unicode.ToTitle(r)
|
|
return string(r0) + str[size:]
|
|
}
|
|
|
|
func PrepareExpirationHeader(ctx context.Context, p *pool.Pool, headers map[string]string, now time.Time) error {
|
|
formatsNum := 0
|
|
index := -1
|
|
for i, transformer := range transformers {
|
|
if transformer.existsExpirationAttributes(headers) {
|
|
formatsNum++
|
|
index = i
|
|
}
|
|
}
|
|
|
|
switch formatsNum {
|
|
case 0:
|
|
return nil
|
|
case 1:
|
|
epochDuration, err := GetEpochDurations(ctx, p)
|
|
if err != nil {
|
|
return fmt.Errorf("couldn't get epoch durations from network info: %w", err)
|
|
}
|
|
return transformers[index].prepareExpirationHeader(headers, epochDuration, now)
|
|
default:
|
|
return errors.New("both deprecated and new system attributes formats are used, please use only one")
|
|
}
|
|
}
|
|
|
|
func (t systemTransformer) prepareExpirationHeader(headers map[string]string, epochDurations *EpochDurations, now time.Time) error {
|
|
expirationInEpoch := headers[t.expirationEpochAttr()]
|
|
|
|
if timeRFC3339, ok := headers[t.expirationRFC3339Attr()]; ok {
|
|
expTime, err := time.Parse(time.RFC3339, timeRFC3339)
|
|
if err != nil {
|
|
return fmt.Errorf("couldn't parse value %s of header %s", timeRFC3339, t.expirationRFC3339Attr())
|
|
}
|
|
|
|
if expTime.Before(now) {
|
|
return fmt.Errorf("value %s of header %s must be in the future", timeRFC3339, t.expirationRFC3339Attr())
|
|
}
|
|
t.updateExpirationHeader(headers, epochDurations, expTime.Sub(now))
|
|
delete(headers, t.expirationRFC3339Attr())
|
|
}
|
|
|
|
if timestamp, ok := headers[t.expirationTimestampAttr()]; ok {
|
|
value, err := strconv.ParseInt(timestamp, 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("couldn't parse value %s of header %s", timestamp, t.expirationTimestampAttr())
|
|
}
|
|
expTime := time.Unix(value, 0)
|
|
|
|
if expTime.Before(now) {
|
|
return fmt.Errorf("value %s of header %s must be in the future", timestamp, t.expirationTimestampAttr())
|
|
}
|
|
t.updateExpirationHeader(headers, epochDurations, expTime.Sub(now))
|
|
delete(headers, t.expirationTimestampAttr())
|
|
}
|
|
|
|
if duration, ok := headers[t.expirationDurationAttr()]; ok {
|
|
expDuration, err := time.ParseDuration(duration)
|
|
if err != nil {
|
|
return fmt.Errorf("couldn't parse value %s of header %s", duration, t.expirationDurationAttr())
|
|
}
|
|
if expDuration <= 0 {
|
|
return fmt.Errorf("value %s of header %s must be positive", expDuration, t.expirationDurationAttr())
|
|
}
|
|
t.updateExpirationHeader(headers, epochDurations, expDuration)
|
|
delete(headers, t.expirationDurationAttr())
|
|
}
|
|
|
|
if expirationInEpoch != "" {
|
|
expEpoch, err := strconv.ParseUint(expirationInEpoch, 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("parse expiration epoch '%s': %w", expirationInEpoch, err)
|
|
}
|
|
if expEpoch < epochDurations.CurrentEpoch {
|
|
return fmt.Errorf("expiration epoch '%d' must be greater than current epoch '%d'", expEpoch, epochDurations.CurrentEpoch)
|
|
}
|
|
|
|
headers[t.expirationEpochAttr()] = expirationInEpoch
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t systemTransformer) updateExpirationHeader(headers map[string]string, durations *EpochDurations, expDuration time.Duration) {
|
|
epochDuration := uint64(durations.MsPerBlock) * durations.BlockPerEpoch
|
|
currentEpoch := durations.CurrentEpoch
|
|
numEpoch := uint64(expDuration.Milliseconds()) / epochDuration
|
|
|
|
if uint64(expDuration.Milliseconds())%epochDuration != 0 {
|
|
numEpoch++
|
|
}
|
|
|
|
expirationEpoch := uint64(math.MaxUint64)
|
|
if numEpoch < math.MaxUint64-currentEpoch {
|
|
expirationEpoch = currentEpoch + numEpoch
|
|
}
|
|
|
|
headers[t.expirationEpochAttr()] = strconv.FormatUint(expirationEpoch, 10)
|
|
}
|