| File: System\Net\Http\Headers\StringWithQualityHeaderValue.cs | Web Access |
| Project: src\runtime\src\libraries\System.Net.Http\src\System.Net.Http.csproj (System.Net.Http) |
// 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); } } }