Remove cookie name decoding (#24389)

This commit is contained in:
Chris Ross 2020-08-13 10:10:49 -07:00 committed by GitHub
parent a8e8fe2ab3
commit ad4f0a0cb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 42 additions and 24 deletions

View File

@ -37,6 +37,7 @@ namespace Microsoft.AspNetCore.Http
public bool ContainsKey(string key) { throw null; } public bool ContainsKey(string key) { throw null; }
public Microsoft.AspNetCore.Http.RequestCookieCollection.Enumerator GetEnumerator() { throw null; } public Microsoft.AspNetCore.Http.RequestCookieCollection.Enumerator GetEnumerator() { throw null; }
public static Microsoft.AspNetCore.Http.RequestCookieCollection Parse(System.Collections.Generic.IList<string> values) { throw null; } public static Microsoft.AspNetCore.Http.RequestCookieCollection Parse(System.Collections.Generic.IList<string> values) { throw null; }
internal static RequestCookieCollection ParseInternal(System.Collections.Generic.IList<string> values, bool enableCookieNameDecoding) { throw null; }
System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string, string>> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.String,System.String>>.GetEnumerator() { throw null; } System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string, string>> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.String,System.String>>.GetEnumerator() { throw null; }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
public bool TryGetValue(string key, out string value) { throw null; } public bool TryGetValue(string key, out string value) { throw null; }

View File

@ -10,6 +10,9 @@ namespace Microsoft.AspNetCore.Http
{ {
internal class RequestCookieCollection : IRequestCookieCollection internal class RequestCookieCollection : IRequestCookieCollection
{ {
private const string EnableCookieNameDecoding = "Microsoft.AspNetCore.Http.EnableCookieNameDecoding";
private bool _enableCookieNameDecoding;
public static readonly RequestCookieCollection Empty = new RequestCookieCollection(); public static readonly RequestCookieCollection Empty = new RequestCookieCollection();
private static readonly string[] EmptyKeys = Array.Empty<string>(); private static readonly string[] EmptyKeys = Array.Empty<string>();
private static readonly Enumerator EmptyEnumerator = new Enumerator(); private static readonly Enumerator EmptyEnumerator = new Enumerator();
@ -21,14 +24,15 @@ namespace Microsoft.AspNetCore.Http
public RequestCookieCollection() public RequestCookieCollection()
{ {
_enableCookieNameDecoding = AppContext.TryGetSwitch(EnableCookieNameDecoding, out var enabled) && enabled;
} }
public RequestCookieCollection(Dictionary<string, string> store) public RequestCookieCollection(Dictionary<string, string> store) : this()
{ {
Store = store; Store = store;
} }
public RequestCookieCollection(int capacity) public RequestCookieCollection(int capacity) : this()
{ {
Store = new Dictionary<string, string>(capacity, StringComparer.OrdinalIgnoreCase); Store = new Dictionary<string, string>(capacity, StringComparer.OrdinalIgnoreCase);
} }
@ -57,6 +61,9 @@ namespace Microsoft.AspNetCore.Http
} }
public static RequestCookieCollection Parse(IList<string> values) public static RequestCookieCollection Parse(IList<string> values)
=> ParseInternal(values, AppContext.TryGetSwitch(EnableCookieNameDecoding, out var enabled) && enabled);
internal static RequestCookieCollection ParseInternal(IList<string> values, bool enableCookieNameDecoding)
{ {
if (values.Count == 0) if (values.Count == 0)
{ {
@ -76,7 +83,11 @@ namespace Microsoft.AspNetCore.Http
for (var i = 0; i < cookies.Count; i++) for (var i = 0; i < cookies.Count; i++)
{ {
var cookie = cookies[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); var value = Uri.UnescapeDataString(cookie.Value.Value);
store[name] = value; store[name] = value;
} }
@ -116,7 +127,8 @@ namespace Microsoft.AspNetCore.Http
{ {
return false; return false;
} }
return Store.ContainsKey(key); return Store.ContainsKey(key)
|| !_enableCookieNameDecoding && Store.ContainsKey(Uri.EscapeDataString(key));
} }
public bool TryGetValue(string key, out string value) public bool TryGetValue(string key, out string value)
@ -126,7 +138,9 @@ namespace Microsoft.AspNetCore.Http
value = null; value = null;
return false; return false;
} }
return Store.TryGetValue(key, out value);
return Store.TryGetValue(key, out value)
|| !_enableCookieNameDecoding && Store.TryGetValue(Uri.EscapeDataString(key), out value);
} }
/// <summary> /// <summary>

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq; using System.Linq;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Xunit; using Xunit;
@ -9,30 +10,32 @@ namespace Microsoft.AspNetCore.Http.Tests
{ {
public class RequestCookiesCollectionTests 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 var cookies = RequestCookieCollection.Parse(new StringValues(input));
{
// key, value, expected Assert.Equal(1, cookies.Count);
return new TheoryData<string, string, string> Assert.Equal(Uri.EscapeDataString(expectedKey), cookies.Keys.Single());
{ Assert.Equal(expectedValue, cookies[expectedKey]);
{ "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==" },
};
}
} }
[Theory] [Theory]
[MemberData(nameof(UnEscapesKeyValues_Data))] [InlineData("key=value", "key", "value")]
public void UnEscapesKeyValues( [InlineData("__secure-key=value", "__secure-key", "value")]
string input, [InlineData("key%2C=%21value", "key,", "!value")]
string expectedKey, [InlineData("ke%23y%2C=val%5Eue", "ke#y,", "val^ue")]
string expectedValue) [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(1, cookies.Count);
Assert.Equal(expectedKey, cookies.Keys.Single()); Assert.Equal(expectedKey, cookies.Keys.Single());