Only format non-negative int64 #760

This commit is contained in:
John Luo 2017-02-24 14:17:12 -08:00
parent b9bba9cbb1
commit 15adff9433
12 changed files with 53 additions and 62 deletions

View File

@ -156,7 +156,7 @@ namespace Microsoft.AspNetCore.Http
{ typeof(RangeHeaderValue), new Func<string, RangeHeaderValue>(value => { RangeHeaderValue result; return RangeHeaderValue.TryParse(value, out result) ? result : null; }) }, { typeof(RangeHeaderValue), new Func<string, RangeHeaderValue>(value => { RangeHeaderValue result; return RangeHeaderValue.TryParse(value, out result) ? result : null; }) },
{ typeof(EntityTagHeaderValue), new Func<string, EntityTagHeaderValue>(value => { EntityTagHeaderValue result; return EntityTagHeaderValue.TryParse(value, out result) ? result : null; }) }, { typeof(EntityTagHeaderValue), new Func<string, EntityTagHeaderValue>(value => { EntityTagHeaderValue result; return EntityTagHeaderValue.TryParse(value, out result) ? result : null; }) },
{ typeof(DateTimeOffset?), new Func<string, DateTimeOffset?>(value => { DateTimeOffset result; return HeaderUtilities.TryParseDate(value, out result) ? result : (DateTimeOffset?)null; }) }, { typeof(DateTimeOffset?), new Func<string, DateTimeOffset?>(value => { DateTimeOffset result; return HeaderUtilities.TryParseDate(value, out result) ? result : (DateTimeOffset?)null; }) },
{ typeof(long?), new Func<string, long?>(value => { long result; return HeaderUtilities.TryParseInt64(value, out result) ? result : (long?)null; }) }, { typeof(long?), new Func<string, long?>(value => { long result; return HeaderUtilities.TryParseNonNegativeInt64(value, out result) ? result : (long?)null; }) },
}; };
private static IDictionary<Type, object> KnownListParsers = new Dictionary<Type, object>() private static IDictionary<Type, object> KnownListParsers = new Dictionary<Type, object>()

View File

@ -97,11 +97,11 @@ namespace Microsoft.AspNetCore.Http.Headers
{ {
get get
{ {
return Headers.Get<long?>(HeaderNames.ContentLength); return Headers.ContentLength;
} }
set set
{ {
Headers.Set(HeaderNames.ContentLength, value.HasValue ? HeaderUtilities.FormatInt64(value.Value) : null); Headers.ContentLength = value;
} }
} }

View File

@ -50,11 +50,11 @@ namespace Microsoft.AspNetCore.Http.Headers
{ {
get get
{ {
return Headers.Get<long?>(HeaderNames.ContentLength); return Headers.ContentLength;
} }
set set
{ {
Headers.Set(HeaderNames.ContentLength, value.HasValue ? HeaderUtilities.FormatInt64(value.Value) : null); Headers.ContentLength = value;
} }
} }

View File

