Skip to content

Commit ad4f0a0

Browse files
authored
Remove cookie name decoding (#24389)
1 parent a8e8fe2 commit ad4f0a0

File tree

3 files changed

+42
-24
lines changed

3 files changed

+42
-24
lines changed

src/Http/Http/ref/Microsoft.AspNetCore.Http.Manual.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public RequestCookieCollection(int capacity) { }
3737
public bool ContainsKey(string key) { throw null; }
3838
public Microsoft.AspNetCore.Http.RequestCookieCollection.Enumerator GetEnumerator() { throw null; }
3939
public static Microsoft.AspNetCore.Http.RequestCookieCollection Parse(System.Collections.Generic.IList<string> values) { throw null; }
40+
internal static RequestCookieCollection ParseInternal(System.Collections.Generic.IList<string> values, bool enableCookieNameDecoding) { throw null; }
4041
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; }
4142
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
4243
public bool TryGetValue(string key, out string value) { throw null; }

src/Http/Http/src/Internal/RequestCookieCollection.cs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ namespace Microsoft.AspNetCore.Http
1010
{
1111
internal class RequestCookieCollection : IRequestCookieCollection
1212
{
13+
private const string EnableCookieNameDecoding = "Microsoft.AspNetCore.Http.EnableCookieNameDecoding";
14+
private bool _enableCookieNameDecoding;
15+
1316
public static readonly RequestCookieCollection Empty = new RequestCookieCollection();
1417
private static readonly string[] EmptyKeys = Array.Empty<string>();
1518
private static readonly Enumerator EmptyEnumerator = new Enumerator();
@@ -21,14 +24,15 @@ internal class RequestCookieCollection : IRequestCookieCollection
2124

2225
public RequestCookieCollection()
2326
{
27+
_enableCookieNameDecoding = AppContext.TryGetSwitch(EnableCookieNameDecoding, out var enabled) && enabled;
2428
}
2529

26-
public RequestCookieCollection(Dictionary<string, string> store)
30+
public RequestCookieCollection(Dictionary<string, string> store) : this()
2731
{
2832
Store = store;
2933
}
3034

31-
public RequestCookieCollection(int capacity)
35+
public RequestCookieCollection(int capacity) : this()
3236
{
3337
Store = new Dictionary<string, string>(capacity, StringComparer.OrdinalIgnoreCase);
3438
}
@@ -57,6 +61,9 @@ public string this[string key]
5761
}
5862

5963
public static RequestCookieCollection Parse(IList<string> values)
64+
=> ParseInternal(values, AppContext.TryGetSwitch(EnableCookieNameDecoding, out var enabled) && enabled);
65+
66+
internal static RequestCookieCollection ParseInternal(IList<string> values, bool enableCookieNameDecoding)
6067
{
6168
if (values.Count == 0)
6269
{
@@ -76,7 +83,11 @@ public static RequestCookieCollection Parse(IList<string> values)
7683
for (var i = 0; i < cookies.Count; i++)
7784
{
7885
var cookie = cookies[i];
79-
var name = Uri.UnescapeDataString(cookie.Name.Value);
86+
var name = cookie.Name.Value;
87+
if (enableCookieNameDecoding)
88+
{
89+
name = Uri.UnescapeDataString(name);
90+
}
8091
var value = Uri.UnescapeDataString(cookie.Value.Value);
8192
store[name] = value;
8293
}
@@ -116,7 +127,8 @@ public bool ContainsKey(string key)
116127
{
117128
return false;
118129
}
119-
return Store.ContainsKey(key);
130+
return Store.ContainsKey(key)
131+
|| !_enableCookieNameDecoding && Store.ContainsKey(Uri.EscapeDataString(key));
120132
}
121133

122134
public bool TryGetValue(string key, out string value)
@@ -126,7 +138,9 @@ public bool TryGetValue(string key, out string value)
126138
value = null;
127139
return false;
128140
}
129-
return Store.TryGetValue(key, out value);
141+
142+
return Store.TryGetValue(key, out value)
143+
|| !_enableCookieNameDecoding && Store.TryGetValue(Uri.EscapeDataString(key), out value);
130144
}
131145

132146
/// <summary>

src/Http/Http/test/RequestCookiesCollectionTests.cs

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Linq;
56
using Microsoft.Extensions.Primitives;
67
using Xunit;
@@ -9,30 +10,32 @@ namespace Microsoft.AspNetCore.Http.Tests
910
{
1011
public class RequestCookiesCollectionTests
1112
{
12-
public static TheoryData UnEscapesKeyValues_Data
13+
[Theory]
14+
[InlineData("key=value", "key", "value")]
15+
[InlineData("__secure-key=value", "__secure-key", "value")]
16+
[InlineData("key%2C=%21value", "key,", "!value")]
17+
[InlineData("ke%23y%2C=val%5Eue", "ke#y,", "val^ue")]
18+
[InlineData("base64=QUI%2BREU%2FRw%3D%3D", "base64", "QUI+REU/Rw==")]
19+
[InlineData("base64=QUI+REU/Rw==", "base64", "QUI+REU/Rw==")]
20+
public void UnEscapesValues(string input, string expectedKey, string expectedValue)
1321
{
14-
get
15-
{
16-
// key, value, expected
17-
return new TheoryData<string, string, string>
18-
{
19-
{ "key=value", "key", "value" },
20-
{ "key%2C=%21value", "key,", "!value" },
21-
{ "ke%23y%2C=val%5Eue", "ke#y,", "val^ue" },
22-
{ "base64=QUI%2BREU%2FRw%3D%3D", "base64", "QUI+REU/Rw==" },
23-
{ "base64=QUI+REU/Rw==", "base64", "QUI+REU/Rw==" },
24-
};
25-
}
22+
var cookies = RequestCookieCollection.Parse(new StringValues(input));
23+
24+
Assert.Equal(1, cookies.Count);
25+
Assert.Equal(Uri.EscapeDataString(expectedKey), cookies.Keys.Single());
26+
Assert.Equal(expectedValue, cookies[expectedKey]);
2627
}
2728

2829
[Theory]
29-
[MemberData(nameof(UnEscapesKeyValues_Data))]
30-
public void UnEscapesKeyValues(
31-
string input,
32-
string expectedKey,
33-
string expectedValue)
30+
[InlineData("key=value", "key", "value")]
31+
[InlineData("__secure-key=value", "__secure-key", "value")]
32+
[InlineData("key%2C=%21value", "key,", "!value")]
33+
[InlineData("ke%23y%2C=val%5Eue", "ke#y,", "val^ue")]
34+
[InlineData("base64=QUI%2BREU%2FRw%3D%3D", "base64", "QUI+REU/Rw==")]
35+
[InlineData("base64=QUI+REU/Rw==", "base64", "QUI+REU/Rw==")]
36+
public void AppContextSwitchUnEscapesKeyValues(string input, string expectedKey, string expectedValue)
3437
{
35-
var cookies = RequestCookieCollection.Parse(new StringValues(input));
38+
var cookies = RequestCookieCollection.ParseInternal(new StringValues(input), enableCookieNameDecoding: true);
3639

3740
Assert.Equal(1, cookies.Count);
3841
Assert.Equal(expectedKey, cookies.Keys.Single());

0 commit comments

Comments
 (0)