| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018 |
- // 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))
- }
|