@ -106,7 +106,7 @@ namespace Microsoft.AspNetCore.Http
var rawValue = this[HeaderNames.ContentLength]; var rawValue = this[HeaderNames.ContentLength];
if (rawValue.Count == 1 && if (rawValue.Count == 1 &&
!string.IsNullOrWhiteSpace(rawValue[0]) && !string.IsNullOrWhiteSpace(rawValue[0]) &&
HeaderUtilities.TryParseInt64(new StringSegment(rawValue[0]).Trim(), out value)) HeaderUtilities.TryParseNonNegativeInt64(new StringSegment(rawValue[0]).Trim(), out value))
{ {
return value; return value;
} }
@ -117,7 +117,7 @@ namespace Microsoft.AspNetCore.Http
{ {
if (value.HasValue) if (value.HasValue)
{ {
this[HeaderNames.ContentLength] = HeaderUtilities.FormatInt64(value.Value); this[HeaderNames.ContentLength] = HeaderUtilities.FormatNonNegativeInt64(value.Value);
} }
else else
{ {

View File

@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Owin
if (rawValue.Length == 1 && if (rawValue.Length == 1 &&
!string.IsNullOrWhiteSpace(rawValue[0]) && !string.IsNullOrWhiteSpace(rawValue[0]) &&
HeaderUtilities.TryParseInt64(new StringSegment(rawValue[0]).Trim(), out value)) HeaderUtilities.TryParseNonNegativeInt64(new StringSegment(rawValue[0]).Trim(), out value))
{ {
return value; return value;
} }
@ -68,7 +68,7 @@ namespace Microsoft.AspNetCore.Owin
{ {
if (value.HasValue) if (value.HasValue)
{ {
Inner[HeaderNames.ContentLength] = (StringValues)HeaderUtilities.FormatInt64(value.Value); Inner[HeaderNames.ContentLength] = (StringValues)HeaderUtilities.FormatNonNegativeInt64(value.Value);
} }
else else
{ {

View File

@ -610,7 +610,7 @@ namespace Microsoft.Net.Http.Headers
} }
int seconds; int seconds;
if (!HeaderUtilities.TryParseInt32(nameValue.Value, out seconds)) if (!HeaderUtilities.TryParseNonNegativeInt32(nameValue.Value, out seconds))
{ {
return false; return false;
} }

View File

@ -108,7 +108,7 @@ namespace Microsoft.Net.Http.Headers
if (sizeParameter != null) if (sizeParameter != null)
{ {
var sizeString = sizeParameter.Value; var sizeString = sizeParameter.Value;
if (HeaderUtilities.TryParseInt64(sizeString, out value)) if (HeaderUtilities.TryParseNonNegativeInt64(sizeString, out value))
{ {
return value; return value;
} }

View File

@ -354,13 +354,13 @@ namespace Microsoft.Net.Http.Headers
parsedValue = null; parsedValue = null;
long from = 0; 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; return false;
} }
long to = 0; 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; return false;
} }
@ -372,7 +372,7 @@ namespace Microsoft.Net.Http.Headers
} }
long length = 0; long length = 0;
if ((lengthLength > 0) && !HeaderUtilities.TryParseInt64(input.Substring(lengthStartIndex, lengthLength), if ((lengthLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Substring(lengthStartIndex, lengthLength),
out length)) out length))
{ {
return false; return false;

View File

@ -12,7 +12,7 @@ namespace Microsoft.Net.Http.Headers
{ {
public static class HeaderUtilities public static class HeaderUtilities
{ {
private static readonly int _int64MaxStringLength = 20; private static readonly int _int64MaxStringLength = 19;
private const string QualityName = "q"; private const string QualityName = "q";
internal const string BytesUnit = "bytes"; internal const string BytesUnit = "bytes";
@ -269,7 +269,7 @@ namespace Microsoft.Net.Http.Headers
var tokenLength = HttpRuleParser.GetTokenLength(headerValues[i], current); var tokenLength = HttpRuleParser.GetTokenLength(headerValues[i], current);
if (tokenLength == targetValue.Length if (tokenLength == targetValue.Length
&& string.Compare(headerValues[i], current, targetValue, 0, tokenLength, StringComparison.OrdinalIgnoreCase) == 0 && 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 // Token matches target value and seconds were parsed
value = TimeSpan.FromSeconds(seconds); value = TimeSpan.FromSeconds(seconds);
@ -342,7 +342,7 @@ namespace Microsoft.Net.Http.Headers
return false; 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 // Trim leading whitespace
startIndex += HttpRuleParser.GetWhitespaceLength(headerValue, startIndex); startIndex += HttpRuleParser.GetWhitespaceLength(headerValue, startIndex);
@ -359,7 +359,7 @@ namespace Microsoft.Net.Http.Headers
startIndex += HttpRuleParser.GetWhitespaceLength(headerValue, startIndex); startIndex += HttpRuleParser.GetWhitespaceLength(headerValue, startIndex);
// Try parse the number // 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; return true;
} }
@ -368,9 +368,9 @@ namespace Microsoft.Net.Http.Headers
return false; 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);
} }
/// <summary> /// <summary>
@ -388,12 +388,12 @@ namespace Microsoft.Net.Http.Headers
/// result will be overwritten. /// result will be overwritten.
/// </param> /// </param>
/// <returns><code>true</code> if parsing succeeded; otherwise, <code>false</code>.</returns> /// <returns><code>true</code> if parsing succeeded; otherwise, <code>false</code>.</returns>
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) if (string.IsNullOrEmpty(value.Buffer) || value.Length == 0)
{ {
@ -444,7 +444,7 @@ namespace Microsoft.Net.Http.Headers
/// originally supplied in result will be overwritten. /// originally supplied in result will be overwritten.
/// </param> /// </param>
/// <returns><code>true</code> if parsing succeeded; otherwise, <code>false</code>.</returns> /// <returns><code>true</code> if parsing succeeded; otherwise, <code>false</code>.</returns>
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) if (string.IsNullOrEmpty(value.Buffer) || value.Length == 0)
{ {
@ -481,31 +481,22 @@ namespace Microsoft.Net.Http.Headers
} }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
/// <param name="value"> /// <param name="value">
/// The number to convert. /// The number to convert.
/// </param> /// </param>
/// <returns> /// <returns>
/// The string representation of the value of this instance, consisting of a minus sign if the value is /// The string representation of the value of this instance, consisting of a sequence of digits ranging from 0 to 9 with no leading zeroes.
/// negative, and a sequence of digits ranging from 0 to 9 with no leading zeroes.
/// </returns> /// </returns>
public unsafe static string FormatInt64(long value) public unsafe static string FormatNonNegativeInt64(long value)
{ {
var position = _int64MaxStringLength;
var negative = false;
if (value < 0) if (value < 0)
{ {
// Not possible to compute absolute value of MinValue, return the exact string instead. throw new ArgumentOutOfRangeException(nameof(value), value, "The value to be formatted must be non-negative.");
if (value == long.MinValue)
{
return "-9223372036854775808";
}
negative = true;
value = -value;
} }
var position = _int64MaxStringLength;
char* charBuffer = stackalloc char[_int64MaxStringLength]; char* charBuffer = stackalloc char[_int64MaxStringLength];
do do
@ -517,11 +508,6 @@ namespace Microsoft.Net.Http.Headers
} }
while (value != 0); while (value != 0);
if (negative)
{
charBuffer[--position] = '-';
}
return new string(charBuffer, position, _int64MaxStringLength - position); return new string(charBuffer, position, _int64MaxStringLength - position);
} }

View File

@ -202,14 +202,14 @@ namespace Microsoft.Net.Http.Headers
// Try convert first value to int64 // Try convert first value to int64
long from = 0; 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; return 0;
} }
// Try convert second value to int64 // Try convert second value to int64
long to = 0; 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; return 0;
} }

