baggage.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. // Copyright The OpenTelemetry Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package baggage // import "go.opentelemetry.io/otel/baggage"
  15. import (
  16. "errors"
  17. "fmt"
  18. "net/url"
  19. "regexp"
  20. "strings"
  21. "go.opentelemetry.io/otel/internal/baggage"
  22. )
  23. const (
  24. maxMembers = 180
  25. maxBytesPerMembers = 4096
  26. maxBytesPerBaggageString = 8192
  27. listDelimiter = ","
  28. keyValueDelimiter = "="
  29. propertyDelimiter = ";"
  30. keyDef = `([\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5a\x5e-\x7a\x7c\x7e]+)`
  31. valueDef = `([\x21\x23-\x2b\x2d-\x3a\x3c-\x5B\x5D-\x7e]*)`
  32. keyValueDef = `\s*` + keyDef + `\s*` + keyValueDelimiter + `\s*` + valueDef + `\s*`
  33. )
  34. var (
  35. keyRe = regexp.MustCompile(`^` + keyDef + `$`)
  36. valueRe = regexp.MustCompile(`^` + valueDef + `$`)
  37. propertyRe = regexp.MustCompile(`^(?:\s*` + keyDef + `\s*|` + keyValueDef + `)$`)
  38. )
  39. var (
  40. errInvalidKey = errors.New("invalid key")
  41. errInvalidValue = errors.New("invalid value")
  42. errInvalidProperty = errors.New("invalid baggage list-member property")
  43. errInvalidMember = errors.New("invalid baggage list-member")
  44. errMemberNumber = errors.New("too many list-members in baggage-string")
  45. errMemberBytes = errors.New("list-member too large")
  46. errBaggageBytes = errors.New("baggage-string too large")
  47. )
  48. // Property is an additional metadata entry for a baggage list-member.
  49. type Property struct {
  50. key, value string
  51. // hasValue indicates if a zero-value value means the property does not
  52. // have a value or if it was the zero-value.
  53. hasValue bool
  54. // hasData indicates whether the created property contains data or not.
  55. // Properties that do not contain data are invalid with no other check
  56. // required.
  57. hasData bool
  58. }
  59. // NewKeyProperty returns a new Property for key.
  60. //
  61. // If key is invalid, an error will be returned.
  62. func NewKeyProperty(key string) (Property, error) {
  63. if !keyRe.MatchString(key) {
  64. return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
  65. }
  66. p := Property{key: key, hasData: true}
  67. return p, nil
  68. }
  69. // NewKeyValueProperty returns a new Property for key with value.
  70. //
  71. // If key or value are invalid, an error will be returned.
  72. func NewKeyValueProperty(key, value string) (Property, error) {
  73. if !keyRe.MatchString(key) {
  74. return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
  75. }
  76. if !valueRe.MatchString(value) {
  77. return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value)
  78. }
  79. p := Property{
  80. key: key,
  81. value: value,
  82. hasValue: true,
  83. hasData: true,
  84. }
  85. return p, nil
  86. }
  87. func newInvalidProperty() Property {
  88. return Property{}
  89. }
  90. // parseProperty attempts to decode a Property from the passed string. It
  91. // returns an error if the input is invalid according to the W3C Baggage
  92. // specification.
  93. func parseProperty(property string) (Property, error) {
  94. if property == "" {
  95. return newInvalidProperty(), nil
  96. }
  97. match := propertyRe.FindStringSubmatch(property)
  98. if len(match) != 4 {
  99. return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidProperty, property)
  100. }
  101. p := Property{hasData: true}
  102. if match[1] != "" {
  103. p.key = match[1]
  104. } else {
  105. p.key = match[2]
  106. p.value = match[3]
  107. p.hasValue = true
  108. }
  109. return p, nil
  110. }
  111. // validate ensures p conforms to the W3C Baggage specification, returning an
  112. // error otherwise.
  113. func (p Property) validate() error {
  114. errFunc := func(err error) error {
  115. return fmt.Errorf("invalid property: %w", err)
  116. }
  117. if !p.hasData {
  118. return errFunc(fmt.Errorf("%w: %q", errInvalidProperty, p))
  119. }
  120. if !keyRe.MatchString(p.key) {
  121. return errFunc(fmt.Errorf("%w: %q", errInvalidKey, p.key))
  122. }
  123. if p.hasValue && !valueRe.MatchString(p.value) {
  124. return errFunc(fmt.Errorf("%w: %q", errInvalidValue, p.value))
  125. }
  126. if !p.hasValue && p.value != "" {
  127. return errFunc(errors.New("inconsistent value"))
  128. }
  129. return nil
  130. }
  131. // Key returns the Property key.
  132. func (p Property) Key() string {
  133. return p.key
  134. }
  135. // Value returns the Property value. Additionally, a boolean value is returned
  136. // indicating if the returned value is the empty if the Property has a value
  137. // that is empty or if the value is not set.
  138. func (p Property) Value() (string, bool) {
  139. return p.value, p.hasValue
  140. }
  141. // String encodes Property into a string compliant with the W3C Baggage
  142. // specification.
  143. func (p Property) String() string {
  144. if p.hasValue {
  145. return fmt.Sprintf("%s%s%v", p.key, keyValueDelimiter, p.value)
  146. }
  147. return p.key
  148. }
  149. type properties []Property
  150. func fromInternalProperties(iProps []baggage.Property) properties {
  151. if len(iProps) == 0 {
  152. return nil
  153. }
  154. props := make(properties, len(iProps))
  155. for i, p := range iProps {
  156. props[i] = Property{
  157. key: p.Key,
  158. value: p.Value,
  159. hasValue: p.HasValue,
  160. }
  161. }
  162. return props
  163. }
  164. func (p properties) asInternal() []baggage.Property {
  165. if len(p) == 0 {
  166. return nil
  167. }
  168. iProps := make([]baggage.Property, len(p))
  169. for i, prop := range p {
  170. iProps[i] = baggage.Property{
  171. Key: prop.key,
  172. Value: prop.value,
  173. HasValue: prop.hasValue,
  174. }
  175. }
  176. return iProps
  177. }
  178. func (p properties) Copy() properties {
  179. if len(p) == 0 {
  180. return nil
  181. }
  182. props := make(properties, len(p))
  183. copy(props, p)
  184. return props
  185. }
  186. // validate ensures each Property in p conforms to the W3C Baggage
  187. // specification, returning an error otherwise.
  188. func (p properties) validate() error {
  189. for _, prop := range p {
  190. if err := prop.validate(); err != nil {
  191. return err
  192. }
  193. }
  194. return nil
  195. }
  196. // String encodes properties into a string compliant with the W3C Baggage
  197. // specification.
  198. func (p properties) String() string {
  199. props := make([]string, len(p))
  200. for i, prop := range p {
  201. props[i] = prop.String()
  202. }
  203. return strings.Join(props, propertyDelimiter)
  204. }
  205. // Member is a list-member of a baggage-string as defined by the W3C Baggage
  206. // specification.
  207. type Member struct {
  208. key, value string
  209. properties properties
  210. // hasData indicates whether the created property contains data or not.
  211. // Properties that do not contain data are invalid with no other check
  212. // required.
  213. hasData bool
  214. }
  215. // NewMember returns a new Member from the passed arguments. The key will be
  216. // used directly while the value will be url decoded after validation. An error
  217. // is returned if the created Member would be invalid according to the W3C
  218. // Baggage specification.
  219. func NewMember(key, value string, props ...Property) (Member, error) {
  220. m := Member{
  221. key: key,
  222. value: value,
  223. properties: properties(props).Copy(),
  224. hasData: true,
  225. }
  226. if err := m.validate(); err != nil {
  227. return newInvalidMember(), err
  228. }
  229. decodedValue, err := url.QueryUnescape(value)
  230. if err != nil {
  231. return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
  232. }
  233. m.value = decodedValue
  234. return m, nil
  235. }
  236. func newInvalidMember() Member {
  237. return Member{}
  238. }
  239. // parseMember attempts to decode a Member from the passed string. It returns
  240. // an error if the input is invalid according to the W3C Baggage
  241. // specification.
  242. func parseMember(member string) (Member, error) {
  243. if n := len(member); n > maxBytesPerMembers {
  244. return newInvalidMember(), fmt.Errorf("%w: %d", errMemberBytes, n)
  245. }
  246. var (
  247. key, value string
  248. props properties
  249. )
  250. parts := strings.SplitN(member, propertyDelimiter, 2)
  251. switch len(parts) {
  252. case 2:
  253. // Parse the member properties.
  254. for _, pStr := range strings.Split(parts[1], propertyDelimiter) {
  255. p, err := parseProperty(pStr)
  256. if err != nil {
  257. return newInvalidMember(), err
  258. }
  259. props = append(props, p)
  260. }
  261. fallthrough
  262. case 1:
  263. // Parse the member key/value pair.
  264. // Take into account a value can contain equal signs (=).
  265. kv := strings.SplitN(parts[0], keyValueDelimiter, 2)
  266. if len(kv) != 2 {
  267. return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidMember, member)
  268. }
  269. // "Leading and trailing whitespaces are allowed but MUST be trimmed
  270. // when converting the header into a data structure."
  271. key = strings.TrimSpace(kv[0])
  272. var err error
  273. value, err = url.QueryUnescape(strings.TrimSpace(kv[1]))
  274. if err != nil {
  275. return newInvalidMember(), fmt.Errorf("%w: %q", err, value)
  276. }
  277. if !keyRe.MatchString(key) {
  278. return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key)
  279. }
  280. if !valueRe.MatchString(value) {
  281. return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
  282. }
  283. default:
  284. // This should never happen unless a developer has changed the string
  285. // splitting somehow. Panic instead of failing silently and allowing
  286. // the bug to slip past the CI checks.
  287. panic("failed to parse baggage member")
  288. }
  289. return Member{key: key, value: value, properties: props, hasData: true}, nil
  290. }
  291. // validate ensures m conforms to the W3C Baggage specification.
  292. // A key is just an ASCII string, but a value must be URL encoded UTF-8,
  293. // returning an error otherwise.
  294. func (m Member) validate() error {
  295. if !m.hasData {
  296. return fmt.Errorf("%w: %q", errInvalidMember, m)
  297. }
  298. if !keyRe.MatchString(m.key) {
  299. return fmt.Errorf("%w: %q", errInvalidKey, m.key)
  300. }
  301. if !valueRe.MatchString(m.value) {
  302. return fmt.Errorf("%w: %q", errInvalidValue, m.value)
  303. }
  304. return m.properties.validate()
  305. }
  306. // Key returns the Member key.
  307. func (m Member) Key() string { return m.key }
  308. // Value returns the Member value.
  309. func (m Member) Value() string { return m.value }
  310. // Properties returns a copy of the Member properties.
  311. func (m Member) Properties() []Property { return m.properties.Copy() }
  312. // String encodes Member into a string compliant with the W3C Baggage
  313. // specification.
  314. func (m Member) String() string {
  315. // A key is just an ASCII string, but a value is URL encoded UTF-8.
  316. s := fmt.Sprintf("%s%s%s", m.key, keyValueDelimiter, url.QueryEscape(m.value))
  317. if len(m.properties) > 0 {
  318. s = fmt.Sprintf("%s%s%s", s, propertyDelimiter, m.properties.String())
  319. }
  320. return s
  321. }
  322. // Baggage is a list of baggage members representing the baggage-string as
  323. // defined by the W3C Baggage specification.
  324. type Baggage struct { //nolint:golint
  325. list baggage.List
  326. }
  327. // New returns a new valid Baggage. It returns an error if it results in a
  328. // Baggage exceeding limits set in that specification.
  329. //
  330. // It expects all the provided members to have already been validated.
  331. func New(members ...Member) (Baggage, error) {
  332. if len(members) == 0 {
  333. return Baggage{}, nil
  334. }
  335. b := make(baggage.List)
  336. for _, m := range members {
  337. if !m.hasData {
  338. return Baggage{}, errInvalidMember
  339. }
  340. // OpenTelemetry resolves duplicates by last-one-wins.
  341. b[m.key] = baggage.Item{
  342. Value: m.value,
  343. Properties: m.properties.asInternal(),
  344. }
  345. }
  346. // Check member numbers after deduplication.
  347. if len(b) > maxMembers {
  348. return Baggage{}, errMemberNumber
  349. }
  350. bag := Baggage{b}
  351. if n := len(bag.String()); n > maxBytesPerBaggageString {
  352. return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
  353. }
  354. return bag, nil
  355. }
  356. // Parse attempts to decode a baggage-string from the passed string. It
  357. // returns an error if the input is invalid according to the W3C Baggage
  358. // specification.
  359. //
  360. // If there are duplicate list-members contained in baggage, the last one
  361. // defined (reading left-to-right) will be the only one kept. This diverges
  362. // from the W3C Baggage specification which allows duplicate list-members, but
  363. // conforms to the OpenTelemetry Baggage specification.
  364. func Parse(bStr string) (Baggage, error) {
  365. if bStr == "" {
  366. return Baggage{}, nil
  367. }
  368. if n := len(bStr); n > maxBytesPerBaggageString {
  369. return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
  370. }
  371. b := make(baggage.List)
  372. for _, memberStr := range strings.Split(bStr, listDelimiter) {
  373. m, err := parseMember(memberStr)
  374. if err != nil {
  375. return Baggage{}, err
  376. }
  377. // OpenTelemetry resolves duplicates by last-one-wins.
  378. b[m.key] = baggage.Item{
  379. Value: m.value,
  380. Properties: m.properties.asInternal(),
  381. }
  382. }
  383. // OpenTelemetry does not allow for duplicate list-members, but the W3C
  384. // specification does. Now that we have deduplicated, ensure the baggage
  385. // does not exceed list-member limits.
  386. if len(b) > maxMembers {
  387. return Baggage{}, errMemberNumber
  388. }
  389. return Baggage{b}, nil
  390. }
  391. // Member returns the baggage list-member identified by key.
  392. //
  393. // If there is no list-member matching the passed key the returned Member will
  394. // be a zero-value Member.
  395. // The returned member is not validated, as we assume the validation happened
  396. // when it was added to the Baggage.
  397. func (b Baggage) Member(key string) Member {
  398. v, ok := b.list[key]
  399. if !ok {
  400. // We do not need to worry about distinguishing between the situation
  401. // where a zero-valued Member is included in the Baggage because a
  402. // zero-valued Member is invalid according to the W3C Baggage
  403. // specification (it has an empty key).
  404. return newInvalidMember()
  405. }
  406. return Member{
  407. key: key,
  408. value: v.Value,
  409. properties: fromInternalProperties(v.Properties),
  410. hasData: true,
  411. }
  412. }
  413. // Members returns all the baggage list-members.
  414. // The order of the returned list-members does not have significance.
  415. //
  416. // The returned members are not validated, as we assume the validation happened
  417. // when they were added to the Baggage.
  418. func (b Baggage) Members() []Member {
  419. if len(b.list) == 0 {
  420. return nil
  421. }
  422. members := make([]Member, 0, len(b.list))
  423. for k, v := range b.list {
  424. members = append(members, Member{
  425. key: k,
  426. value: v.Value,
  427. properties: fromInternalProperties(v.Properties),
  428. hasData: true,
  429. })
  430. }
  431. return members
  432. }
  433. // SetMember returns a copy the Baggage with the member included. If the
  434. // baggage contains a Member with the same key the existing Member is
  435. // replaced.
  436. //
  437. // If member is invalid according to the W3C Baggage specification, an error
  438. // is returned with the original Baggage.
  439. func (b Baggage) SetMember(member Member) (Baggage, error) {
  440. if !member.hasData {
  441. return b, errInvalidMember
  442. }
  443. n := len(b.list)
  444. if _, ok := b.list[member.key]; !ok {
  445. n++
  446. }
  447. list := make(baggage.List, n)
  448. for k, v := range b.list {
  449. // Do not copy if we are just going to overwrite.
  450. if k == member.key {
  451. continue
  452. }
  453. list[k] = v
  454. }
  455. list[member.key] = baggage.Item{
  456. Value: member.value,
  457. Properties: member.properties.asInternal(),
  458. }
  459. return Baggage{list: list}, nil
  460. }
  461. // DeleteMember returns a copy of the Baggage with the list-member identified
  462. // by key removed.
  463. func (b Baggage) DeleteMember(key string) Baggage {
  464. n := len(b.list)
  465. if _, ok := b.list[key]; ok {
  466. n--
  467. }
  468. list := make(baggage.List, n)
  469. for k, v := range b.list {
  470. if k == key {
  471. continue
  472. }
  473. list[k] = v
  474. }
  475. return Baggage{list: list}
  476. }
  477. // Len returns the number of list-members in the Baggage.
  478. func (b Baggage) Len() int {
  479. return len(b.list)
  480. }
  481. // String encodes Baggage into a string compliant with the W3C Baggage
  482. // specification. The returned string will be invalid if the Baggage contains
  483. // any invalid list-members.
  484. func (b Baggage) String() string {
  485. members := make([]string, 0, len(b.list))
  486. for k, v := range b.list {
  487. members = append(members, Member{
  488. key: k,
  489. value: v.Value,
  490. properties: fromInternalProperties(v.Properties),
  491. }.String())
  492. }
  493. return strings.Join(members, listDelimiter)
  494. }