diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/CookieActionFactory.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/CookieActionFactory.cs
new file mode 100644
index 0000000000..27cb805064
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/CookieActionFactory.cs
@@ -0,0 +1,120 @@
+// 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.Globalization;
+using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
+
+namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite
+{
+ public class CookieActionFactory
+ {
+ ///
+ /// Creates a for details.
+ ///
+ /// The flag
+ /// The action
+ public ChangeCookieAction Create(string flagValue)
+ {
+ if (string.IsNullOrEmpty(flagValue))
+ {
+ throw new ArgumentException(nameof(flagValue));
+ }
+
+ var i = 0;
+ var separator = ':';
+ if (flagValue[0] == ';')
+ {
+ separator = ';';
+ i++;
+ }
+
+ ChangeCookieAction action = null;
+ var currentField = Fields.Name;
+ var start = i;
+ for (; i < flagValue.Length; i++)
+ {
+ if (flagValue[i] == separator)
+ {
+ var length = i - start;
+ SetActionOption(flagValue.Substring(start, length).Trim(), currentField, ref action);
+
+ currentField++;
+ start = i + 1;
+ }
+ }
+
+ if (i != start)
+ {
+ SetActionOption(flagValue.Substring(start).Trim(new[] { ' ', separator }), currentField, ref action);
+ }
+
+ if (currentField < Fields.Domain)
+ {
+ throw new FormatException(Resources.FormatError_InvalidChangeCookieFlag(flagValue));
+ }
+
+ return action;
+ }
+
+ private static void SetActionOption(string value, Fields tokenType, ref ChangeCookieAction action)
+ {
+ switch (tokenType)
+ {
+ case Fields.Name:
+ action = new ChangeCookieAction(value);
+ break;
+ case Fields.Value:
+ action.Value = value;
+ break;
+ case Fields.Domain:
+ // despite what spec says, an empty domain field is allowed in mod_rewrite
+ // by specifying NAME:VALUE:;
+ action.Domain = string.IsNullOrEmpty(value) || value == ";"
+ ? null
+ : value;
+ break;
+ case Fields.Lifetime:
+ if (string.IsNullOrEmpty(value))
+ {
+ break;
+ }
+
+ uint minutes;
+ if (!uint.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out minutes))
+ {
+ throw new FormatException(Resources.FormatError_CouldNotParseInteger(value));
+ }
+
+ action.Lifetime = TimeSpan.FromMinutes(minutes);
+ break;
+ case Fields.Path:
+ action.Path = value;
+ break;
+ case Fields.Secure:
+ action.Secure = "secure".Equals(value, StringComparison.OrdinalIgnoreCase)
+ || "true".Equals(value, StringComparison.OrdinalIgnoreCase)
+ || value == "1";
+ break;
+ case Fields.HttpOnly:
+ action.HttpOnly = "httponly".Equals(value, StringComparison.OrdinalIgnoreCase)
+ || "true".Equals(value, StringComparison.OrdinalIgnoreCase)
+ || value == "1";
+ break;
+ }
+ }
+
+ // order matters
+ // see https://httpd.apache.org/docs/current/rewrite/flags.html#flag_co
+ private enum Fields
+ {
+ Name,
+ Value,
+ Domain,
+ Lifetime,
+ Path,
+ Secure,
+ HttpOnly
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/FlagParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/FlagParser.cs
index b3e957a5c7..3d736d7b56 100644
--- a/src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/FlagParser.cs
+++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/FlagParser.cs
@@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite
{
if (string.IsNullOrEmpty(flagString))
{
- throw new ArgumentNullException(nameof(flagString));
+ throw new ArgumentException(nameof(flagString));
}
// Check that flags are contained within []
diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/RuleBuilder.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/RuleBuilder.cs
index 8e93d1db16..7bb0d1cd3f 100644
--- a/src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/RuleBuilder.cs
+++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/RuleBuilder.cs
@@ -14,6 +14,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite
private IList _conditions;
private IList _actions = new List();
private UrlMatch _match;
+ private CookieActionFactory _cookieActionFactory = new CookieActionFactory();
private readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(1);
@@ -172,8 +173,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite
string flag;
if (flags.GetValue(FlagType.Cookie, out flag))
{
- // parse cookie
- _actions.Add(new ChangeCookieAction(flag));
+ var action = _cookieActionFactory.Create(flag);
+ _actions.Add(action);
}
if (flags.GetValue(FlagType.Env, out flag))
diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/RedirectRule.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/RedirectRule.cs
index 2c9f471040..1198f27a6a 100644
--- a/src/Microsoft.AspNetCore.Rewrite/Internal/RedirectRule.cs
+++ b/src/Microsoft.AspNetCore.Rewrite/Internal/RedirectRule.cs
@@ -18,12 +18,12 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
{
if (string.IsNullOrEmpty(regex))
{
- throw new ArgumentNullException(nameof(regex));
+ throw new ArgumentException(nameof(regex));
}
if (string.IsNullOrEmpty(replacement))
{
- throw new ArgumentNullException(nameof(replacement));
+ throw new ArgumentException(nameof(replacement));
}
InitialMatch = new Regex(regex, RegexOptions.Compiled | RegexOptions.CultureInvariant, _regexTimeout);
diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/RewriteRule.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/RewriteRule.cs
index 336c2ba87d..d6452e8bc8 100644
--- a/src/Microsoft.AspNetCore.Rewrite/Internal/RewriteRule.cs
+++ b/src/Microsoft.AspNetCore.Rewrite/Internal/RewriteRule.cs
@@ -18,12 +18,12 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
{
if (string.IsNullOrEmpty(regex))
{
- throw new ArgumentNullException(nameof(regex));
+ throw new ArgumentException(nameof(regex));
}
if (string.IsNullOrEmpty(replacement))
{
- throw new ArgumentNullException(nameof(replacement));
+ throw new ArgumentException(nameof(replacement));
}
InitialMatch = new Regex(regex, RegexOptions.Compiled | RegexOptions.CultureInvariant, _regexTimeout);
diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/ChangeCookieAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/ChangeCookieAction.cs
index 3bdd95e990..640a022672 100644
--- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/ChangeCookieAction.cs
+++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/ChangeCookieAction.cs
@@ -2,21 +2,74 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{
public class ChangeCookieAction : UrlAction
{
- public ChangeCookieAction(string cookie)
+ private readonly Func _timeSource;
+ private CookieOptions _cachedOptions;
+
+ public ChangeCookieAction(string name)
+ : this(name, () => DateTimeOffset.UtcNow)
{
- // TODO
- throw new NotImplementedException("Changing the cookie is not implemented");
}
+ // for testing
+ internal ChangeCookieAction(string name, Func timeSource)
+ {
+ _timeSource = timeSource;
+
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentException(nameof(name));
+ }
+
+ Name = name;
+ }
+
+ public string Name { get; }
+ public string Value { get; set; }
+ public string Domain { get; set; }
+ public TimeSpan Lifetime { get; set; }
+ public string Path { get; set; }
+ public bool Secure { get; set; }
+ public bool HttpOnly { get; set; }
+
public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
- // modify the cookies
- throw new NotImplementedException("Changing the cookie is not implemented");
+ var options = GetOrCreateOptions();
+ context.HttpContext.Response.Cookies.Append(Name, Value ?? string.Empty, options);
+ }
+
+ private CookieOptions GetOrCreateOptions()
+ {
+ if (Lifetime > TimeSpan.Zero)
+ {
+ var now = _timeSource();
+ return new CookieOptions()
+ {
+ Domain = Domain,
+ HttpOnly = HttpOnly,
+ Secure = Secure,
+ Path = Path,
+ Expires = now.Add(Lifetime)
+ };
+ }
+
+ if (_cachedOptions == null)
+ {
+ _cachedOptions = new CookieOptions()
+ {
+ Domain = Domain,
+ HttpOnly = HttpOnly,
+ Secure = Secure,
+ Path = Path
+ };
+ }
+
+ return _cachedOptions;
}
}
}
diff --git a/src/Microsoft.AspNetCore.Rewrite/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Rewrite/Properties/Resources.Designer.cs
index 3c2e0d6b64..bda94e5268 100644
--- a/src/Microsoft.AspNetCore.Rewrite/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNetCore.Rewrite/Properties/Resources.Designer.cs
@@ -11,19 +11,35 @@ namespace Microsoft.AspNetCore.Rewrite
= new ResourceManager("Microsoft.AspNetCore.Rewrite.Resources", typeof(Resources).GetTypeInfo().Assembly);
///
- /// Could not parse the UrlRewrite file. Message: '{0}'. Line number '{1}': '{2}'.
+ /// Error adding a mod_rewrite rule. The change environment flag is not supported.
///
- internal static string Error_UrlRewriteParseError
+ internal static string Error_ChangeEnvironmentNotSupported
{
- get { return GetString("Error_UrlRewriteParseError"); }
+ get { return GetString("Error_ChangeEnvironmentNotSupported"); }
}
///
- /// Could not parse the UrlRewrite file. Message: '{0}'. Line number '{1}': '{2}'.
+ /// Error adding a mod_rewrite rule. The change environment flag is not supported.
///
- internal static string FormatError_UrlRewriteParseError(object p0, object p1, object p2)
+ internal static string FormatError_ChangeEnvironmentNotSupported()
{
- return string.Format(CultureInfo.CurrentCulture, GetString("Error_UrlRewriteParseError"), p0, p1, p2);
+ return GetString("Error_ChangeEnvironmentNotSupported");
+ }
+
+ ///
+ /// Could not parse integer from value '{0}'.
+ ///
+ internal static string Error_CouldNotParseInteger
+ {
+ get { return GetString("Error_CouldNotParseInteger"); }
+ }
+
+ ///
+ /// Could not parse integer from value '{0}'.
+ ///
+ internal static string FormatError_CouldNotParseInteger(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("Error_CouldNotParseInteger"), p0);
}
///
@@ -106,6 +122,38 @@ namespace Microsoft.AspNetCore.Rewrite
return string.Format(CultureInfo.CurrentCulture, GetString("Error_InputParserUnrecognizedParameter"), p0, p1);
}
+ ///
+ /// Syntax error for integers in comparison.
+ ///
+ internal static string Error_IntegerMatch_FormatExceptionMessage
+ {
+ get { return GetString("Error_IntegerMatch_FormatExceptionMessage"); }
+ }
+
+ ///
+ /// Syntax error for integers in comparison.
+ ///
+ internal static string FormatError_IntegerMatch_FormatExceptionMessage()
+ {
+ return GetString("Error_IntegerMatch_FormatExceptionMessage");
+ }
+
+ ///
+ /// Error parsing the mod_rewrite rule. The cookie flag (CO) has an incorrect format '{0}'.
+ ///
+ internal static string Error_InvalidChangeCookieFlag
+ {
+ get { return GetString("Error_InvalidChangeCookieFlag"); }
+ }
+
+ ///
+ /// Error parsing the mod_rewrite rule. The cookie flag (CO) has an incorrect format '{0}'.
+ ///
+ internal static string FormatError_InvalidChangeCookieFlag(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("Error_InvalidChangeCookieFlag"), p0);
+ }
+
///
/// Could not parse the mod_rewrite file. Message: '{0}'. Line number '{1}'.
///
@@ -139,35 +187,19 @@ namespace Microsoft.AspNetCore.Rewrite
}
///
- /// Syntax error for integers in comparison.
+ /// Could not parse the UrlRewrite file. Message: '{0}'. Line number '{1}': '{2}'.
///
- internal static string Error_IntegerMatch_FormatExceptionMessage
+ internal static string Error_UrlRewriteParseError
{
- get { return GetString("Error_IntegerMatch_FormatExceptionMessage"); }
+ get { return GetString("Error_UrlRewriteParseError"); }
}
///
- /// Syntax error for integers in comparison.
+ /// Could not parse the UrlRewrite file. Message: '{0}'. Line number '{1}': '{2}'.
///
- internal static string FormatError_IntegerMatch_FormatExceptionMessage()
+ internal static string FormatError_UrlRewriteParseError(object p0, object p1, object p2)
{
- return GetString("Error_IntegerMatch_FormatExceptionMessage");
- }
-
- ///
- /// Error adding a mod_rewrite rule. The change environment flag is not supported.
- ///
- internal static string Error_ChangeEnvironmentNotSupported
- {
- get { return GetString("Error_ChangeEnvironmentNotSupported"); }
- }
-
- ///
- /// Error adding a mod_rewrite rule. The change environment flag is not supported.
- ///
- internal static string FormatError_ChangeEnvironmentNotSupported()
- {
- return GetString("Error_ChangeEnvironmentNotSupported");
+ return string.Format(CultureInfo.CurrentCulture, GetString("Error_UrlRewriteParseError"), p0, p1, p2);
}
private static string GetString(string name, params string[] formatterNames)
diff --git a/src/Microsoft.AspNetCore.Rewrite/Resources.resx b/src/Microsoft.AspNetCore.Rewrite/Resources.resx
index 7b477cc447..2c68d33ec3 100644
--- a/src/Microsoft.AspNetCore.Rewrite/Resources.resx
+++ b/src/Microsoft.AspNetCore.Rewrite/Resources.resx
@@ -117,8 +117,11 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
- Could not parse the UrlRewrite file. Message: '{0}'. Line number '{1}': '{2}'.
+
+ Error adding a mod_rewrite rule. The change environment flag is not supported.
+
+
+ Could not parse integer from value '{0}'.
Index out of range for backreference: '{0}' at string index: '{1}'
@@ -135,16 +138,19 @@
Unrecognized parameter type: '{0}', terminated at string index: '{1}'
+
+ Syntax error for integers in comparison.
+
+
+ Error parsing the mod_rewrite rule. The cookie flag (CO) has an incorrect format '{0}'.
+
Could not parse the mod_rewrite file. Message: '{0}'. Line number '{1}'.
Could not parse the mod_rewrite file. Line number '{0}'.
-
- Syntax error for integers in comparison.
-
-
- Error adding a mod_rewrite rule. The change environment flag is not supported.
+
+ Could not parse the UrlRewrite file. Message: '{0}'. Line number '{1}': '{2}'.
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/ApacheModRewrite/CookieActionFactoryTest.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/ApacheModRewrite/CookieActionFactoryTest.cs
new file mode 100644
index 0000000000..d992eb7937
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Rewrite.Tests/ApacheModRewrite/CookieActionFactoryTest.cs
@@ -0,0 +1,93 @@
+// 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 Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Rewrite.Test
+{
+ public class CookieActionFactoryTest
+ {
+ [Fact]
+ public void Creates_OneCookie()
+ {
+ var cookie = new CookieActionFactory().Create("NAME:VALUE:DOMAIN:1440:path:secure:httponly");
+
+ Assert.Equal("NAME", cookie.Name);
+ Assert.Equal("VALUE", cookie.Value);
+ Assert.Equal("DOMAIN", cookie.Domain);
+ Assert.Equal(TimeSpan.FromMinutes(1440), cookie.Lifetime);
+ Assert.Equal("path", cookie.Path);
+ Assert.True(cookie.Secure);
+ Assert.True(cookie.HttpOnly);
+ }
+
+ [Fact]
+ public void Creates_OneCookie_AltSeparator()
+ {
+ var action = new CookieActionFactory().Create(";NAME;VALUE:WithColon;DOMAIN;1440;path;secure;httponly");
+
+ Assert.Equal("NAME", action.Name);
+ Assert.Equal("VALUE:WithColon", action.Value);
+ Assert.Equal("DOMAIN", action.Domain);
+ Assert.Equal(TimeSpan.FromMinutes(1440), action.Lifetime);
+ Assert.Equal("path", action.Path);
+ Assert.True(action.Secure);
+ Assert.True(action.HttpOnly);
+ }
+
+ [Fact]
+ public void Creates_HttpOnly()
+ {
+ var action = new CookieActionFactory().Create(";NAME;VALUE;DOMAIN;;;;httponly");
+
+ Assert.Equal("NAME", action.Name);
+ Assert.Equal("VALUE", action.Value);
+ Assert.Equal("DOMAIN", action.Domain);
+ Assert.Equal(0, action.Lifetime.TotalSeconds);
+ Assert.Equal(string.Empty, action.Path);
+ Assert.False(action.Secure);
+ Assert.True(action.HttpOnly);
+ }
+
+ [Theory]
+ [InlineData("NAME::", "", null)]
+ [InlineData("NAME::domain", "", "domain")]
+ [InlineData("NAME:VALUE:;", "VALUE", null)] // special case with dangling ';'
+ [InlineData("NAME:value:", "value", null)]
+ [InlineData(" NAME : v : ", "v", null)] // trims values
+ public void TrimsValues(string flagValue, string value, string domain)
+ {
+ var factory = new CookieActionFactory();
+ var action = factory.Create(flagValue);
+ Assert.Equal("NAME", action.Name);
+ Assert.NotNull(action.Value);
+ Assert.Equal(value, action.Value);
+ Assert.Equal(domain, action.Domain);
+ }
+
+ [Theory]
+ [InlineData("NAME")] // missing value and domain
+ [InlineData("NAME: ")] // missing domain
+ [InlineData("NAME:VALUE")] // missing domain
+ [InlineData(";NAME;VAL:UE")] // missing domain
+ public void ThrowsForInvalidFormat(string flagValue)
+ {
+ var factory = new CookieActionFactory();
+ var ex = Assert.Throws(() => factory.Create(flagValue));
+ Assert.Equal(Resources.FormatError_InvalidChangeCookieFlag(flagValue), ex.Message);
+ }
+
+ [Theory]
+ [InlineData("bad_number")]
+ [InlineData("-1")]
+ [InlineData("0.9")]
+ public void ThrowsForInvalidIntFormat(string badInt)
+ {
+ var factory = new CookieActionFactory();
+ var ex = Assert.Throws(() => factory.Create("NAME:VALUE:DOMAIN:" + badInt));
+ Assert.Equal(Resources.FormatError_CouldNotParseInteger(badInt), ex.Message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/ApacheModRewrite/FlagParserTest.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/ApacheModRewrite/FlagParserTest.cs
index 8f8d0cf7a8..bfb4c6420f 100644
--- a/test/Microsoft.AspNetCore.Rewrite.Tests/ApacheModRewrite/FlagParserTest.cs
+++ b/test/Microsoft.AspNetCore.Rewrite.Tests/ApacheModRewrite/FlagParserTest.cs
@@ -62,8 +62,20 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
[Fact]
public void FlagParser_AssertArgumentExceptionWhenFlagsAreNullOrEmpty()
{
- Assert.Throws(() => new FlagParser().Parse(null));
- Assert.Throws(() => new FlagParser().Parse(string.Empty));
+ Assert.Throws(() => new FlagParser().Parse(null));
+ Assert.Throws(() => new FlagParser().Parse(string.Empty));
+ }
+
+ [Theory]
+ [InlineData("[CO=VAR:VAL]", "VAR:VAL")]
+ [InlineData("[CO=!VAR]", "!VAR")]
+ [InlineData("[CO=;NAME:VALUE;ABC:123]", ";NAME:VALUE;ABC:123")]
+ public void Flag_ParserHandlesComplexFlags(string flagString, string expected)
+ {
+ var results = new FlagParser().Parse(flagString);
+ string value;
+ Assert.True(results.GetValue(FlagType.Cookie, out value));
+ Assert.Equal(expected, value);
}
public bool DictionaryContentsEqual(IDictionary dictionary, IDictionary other)
diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlActions/ChangeCookieActionTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlActions/ChangeCookieActionTests.cs
new file mode 100644
index 0000000000..20096ef162
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlActions/ChangeCookieActionTests.cs
@@ -0,0 +1,66 @@
+// 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 Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
+using Microsoft.Net.Http.Headers;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Rewrite.Tests.UrlActions
+{
+ public class ChangeCookieActionTests
+ {
+ [Fact]
+ public void SetsCookie()
+ {
+ var now = DateTimeOffset.UtcNow;
+ var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
+ var action = new ChangeCookieAction("Cookie", () => now)
+ {
+ Value = "Chocolate Chip",
+ Domain = "contoso.com",
+ Lifetime = TimeSpan.FromMinutes(1440),
+ Path = "/recipes",
+ Secure = true,
+ HttpOnly = true
+ };
+
+ action.ApplyAction(context, null, null);
+
+ var cookieHeaders = context.HttpContext.Response.Headers[HeaderNames.SetCookie];
+ var header = Assert.Single(cookieHeaders);
+ Assert.Equal($"Cookie=Chocolate%20Chip; expires={HeaderUtilities.FormatDate(now.AddMinutes(1440))}; domain=contoso.com; path=/recipes; secure; httponly", header);
+ }
+
+ [Fact]
+ public void ZeroLifetime()
+ {
+ var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
+ var action = new ChangeCookieAction("Cookie")
+ {
+ Value = "Chocolate Chip",
+ };
+
+ action.ApplyAction(context, null, null);
+
+ var cookieHeaders = context.HttpContext.Response.Headers[HeaderNames.SetCookie];
+ var header = Assert.Single(cookieHeaders);
+ Assert.Equal($"Cookie=Chocolate%20Chip", header);
+ }
+
+
+ [Fact]
+ public void UnsetCookie()
+ {
+ var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
+ var action = new ChangeCookieAction("Cookie");
+
+ action.ApplyAction(context, null, null);
+
+ var cookieHeaders = context.HttpContext.Response.Headers[HeaderNames.SetCookie];
+ var header = Assert.Single(cookieHeaders);
+ Assert.Equal($"Cookie=", header);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlActions/ForbiddenActionTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlActions/ForbiddenActionTests.cs
index b15354b672..a9eac5a807 100644
--- a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlActions/ForbiddenActionTests.cs
+++ b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlActions/ForbiddenActionTests.cs
@@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlActions
public void Forbidden_Verify403IsInStatusCode()
{
- var context = new RewriteContext {HttpContext = new DefaultHttpContext()};
+ var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
var action = new ForbiddenAction();
action.ApplyAction(context, null, null);