Skip to content

Commit

Permalink
Allow UTF-8 string in baggage key
Browse files Browse the repository at this point in the history
  • Loading branch information
XSAM committed Mar 30, 2024
1 parent ba5d126 commit c81a51e
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 2 deletions.
31 changes: 29 additions & 2 deletions baggage/baggage.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"net/url"
"strings"
"unicode/utf8"

"go.opentelemetry.io/otel/internal/baggage"
)
Expand Down Expand Up @@ -241,7 +242,12 @@ func NewMember(key, value string, props ...Property) (Member, error) {

// NewMemberRaw returns a new Member from the passed arguments.
//
// The passed key must be compliant with W3C Baggage specification.
// The passed key and value must be valid UTF-8 string.
// However, the specific Propagators that are used to transmit baggage entries across
// component boundaries may impose their own restrictions on baggage key.
// For example, the W3C Baggage specification restricts the baggage keys to strings that
// satisfy the token definition from RFC7230, Section 3.2.6.
// For maximum compatibility, alpha-numeric value are strongly recommended to be used as baggage key.
func NewMemberRaw(key, value string, props ...Property) (Member, error) {
m := Member{
key: key,
Expand Down Expand Up @@ -313,9 +319,12 @@ func (m Member) validate() error {
return fmt.Errorf("%w: %q", errInvalidMember, m)
}

if !validateKey(m.key) {
if !validateBaggageName(m.key) {
return fmt.Errorf("%w: %q", errInvalidKey, m.key)
}
if !validateBaggageValue(m.value) {
return fmt.Errorf("%w: %q", errInvalidValue, m.value)
}
return m.properties.validate()
}

Expand Down Expand Up @@ -630,6 +639,23 @@ func skipSpace(s string, offset int) int {
return i
}

// validateBaggageName checks if the string is a valid OpenTelemetry Baggage name.
// Baggage name is a valid UTF-8 string.
func validateBaggageName(s string) bool {
if len(s) == 0 {
return false
}

return utf8.ValidString(s)
}

// validateBaggageValue checks if the string is a valid OpenTelemetry Baggage value.
// Baggage value is a valid UTF-8 strings.
func validateBaggageValue(s string) bool {
return utf8.ValidString(s)
}

// validateKey checks if the string is a valid W3C Baggage key.
func validateKey(s string) bool {
if len(s) == 0 {
return false
Expand Down Expand Up @@ -658,6 +684,7 @@ func validateKeyChar(c int32) bool {
c == 0x7e
}

// validateValue checks if the string is a valid W3C Baggage value.
func validateValue(s string) bool {
for _, c := range s {
if !validateValueChar(c) {
Expand Down
48 changes: 48 additions & 0 deletions baggage/baggage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ func TestNewKeyValueProperty(t *testing.T) {
assert.ErrorIs(t, err, errInvalidValue)
assert.Equal(t, Property{}, p)

// wrong value with wrong decoding
p, err = NewKeyValueProperty("key", "%zzzzz")
assert.ErrorIs(t, err, errInvalidValue)
assert.Equal(t, Property{}, p)

p, err = NewKeyValueProperty("key", "value")
assert.NoError(t, err)
assert.Equal(t, Property{key: "key", value: "value", hasValue: true}, p)
Expand Down Expand Up @@ -409,6 +414,15 @@ func TestBaggageParse(t *testing.T) {
"foo": {Value: "ąść"},
},
},
{
name: "encoded UTF-8 string in key",
in: "a=b,%C4%85%C5%9B%C4%87=%C4%85%C5%9B%C4%87",
want: baggage.List{
"a": {Value: "b"},
// The percent-encoded key won't be decoded.
"%C4%85%C5%9B%C4%87": {Value: "ąść"},
},
},
{
name: "invalid member: empty",
in: "foo=,,bar=",
Expand Down Expand Up @@ -861,6 +875,10 @@ func TestMemberValidation(t *testing.T) {
m.hasData = true
assert.ErrorIs(t, m.validate(), errInvalidKey)

// Invalid UTF-8 in value
m.key, m.value = "k", string([]byte{255})
assert.ErrorIs(t, m.validate(), errInvalidValue)

m.key, m.value = "k", "\\"
assert.NoError(t, m.validate())
}
Expand All @@ -882,6 +900,11 @@ func TestNewMember(t *testing.T) {
}
assert.Equal(t, expected, m)

// wrong value with invalid token
val = ";"
_, err = NewMember(key, val, p)
assert.ErrorIs(t, err, errInvalidValue)

// wrong value with wrong decoding
val = "%zzzzz"
_, err = NewMember(key, val, p)
Expand Down Expand Up @@ -926,6 +949,31 @@ func TestNewMemberRaw(t *testing.T) {
assert.Equal(t, expected, m)
}

func TestBaggageUTF8(t *testing.T) {
testCases := map[string]string{
"ąść": "B% 💼",

// Case sensitive
"a": "a",
"A": "A",
}

var members []Member
for k, v := range testCases {
m, err := NewMemberRaw(k, v)
require.NoError(t, err)

members = append(members, m)
}

b, err := New(members...)
require.NoError(t, err)

for k, v := range testCases {
assert.Equal(t, v, b.Member(k).Value())
}
}

func TestPropertiesValidate(t *testing.T) {
p := properties{{}}
assert.ErrorIs(t, p.validate(), errInvalidKey)
Expand Down

0 comments on commit c81a51e

Please sign in to comment.