baggage.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  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
  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. }
  55. func NewKeyProperty(key string) (Property, error) {
  56. p := Property{}
  57. if !keyRe.MatchString(key) {
  58. return p, fmt.Errorf("%w: %q", errInvalidKey, key)
  59. }
  60. p.key = key
  61. return p, nil
  62. }
  63. func NewKeyValueProperty(key, value string) (Property, error) {
  64. p := Property{}
  65. if !keyRe.MatchString(key) {
  66. return p, fmt.Errorf("%w: %q", errInvalidKey, key)
  67. }
  68. if !valueRe.MatchString(value) {
  69. return p, fmt.Errorf("%w: %q", errInvalidValue, value)
  70. }
  71. p.key = key
  72. p.value = value
  73. p.hasValue = true
  74. return p, nil
  75. }
  76. // parseProperty attempts to decode a Property from the passed string. It
  77. // returns an error if the input is invalid according to the W3C Baggage
  78. // specification.
  79. func parseProperty(property string) (Property, error) {
  80. p := Property{}
  81. if property == "" {
  82. return p, nil
  83. }
  84. match := propertyRe.FindStringSubmatch(property)
  85. if len(match) != 4 {
  86. return p, fmt.Errorf("%w: %q", errInvalidProperty, property)
  87. }
  88. if match[1] != "" {
  89. p.key = match[1]
  90. } else {
  91. p.key = match[2]
  92. p.value = match[3]
  93. p.hasValue = true
  94. }
  95. return p, nil
  96. }
  97. // validate ensures p conforms to the W3C Baggage specification, returning an
  98. // error otherwise.
  99. func (p Property) validate() error {
  100. errFunc := func(err error) error {
  101. return fmt.Errorf("invalid property: %w", err)
  102. }
  103. if !keyRe.MatchString(p.key) {
  104. return errFunc(fmt.Errorf("%w: %q", errInvalidKey, p.key))
  105. }
  106. if p.hasValue && !valueRe.MatchString(p.value) {
  107. return errFunc(fmt.Errorf("%w: %q", errInvalidValue, p.value))
  108. }
  109. if !p.hasValue && p.value != "" {
  110. return errFunc(errors.New("inconsistent value"))
  111. }
  112. return nil
  113. }
  114. // Key returns the Property key.
  115. func (p Property) Key() string {
  116. return p.key
  117. }
  118. // Value returns the Property value. Additionally a boolean value is returned
  119. // indicating if the returned value is the empty if the Property has a value
  120. // that is empty or if the value is not set.
  121. func (p Property) Value() (string, bool) {
  122. return p.value, p.hasValue
  123. }
  124. // String encodes Property into a string compliant with the W3C Baggage
  125. // specification.
  126. func (p Property) String() string {
  127. if p.hasValue {
  128. return fmt.Sprintf("%s%s%v", p.key, keyValueDelimiter, p.value)
  129. }
  130. return p.key
  131. }
  132. type properties []Property
  133. func fromInternalProperties(iProps []baggage.Property) properties {
  134. if len(iProps) == 0 {
  135. return nil
  136. }
  137. props := make(properties, len(iProps))
  138. for i, p := range iProps {
  139. props[i] = Property{
  140. key: p.Key,
  141. value: p.Value,
  142. hasValue: p.HasValue,
  143. }
  144. }
  145. return props
  146. }
  147. func (p properties) asInternal() []baggage.Property {
  148. if len(p) == 0 {
  149. return nil
  150. }
  151. iProps := make([]baggage.Property, len(p))
  152. for i, prop := range p {
  153. iProps[i] = baggage.Property{
  154. Key: prop.key,
  155. Value: prop.value,
  156. HasValue: prop.hasValue,
  157. }
  158. }
  159. return iProps
  160. }
  161. func (p properties) Copy() properties {
  162. if len(p) == 0 {
  163. return nil
  164. }
  165. props := make(properties, len(p))
  166. copy(props, p)
  167. return props
  168. }
  169. // validate ensures each Property in p conforms to the W3C Baggage
  170. // specification, returning an error otherwise.
  171. func (p properties) validate() error {
  172. for _, prop := range p {
  173. if err := prop.validate(); err != nil {
  174. return err
  175. }
  176. }
  177. return nil
  178. }
  179. // String encodes properties into a string compliant with the W3C Baggage
  180. // specification.
  181. func (p properties) String() string {
  182. props := make([]string, len(p))
  183. for i, prop := range p {
  184. props[i] = prop.String()
  185. }
  186. return strings.Join(props, propertyDelimiter)
  187. }
  188. // Member is a list-member of a baggage-string as defined by the W3C Baggage
  189. // specification.
  190. type Member struct {
  191. key, value string
  192. properties properties
  193. }
  194. // NewMember returns a new Member from the passed arguments. An error is
  195. // returned if the created Member would be invalid according to the W3C
  196. // Baggage specification.
  197. func NewMember(key, value string, props ...Property) (Member, error) {
  198. m := Member{key: key, value: value, properties: properties(props).Copy()}
  199. if err := m.validate(); err != nil {
  200. return Member{}, err
  201. }
  202. return m, nil
  203. }
  204. // parseMember attempts to decode a Member from the passed string. It returns
  205. // an error if the input is invalid according to the W3C Baggage
  206. // specification.
  207. func parseMember(member string) (Member, error) {
  208. if n := len(member); n > maxBytesPerMembers {
  209. return Member{}, fmt.Errorf("%w: %d", errMemberBytes, n)
  210. }
  211. var (
  212. key, value string
  213. props properties
  214. )
  215. parts := strings.SplitN(member, propertyDelimiter, 2)
  216. switch len(parts) {
  217. case 2:
  218. // Parse the member properties.
  219. for _, pStr := range strings.Split(parts[1], propertyDelimiter) {
  220. p, err := parseProperty(pStr)
  221. if err != nil {
  222. return Member{}, err
  223. }
  224. props = append(props, p)
  225. }
  226. fallthrough
  227. case 1:
  228. // Parse the member key/value pair.
  229. // Take into account a value can contain equal signs (=).
  230. kv := strings.SplitN(parts[0], keyValueDelimiter, 2)
  231. if len(kv) != 2 {
  232. return Member{}, fmt.Errorf("%w: %q", errInvalidMember, member)
  233. }
  234. // "Leading and trailing whitespaces are allowed but MUST be trimmed
  235. // when converting the header into a data structure."
  236. key, value = strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])
  237. if !keyRe.MatchString(key) {
  238. return Member{}, fmt.Errorf("%w: %q", errInvalidKey, key)
  239. }
  240. if !valueRe.MatchString(value) {
  241. return Member{}, fmt.Errorf("%w: %q", errInvalidValue, value)
  242. }
  243. default:
  244. // This should never happen unless a developer has changed the string
  245. // splitting somehow. Panic instead of failing silently and allowing
  246. // the bug to slip past the CI checks.
  247. panic("failed to parse baggage member")
  248. }
  249. return Member{key: key, value: value, properties: props}, nil
  250. }
  251. // validate ensures m conforms to the W3C Baggage specification, returning an
  252. // error otherwise.
  253. func (m Member) validate() error {
  254. if !keyRe.MatchString(m.key) {
  255. return fmt.Errorf("%w: %q", errInvalidKey, m.key)
  256. }
  257. if !valueRe.MatchString(m.value) {
  258. return fmt.Errorf("%w: %q", errInvalidValue, m.value)
  259. }
  260. return m.properties.validate()
  261. }
  262. // Key returns the Member key.
  263. func (m Member) Key() string { return m.key }
  264. // Value returns the Member value.
  265. func (m Member) Value() string { return m.value }
  266. // Properties returns a copy of the Member properties.
  267. func (m Member) Properties() []Property { return m.properties.Copy() }
  268. // String encodes Member into a string compliant with the W3C Baggage
  269. // specification.
  270. func (m Member) String() string {
  271. // A key is just an ASCII string, but a value is URL encoded UTF-8.
  272. s := fmt.Sprintf("%s%s%s", m.key, keyValueDelimiter, url.QueryEscape(m.value))
  273. if len(m.properties) > 0 {
  274. s = fmt.Sprintf("%s%s%s", s, propertyDelimiter, m.properties.String())
  275. }
  276. return s
  277. }
  278. // Baggage is a list of baggage members representing the baggage-string as
  279. // defined by the W3C Baggage specification.
  280. type Baggage struct { //nolint:golint
  281. list baggage.List
  282. }
  283. // New returns a new valid Baggage. It returns an error if the passed members
  284. // are invalid according to the W3C Baggage specification or if it results in
  285. // a Baggage exceeding limits set in that specification.
  286. func New(members ...Member) (Baggage, error) {
  287. if len(members) == 0 {
  288. return Baggage{}, nil
  289. }
  290. b := make(baggage.List)
  291. for _, m := range members {
  292. if err := m.validate(); err != nil {
  293. return Baggage{}, err
  294. }
  295. // OpenTelemetry resolves duplicates by last-one-wins.
  296. b[m.key] = baggage.Item{
  297. Value: m.value,
  298. Properties: m.properties.asInternal(),
  299. }
  300. }
  301. // Check member numbers after deduplicating.
  302. if len(b) > maxMembers {
  303. return Baggage{}, errMemberNumber
  304. }
  305. bag := Baggage{b}
  306. if n := len(bag.String()); n > maxBytesPerBaggageString {
  307. return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
  308. }
  309. return bag, nil
  310. }
  311. // Parse attempts to decode a baggage-string from the passed string. It
  312. // returns an error if the input is invalid according to the W3C Baggage
  313. // specification.
  314. //
  315. // If there are duplicate list-members contained in baggage, the last one
  316. // defined (reading left-to-right) will be the only one kept. This diverges
  317. // from the W3C Baggage specification which allows duplicate list-members, but
  318. // conforms to the OpenTelemetry Baggage specification.
  319. func Parse(bStr string) (Baggage, error) {
  320. if bStr == "" {
  321. return Baggage{}, nil
  322. }
  323. if n := len(bStr); n > maxBytesPerBaggageString {
  324. return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
  325. }
  326. b := make(baggage.List)
  327. for _, memberStr := range strings.Split(bStr, listDelimiter) {
  328. m, err := parseMember(memberStr)
  329. if err != nil {
  330. return Baggage{}, err
  331. }
  332. // OpenTelemetry resolves duplicates by last-one-wins.
  333. b[m.key] = baggage.Item{
  334. Value: m.value,
  335. Properties: m.properties.asInternal(),
  336. }
  337. }
  338. // OpenTelemetry does not allow for duplicate list-members, but the W3C
  339. // specification does. Now that we have deduplicated, ensure the baggage
  340. // does not exceed list-member limits.
  341. if len(b) > maxMembers {
  342. return Baggage{}, errMemberNumber
  343. }
  344. return Baggage{b}, nil
  345. }
  346. // Member returns the baggage list-member identified by key.
  347. //
  348. // If there is no list-member matching the passed key the returned Member will
  349. // be a zero-value Member.
  350. func (b Baggage) Member(key string) Member {
  351. v, ok := b.list[key]
  352. if !ok {
  353. // We do not need to worry about distiguising between the situation
  354. // where a zero-valued Member is included in the Baggage because a
  355. // zero-valued Member is invalid according to the W3C Baggage
  356. // specification (it has an empty key).
  357. return Member{}
  358. }
  359. return Member{
  360. key: key,
  361. value: v.Value,
  362. properties: fromInternalProperties(v.Properties),
  363. }
  364. }
  365. // Members returns all the baggage list-members.
  366. // The order of the returned list-members does not have significance.
  367. func (b Baggage) Members() []Member {
  368. if len(b.list) == 0 {
  369. return nil
  370. }
  371. members := make([]Member, 0, len(b.list))
  372. for k, v := range b.list {
  373. members = append(members, Member{
  374. key: k,
  375. value: v.Value,
  376. properties: fromInternalProperties(v.Properties),
  377. })
  378. }
  379. return members
  380. }
  381. // SetMember returns a copy the Baggage with the member included. If the
  382. // baggage contains a Member with the same key the existing Member is
  383. // replaced.
  384. //
  385. // If member is invalid according to the W3C Baggage specification, an error
  386. // is returned with the original Baggage.
  387. func (b Baggage) SetMember(member Member) (Baggage, error) {
  388. if err := member.validate(); err != nil {
  389. return b, fmt.Errorf("%w: %s", errInvalidMember, err)
  390. }
  391. n := len(b.list)
  392. if _, ok := b.list[member.key]; !ok {
  393. n++
  394. }
  395. list := make(baggage.List, n)
  396. for k, v := range b.list {
  397. // Do not copy if we are just going to overwrite.
  398. if k == member.key {
  399. continue
  400. }
  401. list[k] = v
  402. }
  403. list[member.key] = baggage.Item{
  404. Value: member.value,
  405. Properties: member.properties.asInternal(),
  406. }
  407. return Baggage{list: list}, nil
  408. }
  409. // DeleteMember returns a copy of the Baggage with the list-member identified
  410. // by key removed.
  411. func (b Baggage) DeleteMember(key string) Baggage {
  412. n := len(b.list)
  413. if _, ok := b.list[key]; ok {
  414. n--
  415. }
  416. list := make(baggage.List, n)
  417. for k, v := range b.list {
  418. if k == key {
  419. continue
  420. }
  421. list[k] = v
  422. }
  423. return Baggage{list: list}
  424. }
  425. // Len returns the number of list-members in the Baggage.
  426. func (b Baggage) Len() int {
  427. return len(b.list)
  428. }
  429. // String encodes Baggage into a string compliant with the W3C Baggage
  430. // specification. The returned string will be invalid if the Baggage contains
  431. // any invalid list-members.
  432. func (b Baggage) String() string {
  433. members := make([]string, 0, len(b.list))
  434. for k, v := range b.list {
  435. members = append(members, Member{
  436. key: k,
  437. value: v.Value,
  438. properties: fromInternalProperties(v.Properties),
  439. }.String())
  440. }
  441. return strings.Join(members, listDelimiter)
  442. }