Browse Source

Support max-age negative value (#45099)

Ric 3 years ago
parent
commit
196a2f2b32

+ 21 - 0
src/Http/Headers/src/HeaderUtilities.cs

@@ -517,6 +517,27 @@ public static class HeaderUtilities
         return ((ulong)value).ToString(NumberFormatInfo.InvariantInfo);
     }
 
+    /// <summary>
+    /// Converts the 64-bit numeric value to its equivalent string representation.
+    /// </summary>
+    /// <param name="value">
+    /// The number to convert.
+    /// </param>
+    /// <returns>
+    /// The string representation of the value of this instance, consisting of a sequence of digits ranging from 0 to 9 with no leading zeroes.
+    /// In case of negative numeric value it will have a leading minus sign.
+    /// </returns>
+    internal static string FormatInt64(long value)
+    {
+        return value switch
+        {
+            0 => "0",
+            1 => "1",
+            -1 => "-1",
+            _ => value.ToString(NumberFormatInfo.InvariantInfo)
+        };
+    }
+
     /// <summary>
     ///Attempts to parse the specified <paramref name="input"/> as a <see cref="DateTimeOffset"/> value.
     /// </summary>

+ 17 - 3
src/Http/Headers/src/SetCookieHeaderValue.cs

@@ -200,7 +200,7 @@ public class SetCookieHeaderValue
 
         if (MaxAge.HasValue)
         {
-            maxAge = HeaderUtilities.FormatNonNegativeInt64((long)MaxAge.GetValueOrDefault().TotalSeconds);
+            maxAge = HeaderUtilities.FormatInt64((long)MaxAge.GetValueOrDefault().TotalSeconds);
             length += SeparatorToken.Length + MaxAgeToken.Length + EqualsToken.Length + maxAge.Length;
         }
 
@@ -347,7 +347,7 @@ public class SetCookieHeaderValue
 
         if (MaxAge.HasValue)
         {
-            AppendSegment(builder, MaxAgeToken, HeaderUtilities.FormatNonNegativeInt64((long)MaxAge.GetValueOrDefault().TotalSeconds));
+            AppendSegment(builder, MaxAgeToken, HeaderUtilities.FormatInt64((long)MaxAge.GetValueOrDefault().TotalSeconds));
         }
 
         if (Domain != null)
@@ -552,7 +552,7 @@ public class SetCookieHeaderValue
                 }
                 result.Expires = expirationDate;
             }
-            // max-age-av = "Max-Age=" non-zero-digit *DIGIT
+            // max-age-av = "Max-Age=" digit *DIGIT ; valid positive and negative values following the RFC6265, Section 5.2.2
             else if (StringSegment.Equals(token, MaxAgeToken, StringComparison.OrdinalIgnoreCase))
             {
                 // = (no spaces)
@@ -561,11 +561,19 @@ public class SetCookieHeaderValue
                     return 0;
                 }
 
+                var isNegative = false;
+                if (input[offset] == '-')
+                {
+                    isNegative = true;
+                    offset++;
+                }
+
                 itemLength = HttpRuleParser.GetNumberLength(input, offset, allowDecimal: false);
                 if (itemLength == 0)
                 {
                     return 0;
                 }
+
                 var numberString = input.Subsegment(offset, itemLength);
                 long maxAge;
                 if (!HeaderUtilities.TryParseNonNegativeInt64(numberString, out maxAge))
@@ -573,6 +581,12 @@ public class SetCookieHeaderValue
                     // Invalid expiration date, abort
                     return 0;
                 }
+
+                if (isNegative)
+                {
+                    maxAge = -maxAge;
+                }
+
                 result.MaxAge = TimeSpan.FromSeconds(maxAge);
                 offset += itemLength;
             }

+ 13 - 0
src/Http/Headers/test/HeaderUtilitiesTest.cs

@@ -115,6 +115,19 @@ public class HeaderUtilitiesTest
         Assert.Throws<ArgumentOutOfRangeException>(() => HeaderUtilities.FormatNonNegativeInt64(value));
     }
 
+    [Theory]
+    [InlineData(0)]
+    [InlineData(1)]
+    [InlineData(-1)]
+    [InlineData(-1234567890)]
+    [InlineData(1234567890)]
+    [InlineData(long.MinValue)]
+    [InlineData(long.MaxValue)]
+    public void FormatInt64_MatchesToString(long value)
+    {
+        Assert.Equal(value.ToString(CultureInfo.InvariantCulture), HeaderUtilities.FormatInt64(value));
+    }
+
     [Theory]
     [InlineData("h", "h", true)]
     [InlineData("h=", "h", true)]

+ 12 - 1
src/Http/Headers/test/SetCookieHeaderValueTest.cs

@@ -62,6 +62,18 @@ public class SetCookieHeaderValueTest
             header8.Extensions.Add("extension2=value");
             dataset.Add(header8, "name8=value8; extension1; extension2=value");
 
+            var header9 = new SetCookieHeaderValue("name9", "value9")
+            {
+                MaxAge = TimeSpan.FromDays(-1),
+            };
+            dataset.Add(header9, "name9=value9; max-age=-86400");
+
+            var header10 = new SetCookieHeaderValue("name10", "value10")
+            {
+                MaxAge = TimeSpan.FromDays(0),
+            };
+            dataset.Add(header10, "name10=value10; max-age=0");
+
             return dataset;
         }
     }
@@ -74,7 +86,6 @@ public class SetCookieHeaderValueTest
                 {
                     "expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1",
                     "name=value; expires=Sun, 06 Nov 1994 08:49:37 ZZZ; max-age=86400; domain=domain1",
-                    "name=value; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=-86400; domain=domain1",
                 };
         }
     }