Add functionalities to HeaderUtilities

- Add allocation free parsing of int32, int64
- Improve performance of converting int64 to string
- Add parsing of seconds from header values
- Add check for existence of cache directives
- Expose CacheControlHeaderValue constants
This commit is contained in:
John Luo 2016-12-08 17:19:33 -08:00
parent b727f0e1ac
commit d50a24145d
11 changed files with 573 additions and 80 deletions

View File

@ -98,7 +98,7 @@ namespace Microsoft.AspNetCore.Http
{
return null;
}
return p;
}
}

View File

@ -68,10 +68,10 @@ namespace Microsoft.AspNetCore.Http.Internal
// Quote items that contain comas and are not already quoted.
private static string QuoteIfNeeded(string value)
{
if (!string.IsNullOrWhiteSpace(value) &&
value.Contains(',') &&
if (!string.IsNullOrWhiteSpace(value) &&
value.Contains(',') &&
(value[0] != '"' || value[value.Length - 1] != '"'))
{
{
return $"\"{value}\"";
}
return value;
@ -79,7 +79,7 @@ namespace Microsoft.AspNetCore.Http.Internal
private static string DeQuote(string value)
{
if (!string.IsNullOrWhiteSpace(value) &&
if (!string.IsNullOrWhiteSpace(value) &&
(value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"'))
{
value = value.Substring(1, value.Length - 2);

View File

@ -411,12 +411,11 @@ namespace Microsoft.AspNetCore.Http.Internal
throw new ArgumentNullException(nameof(headers));
}
const NumberStyles styles = NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite;
long value;
var rawValue = headers[HeaderNames.ContentLength];
if (rawValue.Count == 1 &&
!string.IsNullOrWhiteSpace(rawValue[0]) &&
long.TryParse(rawValue[0], styles, CultureInfo.InvariantCulture, out value))
HeaderUtilities.TryParseInt64(new StringSegment(rawValue[0]).Trim(), out value))
{
return value;
}
@ -433,7 +432,7 @@ namespace Microsoft.AspNetCore.Http.Internal
if (value.HasValue)
{
headers[HeaderNames.ContentLength] = value.Value.ToString(CultureInfo.InvariantCulture);
headers[HeaderNames.ContentLength] = HeaderUtilities.FormatInt64(value.Value);
}
else
{

View File

@ -11,18 +11,18 @@ namespace Microsoft.Net.Http.Headers
{
public class CacheControlHeaderValue
{
private const string MaxAgeString = "max-age";
private const string MaxStaleString = "max-stale";
private const string MinFreshString = "min-fresh";
private const string MustRevalidateString = "must-revalidate";
private const string NoCacheString = "no-cache";
private const string NoStoreString = "no-store";
private const string NoTransformString = "no-transform";
private const string OnlyIfCachedString = "only-if-cached";
private const string PrivateString = "private";
private const string ProxyRevalidateString = "proxy-revalidate";
private const string PublicString = "public";
private const string SharedMaxAgeString = "s-maxage";
public static readonly string PublicString = "public";
public static readonly string PrivateString = "private";
public static readonly string MaxAgeString = "max-age";
public static readonly string SharedMaxAgeString = "s-maxage";
public static readonly string NoCacheString = "no-cache";
public static readonly string NoStoreString = "no-store";
public static readonly string MaxStaleString = "max-stale";
public static readonly string MinFreshString = "min-fresh";
public static readonly string NoTransformString = "no-transform";
public static readonly string OnlyIfCachedString = "only-if-cached";
public static readonly string MustRevalidateString = "must-revalidate";
public static readonly string ProxyRevalidateString = "proxy-revalidate";
// The Cache-Control header is special: It is a header supporting a list of values, but we represent the list
// as _one_ instance of CacheControlHeaderValue. I.e we set 'SupportsMultipleValues' to 'true' since it is
@ -394,63 +394,120 @@ namespace Microsoft.Net.Http.Headers
CacheControlHeaderValue cc,
List<NameValueHeaderValue> nameValueList)
{
foreach (NameValueHeaderValue nameValue in nameValueList)
for (var i = 0; i < nameValueList.Count; i++)
{
var nameValue = nameValueList[i];
var name = nameValue.Name;
var success = true;
string name = nameValue.Name.ToLowerInvariant();
switch (name)
switch (name.Length)
{
case NoCacheString:
success = TrySetOptionalTokenList(nameValue, ref cc._noCache, ref cc._noCacheHeaders);
break;
case NoStoreString:
success = TrySetTokenOnlyValue(nameValue, ref cc._noStore);
break;
case MaxAgeString:
success = TrySetTimeSpan(nameValue, ref cc._maxAge);
break;
case MaxStaleString:
success = ((nameValue.Value == null) || TrySetTimeSpan(nameValue, ref cc._maxStaleLimit));
if (success)
case 6:
if (string.Equals(PublicString, name, StringComparison.OrdinalIgnoreCase))
{
cc._maxStale = true;
success = TrySetTokenOnlyValue(nameValue, ref cc._public);
}
else
{
goto default;
}
break;
case MinFreshString:
success = TrySetTimeSpan(nameValue, ref cc._minFresh);
case 7:
if (string.Equals(MaxAgeString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetTimeSpan(nameValue, ref cc._maxAge);
}
else if(string.Equals(PrivateString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetOptionalTokenList(nameValue, ref cc._private, ref cc._privateHeaders);
}
else
{
goto default;
}
break;
case NoTransformString:
success = TrySetTokenOnlyValue(nameValue, ref cc._noTransform);
case 8:
if (string.Equals(NoCacheString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetOptionalTokenList(nameValue, ref cc._noCache, ref cc._noCacheHeaders);
}
else if (string.Equals(NoStoreString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetTokenOnlyValue(nameValue, ref cc._noStore);
}
else if (string.Equals(SharedMaxAgeString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetTimeSpan(nameValue, ref cc._sharedMaxAge);
}
else
{
goto default;
}
break;
case OnlyIfCachedString:
success = TrySetTokenOnlyValue(nameValue, ref cc._onlyIfCached);
case 9:
if (string.Equals(MaxStaleString, name, StringComparison.OrdinalIgnoreCase))
{
success = ((nameValue.Value == null) || TrySetTimeSpan(nameValue, ref cc._maxStaleLimit));
if (success)
{
cc._maxStale = true;
}
}
else if (string.Equals(MinFreshString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetTimeSpan(nameValue, ref cc._minFresh);
}
else
{
goto default;
}
break;
case PublicString:
success = TrySetTokenOnlyValue(nameValue, ref cc._public);
case 12:
if (string.Equals(NoTransformString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetTokenOnlyValue(nameValue, ref cc._noTransform);
}
else
{
goto default;
}
break;
case PrivateString:
success = TrySetOptionalTokenList(nameValue, ref cc._private, ref cc._privateHeaders);
case 14:
if (string.Equals(OnlyIfCachedString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetTokenOnlyValue(nameValue, ref cc._onlyIfCached);
}
else
{
goto default;
}
break;
case MustRevalidateString:
success = TrySetTokenOnlyValue(nameValue, ref cc._mustRevalidate);
case 15:
if (string.Equals(MustRevalidateString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetTokenOnlyValue(nameValue, ref cc._mustRevalidate);
}
else
{
goto default;
}
break;
case ProxyRevalidateString:
success = TrySetTokenOnlyValue(nameValue, ref cc._proxyRevalidate);
break;
case SharedMaxAgeString:
success = TrySetTimeSpan(nameValue, ref cc._sharedMaxAge);
case 16:
if (string.Equals(ProxyRevalidateString, name, StringComparison.OrdinalIgnoreCase))
{
success = TrySetTokenOnlyValue(nameValue, ref cc._proxyRevalidate);
}
else
{
goto default;
}
break;
default:

View File

@ -104,13 +104,13 @@ namespace Microsoft.Net.Http.Headers
get
{
var sizeParameter = NameValueHeaderValue.Find(_parameters, SizeString);
ulong value;
long value;
if (sizeParameter != null)
{
string sizeString = sizeParameter.Value;
if (UInt64.TryParse(sizeString, NumberStyles.Integer, CultureInfo.InvariantCulture, out value))
var sizeString = sizeParameter.Value;
if (HeaderUtilities.TryParseInt64(sizeString, out value))
{
return (long)value;
return value;
}
}
return null;

View File

@ -5,11 +5,13 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
public static class HeaderUtilities
{
private static readonly int _int64MaxStringLength = 20;
private const string QualityName = "q";
internal const string BytesUnit = "bytes";
@ -198,19 +200,322 @@ namespace Microsoft.Net.Http.Headers
return current;
}
/// <summary>
/// Try to find a target header value among the set of given header values and parse it as a
/// <see cref="TimeSpan"/>.
/// </summary>
/// <param name="headerValues">
/// The <see cref="StringValues"/> containing the set of header values to search.
/// </param>
/// <param name="targetValue">
/// The target header value to look for.
/// </param>
/// <param name="value">
/// When this method returns, contains the parsed <see cref="TimeSpan"/>, if the parsing succeeded, or
/// null if the parsing failed. The conversion fails if the <paramref name="targetValue"/> was not
/// found or could not be parsed as a <see cref="TimeSpan"/>. This parameter is passed uninitialized;
/// any value originally supplied in result will be overwritten.
/// </param>
/// <returns>
/// <code>true</code> if <paramref name="targetValue"/> is found and successfully parsed; otherwise,
/// <code>false</code>.
/// </returns>
// e.g. { "headerValue=10, targetHeaderValue=30" }
public static bool TryParseSeconds(StringValues headerValues, string targetValue, out TimeSpan? value)
{
if (StringValues.IsNullOrEmpty(headerValues) || string.IsNullOrEmpty(targetValue))
{
value = null;
return false;
}
for (var i = 0; i < headerValues.Count; i++)
{
// Trim leading white space
var current = HttpRuleParser.GetWhitespaceLength(headerValues[i], 0);
while (current < headerValues[i].Length)
{
long seconds;
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))
{
// Token matches target value and seconds were parsed
value = TimeSpan.FromSeconds(seconds);
return true;
}
else
{
// Skip until the next potential name
current += tokenLength;
current += HttpRuleParser.GetWhitespaceLength(headerValues[i], current);
// Skip the value if present
if (current < headerValues[i].Length && headerValues[i][current] == '=')
{
current++; // skip '='
current += NameValueHeaderValue.GetValueLength(headerValues[i], current);
current += HttpRuleParser.GetWhitespaceLength(headerValues[i], current);
}
// Skip the delimiter
if (current < headerValues[i].Length && headerValues[i][current] == ',')
{
current++; // skip ','
current += HttpRuleParser.GetWhitespaceLength(headerValues[i], current);
}
}
}
}
value = null;
return false;
}
/// <summary>
/// Check if a target directive exists among the set of given cache control directives.
/// </summary>
/// <param name="cacheControlDirectives">
/// The <see cref="StringValues"/> containing the set of cache control directives.
/// </param>
/// <param name="targetDirectives">
/// The target cache control directives to look for.
/// </param>
/// <returns>
/// <code>true</code> if <paramref name="targetDirectives"/> is contained in <paramref name="cacheControlDirectives"/>;
/// otherwise, <code>false</code>.
/// </returns>
public static bool ContainsCacheDirective(StringValues cacheControlDirectives, string targetDirectives)
{
if (StringValues.IsNullOrEmpty(cacheControlDirectives) || string.IsNullOrEmpty(targetDirectives))
{
return false;
}
for (var i = 0; i < cacheControlDirectives.Count; i++)
{
// Trim leading white space
var current = HttpRuleParser.GetWhitespaceLength(cacheControlDirectives[i], 0);
while (current < cacheControlDirectives[i].Length)
{
var tokenLength = HttpRuleParser.GetTokenLength(cacheControlDirectives[i], current);
if (tokenLength == targetDirectives.Length
&& string.Compare(cacheControlDirectives[i], current, targetDirectives, 0, tokenLength, StringComparison.OrdinalIgnoreCase) == 0)
{
// Token matches target value
return true;
}
else
{
// Skip until the next potential name
current += tokenLength;
current += HttpRuleParser.GetWhitespaceLength(cacheControlDirectives[i], current);
// Skip the value if present
if (current < cacheControlDirectives[i].Length && cacheControlDirectives[i][current] == '=')
{
current++; // skip '='
current += NameValueHeaderValue.GetValueLength(cacheControlDirectives[i], current);
current += HttpRuleParser.GetWhitespaceLength(cacheControlDirectives[i], current);
}
// Skip the delimiter
if (current < cacheControlDirectives[i].Length && cacheControlDirectives[i][current] == ',')
{
current++; // skip ','
current += HttpRuleParser.GetWhitespaceLength(cacheControlDirectives[i], current);
}
}
}
}
return false;
}
private static unsafe bool TryParseInt64FromHeaderValue(int startIndex, string headerValue, out long result)
{
// Trim leading whitespace
startIndex += HttpRuleParser.GetWhitespaceLength(headerValue, startIndex);
// Match and skip '=', it also can't be the last character in the headerValue
if (startIndex >= headerValue.Length - 1 || headerValue[startIndex] != '=')
{
result = 0;
return false;
}
startIndex++;
// Trim trailing whitespace
startIndex += HttpRuleParser.GetWhitespaceLength(headerValue, startIndex);
// Try parse the number
if (TryParseInt64(new StringSegment(headerValue, startIndex, HttpRuleParser.GetNumberLength(headerValue, startIndex, false)), out result))
{
return true;
}
result = 0;
return false;
}
internal static bool TryParseInt32(string value, out int result)
{
return int.TryParse(value, NumberStyles.None, NumberFormatInfo.InvariantInfo, out result);
return TryParseInt32(new StringSegment(value), out result);
}
/// <summary>
/// Try to convert a string representation of a positive number to its 64-bit signed integer equivalent.
/// A return value indicates whether the conversion succeeded or failed.
/// </summary>
/// <param name="value">
/// A string containing a number to convert.
/// </param>
/// <param name="result">
/// When this method returns, contains the 64-bit signed integer value equivalent of the number contained
/// in the string, if the conversion succeeded, or zero if the conversion failed. The conversion fails if
/// the string is null or String.Empty, is not of the correct format, is negative, or represents a number
/// greater than Int64.MaxValue. This parameter is passed uninitialized; any value originally supplied in
/// result will be overwritten.
/// </param>
/// <returns><code>true</code> if parsing succeeded; otherwise, <code>false</code>.</returns>
public static bool TryParseInt64(string value, out long result)
{
return long.TryParse(value, NumberStyles.None, NumberFormatInfo.InvariantInfo, out result);
return TryParseInt64(new StringSegment(value), out result);
}
public static string FormatInt64(long value)
internal static unsafe bool TryParseInt32(StringSegment value, out int result)
{
return value.ToString(CultureInfo.InvariantCulture);
if (string.IsNullOrEmpty(value.Buffer) || value.Length == 0)
{
result = 0;
return false;
}
result = 0;
fixed (char* ptr = value.Buffer)
{
var ch = (ushort*)ptr + value.Offset;
var end = ch + value.Length;
ushort digit = 0;
while (ch < end && (digit = (ushort)(*ch - 0x30)) <= 9)
{
// Check for overflow
if ((result = result * 10 + digit) < 0)
{
result = 0;
return false;
}
ch++;
}
if (ch != end)
{
result = 0;
return false;
}
return true;
}
}
/// <summary>
/// Try to convert a <see cref="StringSegment"/> representation of a positive number to its 64-bit signed
/// integer equivalent. A return value indicates whether the conversion succeeded or failed.
/// </summary>
/// <param name="value">
/// A <see cref="StringSegment"/> containing a number to convert.
/// </param>
/// <param name="result">
/// When this method returns, contains the 64-bit signed integer value equivalent of the number contained
/// in the string, if the conversion succeeded, or zero if the conversion failed. The conversion fails if
/// the <see cref="StringSegment"/> is null or String.Empty, is not of the correct format, is negative, or
/// represents a number greater than Int64.MaxValue. This parameter is passed uninitialized; any value
/// originally supplied in result will be overwritten.
/// </param>
/// <returns><code>true</code> if parsing succeeded; otherwise, <code>false</code>.</returns>
public static unsafe bool TryParseInt64(StringSegment value, out long result)
{
if (string.IsNullOrEmpty(value.Buffer) || value.Length == 0)
{
result = 0;
return false;
}
result = 0;
fixed (char* ptr = value.Buffer)
{
var ch = (ushort*)ptr + value.Offset;
var end = ch + value.Length;
ushort digit = 0;
while (ch < end && (digit = (ushort)(*ch - 0x30)) <= 9)
{
// Check for overflow
if ((result = result * 10 + digit) < 0)
{
result = 0;
return false;
}
ch++;
}
if (ch != end)
{
result = 0;
return false;
}
return true;
}
}
/// <summary>
/// Converts the signed 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 minus sign if the value is
/// negative, and a sequence of digits ranging from 0 to 9 with no leading zeroes.
/// </returns>
public unsafe static string FormatInt64(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;
}
char* charBuffer = stackalloc char[_int64MaxStringLength];
do
{
// Consider using Math.DivRem() if available
var quotient = value / 10;
charBuffer[--position] = (char)(0x30 + (value - quotient * 10)); // 0x30 = '0'
value = quotient;
}
while (value != 0);
if (negative)
{
charBuffer[--position] = '-';
}
return new string(charBuffer, position, _int64MaxStringLength - position);
}
public static bool TryParseDate(string input, out DateTimeOffset result)

View File

@ -233,18 +233,11 @@ namespace Microsoft.Net.Http.Headers
return HttpParseResult.Parsed;
}
internal static bool TryStringToDate(string input, out DateTimeOffset result)
{
// Try the various date formats in the order listed above.
// We should accept a wide verity of common formats, but only output RFC 1123 style dates.
if (DateTimeOffset.TryParseExact(input, DateFormats, DateTimeFormatInfo.InvariantInfo,
DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeUniversal, out result))
{
return true;
}
return false;
}
// Try the various date formats in the order listed above.
// We should accept a wide verity of common formats, but only output RFC 1123 style dates.
internal static bool TryStringToDate(string input, out DateTimeOffset result) =>
DateTimeOffset.TryParseExact(input, DateFormats, DateTimeFormatInfo.InvariantInfo,
DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeUniversal, out result);
// TEXT = <any OCTET except CTLs, but including LWS>
// LWS = [CRLF] 1*( SP | HT )

View File

@ -3,9 +3,11 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
[assembly: AssemblyMetadata("Serviceable", "True")]
[assembly: NeutralResourcesLanguage("en-us")]
[assembly: AssemblyCompany("Microsoft Corporation.")]
[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
[assembly: AssemblyProduct("Microsoft ASP.NET Core")]
[assembly: InternalsVisibleTo("Microsoft.Net.Http.Headers.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -11,6 +11,7 @@
]
},
"buildOptions": {
"allowUnsafe": true,
"warningsAsErrors": true,
"keyFile": "../../tools/Key.snk",
"nowarn": [

View File

@ -2,17 +2,19 @@
// 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.Extensions.Primitives;
using Xunit;
namespace Microsoft.Net.Http.Headers
{
public static class HeaderUtilitiesTest
public class HeaderUtilitiesTest
{
private const string Rfc1123Format = "r";
[Theory]
[MemberData(nameof(TestValues))]
public static void ReturnsSameResultAsRfc1123String(DateTimeOffset dateTime, bool quoted)
public void ReturnsSameResultAsRfc1123String(DateTimeOffset dateTime, bool quoted)
{
var formatted = dateTime.ToString(Rfc1123Format);
var expected = quoted ? $"\"{formatted}\"" : formatted;
@ -44,5 +46,136 @@ namespace Microsoft.Net.Http.Headers
return data;
}
}
[Theory]
[InlineData("h=1", "h", 1)]
[InlineData("directive1=3, directive2=10", "directive1", 3)]
[InlineData("directive1 =45, directive2=80", "directive1", 45)]
[InlineData("directive1= 89 , directive2=22", "directive1", 89)]
[InlineData("directive1= 89 , directive2= 42", "directive2", 42)]
[InlineData("directive1= 89 , directive= 42", "directive", 42)]
public void TryParseSeconds_Succeeds(string headerValues, string targetValue, int expectedValue)
{
TimeSpan? value;
Assert.True(HeaderUtilities.TryParseSeconds(new StringValues(headerValues), targetValue, out value));
Assert.Equal(TimeSpan.FromSeconds(expectedValue), value);
}
[Theory]
[InlineData("", "")]
[InlineData(null, null)]
[InlineData("h=", "h")]
[InlineData("directive1=, directive2=10", "directive1")]
[InlineData("directive1 , directive2=80", "directive1")]
[InlineData("h=10", "directive")]
[InlineData("directive1", "directive")]
[InlineData("h=directive", "directive")]
[InlineData("directive1, directive2=80", "directive")]
public void TryParseSeconds_Fails(string headerValues, string targetValue)
{
TimeSpan? value;
Assert.False(HeaderUtilities.TryParseSeconds(new StringValues(headerValues), targetValue, out value));
}
[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)
{
Assert.Equal(value.ToString(CultureInfo.InvariantCulture), HeaderUtilities.FormatInt64(value));
}
[Theory]
[InlineData("h", "h", true)]
[InlineData("h=", "h", true)]
[InlineData("h=1", "h", true)]
[InlineData("H", "h", true)]
[InlineData("H=", "h", true)]
[InlineData("H=1", "h", true)]
[InlineData("h", "H", true)]
[InlineData("h=", "H", true)]
[InlineData("h=1", "H", true)]
[InlineData("directive1, directive=10", "directive1", true)]
[InlineData("directive1=, directive=10", "directive1", true)]
[InlineData("directive1=3, directive=10", "directive1", true)]
[InlineData("directive1 , directive=80", "directive1", true)]
[InlineData(" directive1, directive=80", "directive1", true)]
[InlineData("directive1 =45, directive=80", "directive1", true)]
[InlineData("directive1= 89 , directive=22", "directive1", true)]
[InlineData("directive1, directive", "directive", true)]
[InlineData("directive1, directive=", "directive", true)]
[InlineData("directive1, directive=10", "directive", true)]
[InlineData("directive1=3, directive", "directive", true)]
[InlineData("directive1=3, directive=", "directive", true)]
[InlineData("directive1=3, directive=10", "directive", true)]
[InlineData("directive1= 89 , directive= 42", "directive", true)]
[InlineData("directive1= 89 , directive = 42", "directive", true)]
[InlineData(null, null, false)]
[InlineData(null, "", false)]
[InlineData("", null, false)]
[InlineData("", "", false)]
[InlineData("h=10", "directive", false)]
[InlineData("directive1", "directive", false)]
[InlineData("h=directive", "directive", false)]
[InlineData("directive1, directive2=80", "directive", false)]
public void ContainsCacheDirective_MatchesExactValue(string headerValues, string targetValue, bool contains)
{
Assert.Equal(contains, HeaderUtilities.ContainsCacheDirective(new StringValues(headerValues), targetValue));
}
[Theory]
[InlineData("")]
[InlineData(null)]
[InlineData("-1")]
[InlineData("a")]
[InlineData("1.1")]
[InlineData("9223372036854775808")] // long.MaxValue + 1
public void TryParseInt64_Fails(string valueString)
{
long value = 1;
Assert.False(HeaderUtilities.TryParseInt64(valueString, out value));
Assert.Equal(0, value);
}
[Theory]
[InlineData("0", 0)]
[InlineData("9223372036854775807", 9223372036854775807)] // long.MaxValue
public void TryParseInt64_Succeeds(string valueString, long expected)
{
long value = 1;
Assert.True(HeaderUtilities.TryParseInt64(valueString, out value));
Assert.Equal(expected, value);
}
[Theory]
[InlineData("")]
[InlineData(null)]
[InlineData("-1")]
[InlineData("a")]
[InlineData("1.1")]
[InlineData("1,000")]
[InlineData("2147483648")] // int.MaxValue + 1
public void TryParseInt32_Fails(string valueString)
{
int value = 1;
Assert.False(HeaderUtilities.TryParseInt32(valueString, out value));
Assert.Equal(0, value);
}
[Theory]
[InlineData("0", 0)]
[InlineData("2147483647", 2147483647)] // int.MaxValue
public void TryParseInt32_Succeeds(string valueString, long expected)
{
int value = 1;
Assert.True(HeaderUtilities.TryParseInt32(valueString, out value));
Assert.Equal(expected, value);
}
}
}

View File

@ -1,10 +1,13 @@
{
"version": "1.1.0-*",
"dependencies": {
"dotnet-test-xunit": "2.2.0-*",
"Microsoft.Net.Http.Headers": "1.2.0-*",
"xunit": "2.2.0-*"
},
"buildOptions": {
"warningsAsErrors": true,
"keyFile": "../../tools/Key.snk"
},
"frameworks": {
"netcoreapp1.1": {
"dependencies": {