| // Copyright The OpenTelemetry Authors |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| package baggage // import "go.opentelemetry.io/otel/baggage" |
| |
| import ( |
| "errors" |
| "fmt" |
| "net/url" |
| "strings" |
| "unicode/utf8" |
| |
| "go.opentelemetry.io/otel/internal/baggage" |
| ) |
| |
| const ( |
| maxMembers = 180 |
| maxBytesPerMembers = 4096 |
| maxBytesPerBaggageString = 8192 |
| |
| listDelimiter = "," |
| keyValueDelimiter = "=" |
| propertyDelimiter = ";" |
| ) |
| |
| var ( |
| errInvalidKey = errors.New("invalid key") |
| errInvalidValue = errors.New("invalid value") |
| errInvalidProperty = errors.New("invalid baggage list-member property") |
| errInvalidMember = errors.New("invalid baggage list-member") |
| errMemberNumber = errors.New("too many list-members in baggage-string") |
| errMemberBytes = errors.New("list-member too large") |
| errBaggageBytes = errors.New("baggage-string too large") |
| ) |
| |
| // Property is an additional metadata entry for a baggage list-member. |
| type Property struct { |
| key, value string |
| |
| // hasValue indicates if a zero-value value means the property does not |
| // have a value or if it was the zero-value. |
| hasValue bool |
| } |
| |
| // NewKeyProperty returns a new Property for key. |
| // |
| // The passed key must be valid, non-empty UTF-8 string. |
| // If key is invalid, an error will be returned. |
| // However, the specific Propagators that are used to transmit baggage entries across |
| // component boundaries may impose their own restrictions on Property key. |
| // For example, the W3C Baggage specification restricts the Property keys to strings that |
| // satisfy the token definition from RFC7230, Section 3.2.6. |
| // For maximum compatibility, alphanumeric value are strongly recommended to be used as Property key. |
| func NewKeyProperty(key string) (Property, error) { |
| if !validateBaggageName(key) { |
| return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key) |
| } |
| |
| p := Property{key: key} |
| return p, nil |
| } |
| |
| // NewKeyValueProperty returns a new Property for key with value. |
| // |
| // The passed key must be compliant with W3C Baggage specification. |
| // The passed value must be percent-encoded as defined in W3C Baggage specification. |
| // |
| // Notice: Consider using [NewKeyValuePropertyRaw] instead |
| // that does not require percent-encoding of the value. |
| func NewKeyValueProperty(key, value string) (Property, error) { |
| if !validateKey(key) { |
| return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key) |
| } |
| |
| if !validateValue(value) { |
| return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value) |
| } |
| decodedValue, err := url.PathUnescape(value) |
| if err != nil { |
| return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value) |
| } |
| return NewKeyValuePropertyRaw(key, decodedValue) |
| } |
| |
| // NewKeyValuePropertyRaw returns a new Property for key with value. |
| // |
| // The passed key must be valid, non-empty UTF-8 string. |
| // The passed 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 Property key. |
| // For example, the W3C Baggage specification restricts the Property keys to strings that |
| // satisfy the token definition from RFC7230, Section 3.2.6. |
| // For maximum compatibility, alphanumeric value are strongly recommended to be used as Property key. |
| func NewKeyValuePropertyRaw(key, value string) (Property, error) { |
| if !validateBaggageName(key) { |
| return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key) |
| } |
| if !validateBaggageValue(value) { |
| return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value) |
| } |
| |
| p := Property{ |
| key: key, |
| value: value, |
| hasValue: true, |
| } |
| return p, nil |
| } |
| |
| func newInvalidProperty() Property { |
| return Property{} |
| } |
| |
| // parseProperty attempts to decode a Property from the passed string. It |
| // returns an error if the input is invalid according to the W3C Baggage |
| // specification. |
| func parseProperty(property string) (Property, error) { |
| if property == "" { |
| return newInvalidProperty(), nil |
| } |
| |
| p, ok := parsePropertyInternal(property) |
| if !ok { |
| return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidProperty, property) |
| } |
| |
| return p, nil |
| } |
| |
| // validate ensures p conforms to the W3C Baggage specification, returning an |
| // error otherwise. |
| func (p Property) validate() error { |
| errFunc := func(err error) error { |
| return fmt.Errorf("invalid property: %w", err) |
| } |
| |
| if !validateBaggageName(p.key) { |
| return errFunc(fmt.Errorf("%w: %q", errInvalidKey, p.key)) |
| } |
| if !p.hasValue && p.value != "" { |
| return errFunc(errors.New("inconsistent value")) |
| } |
| if p.hasValue && !validateBaggageValue(p.value) { |
| return errFunc(fmt.Errorf("%w: %q", errInvalidValue, p.value)) |
| } |
| return nil |
| } |
| |
| // Key returns the Property key. |
| func (p Property) Key() string { |
| return p.key |
| } |
| |
| // Value returns the Property value. Additionally, a boolean value is returned |
| // indicating if the returned value is the empty if the Property has a value |
| // that is empty or if the value is not set. |
| func (p Property) Value() (string, bool) { |
| return p.value, p.hasValue |
| } |
| |
| // String encodes Property into a header string compliant with the W3C Baggage |
| // specification. |
| // It would return empty string if the key is invalid with the W3C Baggage |
| // specification. This could happen for a UTF-8 key, as it may contain |
| // invalid characters. |
| func (p Property) String() string { |
| // W3C Baggage specification does not allow percent-encoded keys. |
| if !validateKey(p.key) { |
| return "" |
| } |
| |
| if p.hasValue { |
| return fmt.Sprintf("%s%s%v", p.key, keyValueDelimiter, valueEscape(p.value)) |
| } |
| return p.key |
| } |
| |
| type properties []Property |
| |
| func fromInternalProperties(iProps []baggage.Property) properties { |
| if len(iProps) == 0 { |
| return nil |
| } |
| |
| props := make(properties, len(iProps)) |
| for i, p := range iProps { |
| props[i] = Property{ |
| key: p.Key, |
| value: p.Value, |
| hasValue: p.HasValue, |
| } |
| } |
| return props |
| } |
| |
| func (p properties) asInternal() []baggage.Property { |
| if len(p) == 0 { |
| return nil |
| } |
| |
| iProps := make([]baggage.Property, len(p)) |
| for i, prop := range p { |
| iProps[i] = baggage.Property{ |
| Key: prop.key, |
| Value: prop.value, |
| HasValue: prop.hasValue, |
| } |
| } |
| return iProps |
| } |
| |
| func (p properties) Copy() properties { |
| if len(p) == 0 { |
| return nil |
| } |
| |
| props := make(properties, len(p)) |
| copy(props, p) |
| return props |
| } |
| |
| // validate ensures each Property in p conforms to the W3C Baggage |
| // specification, returning an error otherwise. |
| func (p properties) validate() error { |
| for _, prop := range p { |
| if err := prop.validate(); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // String encodes properties into a header string compliant with the W3C Baggage |
| // specification. |
| func (p properties) String() string { |
| props := make([]string, 0, len(p)) |
| for _, prop := range p { |
| s := prop.String() |
| |
| // Ignored empty properties. |
| if s != "" { |
| props = append(props, s) |
| } |
| } |
| return strings.Join(props, propertyDelimiter) |
| } |
| |
| // Member is a list-member of a baggage-string as defined by the W3C Baggage |
| // specification. |
| type Member struct { |
| key, value string |
| properties properties |
| |
| // hasData indicates whether the created property contains data or not. |
| // Properties that do not contain data are invalid with no other check |
| // required. |
| hasData bool |
| } |
| |
| // NewMember returns a new Member from the passed arguments. |
| // |
| // The passed key must be compliant with W3C Baggage specification. |
| // The passed value must be percent-encoded as defined in W3C Baggage specification. |
| // |
| // Notice: Consider using [NewMemberRaw] instead |
| // that does not require percent-encoding of the value. |
| func NewMember(key, value string, props ...Property) (Member, error) { |
| if !validateKey(key) { |
| return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key) |
| } |
| |
| if !validateValue(value) { |
| return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value) |
| } |
| decodedValue, err := url.PathUnescape(value) |
| if err != nil { |
| return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value) |
| } |
| return NewMemberRaw(key, decodedValue, props...) |
| } |
| |
| // NewMemberRaw returns a new Member from the passed arguments. |
| // |
| // The passed key must be valid, non-empty UTF-8 string. |
| // The passed 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, alphanumeric value are strongly recommended to be used as baggage key. |
| func NewMemberRaw(key, value string, props ...Property) (Member, error) { |
| m := Member{ |
| key: key, |
| value: value, |
| properties: properties(props).Copy(), |
| hasData: true, |
| } |
| if err := m.validate(); err != nil { |
| return newInvalidMember(), err |
| } |
| return m, nil |
| } |
| |
| func newInvalidMember() Member { |
| return Member{} |
| } |
| |
| // parseMember attempts to decode a Member from the passed string. It returns |
| // an error if the input is invalid according to the W3C Baggage |
| // specification. |
| func parseMember(member string) (Member, error) { |
| if n := len(member); n > maxBytesPerMembers { |
| return newInvalidMember(), fmt.Errorf("%w: %d", errMemberBytes, n) |
| } |
| |
| var props properties |
| keyValue, properties, found := strings.Cut(member, propertyDelimiter) |
| if found { |
| // Parse the member properties. |
| for _, pStr := range strings.Split(properties, propertyDelimiter) { |
| p, err := parseProperty(pStr) |
| if err != nil { |
| return newInvalidMember(), err |
| } |
| props = append(props, p) |
| } |
| } |
| // Parse the member key/value pair. |
| |
| // Take into account a value can contain equal signs (=). |
| k, v, found := strings.Cut(keyValue, keyValueDelimiter) |
| if !found { |
| return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidMember, member) |
| } |
| // "Leading and trailing whitespaces are allowed but MUST be trimmed |
| // when converting the header into a data structure." |
| key := strings.TrimSpace(k) |
| if !validateKey(key) { |
| return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key) |
| } |
| |
| rawVal := strings.TrimSpace(v) |
| if !validateValue(rawVal) { |
| return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, v) |
| } |
| |
| // Decode a percent-encoded value. |
| unescapeVal, err := url.PathUnescape(rawVal) |
| if err != nil { |
| return newInvalidMember(), fmt.Errorf("%w: %w", errInvalidValue, err) |
| } |
| |
| value := replaceInvalidUTF8Sequences(len(rawVal), unescapeVal) |
| return Member{key: key, value: value, properties: props, hasData: true}, nil |
| } |
| |
| // replaceInvalidUTF8Sequences replaces invalid UTF-8 sequences with '�'. |
| func replaceInvalidUTF8Sequences(c int, unescapeVal string) string { |
| if utf8.ValidString(unescapeVal) { |
| return unescapeVal |
| } |
| // W3C baggage spec: |
| // https://github.com/w3c/baggage/blob/8c215efbeebd3fa4b1aceb937a747e56444f22f3/baggage/HTTP_HEADER_FORMAT.md?plain=1#L69 |
| |
| var b strings.Builder |
| b.Grow(c) |
| for i := 0; i < len(unescapeVal); { |
| r, size := utf8.DecodeRuneInString(unescapeVal[i:]) |
| if r == utf8.RuneError && size == 1 { |
| // Invalid UTF-8 sequence found, replace it with '�' |
| _, _ = b.WriteString("�") |
| } else { |
| _, _ = b.WriteRune(r) |
| } |
| i += size |
| } |
| |
| return b.String() |
| } |
| |
| // validate ensures m conforms to the W3C Baggage specification. |
| // A key must be an ASCII string, returning an error otherwise. |
| func (m Member) validate() error { |
| if !m.hasData { |
| return fmt.Errorf("%w: %q", errInvalidMember, m) |
| } |
| |
| 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() |
| } |
| |
| // Key returns the Member key. |
| func (m Member) Key() string { return m.key } |
| |
| // Value returns the Member value. |
| func (m Member) Value() string { return m.value } |
| |
| // Properties returns a copy of the Member properties. |
| func (m Member) Properties() []Property { return m.properties.Copy() } |
| |
| // String encodes Member into a header string compliant with the W3C Baggage |
| // specification. |
| // It would return empty string if the key is invalid with the W3C Baggage |
| // specification. This could happen for a UTF-8 key, as it may contain |
| // invalid characters. |
| func (m Member) String() string { |
| // W3C Baggage specification does not allow percent-encoded keys. |
| if !validateKey(m.key) { |
| return "" |
| } |
| |
| s := m.key + keyValueDelimiter + valueEscape(m.value) |
| if len(m.properties) > 0 { |
| s += propertyDelimiter + m.properties.String() |
| } |
| return s |
| } |
| |
| // Baggage is a list of baggage members representing the baggage-string as |
| // defined by the W3C Baggage specification. |
| type Baggage struct { //nolint:golint |
| list baggage.List |
| } |
| |
| // New returns a new valid Baggage. It returns an error if it results in a |
| // Baggage exceeding limits set in that specification. |
| // |
| // It expects all the provided members to have already been validated. |
| func New(members ...Member) (Baggage, error) { |
| if len(members) == 0 { |
| return Baggage{}, nil |
| } |
| |
| b := make(baggage.List) |
| for _, m := range members { |
| if !m.hasData { |
| return Baggage{}, errInvalidMember |
| } |
| |
| // OpenTelemetry resolves duplicates by last-one-wins. |
| b[m.key] = baggage.Item{ |
| Value: m.value, |
| Properties: m.properties.asInternal(), |
| } |
| } |
| |
| // Check member numbers after deduplication. |
| if len(b) > maxMembers { |
| return Baggage{}, errMemberNumber |
| } |
| |
| bag := Baggage{b} |
| if n := len(bag.String()); n > maxBytesPerBaggageString { |
| return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n) |
| } |
| |
| return bag, nil |
| } |
| |
| // Parse attempts to decode a baggage-string from the passed string. It |
| // returns an error if the input is invalid according to the W3C Baggage |
| // specification. |
| // |
| // If there are duplicate list-members contained in baggage, the last one |
| // defined (reading left-to-right) will be the only one kept. This diverges |
| // from the W3C Baggage specification which allows duplicate list-members, but |
| // conforms to the OpenTelemetry Baggage specification. |
| func Parse(bStr string) (Baggage, error) { |
| if bStr == "" { |
| return Baggage{}, nil |
| } |
| |
| if n := len(bStr); n > maxBytesPerBaggageString { |
| return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n) |
| } |
| |
| b := make(baggage.List) |
| for _, memberStr := range strings.Split(bStr, listDelimiter) { |
| m, err := parseMember(memberStr) |
| if err != nil { |
| return Baggage{}, err |
| } |
| // OpenTelemetry resolves duplicates by last-one-wins. |
| b[m.key] = baggage.Item{ |
| Value: m.value, |
| Properties: m.properties.asInternal(), |
| } |
| } |
| |
| // OpenTelemetry does not allow for duplicate list-members, but the W3C |
| // specification does. Now that we have deduplicated, ensure the baggage |
| // does not exceed list-member limits. |
| if len(b) > maxMembers { |
| return Baggage{}, errMemberNumber |
| } |
| |
| return Baggage{b}, nil |
| } |
| |
| // Member returns the baggage list-member identified by key. |
| // |
| // If there is no list-member matching the passed key the returned Member will |
| // be a zero-value Member. |
| // The returned member is not validated, as we assume the validation happened |
| // when it was added to the Baggage. |
| func (b Baggage) Member(key string) Member { |
| v, ok := b.list[key] |
| if !ok { |
| // We do not need to worry about distinguishing between the situation |
| // where a zero-valued Member is included in the Baggage because a |
| // zero-valued Member is invalid according to the W3C Baggage |
| // specification (it has an empty key). |
| return newInvalidMember() |
| } |
| |
| return Member{ |
| key: key, |
| value: v.Value, |
| properties: fromInternalProperties(v.Properties), |
| hasData: true, |
| } |
| } |
| |
| // Members returns all the baggage list-members. |
| // The order of the returned list-members is not significant. |
| // |
| // The returned members are not validated, as we assume the validation happened |
| // when they were added to the Baggage. |
| func (b Baggage) Members() []Member { |
| if len(b.list) == 0 { |
| return nil |
| } |
| |
| members := make([]Member, 0, len(b.list)) |
| for k, v := range b.list { |
| members = append(members, Member{ |
| key: k, |
| value: v.Value, |
| properties: fromInternalProperties(v.Properties), |
| hasData: true, |
| }) |
| } |
| return members |
| } |
| |
| // SetMember returns a copy of the Baggage with the member included. If the |
| // baggage contains a Member with the same key, the existing Member is |
| // replaced. |
| // |
| // If member is invalid according to the W3C Baggage specification, an error |
| // is returned with the original Baggage. |
| func (b Baggage) SetMember(member Member) (Baggage, error) { |
| if !member.hasData { |
| return b, errInvalidMember |
| } |
| |
| n := len(b.list) |
| if _, ok := b.list[member.key]; !ok { |
| n++ |
| } |
| list := make(baggage.List, n) |
| |
| for k, v := range b.list { |
| // Do not copy if we are just going to overwrite. |
| if k == member.key { |
| continue |
| } |
| list[k] = v |
| } |
| |
| list[member.key] = baggage.Item{ |
| Value: member.value, |
| Properties: member.properties.asInternal(), |
| } |
| |
| return Baggage{list: list}, nil |
| } |
| |
| // DeleteMember returns a copy of the Baggage with the list-member identified |
| // by key removed. |
| func (b Baggage) DeleteMember(key string) Baggage { |
| n := len(b.list) |
| if _, ok := b.list[key]; ok { |
| n-- |
| } |
| list := make(baggage.List, n) |
| |
| for k, v := range b.list { |
| if k == key { |
| continue |
| } |
| list[k] = v |
| } |
| |
| return Baggage{list: list} |
| } |
| |
| // Len returns the number of list-members in the Baggage. |
| func (b Baggage) Len() int { |
| return len(b.list) |
| } |
| |
| // String encodes Baggage into a header string compliant with the W3C Baggage |
| // specification. |
| // It would ignore members where the member key is invalid with the W3C Baggage |
| // specification. This could happen for a UTF-8 key, as it may contain |
| // invalid characters. |
| func (b Baggage) String() string { |
| members := make([]string, 0, len(b.list)) |
| for k, v := range b.list { |
| s := Member{ |
| key: k, |
| value: v.Value, |
| properties: fromInternalProperties(v.Properties), |
| }.String() |
| |
| // Ignored empty members. |
| if s != "" { |
| members = append(members, s) |
| } |
| } |
| return strings.Join(members, listDelimiter) |
| } |
| |
| // parsePropertyInternal attempts to decode a Property from the passed string. |
| // It follows the spec at https://www.w3.org/TR/baggage/#definition. |
| func parsePropertyInternal(s string) (p Property, ok bool) { |
| // For the entire function we will use " key = value " as an example. |
| // Attempting to parse the key. |
| // First skip spaces at the beginning "< >key = value " (they could be empty). |
| index := skipSpace(s, 0) |
| |
| // Parse the key: " <key> = value ". |
| keyStart := index |
| keyEnd := index |
| for _, c := range s[keyStart:] { |
| if !validateKeyChar(c) { |
| break |
| } |
| keyEnd++ |
| } |
| |
| // If we couldn't find any valid key character, |
| // it means the key is either empty or invalid. |
| if keyStart == keyEnd { |
| return |
| } |
| |
| // Skip spaces after the key: " key< >= value ". |
| index = skipSpace(s, keyEnd) |
| |
| if index == len(s) { |
| // A key can have no value, like: " key ". |
| ok = true |
| p.key = s[keyStart:keyEnd] |
| return |
| } |
| |
| // If we have not reached the end and we can't find the '=' delimiter, |
| // it means the property is invalid. |
| if s[index] != keyValueDelimiter[0] { |
| return |
| } |
| |
| // Attempting to parse the value. |
| // Match: " key =< >value ". |
| index = skipSpace(s, index+1) |
| |
| // Match the value string: " key = <value> ". |
| // A valid property can be: " key =". |
| // Therefore, we don't have to check if the value is empty. |
| valueStart := index |
| valueEnd := index |
| for _, c := range s[valueStart:] { |
| if !validateValueChar(c) { |
| break |
| } |
| valueEnd++ |
| } |
| |
| // Skip all trailing whitespaces: " key = value< >". |
| index = skipSpace(s, valueEnd) |
| |
| // If after looking for the value and skipping whitespaces |
| // we have not reached the end, it means the property is |
| // invalid, something like: " key = value value1". |
| if index != len(s) { |
| return |
| } |
| |
| // Decode a percent-encoded value. |
| rawVal := s[valueStart:valueEnd] |
| unescapeVal, err := url.PathUnescape(rawVal) |
| if err != nil { |
| return |
| } |
| value := replaceInvalidUTF8Sequences(len(rawVal), unescapeVal) |
| |
| ok = true |
| p.key = s[keyStart:keyEnd] |
| p.hasValue = true |
| |
| p.value = value |
| return |
| } |
| |
| func skipSpace(s string, offset int) int { |
| i := offset |
| for ; i < len(s); i++ { |
| c := s[i] |
| if c != ' ' && c != '\t' { |
| break |
| } |
| } |
| return i |
| } |
| |
| var safeKeyCharset = [utf8.RuneSelf]bool{ |
| // 0x23 to 0x27 |
| '#': true, |
| '$': true, |
| '%': true, |
| '&': true, |
| '\'': true, |
| |
| // 0x30 to 0x39 |
| '0': true, |
| '1': true, |
| '2': true, |
| '3': true, |
| '4': true, |
| '5': true, |
| '6': true, |
| '7': true, |
| '8': true, |
| '9': true, |
| |
| // 0x41 to 0x5a |
| 'A': true, |
| 'B': true, |
| 'C': true, |
| 'D': true, |
| 'E': true, |
| 'F': true, |
| 'G': true, |
| 'H': true, |
| 'I': true, |
| 'J': true, |
| 'K': true, |
| 'L': true, |
| 'M': true, |
| 'N': true, |
| 'O': true, |
| 'P': true, |
| 'Q': true, |
| 'R': true, |
| 'S': true, |
| 'T': true, |
| 'U': true, |
| 'V': true, |
| 'W': true, |
| 'X': true, |
| 'Y': true, |
| 'Z': true, |
| |
| // 0x5e to 0x7a |
| '^': true, |
| '_': true, |
| '`': true, |
| 'a': true, |
| 'b': true, |
| 'c': true, |
| 'd': true, |
| 'e': true, |
| 'f': true, |
| 'g': true, |
| 'h': true, |
| 'i': true, |
| 'j': true, |
| 'k': true, |
| 'l': true, |
| 'm': true, |
| 'n': true, |
| 'o': true, |
| 'p': true, |
| 'q': true, |
| 'r': true, |
| 's': true, |
| 't': true, |
| 'u': true, |
| 'v': true, |
| 'w': true, |
| 'x': true, |
| 'y': true, |
| 'z': true, |
| |
| // remainder |
| '!': true, |
| '*': true, |
| '+': true, |
| '-': true, |
| '.': true, |
| '|': true, |
| '~': true, |
| } |
| |
| // validateBaggageName checks if the string is a valid OpenTelemetry Baggage name. |
| // Baggage name is a valid, non-empty 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. |
| // Empty string is also a valid UTF-8 string. |
| 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 |
| } |
| |
| for _, c := range s { |
| if !validateKeyChar(c) { |
| return false |
| } |
| } |
| |
| return true |
| } |
| |
| func validateKeyChar(c int32) bool { |
| return c >= 0 && c < int32(utf8.RuneSelf) && safeKeyCharset[c] |
| } |
| |
| // validateValue checks if the string is a valid W3C Baggage value. |
| func validateValue(s string) bool { |
| for _, c := range s { |
| if !validateValueChar(c) { |
| return false |
| } |
| } |
| |
| return true |
| } |
| |
| var safeValueCharset = [utf8.RuneSelf]bool{ |
| '!': true, // 0x21 |
| |
| // 0x23 to 0x2b |
| '#': true, |
| '$': true, |
| '%': true, |
| '&': true, |
| '\'': true, |
| '(': true, |
| ')': true, |
| '*': true, |
| '+': true, |
| |
| // 0x2d to 0x3a |
| '-': true, |
| '.': true, |
| '/': true, |
| '0': true, |
| '1': true, |
| '2': true, |
| '3': true, |
| '4': true, |
| '5': true, |
| '6': true, |
| '7': true, |
| '8': true, |
| '9': true, |
| ':': true, |
| |
| // 0x3c to 0x5b |
| '<': true, // 0x3C |
| '=': true, // 0x3D |
| '>': true, // 0x3E |
| '?': true, // 0x3F |
| '@': true, // 0x40 |
| 'A': true, // 0x41 |
| 'B': true, // 0x42 |
| 'C': true, // 0x43 |
| 'D': true, // 0x44 |
| 'E': true, // 0x45 |
| 'F': true, // 0x46 |
| 'G': true, // 0x47 |
| 'H': true, // 0x48 |
| 'I': true, // 0x49 |
| 'J': true, // 0x4A |
| 'K': true, // 0x4B |
| 'L': true, // 0x4C |
| 'M': true, // 0x4D |
| 'N': true, // 0x4E |
| 'O': true, // 0x4F |
| 'P': true, // 0x50 |
| 'Q': true, // 0x51 |
| 'R': true, // 0x52 |
| 'S': true, // 0x53 |
| 'T': true, // 0x54 |
| 'U': true, // 0x55 |
| 'V': true, // 0x56 |
| 'W': true, // 0x57 |
| 'X': true, // 0x58 |
| 'Y': true, // 0x59 |
| 'Z': true, // 0x5A |
| '[': true, // 0x5B |
| |
| // 0x5d to 0x7e |
| ']': true, // 0x5D |
| '^': true, // 0x5E |
| '_': true, // 0x5F |
| '`': true, // 0x60 |
| 'a': true, // 0x61 |
| 'b': true, // 0x62 |
| 'c': true, // 0x63 |
| 'd': true, // 0x64 |
| 'e': true, // 0x65 |
| 'f': true, // 0x66 |
| 'g': true, // 0x67 |
| 'h': true, // 0x68 |
| 'i': true, // 0x69 |
| 'j': true, // 0x6A |
| 'k': true, // 0x6B |
| 'l': true, // 0x6C |
| 'm': true, // 0x6D |
| 'n': true, // 0x6E |
| 'o': true, // 0x6F |
| 'p': true, // 0x70 |
| 'q': true, // 0x71 |
| 'r': true, // 0x72 |
| 's': true, // 0x73 |
| 't': true, // 0x74 |
| 'u': true, // 0x75 |
| 'v': true, // 0x76 |
| 'w': true, // 0x77 |
| 'x': true, // 0x78 |
| 'y': true, // 0x79 |
| 'z': true, // 0x7A |
| '{': true, // 0x7B |
| '|': true, // 0x7C |
| '}': true, // 0x7D |
| '~': true, // 0x7E |
| } |
| |
| func validateValueChar(c int32) bool { |
| return c >= 0 && c < int32(utf8.RuneSelf) && safeValueCharset[c] |
| } |
| |
| // valueEscape escapes the string so it can be safely placed inside a baggage value, |
| // replacing special characters with %XX sequences as needed. |
| // |
| // The implementation is based on: |
| // https://github.com/golang/go/blob/f6509cf5cdbb5787061b784973782933c47f1782/src/net/url/url.go#L285. |
| func valueEscape(s string) string { |
| hexCount := 0 |
| for i := 0; i < len(s); i++ { |
| c := s[i] |
| if shouldEscape(c) { |
| hexCount++ |
| } |
| } |
| |
| if hexCount == 0 { |
| return s |
| } |
| |
| var buf [64]byte |
| var t []byte |
| |
| required := len(s) + 2*hexCount |
| if required <= len(buf) { |
| t = buf[:required] |
| } else { |
| t = make([]byte, required) |
| } |
| |
| j := 0 |
| for i := 0; i < len(s); i++ { |
| c := s[i] |
| if shouldEscape(s[i]) { |
| const upperhex = "0123456789ABCDEF" |
| t[j] = '%' |
| t[j+1] = upperhex[c>>4] |
| t[j+2] = upperhex[c&15] |
| j += 3 |
| } else { |
| t[j] = c |
| j++ |
| } |
| } |
| |
| return string(t) |
| } |
| |
| // shouldEscape returns true if the specified byte should be escaped when |
| // appearing in a baggage value string. |
| func shouldEscape(c byte) bool { |
| if c == '%' { |
| // The percent character must be encoded so that percent-encoding can work. |
| return true |
| } |
| return !validateValueChar(int32(c)) |
| } |