baggage.go 26 KB

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