diff --git a/src/OpenTelemetry.Extensions.Propagators/JaegerPropagator.cs b/src/OpenTelemetry.Extensions.Propagators/JaegerPropagator.cs index e20d860958c..c8d520962c8 100644 --- a/src/OpenTelemetry.Extensions.Propagators/JaegerPropagator.cs +++ b/src/OpenTelemetry.Extensions.Propagators/JaegerPropagator.cs @@ -1,6 +1,9 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +#if NET +using System.Buffers; +#endif using System.Diagnostics; using OpenTelemetry.Context.Propagation; using OpenTelemetry.Internal; @@ -25,6 +28,10 @@ public class JaegerPropagator : TextMapPropagator private static readonly int TraceId128BitLength = "0af7651916cd43dd8448eb211c80319c".Length; private static readonly int SpanIdLength = "00f067aa0ba902b7".Length; +#if NET + private static readonly SearchValues DelimiterHintChars = SearchValues.Create(":%"); +#endif + /// #pragma warning disable CS0809 // Obsolete member overrides non-obsolete member public override ISet Fields => new HashSet { JaegerHeader }; @@ -236,6 +243,40 @@ private static bool TryExtractTraceParts( private static ReadOnlySpan ReadNextComponent(string header, ref int position) { +#if NET + var remaining = header.AsSpan(position); + var scanOffset = 0; + while (true) + { + var delimiterIndex = remaining.Slice(scanOffset).IndexOfAny(DelimiterHintChars); + if (delimiterIndex < 0) + { + var result = remaining; + position = header.Length; + return result; + } + + delimiterIndex += scanOffset; + + if (remaining[delimiterIndex] == ':') + { + var component = remaining.Slice(0, delimiterIndex); + position += delimiterIndex + 1; + return component; + } + + if (remaining.Length - delimiterIndex >= JaegerDelimiterEncoded.Length && + remaining[delimiterIndex + 1] == '3' && + remaining[delimiterIndex + 2] == 'A') + { + var component = remaining.Slice(0, delimiterIndex); + position += delimiterIndex + JaegerDelimiterEncoded.Length; + return component; + } + + scanOffset = delimiterIndex + 1; + } +#else var colonIndex = header.IndexOf(JaegerDelimiter, position, StringComparison.Ordinal); var encodedIndex = header.IndexOf(JaegerDelimiterEncoded, position, StringComparison.Ordinal); @@ -263,5 +304,6 @@ private static ReadOnlySpan ReadNextComponent(string header, ref int posit var component = header.AsSpan(position, nextIndex - position); position = nextIndex + delimiterLength; return component; +#endif } } diff --git a/test/OpenTelemetry.Extensions.Propagators.Tests/JaegerPropagatorTests.cs b/test/OpenTelemetry.Extensions.Propagators.Tests/JaegerPropagatorTests.cs index 0e00823d267..0969477c667 100644 --- a/test/OpenTelemetry.Extensions.Propagators.Tests/JaegerPropagatorTests.cs +++ b/test/OpenTelemetry.Extensions.Propagators.Tests/JaegerPropagatorTests.cs @@ -162,6 +162,20 @@ public void ExtractReturnsNewContextIfHeaderContainsEmptyComponent() Assert.Equal(ActivityTraceFlags.Recorded, result.ActivityContext.TraceFlags); } + [Fact] + public void ExtractReturnsOriginalContextIfHeaderContainsUnexpectedPercentCharacter() + { + var formattedHeader = $"%{TraceId}{JaegerDelimiter}{SpanId}{JaegerDelimiter}{ParentSpanId}{JaegerDelimiter}{FlagSampled}"; + var headers = new Dictionary + { + [JaegerHeader] = [formattedHeader], + }; + + var result = new JaegerPropagator().Extract(default, headers, Getter); + + Assert.Equal(default, result); + } + [Fact] public void InjectDoesNoopIfContextIsInvalid() {