-
Notifications
You must be signed in to change notification settings - Fork 778
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[baggage] validate key and values during baggage injection/extraction #5647
Changes from all commits
065f4d6
b557142
a78a6da
a18c116
6b88989
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -84,20 +84,35 @@ public override void Inject<T>(PropagationContext context, T carrier, Action<T, | |
if (e.MoveNext() == true) | ||
{ | ||
int itemCount = 0; | ||
StringBuilder baggage = new StringBuilder(); | ||
StringBuilder baggage = null; | ||
do | ||
{ | ||
KeyValuePair<string, string> item = e.Current; | ||
if (string.IsNullOrEmpty(item.Value)) | ||
|
||
if (!ValidateKey(item.Key)) | ||
{ | ||
continue; | ||
} | ||
|
||
baggage.Append(Uri.EscapeDataString(item.Key)).Append('=').Append(Uri.EscapeDataString(item.Value)).Append(','); | ||
if (baggage == null) | ||
{ | ||
baggage = new StringBuilder(); | ||
} | ||
|
||
baggage.Append(item.Key).Append('=').Append(Uri.EscapeDataString(item.Value)).Append(','); | ||
itemCount++; | ||
if (baggage.Length >= MaxBaggageLength) | ||
{ | ||
break; | ||
} | ||
} | ||
while (e.MoveNext() && itemCount < MaxBaggageItems); | ||
|
||
if (baggage is not null) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the expected behavior if baggage is null? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
{ | ||
baggage.Remove(baggage.Length - 1, 1); | ||
setter(carrier, BaggageHeaderName, baggage.ToString()); | ||
} | ||
while (e.MoveNext() && ++itemCount < MaxBaggageItems && baggage.Length < MaxBaggageLength); | ||
baggage.Remove(baggage.Length - 1, 1); | ||
setter(carrier, BaggageHeaderName, baggage.ToString()); | ||
} | ||
} | ||
|
||
|
@@ -116,7 +131,8 @@ internal static bool TryExtractBaggage(string[] baggageCollection, out Dictionar | |
|
||
if (string.IsNullOrEmpty(item)) | ||
{ | ||
continue; | ||
baggage = null; | ||
return false; | ||
Comment on lines
+134
to
+135
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It feels very strict to ignore the entire baggage collection if any entry is invalid, or in this case an entry is empty. I'm not so familiar with the baggage spec, but the tracestate spec specifically calls out that empty list members are allowed:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to W3C Baggage spec empty entries are invalid - entry needs to at least contain a Also note that current implementation of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. They are indeed different on whether allowing list-member to be OWS. |
||
} | ||
|
||
foreach (var pair in item.Split(CommaSignSeparator)) | ||
|
@@ -131,33 +147,146 @@ internal static bool TryExtractBaggage(string[] baggageCollection, out Dictionar | |
|
||
if (pair.IndexOf('=') < 0) | ||
{ | ||
continue; | ||
baggage = null; | ||
return false; | ||
} | ||
|
||
var parts = pair.Split(EqualSignSeparator, 2); | ||
if (parts.Length != 2) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. List-member could contain multiple
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ad 1. Properties are not handled/recognized properly currently, this is something that needs to be addressed, but as it requires changes in both Baggage API and propagator, I'd prefer to address it in a separate PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I created issue to track handling of properties. |
||
{ | ||
continue; | ||
baggage = null; | ||
return false; | ||
} | ||
|
||
var key = Uri.UnescapeDataString(parts[0]); | ||
var value = Uri.UnescapeDataString(parts[1]); | ||
var key = parts[0].Trim(); | ||
|
||
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) | ||
if (!ValidateKey(key)) | ||
{ | ||
continue; | ||
baggage = null; | ||
return false; | ||
} | ||
|
||
var encodedValue = parts[1].Trim(); | ||
|
||
if (!ValidateValue(encodedValue)) | ||
{ | ||
baggage = null; | ||
return false; | ||
} | ||
|
||
var decodedValue = Uri.UnescapeDataString(encodedValue); | ||
|
||
if (baggageDictionary == null) | ||
{ | ||
baggageDictionary = new Dictionary<string, string>(); | ||
} | ||
|
||
baggageDictionary[key] = value; | ||
baggageDictionary[key] = decodedValue; | ||
} | ||
} | ||
|
||
baggage = baggageDictionary; | ||
return baggageDictionary != null; | ||
} | ||
|
||
private static bool ValidateValue(string encodedValue) | ||
{ | ||
var index = 0; | ||
while (index < encodedValue.Length) | ||
{ | ||
var c = encodedValue[index]; | ||
|
||
if (c == '%') | ||
{ | ||
if (!ValidatePercentEncoding(index, encodedValue)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if the Relevant spec:
For example, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've realized this is assuming the string is encoded value. You can ignore above comment's case. However, the next |
||
{ | ||
OpenTelemetryApiEventSource.Log.BaggageItemValueIsInvalid(encodedValue); | ||
return false; | ||
} | ||
|
||
index += 3; | ||
} | ||
else if (!ValidateValueCharInRange(c)) | ||
{ | ||
OpenTelemetryApiEventSource.Log.BaggageItemValueIsInvalid(encodedValue); | ||
return false; | ||
} | ||
else | ||
{ | ||
index++; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private static bool ValidatePercentEncoding(int index, string encodedValue) | ||
{ | ||
return index < encodedValue.Length - 2 && | ||
ValidateHexChar(encodedValue[index + 1]) && | ||
ValidateHexChar(encodedValue[index + 2]); | ||
} | ||
|
||
private static bool ValidateHexChar(char c) | ||
{ | ||
return c is | ||
>= '0' and <= '9' or | ||
>= 'A' and <= 'F' or | ||
>= 'a' and <= 'f'; | ||
} | ||
|
||
private static bool ValidateValueCharInRange(char c) | ||
{ | ||
// https://w3c.github.io/baggage/#definition | ||
// value = *baggage-octet | ||
// baggage-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E | ||
// ; US-ASCII characters excluding CTLs, | ||
// ; whitespace, DQUOTE, comma, semicolon, | ||
// ; and backslash | ||
return c is | ||
'\u0021' or | ||
>= '\u0023' and <= '\u002b' or | ||
>= '\u002d' and <= '\u003a' or | ||
>= '\u003c' and <= '\u005b' or | ||
>= '\u005d' and <= '\u007e'; | ||
} | ||
|
||
private static bool ValidateKey(string key) | ||
{ | ||
if (string.IsNullOrEmpty(key)) | ||
{ | ||
return false; | ||
} | ||
|
||
foreach (var c in key) | ||
{ | ||
// chars permitted in token, as defined in: | ||
// https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6 | ||
if (!ValidateTokenChar(c) && !ValidateAsciiLetterOrDigit(c)) | ||
{ | ||
OpenTelemetryApiEventSource.Log.BaggageItemKeyIsInvalid(key); | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private static bool ValidateTokenChar(char c) | ||
{ | ||
// https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6 | ||
// token = 1*tchar | ||
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" | ||
// / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: the code logic is correct but tchar definition also contains |
||
return c is '!' or '#' or '$' or '%' or '&' or '\'' or '*' | ||
or '+' or '-' or '.' or '^' or '_' or '`' or '|' or '~'; | ||
} | ||
|
||
private static bool ValidateAsciiLetterOrDigit(char c) | ||
{ | ||
return c is | ||
>= '0' and <= '9' or | ||
>= 'A' and <= 'Z' or | ||
>= 'a' and <= 'z'; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -108,4 +108,16 @@ public void FailedToInjectBaggage(string format, string error) | |
{ | ||
this.WriteEvent(11, format, error); | ||
} | ||
|
||
[Event(12, Message = "Baggage item key is invalid, key = '{0}'", Level = EventLevel.Warning)] | ||
public void BaggageItemKeyIsInvalid(string key) | ||
{ | ||
this.WriteEvent(12, key); | ||
} | ||
|
||
[Event(13, Message = "Baggage item value is invalid, value = '{0}'", Level = EventLevel.Warning)] | ||
public void BaggageItemValueIsInvalid(string value) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Q: Should we inform what was the key for the given value? It might be useful for debugging. |
||
{ | ||
this.WriteEvent(13, value); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a need to differentiate null vs empty? This would get checked in every iteration of the loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There isn't a need to differentiate between these, I will modify it.