From 0c0c5a6d120a0d239ffeaf57a2338e50585f5608 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 15:21:12 +0000 Subject: [PATCH 1/5] Initial plan From d3550115ad90097db46b0d3ffd0d3d3be6bddc55 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 15:27:17 +0000 Subject: [PATCH 2/5] Enumerate headers via NonValidated in HttpHeadersLogValue Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../src/Logging/HttpHeadersLogValue.cs | 33 ++++++++++++++----- .../Logging/HttpHeadersLogValueTest.cs | 21 ++++++++++++ 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Http/src/Logging/HttpHeadersLogValue.cs b/src/libraries/Microsoft.Extensions.Http/src/Logging/HttpHeadersLogValue.cs index c2c6223c90ba3c..3d1e2c60cd0f77 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/Logging/HttpHeadersLogValue.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/Logging/HttpHeadersLogValue.cs @@ -38,17 +38,11 @@ private List> Values { var values = new List>(); - foreach (KeyValuePair> kvp in Headers) - { - values.Add(new KeyValuePair(kvp.Key, kvp.Value)); - } + AddHeaders(values, Headers); if (ContentHeaders != null) { - foreach (KeyValuePair> kvp in ContentHeaders) - { - values.Add(new KeyValuePair(kvp.Key, kvp.Value)); - } + AddHeaders(values, ContentHeaders); } _values = values; @@ -73,6 +67,29 @@ public KeyValuePair this[int index] public int Count => Values.Count; + // Enumerate the headers without triggering validation/parsing of the values, so that logging + // doesn't alter how the headers are subsequently serialized on the wire. + private static void AddHeaders(List> values, HttpHeaders headers) + { +#if NET + foreach (KeyValuePair kvp in headers.NonValidated) + { + string[] headerValues = new string[kvp.Value.Count]; + int i = 0; + foreach (string value in kvp.Value) + { + headerValues[i++] = value; + } + + values.Add(new KeyValuePair(kvp.Key, headerValues)); + } +#else + foreach (KeyValuePair> kvp in headers) + { + values.Add(new KeyValuePair(kvp.Key, kvp.Value)); + } +#endif + } public IEnumerator> GetEnumerator() { return Values.GetEnumerator(); diff --git a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/Logging/HttpHeadersLogValueTest.cs b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/Logging/HttpHeadersLogValueTest.cs index 81120214152c2c..c56d4641169fe7 100644 --- a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/Logging/HttpHeadersLogValueTest.cs +++ b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/Logging/HttpHeadersLogValueTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Net.Http; using System.Net.Http.Headers; using Xunit; @@ -49,6 +50,26 @@ public void HttpHeadersLogValue_ToString_HidesOnlyLogSensitiveHeadersValue() result); } +#if NET + [Fact] + public void HttpHeadersLogValue_DoesNotValidateHeaderValues() + { + var request = new HttpRequestMessage(HttpMethod.Get, "https://example.com"); + request.Headers.TryAddWithoutValidation("Accept", "application/vnd.example+json;version=1"); + + var httpHeadersLogValue = new HttpHeadersLogValue(HttpHeadersLogValue.Kind.Request, request.Headers, contentHeaders: null, _ => false); + + Assert.Equal( + "Request Headers:" + Environment.NewLine + + "Accept: application/vnd.example+json;version=1" + Environment.NewLine, + httpHeadersLogValue.ToString()); + + Assert.True(request.Headers.NonValidated.TryGetValues("Accept", out HeaderStringValues values)); + Assert.Equal("application/vnd.example+json;version=1", Assert.Single(values)); + } +#endif + private class TestHttpHeaders : HttpHeaders { } + } } From dd36a5aa8c9556569d99cca700e1a45e5bae2a40 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 15:56:49 +0000 Subject: [PATCH 3/5] Use HeaderStringValues.ToString, presize list, fix blank lines Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../src/Logging/HttpHeadersLogValue.cs | 23 +++++++++++-------- .../Logging/HttpHeadersLogValueTest.cs | 1 - 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Http/src/Logging/HttpHeadersLogValue.cs b/src/libraries/Microsoft.Extensions.Http/src/Logging/HttpHeadersLogValue.cs index 3d1e2c60cd0f77..41d4e28a283385 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/Logging/HttpHeadersLogValue.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/Logging/HttpHeadersLogValue.cs @@ -36,7 +36,7 @@ private List> Values { if (_values == null) { - var values = new List>(); + var values = new List>(GetHeaderCount(Headers) + GetHeaderCount(ContentHeaders)); AddHeaders(values, Headers); @@ -74,14 +74,7 @@ private static void AddHeaders(List> values, HttpHe #if NET foreach (KeyValuePair kvp in headers.NonValidated) { - string[] headerValues = new string[kvp.Value.Count]; - int i = 0; - foreach (string value in kvp.Value) - { - headerValues[i++] = value; - } - - values.Add(new KeyValuePair(kvp.Key, headerValues)); + values.Add(new KeyValuePair(kvp.Key, kvp.Value.ToString())); } #else foreach (KeyValuePair> kvp in headers) @@ -90,6 +83,16 @@ private static void AddHeaders(List> values, HttpHe } #endif } + + private static int GetHeaderCount(HttpHeaders? headers) + { +#if NET + return headers?.NonValidated.Count ?? 0; +#else + return 0; +#endif + } + public IEnumerator> GetEnumerator() { return Values.GetEnumerator(); @@ -121,7 +124,7 @@ public override string ToString() else { #if NET - builder.AppendJoin(", ", (IEnumerable)kvp.Value); + builder.Append(kvp.Value.ToString()); builder.AppendLine(); #else foreach (object value in (IEnumerable)kvp.Value) diff --git a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/Logging/HttpHeadersLogValueTest.cs b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/Logging/HttpHeadersLogValueTest.cs index c56d4641169fe7..c4edc16d3abe04 100644 --- a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/Logging/HttpHeadersLogValueTest.cs +++ b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/Logging/HttpHeadersLogValueTest.cs @@ -70,6 +70,5 @@ public void HttpHeadersLogValue_DoesNotValidateHeaderValues() #endif private class TestHttpHeaders : HttpHeaders { } - } } From 110d0900cc8f7520fb8e9c506950e6fcea913301 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 18:06:23 +0000 Subject: [PATCH 4/5] Apply header redaction when building the log values list Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../src/Logging/HttpHeadersLogValue.cs | 24 +++++++++++-------- .../Logging/HttpHeadersLogValueTest.cs | 6 +++++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Http/src/Logging/HttpHeadersLogValue.cs b/src/libraries/Microsoft.Extensions.Http/src/Logging/HttpHeadersLogValue.cs index 41d4e28a283385..1663aedc4d255c 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/Logging/HttpHeadersLogValue.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/Logging/HttpHeadersLogValue.cs @@ -11,6 +11,8 @@ namespace Microsoft.Extensions.Http.Logging { internal sealed class HttpHeadersLogValue : IReadOnlyList> { + private const string RedactedValue = "*"; + private readonly Kind _kind; private readonly Func _shouldRedactHeaderValue; @@ -69,17 +71,19 @@ public KeyValuePair this[int index] // Enumerate the headers without triggering validation/parsing of the values, so that logging // doesn't alter how the headers are subsequently serialized on the wire. - private static void AddHeaders(List> values, HttpHeaders headers) + private void AddHeaders(List> values, HttpHeaders headers) { #if NET foreach (KeyValuePair kvp in headers.NonValidated) { - values.Add(new KeyValuePair(kvp.Key, kvp.Value.ToString())); + object value = _shouldRedactHeaderValue(kvp.Key) ? RedactedValue : kvp.Value.ToString(); + values.Add(new KeyValuePair(kvp.Key, value)); } #else foreach (KeyValuePair> kvp in headers) { - values.Add(new KeyValuePair(kvp.Key, kvp.Value)); + object value = _shouldRedactHeaderValue(kvp.Key) ? RedactedValue : kvp.Value; + values.Add(new KeyValuePair(kvp.Key, value)); } #endif } @@ -116,17 +120,17 @@ public override string ToString() builder.Append(kvp.Key); builder.Append(": "); - if (_shouldRedactHeaderValue(kvp.Key)) +#if NET + builder.Append((string)kvp.Value); + builder.AppendLine(); +#else + if (kvp.Value is string redactedValue) { - builder.Append('*'); + builder.Append(redactedValue); builder.AppendLine(); } else { -#if NET - builder.Append(kvp.Value.ToString()); - builder.AppendLine(); -#else foreach (object value in (IEnumerable)kvp.Value) { builder.Append(value); @@ -136,8 +140,8 @@ public override string ToString() // Remove the extra ', ' builder.Remove(builder.Length - 2, 2); builder.AppendLine(); -#endif } +#endif } _formatted = builder.ToString(); diff --git a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/Logging/HttpHeadersLogValueTest.cs b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/Logging/HttpHeadersLogValueTest.cs index c4edc16d3abe04..b5a4b393188c73 100644 --- a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/Logging/HttpHeadersLogValueTest.cs +++ b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/Logging/HttpHeadersLogValueTest.cs @@ -48,6 +48,12 @@ public void HttpHeadersLogValue_ToString_HidesOnlyLogSensitiveHeadersValue() "unsecureHeader2: value2" + Environment.NewLine + "secureHeader2: *" + Environment.NewLine, result); + + // Redaction is applied to the structured values, not just to the formatted string. + Assert.Equal("secureHeader1", httpHeadersLogValue[0].Key); + Assert.Equal("*", httpHeadersLogValue[0].Value); + Assert.Equal("secureHeader2", httpHeadersLogValue[3].Key); + Assert.Equal("*", httpHeadersLogValue[3].Value); } #if NET From e7af0593e9e7ac10013bdeadd83630087b32887c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 19:02:34 +0000 Subject: [PATCH 5/5] Join header values earlier on non-NET so value type is string on all TFMs Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../src/Logging/HttpHeadersLogValue.cs | 35 +++++-------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Http/src/Logging/HttpHeadersLogValue.cs b/src/libraries/Microsoft.Extensions.Http/src/Logging/HttpHeadersLogValue.cs index 1663aedc4d255c..c5241af88ca420 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/Logging/HttpHeadersLogValue.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/Logging/HttpHeadersLogValue.cs @@ -75,17 +75,19 @@ private void AddHeaders(List> values, HttpHeaders h { #if NET foreach (KeyValuePair kvp in headers.NonValidated) - { - object value = _shouldRedactHeaderValue(kvp.Key) ? RedactedValue : kvp.Value.ToString(); - values.Add(new KeyValuePair(kvp.Key, value)); - } #else foreach (KeyValuePair> kvp in headers) +#endif { - object value = _shouldRedactHeaderValue(kvp.Key) ? RedactedValue : kvp.Value; + string value = _shouldRedactHeaderValue(kvp.Key) + ? RedactedValue +#if NET + : kvp.Value.ToString(); +#else + : string.Join(", ", kvp.Value); +#endif values.Add(new KeyValuePair(kvp.Key, value)); } -#endif } private static int GetHeaderCount(HttpHeaders? headers) @@ -119,29 +121,8 @@ public override string ToString() KeyValuePair kvp = Values[i]; builder.Append(kvp.Key); builder.Append(": "); - -#if NET builder.Append((string)kvp.Value); builder.AppendLine(); -#else - if (kvp.Value is string redactedValue) - { - builder.Append(redactedValue); - builder.AppendLine(); - } - else - { - foreach (object value in (IEnumerable)kvp.Value) - { - builder.Append(value); - builder.Append(", "); - } - - // Remove the extra ', ' - builder.Remove(builder.Length - 2, 2); - builder.AppendLine(); - } -#endif } _formatted = builder.ToString();