diff --git a/src/Microsoft.AspNetCore.Http.Extensions/HeaderDictionaryTypeExtensions.cs b/src/Microsoft.AspNetCore.Http.Extensions/HeaderDictionaryTypeExtensions.cs index 29e4a0efee..6863d61e0e 100644 --- a/src/Microsoft.AspNetCore.Http.Extensions/HeaderDictionaryTypeExtensions.cs +++ b/src/Microsoft.AspNetCore.Http.Extensions/HeaderDictionaryTypeExtensions.cs @@ -156,7 +156,7 @@ namespace Microsoft.AspNetCore.Http { typeof(RangeHeaderValue), new Func(value => { RangeHeaderValue result; return RangeHeaderValue.TryParse(value, out result) ? result : null; }) }, { typeof(EntityTagHeaderValue), new Func(value => { EntityTagHeaderValue result; return EntityTagHeaderValue.TryParse(value, out result) ? result : null; }) }, { typeof(DateTimeOffset?), new Func(value => { DateTimeOffset result; return HeaderUtilities.TryParseDate(value, out result) ? result : (DateTimeOffset?)null; }) }, - { typeof(long?), new Func(value => { long result; return HeaderUtilities.TryParseInt64(value, out result) ? result : (long?)null; }) }, + { typeof(long?), new Func(value => { long result; return HeaderUtilities.TryParseNonNegativeInt64(value, out result) ? result : (long?)null; }) }, }; private static IDictionary KnownListParsers = new Dictionary() diff --git a/src/Microsoft.AspNetCore.Http.Extensions/RequestHeaders.cs b/src/Microsoft.AspNetCore.Http.Extensions/RequestHeaders.cs index 2830e6147d..ddd125985e 100644 --- a/src/Microsoft.AspNetCore.Http.Extensions/RequestHeaders.cs +++ b/src/Microsoft.AspNetCore.Http.Extensions/RequestHeaders.cs @@ -97,11 +97,11 @@ namespace Microsoft.AspNetCore.Http.Headers { get { - return Headers.Get(HeaderNames.ContentLength); + return Headers.ContentLength; } set { - Headers.Set(HeaderNames.ContentLength, value.HasValue ? HeaderUtilities.FormatInt64(value.Value) : null); + Headers.ContentLength = value; } } diff --git a/src/Microsoft.AspNetCore.Http.Extensions/ResponseHeaders.cs b/src/Microsoft.AspNetCore.Http.Extensions/ResponseHeaders.cs index acdd662d2f..87e3c0318c 100644 --- a/src/Microsoft.AspNetCore.Http.Extensions/ResponseHeaders.cs +++ b/src/Microsoft.AspNetCore.Http.Extensions/ResponseHeaders.cs @@ -50,11 +50,11 @@ namespace Microsoft.AspNetCore.Http.Headers { get { - return Headers.Get(HeaderNames.ContentLength); + return Headers.ContentLength; } set { - Headers.Set(HeaderNames.ContentLength, value.HasValue ? HeaderUtilities.FormatInt64(value.Value) : null); + Headers.ContentLength = value; } } diff --git a/src/Microsoft.AspNetCore.Http/HeaderDictionary.cs b/src/Microsoft.AspNetCore.Http/HeaderDictionary.cs index 5e52b1285d..61c307a64e 100644 --- a/src/Microsoft.AspNetCore.Http/HeaderDictionary.cs +++ b/src/Microsoft.AspNetCore.Http/HeaderDictionary.cs @@ -106,7 +106,7 @@ namespace Microsoft.AspNetCore.Http var rawValue = this[HeaderNames.ContentLength]; if (rawValue.Count == 1 && !string.IsNullOrWhiteSpace(rawValue[0]) && - HeaderUtilities.TryParseInt64(new StringSegment(rawValue[0]).Trim(), out value)) + HeaderUtilities.TryParseNonNegativeInt64(new StringSegment(rawValue[0]).Trim(), out value)) { return value; } @@ -117,7 +117,7 @@ namespace Microsoft.AspNetCore.Http { if (value.HasValue) { - this[HeaderNames.ContentLength] = HeaderUtilities.FormatInt64(value.Value); + this[HeaderNames.ContentLength] = HeaderUtilities.FormatNonNegativeInt64(value.Value); } else { diff --git a/src/Microsoft.AspNetCore.Owin/DictionaryStringValuesWrapper.cs b/src/Microsoft.AspNetCore.Owin/DictionaryStringValuesWrapper.cs index d88b2b9544..bef2a0e27c 100644 --- a/src/Microsoft.AspNetCore.Owin/DictionaryStringValuesWrapper.cs +++ b/src/Microsoft.AspNetCore.Owin/DictionaryStringValuesWrapper.cs @@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Owin if (rawValue.Length == 1 && !string.IsNullOrWhiteSpace(rawValue[0]) && - HeaderUtilities.TryParseInt64(new StringSegment(rawValue[0]).Trim(), out value)) + HeaderUtilities.TryParseNonNegativeInt64(new StringSegment(rawValue[0]).Trim(), out value)) { return value; } @@ -68,7 +68,7 @@ namespace Microsoft.AspNetCore.Owin { if (value.HasValue) { - Inner[HeaderNames.ContentLength] = (StringValues)HeaderUtilities.FormatInt64(value.Value); + Inner[HeaderNames.ContentLength] = (StringValues)HeaderUtilities.FormatNonNegativeInt64(value.Value); } else { diff --git a/src/Microsoft.Net.Http.Headers/CacheControlHeaderValue.cs b/src/Microsoft.Net.Http.Headers/CacheControlHeaderValue.cs index c0d28bdc9a..af31316667 100644 --- a/src/Microsoft.Net.Http.Headers/CacheControlHeaderValue.cs +++ b/src/Microsoft.Net.Http.Headers/CacheControlHeaderValue.cs @@ -610,7 +610,7 @@ namespace Microsoft.Net.Http.Headers } int seconds; - if (!HeaderUtilities.TryParseInt32(nameValue.Value, out seconds)) + if (!HeaderUtilities.TryParseNonNegativeInt32(nameValue.Value, out seconds)) { return false; } diff --git a/src/Microsoft.Net.Http.Headers/ContentDispositionHeaderValue.cs b/src/Microsoft.Net.Http.Headers/ContentDispositionHeaderValue.cs index e3542480ba..fbae25b6d1 100644 --- a/src/Microsoft.Net.Http.Headers/ContentDispositionHeaderValue.cs +++ b/src/Microsoft.Net.Http.Headers/ContentDispositionHeaderValue.cs @@ -108,7 +108,7 @@ namespace Microsoft.Net.Http.Headers if (sizeParameter != null) { var sizeString = sizeParameter.Value; - if (HeaderUtilities.TryParseInt64(sizeString, out value)) + if (HeaderUtilities.TryParseNonNegativeInt64(sizeString, out value)) { return value; } diff --git a/src/Microsoft.Net.Http.Headers/ContentRangeHeaderValue.cs b/src/Microsoft.Net.Http.Headers/ContentRangeHeaderValue.cs index c24b42758a..c187491dfc 100644 --- a/src/Microsoft.Net.Http.Headers/ContentRangeHeaderValue.cs +++ b/src/Microsoft.Net.Http.Headers/ContentRangeHeaderValue.cs @@ -354,13 +354,13 @@ namespace Microsoft.Net.Http.Headers parsedValue = null; long from = 0; - if ((fromLength > 0) && !HeaderUtilities.TryParseInt64(input.Substring(fromStartIndex, fromLength), out from)) + if ((fromLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Substring(fromStartIndex, fromLength), out from)) { return false; } long to = 0; - if ((toLength > 0) && !HeaderUtilities.TryParseInt64(input.Substring(toStartIndex, toLength), out to)) + if ((toLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Substring(toStartIndex, toLength), out to)) { return false; } @@ -372,7 +372,7 @@ namespace Microsoft.Net.Http.Headers } long length = 0; - if ((lengthLength > 0) && !HeaderUtilities.TryParseInt64(input.Substring(lengthStartIndex, lengthLength), + if ((lengthLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Substring(lengthStartIndex, lengthLength), out length)) { return false; diff --git a/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs b/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs index 827a99b437..c6580a56fc 100644 --- a/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs +++ b/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs @@ -12,7 +12,7 @@ namespace Microsoft.Net.Http.Headers { public static class HeaderUtilities { - private static readonly int _int64MaxStringLength = 20; + private static readonly int _int64MaxStringLength = 19; private const string QualityName = "q"; internal const string BytesUnit = "bytes"; @@ -269,7 +269,7 @@ namespace Microsoft.Net.Http.Headers var tokenLength = HttpRuleParser.GetTokenLength(headerValues[i], current); if (tokenLength == targetValue.Length && string.Compare(headerValues[i], current, targetValue, 0, tokenLength, StringComparison.OrdinalIgnoreCase) == 0 - && TryParseInt64FromHeaderValue(current + tokenLength, headerValues[i], out seconds)) + && TryParseNonNegativeInt64FromHeaderValue(current + tokenLength, headerValues[i], out seconds)) { // Token matches target value and seconds were parsed value = TimeSpan.FromSeconds(seconds); @@ -342,7 +342,7 @@ namespace Microsoft.Net.Http.Headers return false; } - private static unsafe bool TryParseInt64FromHeaderValue(int startIndex, string headerValue, out long result) + private static unsafe bool TryParseNonNegativeInt64FromHeaderValue(int startIndex, string headerValue, out long result) { // Trim leading whitespace startIndex += HttpRuleParser.GetWhitespaceLength(headerValue, startIndex); @@ -359,7 +359,7 @@ namespace Microsoft.Net.Http.Headers startIndex += HttpRuleParser.GetWhitespaceLength(headerValue, startIndex); // Try parse the number - if (TryParseInt64(new StringSegment(headerValue, startIndex, HttpRuleParser.GetNumberLength(headerValue, startIndex, false)), out result)) + if (TryParseNonNegativeInt64(new StringSegment(headerValue, startIndex, HttpRuleParser.GetNumberLength(headerValue, startIndex, false)), out result)) { return true; } @@ -368,9 +368,9 @@ namespace Microsoft.Net.Http.Headers return false; } - internal static bool TryParseInt32(string value, out int result) + internal static bool TryParseNonNegativeInt32(string value, out int result) { - return TryParseInt32(new StringSegment(value), out result); + return TryParseNonNegativeInt32(new StringSegment(value), out result); } /// @@ -388,12 +388,12 @@ namespace Microsoft.Net.Http.Headers /// result will be overwritten. /// /// true if parsing succeeded; otherwise, false. - public static bool TryParseInt64(string value, out long result) + public static bool TryParseNonNegativeInt64(string value, out long result) { - return TryParseInt64(new StringSegment(value), out result); + return TryParseNonNegativeInt64(new StringSegment(value), out result); } - internal static unsafe bool TryParseInt32(StringSegment value, out int result) + internal static unsafe bool TryParseNonNegativeInt32(StringSegment value, out int result) { if (string.IsNullOrEmpty(value.Buffer) || value.Length == 0) { @@ -444,7 +444,7 @@ namespace Microsoft.Net.Http.Headers /// originally supplied in result will be overwritten. /// /// true if parsing succeeded; otherwise, false. - public static unsafe bool TryParseInt64(StringSegment value, out long result) + public static unsafe bool TryParseNonNegativeInt64(StringSegment value, out long result) { if (string.IsNullOrEmpty(value.Buffer) || value.Length == 0) { @@ -481,31 +481,22 @@ namespace Microsoft.Net.Http.Headers } /// - /// Converts the signed 64-bit numeric value to its equivalent string representation. + /// Converts the non-negative 64-bit numeric value to its equivalent string representation. /// /// /// The number to convert. /// /// - /// The string representation of the value of this instance, consisting of a minus sign if the value is - /// negative, and a sequence of digits ranging from 0 to 9 with no leading zeroes. + /// The string representation of the value of this instance, consisting of a sequence of digits ranging from 0 to 9 with no leading zeroes. /// - public unsafe static string FormatInt64(long value) + public unsafe static string FormatNonNegativeInt64(long value) { - var position = _int64MaxStringLength; - var negative = false; - if (value < 0) { - // Not possible to compute absolute value of MinValue, return the exact string instead. - if (value == long.MinValue) - { - return "-9223372036854775808"; - } - negative = true; - value = -value; + throw new ArgumentOutOfRangeException(nameof(value), value, "The value to be formatted must be non-negative."); } + var position = _int64MaxStringLength; char* charBuffer = stackalloc char[_int64MaxStringLength]; do @@ -517,11 +508,6 @@ namespace Microsoft.Net.Http.Headers } while (value != 0); - if (negative) - { - charBuffer[--position] = '-'; - } - return new string(charBuffer, position, _int64MaxStringLength - position); } diff --git a/src/Microsoft.Net.Http.Headers/RangeItemHeaderValue.cs b/src/Microsoft.Net.Http.Headers/RangeItemHeaderValue.cs index ce62e99f2a..866d4b8e8b 100644 --- a/src/Microsoft.Net.Http.Headers/RangeItemHeaderValue.cs +++ b/src/Microsoft.Net.Http.Headers/RangeItemHeaderValue.cs @@ -202,14 +202,14 @@ namespace Microsoft.Net.Http.Headers // Try convert first value to int64 long from = 0; - if ((fromLength > 0) && !HeaderUtilities.TryParseInt64(input.Substring(fromStartIndex, fromLength), out from)) + if ((fromLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Substring(fromStartIndex, fromLength), out from)) { return 0; } // Try convert second value to int64 long to = 0; - if ((toLength > 0) && !HeaderUtilities.TryParseInt64(input.Substring(toStartIndex, toLength), out to)) + if ((toLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Substring(toStartIndex, toLength), out to)) { return 0; } diff --git a/src/Microsoft.Net.Http.Headers/SetCookieHeaderValue.cs b/src/Microsoft.Net.Http.Headers/SetCookieHeaderValue.cs index 7fda7571a3..8c6d9a565d 100644 --- a/src/Microsoft.Net.Http.Headers/SetCookieHeaderValue.cs +++ b/src/Microsoft.Net.Http.Headers/SetCookieHeaderValue.cs @@ -105,7 +105,7 @@ namespace Microsoft.Net.Http.Headers if (MaxAge.HasValue) { - maxAge = HeaderUtilities.FormatInt64((long)MaxAge.Value.TotalSeconds); + maxAge = HeaderUtilities.FormatNonNegativeInt64((long)MaxAge.Value.TotalSeconds); length += SeparatorToken.Length + MaxAgeToken.Length + EqualsToken.Length + maxAge.Length; } @@ -200,7 +200,7 @@ namespace Microsoft.Net.Http.Headers if (MaxAge.HasValue) { - AppendSegment(builder, MaxAgeToken, HeaderUtilities.FormatInt64((long)MaxAge.Value.TotalSeconds)); + AppendSegment(builder, MaxAgeToken, HeaderUtilities.FormatNonNegativeInt64((long)MaxAge.Value.TotalSeconds)); } if (Domain != null) @@ -365,7 +365,7 @@ namespace Microsoft.Net.Http.Headers } var numberString = input.Substring(offset, itemLength); long maxAge; - if (!HeaderUtilities.TryParseInt64(numberString, out maxAge)) + if (!HeaderUtilities.TryParseNonNegativeInt64(numberString, out maxAge)) { // Invalid expiration date, abort return 0; diff --git a/test/Microsoft.Net.Http.Headers.Tests/HeaderUtilitiesTest.cs b/test/Microsoft.Net.Http.Headers.Tests/HeaderUtilitiesTest.cs index 97b342264a..807e17cc1e 100644 --- a/test/Microsoft.Net.Http.Headers.Tests/HeaderUtilitiesTest.cs +++ b/test/Microsoft.Net.Http.Headers.Tests/HeaderUtilitiesTest.cs @@ -87,15 +87,20 @@ namespace Microsoft.Net.Http.Headers [Theory] [InlineData(0)] [InlineData(1)] - [InlineData(-1)] [InlineData(1234567890)] - [InlineData(-1234567890)] [InlineData(long.MaxValue)] - [InlineData(long.MinValue)] - [InlineData(long.MinValue + 1)] - public void FormatInt64_MatchesToString(long value) + public void FormatNonNegativeInt64_MatchesToString(long value) { - Assert.Equal(value.ToString(CultureInfo.InvariantCulture), HeaderUtilities.FormatInt64(value)); + Assert.Equal(value.ToString(CultureInfo.InvariantCulture), HeaderUtilities.FormatNonNegativeInt64(value)); + } + + [Theory] + [InlineData(-1)] + [InlineData(-1234567890)] + [InlineData(long.MinValue)] + public void FormatNonNegativeInt64_Throws_ForNegativeValues(long value) + { + Assert.Throws(() => HeaderUtilities.FormatNonNegativeInt64(value)); } [Theory] @@ -150,20 +155,20 @@ namespace Microsoft.Net.Http.Headers [InlineData("a")] [InlineData("1.1")] [InlineData("9223372036854775808")] // long.MaxValue + 1 - public void TryParseInt64_Fails(string valueString) + public void TryParseNonNegativeInt64_Fails(string valueString) { long value = 1; - Assert.False(HeaderUtilities.TryParseInt64(valueString, out value)); + Assert.False(HeaderUtilities.TryParseNonNegativeInt64(valueString, out value)); Assert.Equal(0, value); } [Theory] [InlineData("0", 0)] [InlineData("9223372036854775807", 9223372036854775807)] // long.MaxValue - public void TryParseInt64_Succeeds(string valueString, long expected) + public void TryParseNonNegativeInt64_Succeeds(string valueString, long expected) { long value = 1; - Assert.True(HeaderUtilities.TryParseInt64(valueString, out value)); + Assert.True(HeaderUtilities.TryParseNonNegativeInt64(valueString, out value)); Assert.Equal(expected, value); } @@ -175,20 +180,20 @@ namespace Microsoft.Net.Http.Headers [InlineData("1.1")] [InlineData("1,000")] [InlineData("2147483648")] // int.MaxValue + 1 - public void TryParseInt32_Fails(string valueString) + public void TryParseNonNegativeInt32_Fails(string valueString) { int value = 1; - Assert.False(HeaderUtilities.TryParseInt32(valueString, out value)); + Assert.False(HeaderUtilities.TryParseNonNegativeInt32(valueString, out value)); Assert.Equal(0, value); } [Theory] [InlineData("0", 0)] [InlineData("2147483647", 2147483647)] // int.MaxValue - public void TryParseInt32_Succeeds(string valueString, long expected) + public void TryParseNonNegativeInt32_Succeeds(string valueString, long expected) { int value = 1; - Assert.True(HeaderUtilities.TryParseInt32(valueString, out value)); + Assert.True(HeaderUtilities.TryParseNonNegativeInt32(valueString, out value)); Assert.Equal(expected, value); } }