From 3e29d0b708ff09d59410f6e33ebd2c7398cd3ee0 Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Thu, 13 Aug 2020 10:12:16 -0700 Subject: Remove cookie name decoding (#24264) --- eng/PatchConfig.props | 1 + .../Http/src/Internal/RequestCookieCollection.cs | 26 ++++++++++--- src/Http/Http/src/Properties/AssemblyInfo.cs | 3 ++ .../Http/test/RequestCookiesCollectionTests.cs | 43 ++++++++++++---------- 4 files changed, 47 insertions(+), 26 deletions(-) create mode 100644 src/Http/Http/src/Properties/AssemblyInfo.cs diff --git a/eng/PatchConfig.props b/eng/PatchConfig.props index 40c0413b43..714b3a3b03 100644 --- a/eng/PatchConfig.props +++ b/eng/PatchConfig.props @@ -74,6 +74,7 @@ Later on, this will be checked using this condition: + Microsoft.AspNetCore.Http; diff --git a/src/Http/Http/src/Internal/RequestCookieCollection.cs b/src/Http/Http/src/Internal/RequestCookieCollection.cs index 4af0a65246..2868f8678c 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 { public 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 @@ namespace Microsoft.AspNetCore.Http.Internal 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 @@ namespace Microsoft.AspNetCore.Http.Internal } 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 @@ namespace Microsoft.AspNetCore.Http.Internal 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 @@ namespace Microsoft.AspNetCore.Http.Internal { 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 @@ namespace Microsoft.AspNetCore.Http.Internal value = null; return false; } - return Store.TryGetValue(key, out value); + + return Store.TryGetValue(key, out value) + || !_enableCookieNameDecoding && Store.TryGetValue(Uri.EscapeDataString(key), out value); } /// @@ -229,4 +243,4 @@ namespace Microsoft.AspNetCore.Http.Internal } } } -} \ No newline at end of file +} diff --git a/src/Http/Http/src/Properties/AssemblyInfo.cs b/src/Http/Http/src/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2b8d94f4a5 --- /dev/null +++ b/src/Http/Http/src/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Http.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Http/Http/test/RequestCookiesCollectionTests.cs b/src/Http/Http/test/RequestCookiesCollectionTests.cs index 70106df027..def99424c8 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. +// 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.AspNetCore.Http.Internal; using Microsoft.Extensions.Primitives; @@ -10,30 +11,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()); -- cgit v1.2.3