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:
parent
b727f0e1ac
commit
d50a24145d
|
|
@ -98,7 +98,7 @@ namespace Microsoft.AspNetCore.Http
|
|||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
]
|
||||
},
|
||||
"buildOptions": {
|
||||
"allowUnsafe": true,
|
||||
"warningsAsErrors": true,
|
||||
"keyFile": "../../tools/Key.snk",
|
||||
"nowarn": [
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
Loading…
Reference in New Issue