Skip to content

Commit d61bbf1

Browse files
XSAMhanyuancheungpellared
authored
baggage: Accept non-ASCII keys (#5132)
resolves #4946 I also add additional test cases to cover more lines. benchmark results: ``` goos: darwin goarch: arm64 pkg: go.opentelemetry.io/otel/baggage │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ New-10 402.3n ± 1% 422.4n ± 6% +4.98% (p=0.000 n=10) NewMemberRaw-10 10.82n ± 0% 13.90n ± 1% +28.51% (p=0.000 n=10) Parse-10 803.8n ± 1% 795.0n ± 1% -1.09% (p=0.011 n=10) String-10 682.6n ± 0% 610.0n ± 2% -10.63% (p=0.000 n=10) ValueEscape/nothing_to_escape-10 4.856n ± 0% 4.849n ± 0% ~ (p=0.279 n=10) ValueEscape/requires_escaping-10 22.47n ± 1% 22.36n ± 1% ~ (p=0.342 n=10) ValueEscape/long_value-10 513.3n ± 1% 510.1n ± 0% -0.62% (p=0.006 n=10) MemberString-10 430.8n ± 2% 471.3n ± 2% +9.41% (p=0.000 n=10) geomean 124.5n 128.5n +3.22% │ old.txt │ new.txt │ │ B/op │ B/op vs base │ New-10 704.0 ± 0% 704.0 ± 0% ~ (p=1.000 n=10) ¹ NewMemberRaw-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Parse-10 888.0 ± 0% 888.0 ± 0% ~ (p=1.000 n=10) ¹ String-10 936.0 ± 0% 840.0 ± 0% -10.26% (p=0.000 n=10) ValueEscape/nothing_to_escape-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ValueEscape/requires_escaping-10 16.00 ± 0% 16.00 ± 0% ~ (p=1.000 n=10) ¹ ValueEscape/long_value-10 576.0 ± 0% 576.0 ± 0% ~ (p=1.000 n=10) ¹ MemberString-10 656.0 ± 0% 656.0 ± 0% ~ (p=1.000 n=10) ¹ geomean ² -1.34% ² ¹ all samples are equal ² summaries must be >0 to compute geomean │ old.txt │ new.txt │ │ allocs/op │ allocs/op vs base │ New-10 8.000 ± 0% 8.000 ± 0% ~ (p=1.000 n=10) ¹ NewMemberRaw-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Parse-10 9.000 ± 0% 9.000 ± 0% ~ (p=1.000 n=10) ¹ String-10 10.000 ± 0% 8.000 ± 0% -20.00% (p=0.000 n=10) ValueEscape/nothing_to_escape-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ValueEscape/requires_escaping-10 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ ValueEscape/long_value-10 2.000 ± 0% 2.000 ± 0% ~ (p=1.000 n=10) ¹ MemberString-10 4.000 ± 0% 4.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² -2.75% ² ¹ all samples are equal ² summaries must be >0 to compute geomean ``` FYI, the old implementation of `NewMemberRaw` didn't verify the value, so the benchmark result of `NewMemberRaw` is not an apple-to-apple comparison of `utf8.ValidString` and `validateKey`. --------- Co-authored-by: Chester Cheung <[email protected]> Co-authored-by: Robert Pająk <[email protected]>
1 parent 1dc9522 commit d61bbf1

File tree

3 files changed

+235
-20
lines changed

3 files changed

+235
-20
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
2626
- `SimpleProcessor` in `go.opentelemetry.io/otel/sdk/log` synchronizes `OnEmit` calls. (#5666)
2727
- The `SimpleProcessor` type in `go.opentelemetry.io/otel/sdk/log` is no longer comparable. (#5693)
2828
- The `BatchProcessor` type in `go.opentelemetry.io/otel/sdk/log` is no longer comparable. (#5693)
29+
- `NewMemberRaw`, `NewKeyProperty` and `NewKeyValuePropertyRaw` in `go.opentelemetry.io/otel/baggage` allow UTF-8 string in key. (#5132)
2930

3031
### Fixed
3132

baggage/baggage.go

Lines changed: 94 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,15 @@ type Property struct {
4444

4545
// NewKeyProperty returns a new Property for key.
4646
//
47+
// The passed key must be valid, non-empty UTF-8 string.
4748
// If key is invalid, an error will be returned.
49+
// However, the specific Propagators that are used to transmit baggage entries across
50+
// component boundaries may impose their own restrictions on Property key.
51+
// For example, the W3C Baggage specification restricts the Property keys to strings that
52+
// satisfy the token definition from RFC7230, Section 3.2.6.
53+
// For maximum compatibility, alpha-numeric value are strongly recommended to be used as Property key.
4854
func NewKeyProperty(key string) (Property, error) {
49-
if !validateKey(key) {
55+
if !validateBaggageName(key) {
5056
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
5157
}
5258

@@ -62,6 +68,10 @@ func NewKeyProperty(key string) (Property, error) {
6268
// Notice: Consider using [NewKeyValuePropertyRaw] instead
6369
// that does not require percent-encoding of the value.
6470
func NewKeyValueProperty(key, value string) (Property, error) {
71+
if !validateKey(key) {
72+
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
73+
}
74+
6575
if !validateValue(value) {
6676
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value)
6777
}
@@ -74,11 +84,20 @@ func NewKeyValueProperty(key, value string) (Property, error) {
7484

7585
// NewKeyValuePropertyRaw returns a new Property for key with value.
7686
//
77-
// The passed key must be compliant with W3C Baggage specification.
87+
// The passed key must be valid, non-empty UTF-8 string.
88+
// The passed value must be valid UTF-8 string.
89+
// However, the specific Propagators that are used to transmit baggage entries across
90+
// component boundaries may impose their own restrictions on Property key.
91+
// For example, the W3C Baggage specification restricts the Property keys to strings that
92+
// satisfy the token definition from RFC7230, Section 3.2.6.
93+
// For maximum compatibility, alpha-numeric value are strongly recommended to be used as Property key.
7894
func NewKeyValuePropertyRaw(key, value string) (Property, error) {
79-
if !validateKey(key) {
95+
if !validateBaggageName(key) {
8096
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
8197
}
98+
if !validateBaggageValue(value) {
99+
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value)
100+
}
82101

83102
p := Property{
84103
key: key,
@@ -115,12 +134,15 @@ func (p Property) validate() error {
115134
return fmt.Errorf("invalid property: %w", err)
116135
}
117136

118-
if !validateKey(p.key) {
137+
if !validateBaggageName(p.key) {
119138
return errFunc(fmt.Errorf("%w: %q", errInvalidKey, p.key))
120139
}
121140
if !p.hasValue && p.value != "" {
122141
return errFunc(errors.New("inconsistent value"))
123142
}
143+
if p.hasValue && !validateBaggageValue(p.value) {
144+
return errFunc(fmt.Errorf("%w: %q", errInvalidValue, p.value))
145+
}
124146
return nil
125147
}
126148

@@ -138,7 +160,15 @@ func (p Property) Value() (string, bool) {
138160

139161
// String encodes Property into a header string compliant with the W3C Baggage
140162
// specification.
163+
// It would return empty string if the key is invalid with the W3C Baggage
164+
// specification. This could happen for a UTF-8 key, as it may contain
165+
// invalid characters.
141166
func (p Property) String() string {
167+
// W3C Baggage specification does not allow percent-encoded keys.
168+
if !validateKey(p.key) {
169+
return ""
170+
}
171+
142172
if p.hasValue {
143173
return fmt.Sprintf("%s%s%v", p.key, keyValueDelimiter, valueEscape(p.value))
144174
}
@@ -203,9 +233,14 @@ func (p properties) validate() error {
203233
// String encodes properties into a header string compliant with the W3C Baggage
204234
// specification.
205235
func (p properties) String() string {
206-
props := make([]string, len(p))
207-
for i, prop := range p {
208-
props[i] = prop.String()
236+
props := make([]string, 0, len(p))
237+
for _, prop := range p {
238+
s := prop.String()
239+
240+
// Ignored empty properties.
241+
if s != "" {
242+
props = append(props, s)
243+
}
209244
}
210245
return strings.Join(props, propertyDelimiter)
211246
}
@@ -230,6 +265,10 @@ type Member struct {
230265
// Notice: Consider using [NewMemberRaw] instead
231266
// that does not require percent-encoding of the value.
232267
func NewMember(key, value string, props ...Property) (Member, error) {
268+
if !validateKey(key) {
269+
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key)
270+
}
271+
233272
if !validateValue(value) {
234273
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
235274
}
@@ -242,7 +281,13 @@ func NewMember(key, value string, props ...Property) (Member, error) {
242281

243282
// NewMemberRaw returns a new Member from the passed arguments.
244283
//
245-
// The passed key must be compliant with W3C Baggage specification.
284+
// The passed key must be valid, non-empty UTF-8 string.
285+
// The passed value must be valid UTF-8 string.
286+
// However, the specific Propagators that are used to transmit baggage entries across
287+
// component boundaries may impose their own restrictions on baggage key.
288+
// For example, the W3C Baggage specification restricts the baggage keys to strings that
289+
// satisfy the token definition from RFC7230, Section 3.2.6.
290+
// For maximum compatibility, alpha-numeric value are strongly recommended to be used as baggage key.
246291
func NewMemberRaw(key, value string, props ...Property) (Member, error) {
247292
m := Member{
248293
key: key,
@@ -340,9 +385,12 @@ func (m Member) validate() error {
340385
return fmt.Errorf("%w: %q", errInvalidMember, m)
341386
}
342387

343-
if !validateKey(m.key) {
388+
if !validateBaggageName(m.key) {
344389
return fmt.Errorf("%w: %q", errInvalidKey, m.key)
345390
}
391+
if !validateBaggageValue(m.value) {
392+
return fmt.Errorf("%w: %q", errInvalidValue, m.value)
393+
}
346394
return m.properties.validate()
347395
}
348396

@@ -357,10 +405,15 @@ func (m Member) Properties() []Property { return m.properties.Copy() }
357405

358406
// String encodes Member into a header string compliant with the W3C Baggage
359407
// specification.
408+
// It would return empty string if the key is invalid with the W3C Baggage
409+
// specification. This could happen for a UTF-8 key, as it may contain
410+
// invalid characters.
360411
func (m Member) String() string {
361-
// A key is just an ASCII string. A value is restricted to be
362-
// US-ASCII characters excluding CTLs, whitespace,
363-
// DQUOTE, comma, semicolon, and backslash.
412+
// W3C Baggage specification does not allow percent-encoded keys.
413+
if !validateKey(m.key) {
414+
return ""
415+
}
416+
364417
s := m.key + keyValueDelimiter + valueEscape(m.value)
365418
if len(m.properties) > 0 {
366419
s += propertyDelimiter + m.properties.String()
@@ -554,14 +607,22 @@ func (b Baggage) Len() int {
554607

555608
// String encodes Baggage into a header string compliant with the W3C Baggage
556609
// specification.
610+
// It would ignore members where the member key is invalid with the W3C Baggage
611+
// specification. This could happen for a UTF-8 key, as it may contain
612+
// invalid characters.
557613
func (b Baggage) String() string {
558614
members := make([]string, 0, len(b.list))
559615
for k, v := range b.list {
560-
members = append(members, Member{
616+
s := Member{
561617
key: k,
562618
value: v.Value,
563619
properties: fromInternalProperties(v.Properties),
564-
}.String())
620+
}.String()
621+
622+
// Ignored empty members.
623+
if s != "" {
624+
members = append(members, s)
625+
}
565626
}
566627
return strings.Join(members, listDelimiter)
567628
}
@@ -748,6 +809,24 @@ var safeKeyCharset = [utf8.RuneSelf]bool{
748809
'~': true,
749810
}
750811

812+
// validateBaggageName checks if the string is a valid OpenTelemetry Baggage name.
813+
// Baggage name is a valid, non-empty UTF-8 string.
814+
func validateBaggageName(s string) bool {
815+
if len(s) == 0 {
816+
return false
817+
}
818+
819+
return utf8.ValidString(s)
820+
}
821+
822+
// validateBaggageValue checks if the string is a valid OpenTelemetry Baggage value.
823+
// Baggage value is a valid UTF-8 strings.
824+
// Empty string is also a valid UTF-8 string.
825+
func validateBaggageValue(s string) bool {
826+
return utf8.ValidString(s)
827+
}
828+
829+
// validateKey checks if the string is a valid W3C Baggage key.
751830
func validateKey(s string) bool {
752831
if len(s) == 0 {
753832
return false
@@ -766,6 +845,7 @@ func validateKeyChar(c int32) bool {
766845
return c >= 0 && c < int32(utf8.RuneSelf) && safeKeyCharset[c]
767846
}
768847

848+
// validateValue checks if the string is a valid W3C Baggage value.
769849
func validateValue(s string) bool {
770850
for _, c := range s {
771851
if !validateValueChar(c) {

0 commit comments

Comments
 (0)