Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ namespace Microsoft.Extensions.Http.Logging
{
internal sealed class HttpHeadersLogValue : IReadOnlyList<KeyValuePair<string, object>>
{
private const string RedactedValue = "*";

private readonly Kind _kind;
private readonly Func<string, bool> _shouldRedactHeaderValue;

Expand All @@ -36,19 +38,13 @@ private List<KeyValuePair<string, object>> Values
{
if (_values == null)
{
var values = new List<KeyValuePair<string, object>>();
var values = new List<KeyValuePair<string, object>>(GetHeaderCount(Headers) + GetHeaderCount(ContentHeaders));

foreach (KeyValuePair<string, IEnumerable<string>> kvp in Headers)
{
values.Add(new KeyValuePair<string, object>(kvp.Key, kvp.Value));
}
AddHeaders(values, Headers);

if (ContentHeaders != null)
{
foreach (KeyValuePair<string, IEnumerable<string>> kvp in ContentHeaders)
{
values.Add(new KeyValuePair<string, object>(kvp.Key, kvp.Value));
}
AddHeaders(values, ContentHeaders);
}
Comment thread
MihaZupan marked this conversation as resolved.

_values = values;
Expand All @@ -73,6 +69,36 @@ public KeyValuePair<string, object> 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 void AddHeaders(List<KeyValuePair<string, object>> values, HttpHeaders headers)
{
#if NET
foreach (KeyValuePair<string, HeaderStringValues> kvp in headers.NonValidated)
#else
foreach (KeyValuePair<string, IEnumerable<string>> kvp in headers)
#endif
{
string value = _shouldRedactHeaderValue(kvp.Key)
? RedactedValue
#if NET
: kvp.Value.ToString();
#else
: string.Join(", ", kvp.Value);
#endif
values.Add(new KeyValuePair<string, object>(kvp.Key, value));
}
}

private static int GetHeaderCount(HttpHeaders? headers)
{
#if NET
return headers?.NonValidated.Count ?? 0;
#else
return 0;
#endif
}

public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return Values.GetEnumerator();
Expand All @@ -95,29 +121,8 @@ public override string ToString()
KeyValuePair<string, object> kvp = Values[i];
builder.Append(kvp.Key);
builder.Append(": ");

if (_shouldRedactHeaderValue(kvp.Key))
{
builder.Append('*');
builder.AppendLine();
}
else
{
#if NET
builder.AppendJoin(", ", (IEnumerable<object>)kvp.Value);
builder.AppendLine();
#else
foreach (object value in (IEnumerable<object>)kvp.Value)
{
builder.Append(value);
builder.Append(", ");
}

// Remove the extra ', '
builder.Remove(builder.Length - 2, 2);
builder.AppendLine();
#endif
}
builder.Append((string)kvp.Value);
builder.AppendLine();
}

_formatted = builder.ToString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using Xunit;

Expand Down Expand Up @@ -47,7 +48,32 @@ 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
[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 { }
}
Expand Down
Loading