Skip to content

Commit ed3d657

Browse files
srikanthccvocelotllzchen
authored
Make baggage implementation w3c spec complaint (#2167)
* Add regexes to check keys and values Fixes #2010 * Fix tests * Add changelog * Fix test * Add checks to set_baggage * Fix lint * Add changelog entry * Fix mypy * Fix mypy * Cast to string * WIP * Key value format * Mostly done * Remove old changelog entry * fomat * Correct typing * Fix lint * Fix issues * Add CHANGELOG entry * Make changes as discussed in SIG meeting * Update opentelemetry-api/src/opentelemetry/baggage/__init__.py Co-authored-by: Leighton Chen <[email protected]> Co-authored-by: Diego Hurtado <[email protected]> Co-authored-by: Leighton Chen <[email protected]>
1 parent 7867202 commit ed3d657

File tree

5 files changed

+180
-77
lines changed

5 files changed

+180
-77
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3333
([#2145](https://github.com/open-telemetry/opentelemetry-python/pull/2145))
3434
- Add `schema_url` to `TracerProvider.get_tracer`
3535
([#2154](https://github.com/open-telemetry/opentelemetry-python/pull/2154))
36+
- Make baggage implementation w3c spec complaint
37+
([#2167](https://github.com/open-telemetry/opentelemetry-python/pull/2167))
3638

3739
## [1.5.0-0.24b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.5.0-0.24b0) - 2021-08-26
3840

opentelemetry-api/src/opentelemetry/baggage/__init__.py

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,30 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
import typing
15+
from logging import getLogger
16+
from re import compile
1617
from types import MappingProxyType
18+
from typing import Mapping, Optional
1719

1820
from opentelemetry.context import create_key, get_value, set_value
1921
from opentelemetry.context.context import Context
22+
from opentelemetry.util.re import (
23+
_BAGGAGE_PROPERTY_FORMAT,
24+
_KEY_FORMAT,
25+
_VALUE_FORMAT,
26+
)
2027

2128
_BAGGAGE_KEY = create_key("baggage")
29+
_logger = getLogger(__name__)
30+
31+
_KEY_PATTERN = compile(_KEY_FORMAT)
32+
_VALUE_PATTERN = compile(_VALUE_FORMAT)
33+
_PROPERT_PATTERN = compile(_BAGGAGE_PROPERTY_FORMAT)
2234

2335

2436
def get_all(
25-
context: typing.Optional[Context] = None,
26-
) -> typing.Mapping[str, object]:
37+
context: Optional[Context] = None,
38+
) -> Mapping[str, object]:
2739
"""Returns the name/value pairs in the Baggage
2840
2941
Args:
@@ -39,8 +51,8 @@ def get_all(
3951

4052

4153
def get_baggage(
42-
name: str, context: typing.Optional[Context] = None
43-
) -> typing.Optional[object]:
54+
name: str, context: Optional[Context] = None
55+
) -> Optional[object]:
4456
"""Provides access to the value for a name/value pair in the
4557
Baggage
4658
@@ -56,7 +68,7 @@ def get_baggage(
5668

5769

5870
def set_baggage(
59-
name: str, value: object, context: typing.Optional[Context] = None
71+
name: str, value: object, context: Optional[Context] = None
6072
) -> Context:
6173
"""Sets a value in the Baggage
6274
@@ -69,13 +81,20 @@ def set_baggage(
6981
A Context with the value updated
7082
"""
7183
baggage = dict(get_all(context=context))
72-
baggage[name] = value
84+
if not _is_valid_key(name):
85+
_logger.warning(
86+
"Baggage key `%s` does not match format, ignoring", name
87+
)
88+
elif not _is_valid_value(str(value)):
89+
_logger.warning(
90+
"Baggage value `%s` does not match format, ignoring", value
91+
)
92+
else:
93+
baggage[name] = value
7394
return set_value(_BAGGAGE_KEY, baggage, context=context)
7495

7596

76-
def remove_baggage(
77-
name: str, context: typing.Optional[Context] = None
78-
) -> Context:
97+
def remove_baggage(name: str, context: Optional[Context] = None) -> Context:
7998
"""Removes a value from the Baggage
8099
81100
Args:
@@ -91,7 +110,7 @@ def remove_baggage(
91110
return set_value(_BAGGAGE_KEY, baggage, context=context)
92111

93112

94-
def clear(context: typing.Optional[Context] = None) -> Context:
113+
def clear(context: Optional[Context] = None) -> Context:
95114
"""Removes all values from the Baggage
96115
97116
Args:
@@ -101,3 +120,22 @@ def clear(context: typing.Optional[Context] = None) -> Context:
101120
A Context with all baggage entries removed
102121
"""
103122
return set_value(_BAGGAGE_KEY, {}, context=context)
123+
124+
125+
def _is_valid_key(name: str) -> bool:
126+
return _KEY_PATTERN.fullmatch(str(name)) is not None
127+
128+
129+
def _is_valid_value(value: object) -> bool:
130+
parts = str(value).split(";")
131+
is_valid_value = _VALUE_PATTERN.fullmatch(parts[0]) is not None
132+
if len(parts) > 1: # one or more properties metadata
133+
for property in parts[1:]:
134+
if _PROPERT_PATTERN.fullmatch(property) is None:
135+
is_valid_value = False
136+
break
137+
return is_valid_value
138+
139+
140+
def _is_valid_pair(key: str, value: str) -> bool:
141+
return _is_valid_key(key) and _is_valid_value(value)

opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,18 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
#
15-
import typing
15+
from logging import getLogger
16+
from re import split
17+
from typing import Iterable, Mapping, Optional, Set
1618
from urllib.parse import quote_plus, unquote_plus
1719

18-
from opentelemetry.baggage import get_all, set_baggage
20+
from opentelemetry.baggage import _is_valid_pair, get_all, set_baggage
1921
from opentelemetry.context import get_current
2022
from opentelemetry.context.context import Context
2123
from opentelemetry.propagators import textmap
24+
from opentelemetry.util.re import _DELIMITER_PATTERN
25+
26+
_logger = getLogger(__name__)
2227

2328

2429
class W3CBaggagePropagator(textmap.TextMapPropagator):
@@ -32,7 +37,7 @@ class W3CBaggagePropagator(textmap.TextMapPropagator):
3237
def extract(
3338
self,
3439
carrier: textmap.CarrierT,
35-
context: typing.Optional[Context] = None,
40+
context: Optional[Context] = None,
3641
getter: textmap.Getter = textmap.default_getter,
3742
) -> Context:
3843
"""Extract Baggage from the carrier.
@@ -49,20 +54,46 @@ def extract(
4954
)
5055

5156
if not header or len(header) > self._MAX_HEADER_LENGTH:
57+
_logger.warning(
58+
"Baggage header `%s` exceeded the maximum number of bytes per baggage-string",
59+
header,
60+
)
5261
return context
5362

54-
baggage_entries = header.split(",")
63+
baggage_entries = split(_DELIMITER_PATTERN, header)
5564
total_baggage_entries = self._MAX_PAIRS
65+
66+
if len(baggage_entries) > self._MAX_PAIRS:
67+
_logger.warning(
68+
"Baggage header `%s` exceeded the maximum number of list-members",
69+
header,
70+
)
71+
5672
for entry in baggage_entries:
5773
if len(entry) > self._MAX_PAIR_LENGTH:
74+
_logger.warning(
75+
"Baggage entry `%s` exceeded the maximum number of bytes per list-member",
76+
entry,
77+
)
78+
continue
79+
if not entry: # empty string
5880
continue
5981
try:
6082
name, value = entry.split("=", 1)
6183
except Exception: # pylint: disable=broad-except
84+
_logger.warning(
85+
"Baggage list-member `%s` doesn't match the format", entry
86+
)
6287
continue
88+
name = unquote_plus(name).strip().lower()
89+
value = unquote_plus(value).strip()
90+
if not _is_valid_pair(name, value):
91+
_logger.warning("Invalid baggage entry: `%s`", entry)
92+
continue
93+
6394
context = set_baggage(
64-
unquote_plus(name).strip(),
65-
unquote_plus(value).strip(),
95+
name,
96+
value,
6697
context=context,
6798
)
6899
total_baggage_entries -= 1
@@ -74,7 +105,7 @@ def extract(
74105
def inject(
75106
self,
76107
carrier: textmap.CarrierT,
77-
context: typing.Optional[Context] = None,
108+
context: Optional[Context] = None,
78109
setter: textmap.Setter = textmap.default_setter,
79110
) -> None:
80111
"""Injects Baggage into the carrier.
@@ -90,21 +121,21 @@ def inject(
90121
setter.set(carrier, self._BAGGAGE_HEADER_NAME, baggage_string)
91122

92123
@property
93-
def fields(self) -> typing.Set[str]:
124+
def fields(self) -> Set[str]:
94125
"""Returns a set with the fields set in `inject`."""
95126
return {self._BAGGAGE_HEADER_NAME}
96127

97128

98-
def _format_baggage(baggage_entries: typing.Mapping[str, object]) -> str:
129+
def _format_baggage(baggage_entries: Mapping[str, object]) -> str:
99130
return ",".join(
100131
quote_plus(str(key)) + "=" + quote_plus(str(value))
101132
for key, value in baggage_entries.items()
102133
)
103134

104135

105136
def _extract_first_element(
106-
items: typing.Optional[typing.Iterable[textmap.CarrierT]],
107-
) -> typing.Optional[textmap.CarrierT]:
137+
items: Optional[Iterable[textmap.CarrierT]],
138+
) -> Optional[textmap.CarrierT]:
108139
if items is None:
109140
return None
110141
return next(iter(items), None)

opentelemetry-api/src/opentelemetry/util/re.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727
)
2828
# A value contains a URL encoded UTF-8 string.
2929
_VALUE_FORMAT = r"[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*"
30-
_HEADER_FORMAT = _KEY_FORMAT + _OWS + r"=" + _OWS + _VALUE_FORMAT
31-
_HEADER_PATTERN = compile(_HEADER_FORMAT)
30+
_KEY_VALUE_FORMAT = rf"{_OWS}{_KEY_FORMAT}{_OWS}={_OWS}{_VALUE_FORMAT}{_OWS}"
31+
_HEADER_PATTERN = compile(_KEY_VALUE_FORMAT)
3232
_DELIMITER_PATTERN = compile(r"[ \t]*,[ \t]*")
3333

34+
_BAGGAGE_PROPERTY_FORMAT = rf"{_KEY_VALUE_FORMAT}|{_OWS}{_KEY_FORMAT}{_OWS}"
35+
3436

3537
# pylint: disable=invalid-name
3638
def parse_headers(s: str) -> Mapping[str, str]:

0 commit comments

Comments
 (0)