baggage.h 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. // Copyright The OpenTelemetry Authors
  2. // SPDX-License-Identifier: Apache-2.0
  3. #pragma once
  4. #include <cctype>
  5. #include "opentelemetry/common/kv_properties.h"
  6. #include "opentelemetry/common/macros.h"
  7. #include "opentelemetry/nostd/shared_ptr.h"
  8. #include "opentelemetry/nostd/string_view.h"
  9. #include "opentelemetry/version.h"
  10. OPENTELEMETRY_BEGIN_NAMESPACE
  11. namespace baggage
  12. {
  13. class OPENTELEMETRY_EXPORT Baggage
  14. {
  15. public:
  16. static constexpr size_t kMaxKeyValuePairs = 180;
  17. static constexpr size_t kMaxKeyValueSize = 4096;
  18. static constexpr size_t kMaxSize = 8192;
  19. static constexpr char kKeyValueSeparator = '=';
  20. static constexpr char kMembersSeparator = ',';
  21. static constexpr char kMetadataSeparator = ';';
  22. Baggage() noexcept : kv_properties_(new common::KeyValueProperties()) {}
  23. Baggage(size_t size) noexcept : kv_properties_(new common::KeyValueProperties(size)) {}
  24. template <class T>
  25. Baggage(const T &keys_and_values) noexcept
  26. : kv_properties_(new common::KeyValueProperties(keys_and_values))
  27. {}
  28. OPENTELEMETRY_API_SINGLETON static nostd::shared_ptr<Baggage> GetDefault()
  29. {
  30. static nostd::shared_ptr<Baggage> baggage{new Baggage()};
  31. return baggage;
  32. }
  33. /* Get value for key in the baggage
  34. @returns true if key is found, false otherwise
  35. */
  36. bool GetValue(nostd::string_view key, std::string &value) const noexcept
  37. {
  38. return kv_properties_->GetValue(key, value);
  39. }
  40. /* Returns shared_ptr of new baggage object which contains new key-value pair. If key or value is
  41. invalid, copy of current baggage is returned
  42. */
  43. nostd::shared_ptr<Baggage> Set(const nostd::string_view &key,
  44. const nostd::string_view &value) noexcept
  45. {
  46. nostd::shared_ptr<Baggage> baggage(new Baggage(kv_properties_->Size() + 1));
  47. const bool valid_kv = IsValidKey(key) && IsValidValue(value);
  48. if (valid_kv)
  49. {
  50. baggage->kv_properties_->AddEntry(key, value);
  51. }
  52. // add rest of the fields.
  53. kv_properties_->GetAllEntries(
  54. [&baggage, &key, &valid_kv](nostd::string_view e_key, nostd::string_view e_value) {
  55. // if key or value was not valid, add all the entries. Add only remaining entries
  56. // otherwise.
  57. if (!valid_kv || key != e_key)
  58. {
  59. baggage->kv_properties_->AddEntry(e_key, e_value);
  60. }
  61. return true;
  62. });
  63. return baggage;
  64. }
  65. // @return all key-values entries by repeatedly invoking the function reference passed as argument
  66. // for each entry
  67. bool GetAllEntries(
  68. nostd::function_ref<bool(nostd::string_view, nostd::string_view)> callback) const noexcept
  69. {
  70. return kv_properties_->GetAllEntries(callback);
  71. }
  72. // delete key from the baggage if it exists. Returns shared_ptr of new baggage object.
  73. // if key does not exist, copy of current baggage is returned.
  74. // Validity of key is not checked as invalid keys should never be populated in baggage in the
  75. // first place.
  76. nostd::shared_ptr<Baggage> Delete(nostd::string_view key) noexcept
  77. {
  78. // keeping size of baggage same as key might not be found in it
  79. nostd::shared_ptr<Baggage> baggage(new Baggage(kv_properties_->Size()));
  80. kv_properties_->GetAllEntries(
  81. [&baggage, &key](nostd::string_view e_key, nostd::string_view e_value) {
  82. if (key != e_key)
  83. baggage->kv_properties_->AddEntry(e_key, e_value);
  84. return true;
  85. });
  86. return baggage;
  87. }
  88. // Returns shared_ptr of baggage after extracting key-value pairs from header
  89. static nostd::shared_ptr<Baggage> FromHeader(nostd::string_view header) noexcept
  90. {
  91. if (header.size() > kMaxSize)
  92. {
  93. // header size exceeds maximum threshold, return empty baggage
  94. return GetDefault();
  95. }
  96. common::KeyValueStringTokenizer kv_str_tokenizer(header);
  97. size_t cnt = kv_str_tokenizer.NumTokens(); // upper bound on number of kv pairs
  98. if (cnt > kMaxKeyValuePairs)
  99. {
  100. cnt = kMaxKeyValuePairs;
  101. }
  102. nostd::shared_ptr<Baggage> baggage(new Baggage(cnt));
  103. bool kv_valid;
  104. nostd::string_view key, value;
  105. while (kv_str_tokenizer.next(kv_valid, key, value) && baggage->kv_properties_->Size() < cnt)
  106. {
  107. if (!kv_valid || (key.size() + value.size() > kMaxKeyValueSize))
  108. {
  109. // if kv pair is not valid, skip it
  110. continue;
  111. }
  112. // NOTE : metadata is kept as part of value only as it does not have any semantic meaning.
  113. // but, we need to extract it (else Decode on value will return error)
  114. nostd::string_view metadata;
  115. auto metadata_separator = value.find(kMetadataSeparator);
  116. if (metadata_separator != std::string::npos)
  117. {
  118. metadata = value.substr(metadata_separator);
  119. value = value.substr(0, metadata_separator);
  120. }
  121. bool err = 0;
  122. auto key_str = UrlDecode(common::StringUtil::Trim(key), err);
  123. auto value_str = UrlDecode(common::StringUtil::Trim(value), err);
  124. if (err == false && IsValidKey(key_str) && IsValidValue(value_str))
  125. {
  126. if (!metadata.empty())
  127. {
  128. value_str.append(metadata.data(), metadata.size());
  129. }
  130. baggage->kv_properties_->AddEntry(key_str, value_str);
  131. }
  132. }
  133. return baggage;
  134. }
  135. // Creates string from baggage object.
  136. std::string ToHeader() const noexcept
  137. {
  138. std::string header_s;
  139. bool first = true;
  140. kv_properties_->GetAllEntries([&](nostd::string_view key, nostd::string_view value) {
  141. if (!first)
  142. {
  143. header_s.push_back(kMembersSeparator);
  144. }
  145. else
  146. {
  147. first = false;
  148. }
  149. header_s.append(UrlEncode(key));
  150. header_s.push_back(kKeyValueSeparator);
  151. // extracting metadata from value. We do not encode metadata
  152. auto metadata_separator = value.find(kMetadataSeparator);
  153. if (metadata_separator != std::string::npos)
  154. {
  155. header_s.append(UrlEncode(value.substr(0, metadata_separator)));
  156. auto metadata = value.substr(metadata_separator);
  157. header_s.append(std::string(metadata.data(), metadata.size()));
  158. }
  159. else
  160. {
  161. header_s.append(UrlEncode(value));
  162. }
  163. return true;
  164. });
  165. return header_s;
  166. }
  167. private:
  168. static bool IsPrintableString(nostd::string_view str)
  169. {
  170. for (const auto ch : str)
  171. {
  172. if (ch < ' ' || ch > '~')
  173. {
  174. return false;
  175. }
  176. }
  177. return true;
  178. }
  179. static bool IsValidKey(nostd::string_view key) { return key.size() && IsPrintableString(key); }
  180. static bool IsValidValue(nostd::string_view value) { return IsPrintableString(value); }
  181. // Uri encode key value pairs before injecting into header
  182. // Implementation inspired from : https://golang.org/src/net/url/url.go?s=7851:7884#L264
  183. static std::string UrlEncode(nostd::string_view str)
  184. {
  185. auto to_hex = [](char c) -> char {
  186. static const char *hex = "0123456789ABCDEF";
  187. return hex[c & 15];
  188. };
  189. std::string ret;
  190. for (auto c : str)
  191. {
  192. if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
  193. {
  194. ret.push_back(c);
  195. }
  196. else if (c == ' ')
  197. {
  198. ret.push_back('+');
  199. }
  200. else
  201. {
  202. ret.push_back('%');
  203. ret.push_back(to_hex(c >> 4));
  204. ret.push_back(to_hex(c & 15));
  205. }
  206. }
  207. return ret;
  208. }
  209. // Uri decode key value pairs after extracting from header
  210. static std::string UrlDecode(nostd::string_view str, bool &err)
  211. {
  212. auto IsHex = [](char c) {
  213. return std::isdigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
  214. };
  215. auto from_hex = [](char c) -> char {
  216. // c - '0' produces integer type which could trigger error/warning when casting to char,
  217. // but the cast is safe here.
  218. return static_cast<char>(std::isdigit(c) ? c - '0' : std::toupper(c) - 'A' + 10);
  219. };
  220. std::string ret;
  221. for (size_t i = 0; i < str.size(); i++)
  222. {
  223. if (str[i] == '%')
  224. {
  225. if (i + 2 >= str.size() || !IsHex(str[i + 1]) || !IsHex(str[i + 2]))
  226. {
  227. err = 1;
  228. return "";
  229. }
  230. ret.push_back(from_hex(str[i + 1]) << 4 | from_hex(str[i + 2]));
  231. i += 2;
  232. }
  233. else if (str[i] == '+')
  234. {
  235. ret.push_back(' ');
  236. }
  237. else if (std::isalnum(str[i]) || str[i] == '-' || str[i] == '_' || str[i] == '.' ||
  238. str[i] == '~')
  239. {
  240. ret.push_back(str[i]);
  241. }
  242. else
  243. {
  244. err = 1;
  245. return "";
  246. }
  247. }
  248. return ret;
  249. }
  250. private:
  251. // Store entries in a C-style array to avoid using std::array or std::vector.
  252. nostd::unique_ptr<common::KeyValueProperties> kv_properties_;
  253. };
  254. } // namespace baggage
  255. OPENTELEMETRY_END_NAMESPACE