gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

baggage.go (15409B)


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