|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
namespace System.Net.Http.Headers
{
public class StringWithQualityHeaderValue : ICloneable
{
private const double NotSetSentinel = double.PositiveInfinity;
private readonly string _value;
private readonly double _quality;
public string Value => _value;
public double? Quality => _quality == NotSetSentinel ? null : _quality;
public StringWithQualityHeaderValue(string value)
{
HeaderUtilities.CheckValidToken(value);
_value = value;
_quality = NotSetSentinel;
}
public StringWithQualityHeaderValue(string value, double quality)
{
HeaderUtilities.CheckValidToken(value);
ArgumentOutOfRangeException.ThrowIfNegative(quality);
ArgumentOutOfRangeException.ThrowIfGreaterThan(quality, 1.0);
_value = value;
_quality = quality;
}
private StringWithQualityHeaderValue(StringWithQualityHeaderValue source)
{
Debug.Assert(source != null);
_value = source._value;
_quality = source._quality;
}
public override string ToString() =>
_quality == NotSetSentinel
? _value
: string.Create(CultureInfo.InvariantCulture, stackalloc char[128], $"{_value}; q={_quality:0.0##}");
public override bool Equals([NotNullWhen(true)] object? obj) =>
obj is StringWithQualityHeaderValue other &&
string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase) &&
// Note that we don't consider double.Epsilon here. We really consider two values equal if they're
// actually equal. This makes sure that we also get the same hashcode for two values considered equal
// by Equals().
_quality == other._quality;
public override int GetHashCode() =>
HashCode.Combine(StringComparer.OrdinalIgnoreCase.GetHashCode(_value), _quality);
public static StringWithQualityHeaderValue Parse(string input)
{
int index = 0;
return (StringWithQualityHeaderValue)GenericHeaderParser.SingleValueStringWithQualityParser.ParseValue(
input, null, ref index);
}
public static bool TryParse([NotNullWhen(true)] string? input, [NotNullWhen(true)] out StringWithQualityHeaderValue? parsedValue)
{
int index = 0;
parsedValue = null;
if (GenericHeaderParser.SingleValueStringWithQualityParser.TryParseValue(
input, null, ref index, out object? output))
{
parsedValue = (StringWithQualityHeaderValue)output!;
return true;
}
return false;
}
internal static int GetStringWithQualityLength(string? input, int startIndex, out object? parsedValue)
{
Debug.Assert(startIndex >= 0);
parsedValue = null;
if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
{
return 0;
}
// Parse the value string: <value> in '<value>; q=<quality>'
int valueLength = HttpRuleParser.GetTokenLength(input, startIndex);
if (valueLength == 0)
{
return 0;
}
string value = input.Substring(startIndex, valueLength);
int current = startIndex + valueLength;
current += HttpRuleParser.GetWhitespaceLength(input, current);
if ((current == input.Length) || (input[current] != ';'))
{
parsedValue = new StringWithQualityHeaderValue(value);
return current - startIndex; // we have a valid token, but no quality.
}
current++; // skip ';' separator
current += HttpRuleParser.GetWhitespaceLength(input, current);
// If we found a ';' separator, it must be followed by a quality information
if (!TryReadQuality(input, out double quality, ref current))
{
return 0;
}
parsedValue = new StringWithQualityHeaderValue(value, quality);
return current - startIndex;
}
private static bool TryReadQuality(string input, out double quality, ref int index)
{
int current = index;
quality = default;
// See if we have a quality value by looking for "q"
if ((current == input.Length) || ((input[current] != 'q') && (input[current] != 'Q')))
{
return false;
}
current++; // skip 'q' identifier
current += HttpRuleParser.GetWhitespaceLength(input, current);
// If we found "q" it must be followed by "="
if ((current == input.Length) || (input[current] != '='))
{
return false;
}
current++; // skip '=' separator
current += HttpRuleParser.GetWhitespaceLength(input, current);
if (current == input.Length)
{
return false;
}
int qualityLength = HttpRuleParser.GetNumberLength(input, current, true);
if (qualityLength == 0)
{
return false;
}
if (!double.TryParse(input.AsSpan(current, qualityLength), NumberStyles.AllowDecimalPoint,
NumberFormatInfo.InvariantInfo, out quality))
{
return false;
}
if ((quality < 0) || (quality > 1))
{
return false;
}
current += qualityLength;
current += HttpRuleParser.GetWhitespaceLength(input, current);
index = current;
return true;
}
object ICloneable.Clone()
{
return new StringWithQualityHeaderValue(this);
}
}
}
|