frostfs-node/pkg/util/attributes/parser.go
Alex Vanin f9218bf84f [#787] util/attributes: Restore escape characters after caching the result
As soon as resulting list and cache operate with the attribute pointer,
we can add attribute structure immediately and set restored key and
value of the attribute later.

Signed-off-by: Alex Vanin <alexey@nspcc.ru>
2021-09-01 14:29:26 +03:00

121 lines
2.8 KiB
Go

package attributes
import (
"errors"
"fmt"
"strings"
"github.com/nspcc-dev/neofs-api-go/pkg/netmap"
)
const (
pairSeparator = "/"
keyValueSeparator = ":"
)
var (
errEmptyChain = errors.New("empty attribute chain")
errNonUniqueBucket = errors.New("attributes must contain unique keys")
errUnexpectedKey = errors.New("attributes contain unexpected key")
)
// ParseV2Attributes parses strings like "K1:V1/K2:V2/K3:V3" into netmap
// attributes. Supports escaped symbols "\:", "\/" and "\\".
func ParseV2Attributes(attrs []string, excl []string) ([]*netmap.NodeAttribute, error) {
restricted := make(map[string]struct{}, len(excl))
for i := range excl {
restricted[excl[i]] = struct{}{}
}
cache := make(map[string]*netmap.NodeAttribute, len(attrs))
result := make([]*netmap.NodeAttribute, 0, len(attrs))
for i := range attrs {
line := strings.Trim(attrs[i], pairSeparator)
line = replaceEscaping(line, false) // replaced escaped symbols with non-printable symbols
chain := strings.Split(line, pairSeparator)
if len(chain) == 0 {
return nil, errEmptyChain
}
var parentKey string // backtrack parents in next pairs
for j := range chain {
pair := strings.Split(chain[j], keyValueSeparator)
if len(pair) != 2 {
return nil, fmt.Errorf("incorrect attribute pair %s", chain[j])
}
key := pair[0]
value := pair[1]
attribute, present := cache[key]
if present && attribute.Value() != value {
return nil, errNonUniqueBucket
}
if _, ok := restricted[key]; ok {
return nil, errUnexpectedKey
}
if !present {
attribute = netmap.NewNodeAttribute()
cache[key] = attribute
result = append(result, attribute)
// replace non-printable symbols with escaped symbols without escape character
key = replaceEscaping(key, true)
value = replaceEscaping(value, true)
attribute.SetKey(key)
attribute.SetValue(value)
}
if parentKey != "" {
parentKeys := attribute.ParentKeys()
if !hasString(parentKeys, parentKey) {
attribute.SetParentKeys(append(parentKeys, parentKey)...)
}
}
parentKey = key
}
}
return result, nil
}
func replaceEscaping(target string, rollback bool) (s string) {
const escChar = `\`
var (
oldPairSep = escChar + pairSeparator
oldKVSep = escChar + keyValueSeparator
oldEsc = escChar + escChar
newPairSep = string(uint8(1))
newKVSep = string(uint8(2))
newEsc = string(uint8(3))
)
if rollback {
oldPairSep, oldKVSep, oldEsc = newPairSep, newKVSep, newEsc
newPairSep = pairSeparator
newKVSep = keyValueSeparator
newEsc = escChar
}
s = strings.ReplaceAll(target, oldEsc, newEsc)
s = strings.ReplaceAll(s, oldPairSep, newPairSep)
s = strings.ReplaceAll(s, oldKVSep, newKVSep)
return
}
func hasString(lst []string, v string) bool {
for i := range lst {
if lst[i] == v {
return true
}
}
return false
}