diff --git a/src/Microsoft.Owin/Infrastructure/OwinHelpers.cs b/src/Microsoft.Owin/Infrastructure/OwinHelpers.cs index a0d6feec..11a32b61 100644 --- a/src/Microsoft.Owin/Infrastructure/OwinHelpers.cs +++ b/src/Microsoft.Owin/Infrastructure/OwinHelpers.cs @@ -530,13 +530,13 @@ internal static IDictionary GetCookies(IOwinRequest request) if (request.Get("Microsoft.Owin.Cookies#text") != text) { cookies.Clear(); - ParseDelimited(text, SemicolonAndComma, AddCookieCallback, cookies); + ParseDelimited(text, SemicolonAndComma, AddCookieCallback, decodePlus: false, decodeKey: false, state: cookies); request.Set("Microsoft.Owin.Cookies#text", text); } return cookies; } - internal static void ParseDelimited(string text, char[] delimiters, Action callback, object state) + internal static void ParseDelimited(string text, char[] delimiters, Action callback, bool decodePlus, bool decodeKey, object state) { int textLength = text.Length; int equalIndex = text.IndexOf('='); @@ -560,10 +560,17 @@ internal static void ParseDelimited(string text, char[] delimiters, Action GetQuery(IOwinRequest request) { query.Clear(); var accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase); - ParseDelimited(text, AmpersandAndSemicolon, AppendItemCallback, accumulator); + ParseDelimited(text, AmpersandAndSemicolon, AppendItemCallback, decodePlus: true, decodeKey: true, state: accumulator); foreach (var kv in accumulator) { query.Add(kv.Key, kv.Value.ToArray()); @@ -813,7 +820,7 @@ internal static IFormCollection GetForm(string text) { IDictionary form = new Dictionary(StringComparer.OrdinalIgnoreCase); var accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase); - ParseDelimited(text, new[] { '&' }, AppendItemCallback, accumulator); + ParseDelimited(text, new[] { '&' }, AppendItemCallback, decodePlus: false, decodeKey: true, state: accumulator); foreach (var kv in accumulator) { form.Add(kv.Key, kv.Value.ToArray()); diff --git a/src/Microsoft.Owin/RequestCookieCollection.cs b/src/Microsoft.Owin/RequestCookieCollection.cs index aa797cfc..495e8ef9 100644 --- a/src/Microsoft.Owin/RequestCookieCollection.cs +++ b/src/Microsoft.Owin/RequestCookieCollection.cs @@ -37,8 +37,11 @@ public string this[string key] get { string value; - Store.TryGetValue(key, out value); - return value; + if (Store.TryGetValue(key, out value) || Store.TryGetValue(Uri.EscapeDataString(key), out value)) + { + return value; + } + return null; } } diff --git a/tests/Microsoft.Owin.Tests/FormsTests.cs b/tests/Microsoft.Owin.Tests/FormsTests.cs index 08beab73..873becee 100644 --- a/tests/Microsoft.Owin.Tests/FormsTests.cs +++ b/tests/Microsoft.Owin.Tests/FormsTests.cs @@ -14,7 +14,7 @@ public class FormsTests private static readonly string[] RawValues = new[] { "v1", "v2, v3", "\"v4, b\"", "v5, v6", "v7", }; private const string JoinedValues = "v1,v2, v3,\"v4, b\",v5, v6,v7"; - private const string OriginalFormsString = "q1=v1&q2=v2,b&q3=v3&q3=v4&q4&q5=v5&q5=v5"; + private const string OriginalFormsString = "q1=v1&q2=v2,b&q3=v3&q3=v4&q4&q5=v5&q5=v+5"; [Fact] public void ParseForm() @@ -30,7 +30,7 @@ public void ParseForm() Assert.Equal("v2,b", form.Get("Q2")); Assert.Equal("v3,v4", form.Get("q3")); Assert.Null(form.Get("q4")); - Assert.Equal("v5,v5", form.Get("Q5")); + Assert.Equal("v5,v+5", form.Get("Q5")); Assert.True(stream.CanRead); } @@ -89,7 +89,7 @@ public void ReadFromStream() Assert.Equal("v2,b", form.Get("Q2")); Assert.Equal("v3,v4", form.Get("q3")); Assert.Null(form.Get("q4")); - Assert.Equal("v5,v5", form.Get("Q5")); + Assert.Equal("v5,v+5", form.Get("Q5")); } [Fact] @@ -107,14 +107,14 @@ public void ReadFromStreamTwice() Assert.Equal("v2,b", form.Get("Q2")); Assert.Equal("v3,v4", form.Get("q3")); Assert.Null(form.Get("q4")); - Assert.Equal("v5,v5", form.Get("Q5")); + Assert.Equal("v5,v+5", form.Get("Q5")); form = request.ReadFormAsync().Result; Assert.Equal("v1", form.Get("q1")); Assert.Equal("v2,b", form.Get("Q2")); Assert.Equal("v3,v4", form.Get("q3")); Assert.Null(form.Get("q4")); - Assert.Equal("v5,v5", form.Get("Q5")); + Assert.Equal("v5,v+5", form.Get("Q5")); } } } diff --git a/tests/Microsoft.Owin.Tests/Microsoft.Owin.Tests.csproj b/tests/Microsoft.Owin.Tests/Microsoft.Owin.Tests.csproj index 50120c1a..8772ece1 100644 --- a/tests/Microsoft.Owin.Tests/Microsoft.Owin.Tests.csproj +++ b/tests/Microsoft.Owin.Tests/Microsoft.Owin.Tests.csproj @@ -63,6 +63,7 @@ + diff --git a/tests/Microsoft.Owin.Tests/RequestCookieTests.cs b/tests/Microsoft.Owin.Tests/RequestCookieTests.cs new file mode 100644 index 00000000..94ab3ea6 --- /dev/null +++ b/tests/Microsoft.Owin.Tests/RequestCookieTests.cs @@ -0,0 +1,31 @@ +// 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 Xunit; +using Xunit.Extensions; + +namespace Microsoft.Owin.Tests +{ + public class RequestCookieTests + { + [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) + { + var context = new OwinRequest(); + context.Headers["Cookie"] = input; + var cookies = context.Cookies; + + Assert.Equal(1, cookies.Count()); + Assert.Equal(Uri.EscapeDataString(expectedKey), cookies.Single().Key); + Assert.Equal(expectedValue, cookies[expectedKey]); + } + } +}