1// Copyright The OpenTelemetry Authors
2// SPDX-License-Identifier: Apache-2.0
3
4package baggage // import "go.opentelemetry.io/otel/baggage"
5
6import (
7 "errors"
8 "fmt"
9 "net/url"
10 "strings"
11 "unicode/utf8"
12
13 "go.opentelemetry.io/otel/internal/baggage"
14)
15
16const (
17 maxMembers = 180
18 maxBytesPerMembers = 4096
19 maxBytesPerBaggageString = 8192
20
21 listDelimiter = ","
22 keyValueDelimiter = "="
23 propertyDelimiter = ";"
24)
25
26var (
27 errInvalidKey = errors.New("invalid key")
28 errInvalidValue = errors.New("invalid value")
29 errInvalidProperty = errors.New("invalid baggage list-member property")
30 errInvalidMember = errors.New("invalid baggage list-member")
31 errMemberNumber = errors.New("too many list-members in baggage-string")
32 errMemberBytes = errors.New("list-member too large")
33 errBaggageBytes = errors.New("baggage-string too large")
34)
35
36// Property is an additional metadata entry for a baggage list-member.
37type Property struct {
38 key, value string
39
40 // hasValue indicates if a zero-value value means the property does not
41 // have a value or if it was the zero-value.
42 hasValue bool
43}
44
45// NewKeyProperty returns a new Property for key.
46//
47// The passed key must be valid, non-empty UTF-8 string.
48// 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, alphanumeric value are strongly recommended to be used as Property key.
54func NewKeyProperty(key string) (Property, error) {
55 if !validateBaggageName(key) {
56 return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
57 }
58
59 p := Property{key: key}
60 return p, nil
61}
62
63// NewKeyValueProperty returns a new Property for key with value.
64//
65// The passed key must be compliant with W3C Baggage specification.
66// The passed value must be percent-encoded as defined in W3C Baggage specification.
67//
68// Notice: Consider using [NewKeyValuePropertyRaw] instead
69// that does not require percent-encoding of the value.
70func NewKeyValueProperty(key, value string) (Property, error) {
71 if !validateKey(key) {
72 return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
73 }
74
75 if !validateValue(value) {
76 return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value)
77 }
78 decodedValue, err := url.PathUnescape(value)
79 if err != nil {
80 return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value)
81 }
82 return NewKeyValuePropertyRaw(key, decodedValue)
83}
84
85// NewKeyValuePropertyRaw returns a new Property for key with value.
86//
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, alphanumeric value are strongly recommended to be used as Property key.
94func NewKeyValuePropertyRaw(key, value string) (Property, error) {
95 if !validateBaggageName(key) {
96 return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
97 }
98 if !validateBaggageValue(value) {
99 return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value)
100 }
101
102 p := Property{
103 key: key,
104 value: value,
105 hasValue: true,
106 }
107 return p, nil
108}
109
110func newInvalidProperty() Property {
111 return Property{}
112}
113
114// parseProperty attempts to decode a Property from the passed string. It
115// returns an error if the input is invalid according to the W3C Baggage
116// specification.
117func parseProperty(property string) (Property, error) {
118 if property == "" {
119 return newInvalidProperty(), nil
120 }
121
122 p, ok := parsePropertyInternal(property)
123 if !ok {
124 return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidProperty, property)
125 }
126
127 return p, nil
128}
129
130// validate ensures p conforms to the W3C Baggage specification, returning an
131// error otherwise.
132func (p Property) validate() error {
133 errFunc := func(err error) error {
134 return fmt.Errorf("invalid property: %w", err)
135 }
136
137 if !validateBaggageName(p.key) {
138 return errFunc(fmt.Errorf("%w: %q", errInvalidKey, p.key))
139 }
140 if !p.hasValue && p.value != "" {
141 return errFunc(errors.New("inconsistent value"))
142 }
143 if p.hasValue && !validateBaggageValue(p.value) {
144 return errFunc(fmt.Errorf("%w: %q", errInvalidValue, p.value))
145 }
146 return nil
147}
148
149// Key returns the Property key.
150func (p Property) Key() string {
151 return p.key
152}
153
154// Value returns the Property value. Additionally, a boolean value is returned
155// indicating if the returned value is the empty if the Property has a value
156// that is empty or if the value is not set.
157func (p Property) Value() (string, bool) {
158 return p.value, p.hasValue
159}
160
161// String encodes Property into a header string compliant with the W3C Baggage
162// 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.
166func (p Property) String() string {
167 // W3C Baggage specification does not allow percent-encoded keys.
168 if !validateKey(p.key) {
169 return ""
170 }
171
172 if p.hasValue {
173 return fmt.Sprintf("%s%s%v", p.key, keyValueDelimiter, valueEscape(p.value))
174 }
175 return p.key
176}
177
178type properties []Property
179
180func fromInternalProperties(iProps []baggage.Property) properties {
181 if len(iProps) == 0 {
182 return nil
183 }
184
185 props := make(properties, len(iProps))
186 for i, p := range iProps {
187 props[i] = Property{
188 key: p.Key,
189 value: p.Value,
190 hasValue: p.HasValue,
191 }
192 }
193 return props
194}
195
196func (p properties) asInternal() []baggage.Property {
197 if len(p) == 0 {
198 return nil
199 }
200
201 iProps := make([]baggage.Property, len(p))
202 for i, prop := range p {
203 iProps[i] = baggage.Property{
204 Key: prop.key,
205 Value: prop.value,
206 HasValue: prop.hasValue,
207 }
208 }
209 return iProps
210}
211
212func (p properties) Copy() properties {
213 if len(p) == 0 {
214 return nil
215 }
216
217 props := make(properties, len(p))
218 copy(props, p)
219 return props
220}
221
222// validate ensures each Property in p conforms to the W3C Baggage
223// specification, returning an error otherwise.
224func (p properties) validate() error {
225 for _, prop := range p {
226 if err := prop.validate(); err != nil {
227 return err
228 }
229 }
230 return nil
231}
232
233// String encodes properties into a header string compliant with the W3C Baggage
234// specification.
235func (p properties) String() 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 }
244 }
245 return strings.Join(props, propertyDelimiter)
246}
247
248// Member is a list-member of a baggage-string as defined by the W3C Baggage
249// specification.
250type Member struct {
251 key, value string
252 properties properties
253
254 // hasData indicates whether the created property contains data or not.
255 // Properties that do not contain data are invalid with no other check
256 // required.
257 hasData bool
258}
259
260// NewMember returns a new Member from the passed arguments.
261//
262// The passed key must be compliant with W3C Baggage specification.
263// The passed value must be percent-encoded as defined in W3C Baggage specification.
264//
265// Notice: Consider using [NewMemberRaw] instead
266// that does not require percent-encoding of the value.
267func NewMember(key, value string, props ...Property) (Member, error) {
268 if !validateKey(key) {
269 return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key)
270 }
271
272 if !validateValue(value) {
273 return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
274 }
275 decodedValue, err := url.PathUnescape(value)
276 if err != nil {
277 return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
278 }
279 return NewMemberRaw(key, decodedValue, props...)
280}
281
282// NewMemberRaw returns a new Member from the passed arguments.
283//
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, alphanumeric value are strongly recommended to be used as baggage key.
291func NewMemberRaw(key, value string, props ...Property) (Member, error) {
292 m := Member{
293 key: key,
294 value: value,
295 properties: properties(props).Copy(),
296 hasData: true,
297 }
298 if err := m.validate(); err != nil {
299 return newInvalidMember(), err
300 }
301 return m, nil
302}
303
304func newInvalidMember() Member {
305 return Member{}
306}
307
308// parseMember attempts to decode a Member from the passed string. It returns
309// an error if the input is invalid according to the W3C Baggage
310// specification.
311func parseMember(member string) (Member, error) {
312 if n := len(member); n > maxBytesPerMembers {
313 return newInvalidMember(), fmt.Errorf("%w: %d", errMemberBytes, n)
314 }
315
316 var props properties
317 keyValue, properties, found := strings.Cut(member, propertyDelimiter)
318 if found {
319 // Parse the member properties.
320 for _, pStr := range strings.Split(properties, propertyDelimiter) {
321 p, err := parseProperty(pStr)
322 if err != nil {
323 return newInvalidMember(), err
324 }
325 props = append(props, p)
326 }
327 }
328 // Parse the member key/value pair.
329
330 // Take into account a value can contain equal signs (=).
331 k, v, found := strings.Cut(keyValue, keyValueDelimiter)
332 if !found {
333 return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidMember, member)
334 }
335 // "Leading and trailing whitespaces are allowed but MUST be trimmed
336 // when converting the header into a data structure."
337 key := strings.TrimSpace(k)
338 if !validateKey(key) {
339 return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key)
340 }
341
342 rawVal := strings.TrimSpace(v)
343 if !validateValue(rawVal) {
344 return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, v)
345 }
346
347 // Decode a percent-encoded value.
348 unescapeVal, err := url.PathUnescape(rawVal)
349 if err != nil {
350 return newInvalidMember(), fmt.Errorf("%w: %w", errInvalidValue, err)
351 }
352
353 value := replaceInvalidUTF8Sequences(len(rawVal), unescapeVal)
354 return Member{key: key, value: value, properties: props, hasData: true}, nil
355}
356
357// replaceInvalidUTF8Sequences replaces invalid UTF-8 sequences with '�'.
358func replaceInvalidUTF8Sequences(c int, unescapeVal string) string {
359 if utf8.ValidString(unescapeVal) {
360 return unescapeVal
361 }
362 // W3C baggage spec:
363 // https://github.com/w3c/baggage/blob/8c215efbeebd3fa4b1aceb937a747e56444f22f3/baggage/HTTP_HEADER_FORMAT.md?plain=1#L69
364
365 var b strings.Builder
366 b.Grow(c)
367 for i := 0; i < len(unescapeVal); {
368 r, size := utf8.DecodeRuneInString(unescapeVal[i:])
369 if r == utf8.RuneError && size == 1 {
370 // Invalid UTF-8 sequence found, replace it with '�'
371 _, _ = b.WriteString("�")
372 } else {
373 _, _ = b.WriteRune(r)
374 }
375 i += size
376 }
377
378 return b.String()
379}
380
381// validate ensures m conforms to the W3C Baggage specification.
382// A key must be an ASCII string, returning an error otherwise.
383func (m Member) validate() error {
384 if !m.hasData {
385 return fmt.Errorf("%w: %q", errInvalidMember, m)
386 }
387
388 if !validateBaggageName(m.key) {
389 return fmt.Errorf("%w: %q", errInvalidKey, m.key)
390 }
391 if !validateBaggageValue(m.value) {
392 return fmt.Errorf("%w: %q", errInvalidValue, m.value)
393 }
394 return m.properties.validate()
395}
396
397// Key returns the Member key.
398func (m Member) Key() string { return m.key }
399
400// Value returns the Member value.
401func (m Member) Value() string { return m.value }
402
403// Properties returns a copy of the Member properties.
404func (m Member) Properties() []Property { return m.properties.Copy() }
405
406// String encodes Member into a header string compliant with the W3C Baggage
407// 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.
411func (m Member) String() string {
412 // W3C Baggage specification does not allow percent-encoded keys.
413 if !validateKey(m.key) {
414 return ""
415 }
416
417 s := m.key + keyValueDelimiter + valueEscape(m.value)
418 if len(m.properties) > 0 {
419 s += propertyDelimiter + m.properties.String()
420 }
421 return s
422}
423
424// Baggage is a list of baggage members representing the baggage-string as
425// defined by the W3C Baggage specification.
426type Baggage struct { //nolint:golint
427 list baggage.List
428}
429
430// New returns a new valid Baggage. It returns an error if it results in a
431// Baggage exceeding limits set in that specification.
432//
433// It expects all the provided members to have already been validated.
434func New(members ...Member) (Baggage, error) {
435 if len(members) == 0 {
436 return Baggage{}, nil
437 }
438
439 b := make(baggage.List)
440 for _, m := range members {
441 if !m.hasData {
442 return Baggage{}, errInvalidMember
443 }
444
445 // OpenTelemetry resolves duplicates by last-one-wins.
446 b[m.key] = baggage.Item{
447 Value: m.value,
448 Properties: m.properties.asInternal(),
449 }
450 }
451
452 // Check member numbers after deduplication.
453 if len(b) > maxMembers {
454 return Baggage{}, errMemberNumber
455 }
456
457 bag := Baggage{b}
458 if n := len(bag.String()); n > maxBytesPerBaggageString {
459 return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
460 }
461
462 return bag, nil
463}
464
465// Parse attempts to decode a baggage-string from the passed string. It
466// returns an error if the input is invalid according to the W3C Baggage
467// specification.
468//
469// If there are duplicate list-members contained in baggage, the last one
470// defined (reading left-to-right) will be the only one kept. This diverges
471// from the W3C Baggage specification which allows duplicate list-members, but
472// conforms to the OpenTelemetry Baggage specification.
473func Parse(bStr string) (Baggage, error) {
474 if bStr == "" {
475 return Baggage{}, nil
476 }
477
478 if n := len(bStr); n > maxBytesPerBaggageString {
479 return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
480 }
481
482 b := make(baggage.List)
483 for _, memberStr := range strings.Split(bStr, listDelimiter) {
484 m, err := parseMember(memberStr)
485 if err != nil {
486 return Baggage{}, err
487 }
488 // OpenTelemetry resolves duplicates by last-one-wins.
489 b[m.key] = baggage.Item{
490 Value: m.value,
491 Properties: m.properties.asInternal(),
492 }
493 }
494
495 // OpenTelemetry does not allow for duplicate list-members, but the W3C
496 // specification does. Now that we have deduplicated, ensure the baggage
497 // does not exceed list-member limits.
498 if len(b) > maxMembers {
499 return Baggage{}, errMemberNumber
500 }
501
502 return Baggage{b}, nil
503}
504
505// Member returns the baggage list-member identified by key.
506//
507// If there is no list-member matching the passed key the returned Member will
508// be a zero-value Member.
509// The returned member is not validated, as we assume the validation happened
510// when it was added to the Baggage.
511func (b Baggage) Member(key string) Member {
512 v, ok := b.list[key]
513 if !ok {
514 // We do not need to worry about distinguishing between the situation
515 // where a zero-valued Member is included in the Baggage because a
516 // zero-valued Member is invalid according to the W3C Baggage
517 // specification (it has an empty key).
518 return newInvalidMember()
519 }
520
521 return Member{
522 key: key,
523 value: v.Value,
524 properties: fromInternalProperties(v.Properties),
525 hasData: true,
526 }
527}
528
529// Members returns all the baggage list-members.
530// The order of the returned list-members is not significant.
531//
532// The returned members are not validated, as we assume the validation happened
533// when they were added to the Baggage.
534func (b Baggage) Members() []Member {
535 if len(b.list) == 0 {
536 return nil
537 }
538
539 members := make([]Member, 0, len(b.list))
540 for k, v := range b.list {
541 members = append(members, Member{
542 key: k,
543 value: v.Value,
544 properties: fromInternalProperties(v.Properties),
545 hasData: true,
546 })
547 }
548 return members
549}
550
551// SetMember returns a copy of the Baggage with the member included. If the
552// baggage contains a Member with the same key, the existing Member is
553// replaced.
554//
555// If member is invalid according to the W3C Baggage specification, an error
556// is returned with the original Baggage.
557func (b Baggage) SetMember(member Member) (Baggage, error) {
558 if !member.hasData {
559 return b, errInvalidMember
560 }
561
562 n := len(b.list)
563 if _, ok := b.list[member.key]; !ok {
564 n++
565 }
566 list := make(baggage.List, n)
567
568 for k, v := range b.list {
569 // Do not copy if we are just going to overwrite.
570 if k == member.key {
571 continue
572 }
573 list[k] = v
574 }
575
576 list[member.key] = baggage.Item{
577 Value: member.value,
578 Properties: member.properties.asInternal(),
579 }
580
581 return Baggage{list: list}, nil
582}
583
584// DeleteMember returns a copy of the Baggage with the list-member identified
585// by key removed.
586func (b Baggage) DeleteMember(key string) Baggage {
587 n := len(b.list)
588 if _, ok := b.list[key]; ok {
589 n--
590 }
591 list := make(baggage.List, n)
592
593 for k, v := range b.list {
594 if k == key {
595 continue
596 }
597 list[k] = v
598 }
599
600 return Baggage{list: list}
601}
602
603// Len returns the number of list-members in the Baggage.
604func (b Baggage) Len() int {
605 return len(b.list)
606}
607
608// String encodes Baggage into a header string compliant with the W3C Baggage
609// 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.
613func (b Baggage) String() string {
614 members := make([]string, 0, len(b.list))
615 for k, v := range b.list {
616 s := Member{
617 key: k,
618 value: v.Value,
619 properties: fromInternalProperties(v.Properties),
620 }.String()
621
622 // Ignored empty members.
623 if s != "" {
624 members = append(members, s)
625 }
626 }
627 return strings.Join(members, listDelimiter)
628}
629
630// parsePropertyInternal attempts to decode a Property from the passed string.
631// It follows the spec at https://www.w3.org/TR/baggage/#definition.
632func parsePropertyInternal(s string) (p Property, ok bool) {
633 // For the entire function we will use " key = value " as an example.
634 // Attempting to parse the key.
635 // First skip spaces at the beginning "< >key = value " (they could be empty).
636 index := skipSpace(s, 0)
637
638 // Parse the key: " <key> = value ".
639 keyStart := index
640 keyEnd := index
641 for _, c := range s[keyStart:] {
642 if !validateKeyChar(c) {
643 break
644 }
645 keyEnd++
646 }
647
648 // If we couldn't find any valid key character,
649 // it means the key is either empty or invalid.
650 if keyStart == keyEnd {
651 return
652 }
653
654 // Skip spaces after the key: " key< >= value ".
655 index = skipSpace(s, keyEnd)
656
657 if index == len(s) {
658 // A key can have no value, like: " key ".
659 ok = true
660 p.key = s[keyStart:keyEnd]
661 return
662 }
663
664 // If we have not reached the end and we can't find the '=' delimiter,
665 // it means the property is invalid.
666 if s[index] != keyValueDelimiter[0] {
667 return
668 }
669
670 // Attempting to parse the value.
671 // Match: " key =< >value ".
672 index = skipSpace(s, index+1)
673
674 // Match the value string: " key = <value> ".
675 // A valid property can be: " key =".
676 // Therefore, we don't have to check if the value is empty.
677 valueStart := index
678 valueEnd := index
679 for _, c := range s[valueStart:] {
680 if !validateValueChar(c) {
681 break
682 }
683 valueEnd++
684 }
685
686 // Skip all trailing whitespaces: " key = value< >".
687 index = skipSpace(s, valueEnd)
688
689 // If after looking for the value and skipping whitespaces
690 // we have not reached the end, it means the property is
691 // invalid, something like: " key = value value1".
692 if index != len(s) {
693 return
694 }
695
696 // Decode a percent-encoded value.
697 rawVal := s[valueStart:valueEnd]
698 unescapeVal, err := url.PathUnescape(rawVal)
699 if err != nil {
700 return
701 }
702 value := replaceInvalidUTF8Sequences(len(rawVal), unescapeVal)
703
704 ok = true
705 p.key = s[keyStart:keyEnd]
706 p.hasValue = true
707
708 p.value = value
709 return
710}
711
712func skipSpace(s string, offset int) int {
713 i := offset
714 for ; i < len(s); i++ {
715 c := s[i]
716 if c != ' ' && c != '\t' {
717 break
718 }
719 }
720 return i
721}
722
723var safeKeyCharset = [utf8.RuneSelf]bool{
724 // 0x23 to 0x27
725 '#': true,
726 '$': true,
727 '%': true,
728 '&': true,
729 '\'': true,
730
731 // 0x30 to 0x39
732 '0': true,
733 '1': true,
734 '2': true,
735 '3': true,
736 '4': true,
737 '5': true,
738 '6': true,
739 '7': true,
740 '8': true,
741 '9': true,
742
743 // 0x41 to 0x5a
744 'A': true,
745 'B': true,
746 'C': true,
747 'D': true,
748 'E': true,
749 'F': true,
750 'G': true,
751 'H': true,
752 'I': true,
753 'J': true,
754 'K': true,
755 'L': true,
756 'M': true,
757 'N': true,
758 'O': true,
759 'P': true,
760 'Q': true,
761 'R': true,
762 'S': true,
763 'T': true,
764 'U': true,
765 'V': true,
766 'W': true,
767 'X': true,
768 'Y': true,
769 'Z': true,
770
771 // 0x5e to 0x7a
772 '^': true,
773 '_': true,
774 '`': true,
775 'a': true,
776 'b': true,
777 'c': true,
778 'd': true,
779 'e': true,
780 'f': true,
781 'g': true,
782 'h': true,
783 'i': true,
784 'j': true,
785 'k': true,
786 'l': true,
787 'm': true,
788 'n': true,
789 'o': true,
790 'p': true,
791 'q': true,
792 'r': true,
793 's': true,
794 't': true,
795 'u': true,
796 'v': true,
797 'w': true,
798 'x': true,
799 'y': true,
800 'z': true,
801
802 // remainder
803 '!': true,
804 '*': true,
805 '+': true,
806 '-': true,
807 '.': true,
808 '|': true,
809 '~': true,
810}
811
812// validateBaggageName checks if the string is a valid OpenTelemetry Baggage name.
813// Baggage name is a valid, non-empty UTF-8 string.
814func 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.
825func validateBaggageValue(s string) bool {
826 return utf8.ValidString(s)
827}
828
829// validateKey checks if the string is a valid W3C Baggage key.
830func validateKey(s string) bool {
831 if len(s) == 0 {
832 return false
833 }
834
835 for _, c := range s {
836 if !validateKeyChar(c) {
837 return false
838 }
839 }
840
841 return true
842}
843
844func validateKeyChar(c int32) bool {
845 return c >= 0 && c < int32(utf8.RuneSelf) && safeKeyCharset[c]
846}
847
848// validateValue checks if the string is a valid W3C Baggage value.
849func validateValue(s string) bool {
850 for _, c := range s {
851 if !validateValueChar(c) {
852 return false
853 }
854 }
855
856 return true
857}
858
859var safeValueCharset = [utf8.RuneSelf]bool{
860 '!': true, // 0x21
861
862 // 0x23 to 0x2b
863 '#': true,
864 '$': true,
865 '%': true,
866 '&': true,
867 '\'': true,
868 '(': true,
869 ')': true,
870 '*': true,
871 '+': true,
872
873 // 0x2d to 0x3a
874 '-': true,
875 '.': true,
876 '/': true,
877 '0': true,
878 '1': true,
879 '2': true,
880 '3': true,
881 '4': true,
882 '5': true,
883 '6': true,
884 '7': true,
885 '8': true,
886 '9': true,
887 ':': true,
888
889 // 0x3c to 0x5b
890 '<': true, // 0x3C
891 '=': true, // 0x3D
892 '>': true, // 0x3E
893 '?': true, // 0x3F
894 '@': true, // 0x40
895 'A': true, // 0x41
896 'B': true, // 0x42
897 'C': true, // 0x43
898 'D': true, // 0x44
899 'E': true, // 0x45
900 'F': true, // 0x46
901 'G': true, // 0x47
902 'H': true, // 0x48
903 'I': true, // 0x49
904 'J': true, // 0x4A
905 'K': true, // 0x4B
906 'L': true, // 0x4C
907 'M': true, // 0x4D
908 'N': true, // 0x4E
909 'O': true, // 0x4F
910 'P': true, // 0x50
911 'Q': true, // 0x51
912 'R': true, // 0x52
913 'S': true, // 0x53
914 'T': true, // 0x54
915 'U': true, // 0x55
916 'V': true, // 0x56
917 'W': true, // 0x57
918 'X': true, // 0x58
919 'Y': true, // 0x59
920 'Z': true, // 0x5A
921 '[': true, // 0x5B
922
923 // 0x5d to 0x7e
924 ']': true, // 0x5D
925 '^': true, // 0x5E
926 '_': true, // 0x5F
927 '`': true, // 0x60
928 'a': true, // 0x61
929 'b': true, // 0x62
930 'c': true, // 0x63
931 'd': true, // 0x64
932 'e': true, // 0x65
933 'f': true, // 0x66
934 'g': true, // 0x67
935 'h': true, // 0x68
936 'i': true, // 0x69
937 'j': true, // 0x6A
938 'k': true, // 0x6B
939 'l': true, // 0x6C
940 'm': true, // 0x6D
941 'n': true, // 0x6E
942 'o': true, // 0x6F
943 'p': true, // 0x70
944 'q': true, // 0x71
945 'r': true, // 0x72
946 's': true, // 0x73
947 't': true, // 0x74
948 'u': true, // 0x75
949 'v': true, // 0x76
950 'w': true, // 0x77
951 'x': true, // 0x78
952 'y': true, // 0x79
953 'z': true, // 0x7A
954 '{': true, // 0x7B
955 '|': true, // 0x7C
956 '}': true, // 0x7D
957 '~': true, // 0x7E
958}
959
960func validateValueChar(c int32) bool {
961 return c >= 0 && c < int32(utf8.RuneSelf) && safeValueCharset[c]
962}
963
964// valueEscape escapes the string so it can be safely placed inside a baggage value,
965// replacing special characters with %XX sequences as needed.
966//
967// The implementation is based on:
968// https://github.com/golang/go/blob/f6509cf5cdbb5787061b784973782933c47f1782/src/net/url/url.go#L285.
969func valueEscape(s string) string {
970 hexCount := 0
971 for i := 0; i < len(s); i++ {
972 c := s[i]
973 if shouldEscape(c) {
974 hexCount++
975 }
976 }
977
978 if hexCount == 0 {
979 return s
980 }
981
982 var buf [64]byte
983 var t []byte
984
985 required := len(s) + 2*hexCount
986 if required <= len(buf) {
987 t = buf[:required]
988 } else {
989 t = make([]byte, required)
990 }
991
992 j := 0
993 for i := 0; i < len(s); i++ {
994 c := s[i]
995 if shouldEscape(s[i]) {
996 const upperhex = "0123456789ABCDEF"
997 t[j] = '%'
998 t[j+1] = upperhex[c>>4]
999 t[j+2] = upperhex[c&15]
1000 j += 3
1001 } else {
1002 t[j] = c
1003 j++
1004 }
1005 }
1006
1007 return string(t)
1008}
1009
1010// shouldEscape returns true if the specified byte should be escaped when
1011// appearing in a baggage value string.
1012func shouldEscape(c byte) bool {
1013 if c == '%' {
1014 // The percent character must be encoded so that percent-encoding can work.
1015 return true
1016 }
1017 return !validateValueChar(int32(c))
1018}