View File

@ -105,7 +105,7 @@ namespace Microsoft.Net.Http.Headers
if (MaxAge.HasValue) 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; length += SeparatorToken.Length + MaxAgeToken.Length + EqualsToken.Length + maxAge.Length;
} }
@ -200,7 +200,7 @@ namespace Microsoft.Net.Http.Headers
if (MaxAge.HasValue) if (MaxAge.HasValue)
{ {
AppendSegment(builder, MaxAgeToken, HeaderUtilities.FormatInt64((long)MaxAge.Value.TotalSeconds)); AppendSegment(builder, MaxAgeToken, HeaderUtilities.FormatNonNegativeInt64((long)MaxAge.Value.TotalSeconds));
} }
if (Domain != null) if (Domain != null)
@ -365,7 +365,7 @@ namespace Microsoft.Net.Http.Headers
} }
var numberString = input.Substring(offset, itemLength); var numberString = input.Substring(offset, itemLength);
long maxAge; long maxAge;
if (!HeaderUtilities.TryParseInt64(numberString, out maxAge)) if (!HeaderUtilities.TryParseNonNegativeInt64(numberString, out maxAge))
{ {
// Invalid expiration date, abort // Invalid expiration date, abort
return 0; return 0;

View File

@ -87,15 +87,20 @@ namespace Microsoft.Net.Http.Headers
[Theory] [Theory]
[InlineData(0)] [InlineData(0)]
[InlineData(1)] [InlineData(1)]
[InlineData(-1)]
[InlineData(1234567890)] [InlineData(1234567890)]
[InlineData(-1234567890)]
[InlineData(long.MaxValue)] [InlineData(long.MaxValue)]
[InlineData(long.MinValue)] public void FormatNonNegativeInt64_MatchesToString(long value)
[InlineData(long.MinValue + 1)]
public void FormatInt64_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<ArgumentOutOfRangeException>(() => HeaderUtilities.FormatNonNegativeInt64(value));
} }
[Theory] [Theory]
@ -150,20 +155,20 @@ namespace Microsoft.Net.Http.Headers
[InlineData("a")] [InlineData("a")]
[InlineData("1.1")] [InlineData("1.1")]
[InlineData("9223372036854775808")] // long.MaxValue + 1 [InlineData("9223372036854775808")] // long.MaxValue + 1
public void TryParseInt64_Fails(string valueString) public void TryParseNonNegativeInt64_Fails(string valueString)
{ {
long value = 1; long value = 1;
Assert.False(HeaderUtilities.TryParseInt64(valueString, out value)); Assert.False(HeaderUtilities.TryParseNonNegativeInt64(valueString, out value));
Assert.Equal(0, value); Assert.Equal(0, value);
} }
[Theory] [Theory]
[InlineData("0", 0)] [InlineData("0", 0)]
[InlineData("9223372036854775807", 9223372036854775807)] // long.MaxValue [InlineData("9223372036854775807", 9223372036854775807)] // long.MaxValue
public void TryParseInt64_Succeeds(string valueString, long expected) public void TryParseNonNegativeInt64_Succeeds(string valueString, long expected)
{ {
long value = 1; long value = 1;
Assert.True(HeaderUtilities.TryParseInt64(valueString, out value)); Assert.True(HeaderUtilities.TryParseNonNegativeInt64(valueString, out value));
Assert.Equal(expected, value); Assert.Equal(expected, value);
} }
@ -175,20 +180,20 @@ namespace Microsoft.Net.Http.Headers
[InlineData("1.1")] [InlineData("1.1")]
[InlineData("1,000")] [InlineData("1,000")]
[InlineData("2147483648")] // int.MaxValue + 1 [InlineData("2147483648")] // int.MaxValue + 1
public void TryParseInt32_Fails(string valueString) public void TryParseNonNegativeInt32_Fails(string valueString)
{ {
int value = 1; int value = 1;
Assert.False(HeaderUtilities.TryParseInt32(valueString, out value)); Assert.False(HeaderUtilities.TryParseNonNegativeInt32(valueString, out value));
Assert.Equal(0, value); Assert.Equal(0, value);
} }
[Theory] [Theory]
[InlineData("0", 0)] [InlineData("0", 0)]
[InlineData("2147483647", 2147483647)] // int.MaxValue [InlineData("2147483647", 2147483647)] // int.MaxValue
public void TryParseInt32_Succeeds(string valueString, long expected) public void TryParseNonNegativeInt32_Succeeds(string valueString, long expected)
{ {
int value = 1; int value = 1;
Assert.True(HeaderUtilities.TryParseInt32(valueString, out value)); Assert.True(HeaderUtilities.TryParseNonNegativeInt32(valueString, out value));
Assert.Equal(expected, value); Assert.Equal(expected, value);
} }
} }