diff --git a/src/Http/Http/ref/Microsoft.AspNetCore.Http.Manual.cs b/src/Http/Http/ref/Microsoft.AspNetCore.Http.Manual.cs index 67edcfae2a53..97fd5cff8ed5 100644 --- a/src/Http/Http/ref/Microsoft.AspNetCore.Http.Manual.cs +++ b/src/Http/Http/ref/Microsoft.AspNetCore.Http.Manual.cs @@ -37,6 +37,7 @@ public RequestCookieCollection(int capacity) { } public bool ContainsKey(string key) { throw null; } public Microsoft.AspNetCore.Http.RequestCookieCollection.Enumerator GetEnumerator() { throw null; } public static Microsoft.AspNetCore.Http.RequestCookieCollection Parse(System.Collections.Generic.IList values) { throw null; } + internal static RequestCookieCollection ParseInternal(System.Collections.Generic.IList values, bool enableCookieNameDecoding) { throw null; } System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } public bool TryGetValue(string key, out string value) { throw null; } diff --git a/src/Http/Http/src/Internal/RequestCookieCollection.cs b/src/Http/Http/src/Internal/RequestCookieCollection.cs index 2fb738b51b74..885ea0e7a98f 100644 --- a/src/Http/Http/src/Internal/RequestCookieCollection.cs +++ b/src/Http/Http/src/Internal/RequestCookieCollection.cs @@ -10,6 +10,9 @@ namespace Microsoft.AspNetCore.Http { internal class RequestCookieCollection : IRequestCookieCollection { + private const string EnableCookieNameDecoding = "Microsoft.AspNetCore.Http.EnableCookieNameDecoding"; + private bool _enableCookieNameDecoding; + public static readonly RequestCookieCollection Empty = new RequestCookieCollection(); private static readonly string[] EmptyKeys = Array.Empty(); private static readonly Enumerator EmptyEnumerator = new Enumerator(); @@ -21,14 +24,15 @@ internal class RequestCookieCollection : IRequestCookieCollection public RequestCookieCollection() { + _enableCookieNameDecoding = AppContext.TryGetSwitch(EnableCookieNameDecoding, out var enabled) && enabled; } - public RequestCookieCollection(Dictionary store) + public RequestCookieCollection(Dictionary store) : this() { Store = store; } - public RequestCookieCollection(int capacity) + public RequestCookieCollection(int capacity) : this() { Store = new Dictionary(capacity, StringComparer.OrdinalIgnoreCase); } @@ -57,6 +61,9 @@ public string this[string key] } public static RequestCookieCollection Parse(IList values) + => ParseInternal(values, AppContext.TryGetSwitch(EnableCookieNameDecoding, out var enabled) && enabled); + + internal static RequestCookieCollection ParseInternal(IList values, bool enableCookieNameDecoding) { if (values.Count == 0) { @@ -76,7 +83,11 @@ public static RequestCookieCollection Parse(IList values) for (var i = 0; i < cookies.Count; i++) { var cookie = cookies[i]; - var name = Uri.UnescapeDataString(cookie.Name.Value); + var name = cookie.Name.Value; + if (enableCookieNameDecoding) + { + name = Uri.UnescapeDataString(name); + } var value = Uri.UnescapeDataString(cookie.Value.Value); store[name] = value; } @@ -116,7 +127,8 @@ public bool ContainsKey(string key) { return false; } - return Store.ContainsKey(key); + return Store.ContainsKey(key) + || !_enableCookieNameDecoding && Store.ContainsKey(Uri.EscapeDataString(key)); } public bool TryGetValue(string key, out string value) @@ -126,7 +138,9 @@ public bool TryGetValue(string key, out string value) value = null; return false; } - return Store.TryGetValue(key, out value); + + return Store.TryGetValue(key, out value) + || !_enableCookieNameDecoding && Store.TryGetValue(Uri.EscapeDataString(key), out value); } /// diff --git a/src/Http/Http/test/RequestCookiesCollectionTests.cs b/src/Http/Http/test/RequestCookiesCollectionTests.cs index 0d3cf069adb4..2b1d61a10094 100644 --- a/src/Http/Http/test/RequestCookiesCollectionTests.cs +++ b/src/Http/Http/test/RequestCookiesCollectionTests.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Linq; using Microsoft.Extensions.Primitives; using Xunit; @@ -9,30 +10,32 @@ namespace Microsoft.AspNetCore.Http.Tests { public class RequestCookiesCollectionTests { - public static TheoryData UnEscapesKeyValues_Data + [Theory] + [InlineData("key=value", "key", "value")] + [InlineData("__secure-key=value", "__secure-key", "value")] + [InlineData("key%2C=%21value", "key,", "!value")] + [InlineData("ke%23y%2C=val%5Eue", "ke#y,", "val^ue")] + [InlineData("base64=QUI%2BREU%2FRw%3D%3D", "base64", "QUI+REU/Rw==")] + [InlineData("base64=QUI+REU/Rw==", "base64", "QUI+REU/Rw==")] + public void UnEscapesValues(string input, string expectedKey, string expectedValue) { - get - { - // key, value, expected - return new TheoryData - { - { "key=value", "key", "value" }, - { "key%2C=%21value", "key,", "!value" }, - { "ke%23y%2C=val%5Eue", "ke#y,", "val^ue" }, - { "base64=QUI%2BREU%2FRw%3D%3D", "base64", "QUI+REU/Rw==" }, - { "base64=QUI+REU/Rw==", "base64", "QUI+REU/Rw==" }, - }; - } + var cookies = RequestCookieCollection.Parse(new StringValues(input)); + + Assert.Equal(1, cookies.Count); + Assert.Equal(Uri.EscapeDataString(expectedKey), cookies.Keys.Single()); + Assert.Equal(expectedValue, cookies[expectedKey]); } [Theory] - [MemberData(nameof(UnEscapesKeyValues_Data))] - public void UnEscapesKeyValues( - string input, - string expectedKey, - string expectedValue) + [InlineData("key=value", "key", "value")] + [InlineData("__secure-key=value", "__secure-key", "value")] + [InlineData("key%2C=%21value", "key,", "!value")] + [InlineData("ke%23y%2C=val%5Eue", "ke#y,", "val^ue")] + [InlineData("base64=QUI%2BREU%2FRw%3D%3D", "base64", "QUI+REU/Rw==")] + [InlineData("base64=QUI+REU/Rw==", "base64", "QUI+REU/Rw==")] + public void AppContextSwitchUnEscapesKeyValues(string input, string expectedKey, string expectedValue) { - var cookies = RequestCookieCollection.Parse(new StringValues(input)); + var cookies = RequestCookieCollection.ParseInternal(new StringValues(input), enableCookieNameDecoding: true); Assert.Equal(1, cookies.Count); Assert.Equal(expectedKey, cookies.Keys.Single());