diff --git a/src/Http/Http/src/Internal/ResponseCookies.cs b/src/Http/Http/src/Internal/ResponseCookies.cs index 26c1de655ebb..e55a008ed1c0 100644 --- a/src/Http/Http/src/Internal/ResponseCookies.cs +++ b/src/Http/Http/src/Internal/ResponseCookies.cs @@ -158,7 +158,14 @@ public void Delete(string key, CookieOptions options) bool pathHasValue = !string.IsNullOrEmpty(options.Path); Func rejectPredicate; - if (domainHasValue) + if (domainHasValue && pathHasValue) + { + rejectPredicate = (value, encKeyPlusEquals, opts) => + value.StartsWith(encKeyPlusEquals, StringComparison.OrdinalIgnoreCase) && + value.IndexOf($"domain={opts.Domain}", StringComparison.OrdinalIgnoreCase) != -1 && + value.IndexOf($"path={opts.Path}", StringComparison.OrdinalIgnoreCase) != -1; + } + else if (domainHasValue) { rejectPredicate = (value, encKeyPlusEquals, opts) => value.StartsWith(encKeyPlusEquals, StringComparison.OrdinalIgnoreCase) && diff --git a/src/Http/Http/test/ResponseCookiesTest.cs b/src/Http/Http/test/ResponseCookiesTest.cs index a0a224b29fd9..9d520fe95f4c 100644 --- a/src/Http/Http/test/ResponseCookiesTest.cs +++ b/src/Http/Http/test/ResponseCookiesTest.cs @@ -2,6 +2,7 @@ // 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.Features; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -74,6 +75,64 @@ public void DeleteCookieShouldSetDefaultPath() Assert.Contains("expires=Thu, 01 Jan 1970 00:00:00 GMT", cookieHeaderValues[0]); } + [Fact] + public void DeleteCookieWithDomainAndPathDeletesPriorMatchingCookies() + { + var headers = (IHeaderDictionary)new HeaderDictionary(); + var features = MakeFeatures(headers); + var responseCookies = new ResponseCookies(features); + + var testCookies = new (string Key, string Path, string Domain)[] + { + new ("key1", "/path1/", null), + new ("key1", "/path2/", null), + new ("key2", "/path1/", "localhost"), + new ("key2", "/path2/", "localhost"), + }; + + foreach (var cookie in testCookies) + { + responseCookies.Delete(cookie.Key, new CookieOptions() { Domain = cookie.Domain, Path = cookie.Path }); + } + + var deletedCookies = headers.SetCookie.ToArray(); + Assert.Equal(testCookies.Length, deletedCookies.Length); + + Assert.Single(deletedCookies, cookie => cookie.StartsWith("key1", StringComparison.InvariantCulture) && cookie.Contains("path=/path1/")); + Assert.Single(deletedCookies, cookie => cookie.StartsWith("key1", StringComparison.InvariantCulture) && cookie.Contains("path=/path2/")); + Assert.Single(deletedCookies, cookie => cookie.StartsWith("key2", StringComparison.InvariantCulture) && cookie.Contains("path=/path1/") && cookie.Contains("domain=localhost")); + Assert.Single(deletedCookies, cookie => cookie.StartsWith("key2", StringComparison.InvariantCulture) && cookie.Contains("path=/path2/") && cookie.Contains("domain=localhost")); + Assert.All(deletedCookies, cookie => Assert.Contains("expires=Thu, 01 Jan 1970 00:00:00 GMT", cookie)); + } + + [Fact] + public void DeleteRemovesCookieWithDomainAndPathCreatedByAdd() + { + var headers = (IHeaderDictionary)new HeaderDictionary(); + var features = MakeFeatures(headers); + var responseCookies = new ResponseCookies(features); + + var testCookies = new (string Key, string Path, string Domain)[] + { + new ("key1", "/path1/", null), + new ("key1", "/path1/", null), + new ("key2", "/path1/", "localhost"), + new ("key2", "/path1/", "localhost"), + }; + + foreach (var cookie in testCookies) + { + responseCookies.Append(cookie.Key, cookie.Key, new CookieOptions() { Domain = cookie.Domain, Path = cookie.Path }); + responseCookies.Delete(cookie.Key, new CookieOptions() { Domain = cookie.Domain, Path = cookie.Path }); + } + + var deletedCookies = headers.SetCookie.ToArray(); + Assert.Equal(2, deletedCookies.Length); + Assert.Single(deletedCookies, cookie => cookie.StartsWith("key1", StringComparison.InvariantCulture) && cookie.Contains("path=/path1/")); + Assert.Single(deletedCookies, cookie => cookie.StartsWith("key2", StringComparison.InvariantCulture) && cookie.Contains("path=/path1/") && cookie.Contains("domain=localhost")); + Assert.All(deletedCookies, cookie => Assert.Contains("expires=Thu, 01 Jan 1970 00:00:00 GMT", cookie)); + } + [Fact] public void DeleteCookieWithCookieOptionsShouldKeepPropertiesOfCookieOptions() {