[#744] util/attributes: Support escaped symbols

To encode attributes with semicolon or slash, use
backslash as escaped character.

Example:
  User-Agent:NeoFS\/0.23
Signed-off-by: Alex Vanin <alexey@nspcc.ru>
This commit is contained in:
Alex Vanin 2021-08-04 12:21:40 +03:00 committed by Alex Vanin
parent c3e2738a46
commit 6527f9157c
2 changed files with 44 additions and 1 deletions

View file

@ -20,7 +20,7 @@ var (
) )
// ParseV2Attributes parses strings like "K1:V1/K2:V2/K3:V3" into netmap // ParseV2Attributes parses strings like "K1:V1/K2:V2/K3:V3" into netmap
// attributes. // attributes. Supports escaped symbols "\:", "\/" and "\\".
func ParseV2Attributes(attrs []string, excl []string) ([]*netmap.NodeAttribute, error) { func ParseV2Attributes(attrs []string, excl []string) ([]*netmap.NodeAttribute, error) {
restricted := make(map[string]struct{}, len(excl)) restricted := make(map[string]struct{}, len(excl))
for i := range excl { for i := range excl {
@ -31,6 +31,7 @@ func ParseV2Attributes(attrs []string, excl []string) ([]*netmap.NodeAttribute,
for i := range attrs { for i := range attrs {
line := strings.Trim(attrs[i], pairSeparator) line := strings.Trim(attrs[i], pairSeparator)
line = replaceEscaping(line, false) // replaced escaped symbols with non-printable symbols
chain := strings.Split(line, pairSeparator) chain := strings.Split(line, pairSeparator)
if len(chain) == 0 { if len(chain) == 0 {
return nil, errEmptyChain return nil, errEmptyChain
@ -55,6 +56,10 @@ func ParseV2Attributes(attrs []string, excl []string) ([]*netmap.NodeAttribute,
return nil, errUnexpectedKey return nil, errUnexpectedKey
} }
// replace non-printable symbols with escaped symbols without escape character
key = replaceEscaping(key, true)
value = replaceEscaping(value, true)
attribute := netmap.NewNodeAttribute() attribute := netmap.NewNodeAttribute()
attribute.SetKey(key) attribute.SetKey(key)
attribute.SetValue(value) attribute.SetValue(value)
@ -75,3 +80,29 @@ func ParseV2Attributes(attrs []string, excl []string) ([]*netmap.NodeAttribute,
return result, nil 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
}

View file

@ -57,4 +57,16 @@ func TestParseV2Attributes(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Len(t, attrs, 5) require.Len(t, attrs, 5)
}) })
t.Run("escape characters", func(t *testing.T) {
from := []string{
`/K\:ey1:V\/alue\\/Ke\/y2:Va\:lue`,
}
attrs, err := attributes.ParseV2Attributes(from, nil)
require.NoError(t, err)
require.Equal(t, attrs[0].Key(), `K:ey1`)
require.Equal(t, attrs[0].Value(), `V/alue\`)
require.Equal(t, attrs[1].Key(), `Ke/y2`)
require.Equal(t, attrs[1].Value(), `Va:lue`)
})
} }