re.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. # Copyright The OpenTelemetry Authors
  2. # SPDX-License-Identifier: Apache-2.0
  3. from collections.abc import Mapping
  4. from logging import getLogger
  5. from re import compile, split
  6. from urllib.parse import unquote
  7. from typing_extensions import deprecated
  8. _logger = getLogger(__name__)
  9. # The following regexes reference this spec: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#specifying-headers-via-environment-variables
  10. # Optional whitespace
  11. _OWS = r"[ \t]*"
  12. # A key contains printable US-ASCII characters except: SP and "(),/:;<=>?@[\]{}
  13. _KEY_FORMAT = (
  14. r"[\x21\x23-\x27\x2a\x2b\x2d\x2e\x30-\x39\x41-\x5a\x5e-\x7a\x7c\x7e]+"
  15. )
  16. # A value contains a URL-encoded UTF-8 string. The encoded form can contain any
  17. # printable US-ASCII characters (0x20-0x7f) other than SP, DEL, and ",;/
  18. _VALUE_FORMAT = r"[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*"
  19. # Like above with SP included
  20. _LIBERAL_VALUE_FORMAT = r"[\x20\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*"
  21. # A key-value is key=value, with optional whitespace surrounding key and value
  22. _KEY_VALUE_FORMAT = rf"{_OWS}{_KEY_FORMAT}{_OWS}={_OWS}{_VALUE_FORMAT}{_OWS}"
  23. _HEADER_PATTERN = compile(_KEY_VALUE_FORMAT)
  24. _LIBERAL_HEADER_PATTERN = compile(
  25. rf"{_OWS}{_KEY_FORMAT}{_OWS}={_OWS}{_LIBERAL_VALUE_FORMAT}{_OWS}"
  26. )
  27. _DELIMITER_PATTERN = compile(r"[ \t]*,[ \t]*")
  28. _BAGGAGE_PROPERTY_FORMAT = rf"{_KEY_VALUE_FORMAT}|{_OWS}{_KEY_FORMAT}{_OWS}"
  29. _INVALID_HEADER_ERROR_MESSAGE_STRICT_TEMPLATE = (
  30. "Header format invalid! Header values in environment variables must be "
  31. "URL encoded per the OpenTelemetry Protocol Exporter specification: %s"
  32. )
  33. _INVALID_HEADER_ERROR_MESSAGE_LIBERAL_TEMPLATE = (
  34. "Header format invalid! Header values in environment variables must be "
  35. "URL encoded per the OpenTelemetry Protocol Exporter specification or "
  36. "a comma separated list of name=value occurrences: %s"
  37. )
  38. # pylint: disable=invalid-name
  39. @deprecated(
  40. "You should use parse_env_headers. Deprecated since version 1.15.0."
  41. )
  42. def parse_headers(s: str) -> Mapping[str, str]:
  43. return parse_env_headers(s)
  44. def parse_env_headers(s: str, liberal: bool = False) -> Mapping[str, str]:
  45. """
  46. Parse ``s``, which is a ``str`` instance containing HTTP headers encoded
  47. for use in ENV variables per the W3C Baggage HTTP header format at
  48. https://www.w3.org/TR/baggage/#baggage-http-header-format, except that
  49. additional semi-colon delimited metadata is not supported.
  50. If ``liberal`` is True we try to parse ``s`` anyway to be more compatible
  51. with other languages SDKs that accept non URL-encoded headers by default.
  52. """
  53. headers: dict[str, str] = {}
  54. headers_list: list[str] = split(_DELIMITER_PATTERN, s)
  55. for header in headers_list:
  56. if not header: # empty string
  57. continue
  58. header_match = _HEADER_PATTERN.fullmatch(header.strip())
  59. if not header_match and not liberal:
  60. _logger.warning(
  61. _INVALID_HEADER_ERROR_MESSAGE_STRICT_TEMPLATE, header
  62. )
  63. continue
  64. if header_match:
  65. match_string: str = header_match.string
  66. # value may contain any number of `=`
  67. name, value = match_string.split("=", 1)
  68. name = unquote(name).strip().lower()
  69. value = unquote(value).strip()
  70. headers[name] = value
  71. else:
  72. # this is not url-encoded and does not match the spec but we decided to be
  73. # liberal in what we accept to match other languages SDKs behaviour
  74. liberal_header_match = _LIBERAL_HEADER_PATTERN.fullmatch(
  75. header.strip()
  76. )
  77. if not liberal_header_match:
  78. _logger.warning(
  79. _INVALID_HEADER_ERROR_MESSAGE_LIBERAL_TEMPLATE, header
  80. )
  81. continue
  82. liberal_match_string: str = liberal_header_match.string
  83. # value may contain any number of `=`
  84. name, value = liberal_match_string.split("=", 1)
  85. name = name.strip().lower()
  86. value = value.strip()
  87. headers[name] = value
  88. return headers