Skip to content

Commit f0f44e6

Browse files
Fix OtlpExporterOptionsExtensions.GetHeaders incorrectly throwing for comma in header value
1 parent fe5e6bc commit f0f44e6

File tree

2 files changed

+41
-30
lines changed

2 files changed

+41
-30
lines changed

src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsExtensions.cs

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,47 @@ public static THeaders GetHeaders<THeaders>(this OtlpExporterOptions options, Ac
3333
optionHeaders = Uri.UnescapeDataString(optionHeaders);
3434
ReadOnlySpan<char> headersSpan = optionHeaders.AsSpan();
3535

36+
var nextEqualIndex = headersSpan.IndexOf('=');
37+
38+
if (nextEqualIndex == -1)
39+
{
40+
throw new ArgumentException("Headers provided in an invalid format.");
41+
}
42+
3643
while (!headersSpan.IsEmpty)
3744
{
38-
int commaIndex = headersSpan.IndexOf(',');
39-
ReadOnlySpan<char> pair;
40-
if (commaIndex == -1)
45+
var key = headersSpan.Slice(0, nextEqualIndex).Trim().ToString();
46+
47+
headersSpan = headersSpan.Slice(nextEqualIndex + 1);
48+
49+
nextEqualIndex = headersSpan.IndexOf('=');
50+
51+
string value;
52+
if (nextEqualIndex == -1)
4153
{
42-
pair = headersSpan;
54+
// Everything until the end of the string can be considered the value.
55+
value = headersSpan.Trim().ToString();
4356
headersSpan = [];
4457
}
4558
else
4659
{
47-
pair = headersSpan.Slice(0, commaIndex);
48-
headersSpan = headersSpan.Slice(commaIndex + 1);
49-
}
60+
// If we have another = we need to backtrack from it
61+
// and try to find the last comma and consider that as the delimiter.
62+
var potentialValue = headersSpan.Slice(0, nextEqualIndex);
63+
var lastComma = potentialValue.LastIndexOf(',');
5064

51-
int equalIndex = pair.IndexOf('=');
52-
if (equalIndex == -1)
53-
{
54-
throw new ArgumentException("Headers provided in an invalid format.");
65+
if (lastComma == -1)
66+
{
67+
throw new ArgumentException("Headers provided in an invalid format.");
68+
}
69+
70+
potentialValue = potentialValue.Slice(0, lastComma);
71+
72+
value = potentialValue.Trim().ToString();
73+
headersSpan = headersSpan.Slice(lastComma + 1);
74+
nextEqualIndex -= potentialValue.Length + 1;
5575
}
5676

57-
var key = pair.Slice(0, equalIndex).Trim().ToString();
58-
var value = pair.Slice(equalIndex + 1).Trim().ToString();
5977
addHeader(headers, key, value);
6078
}
6179
}

test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,16 @@ public void GetHeaders_InvalidOptionHeaders_ThrowsArgumentException(string input
4949
[InlineData("key1=value1", "key1=value1")]
5050
[InlineData("key1=value1,key2=value2", "key1=value1,key2=value2")]
5151
[InlineData("key1=value1,key2=value2,key3=value3", "key1=value1,key2=value2,key3=value3")]
52+
[InlineData("key1=value1,value2", "key1=value1,value2")]
53+
[InlineData("key1=value1,value2,key2=value3", "key1=value1,value2,key2=value3")]
5254
[InlineData(" key1 = value1 , key2=value2 ", "key1=value1,key2=value2")]
5355
[InlineData("key1= value with spaces ,key2=another value", "key1=value with spaces,key2=another value")]
5456
[InlineData("=value1", "=value1")]
5557
[InlineData("key1=", "key1=")]
5658
[InlineData("key1=value1%2Ckey2=value2", "key1=value1,key2=value2")]
5759
[InlineData("key1=value1%2Ckey2=value2%2Ckey3=value3", "key1=value1,key2=value2,key3=value3")]
60+
[InlineData("key1=value1%2Cvalue2", "key1=value1,value2")]
61+
[InlineData("key1=value1%2Cvalue2%2Ckey2=value3", "key1=value1,value2,key2=value3")]
5862
public void GetHeaders_ValidAndUrlEncodedHeaders_ReturnsCorrectHeaders(string inputOptionHeaders, string expectedNormalizedOptional)
5963
{
6064
VerifyHeaders(inputOptionHeaders, expectedNormalizedOptional);
@@ -175,27 +179,16 @@ private static void VerifyHeaders(string inputOptionHeaders, string expectedNorm
175179
}
176180

177181
var headers = options.GetHeaders<Dictionary<string, string>>((d, k, v) => d.Add(k, v));
178-
var expectedOptional = new Dictionary<string, string>();
179182

180-
if (!string.IsNullOrEmpty(expectedNormalizedOptional))
181-
{
182-
foreach (var segment in expectedNormalizedOptional.Split([','], StringSplitOptions.RemoveEmptyEntries))
183-
{
184-
var parts = segment.Split(['='], 2);
185-
expectedOptional.Add(parts[0].Trim(), parts[1].Trim());
186-
}
187-
}
183+
var actual = string.Join(",", headers.Select(h => $"{h.Key}={h.Value}"));
188184

189-
Assert.Equal(OtlpExporterOptions.StandardHeaders.Length + expectedOptional.Count, headers.Count);
190-
191-
foreach (var kvp in expectedOptional)
185+
var expected = expectedNormalizedOptional;
186+
if (expected.Length > 0)
192187
{
193-
Assert.Contains(headers, h => h.Key == kvp.Key && h.Value == kvp.Value);
188+
expected += ',';
194189
}
190+
expected += string.Join(",", OtlpExporterOptions.StandardHeaders.Select(h => $"{h.Key}={h.Value}"));
195191

196-
foreach (var std in OtlpExporterOptions.StandardHeaders)
197-
{
198-
Assert.Contains(headers, h => h.Key == std.Key && h.Value == std.Value);
199-
}
192+
Assert.Equal(expected, actual);
200193
}
201194
}

0 commit comments

Comments
 (0)