File: System\Net\Http\Headers\RetryConditionHeaderValue.cs
Web Access
Project: src\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 RetryConditionHeaderValue : ICloneable
    {
        private const long DeltaNotSetTicksSentinel = long.MaxValue;
 
        // Only one of date and delta may be set.
        private readonly DateTimeOffset _date;
        private readonly TimeSpan _delta;
 
        public DateTimeOffset? Date => _delta.Ticks == DeltaNotSetTicksSentinel ? _date : null;
 
        public TimeSpan? Delta => _delta.Ticks == DeltaNotSetTicksSentinel ? null : _delta;
 
        public RetryConditionHeaderValue(DateTimeOffset date)
        {
            _date = date;
            _delta = new TimeSpan(DeltaNotSetTicksSentinel);
        }
 
        public RetryConditionHeaderValue(TimeSpan delta)
        {
            // The amount of seconds for 'delta' must be in the range 0..2^31
            ArgumentOutOfRangeException.ThrowIfGreaterThan(delta.TotalSeconds, int.MaxValue);
 
            _delta = delta;
        }
 
        private RetryConditionHeaderValue(RetryConditionHeaderValue source)
        {
            Debug.Assert(source != null);
 
            _delta = source._delta;
            _date = source._date;
        }
 
        public override string ToString() =>
            _delta.Ticks != DeltaNotSetTicksSentinel
                ? ((int)_delta.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo)
                : _date.ToString("r");
 
        public override bool Equals([NotNullWhen(true)] object? obj) =>
            obj is RetryConditionHeaderValue other &&
            _delta == other._delta &&
            _date == other._date;
 
        public override int GetHashCode() =>
            HashCode.Combine(_delta, _date);
 
        public static RetryConditionHeaderValue Parse(string input)
        {
            int index = 0;
            return (RetryConditionHeaderValue)GenericHeaderParser.RetryConditionParser.ParseValue(
                input, null, ref index);
        }
 
        public static bool TryParse([NotNullWhen(true)] string? input, [NotNullWhen(true)] out RetryConditionHeaderValue? parsedValue)
        {
            int index = 0;
            parsedValue = null;
 
            if (GenericHeaderParser.RetryConditionParser.TryParseValue(input, null, ref index, out object? output))
            {
                parsedValue = (RetryConditionHeaderValue)output!;
                return true;
            }
            return false;
        }
 
        internal static int GetRetryConditionLength(string? input, int startIndex, out object? parsedValue)
        {
            Debug.Assert(startIndex >= 0);
 
            parsedValue = null;
 
            if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
            {
                return 0;
            }
 
            int current = startIndex;
 
            // Caller must remove leading whitespace.
            DateTimeOffset date = DateTimeOffset.MinValue;
            int deltaSeconds = -1; // use -1 to indicate that the value was not set. 'delta' values are always >=0
 
            // We either have a timespan or a date/time value. Determine which one we have by looking at the first char.
            // If it is a number, we have a timespan, otherwise we assume we have a date.
            char firstChar = input[current];
 
            if (char.IsAsciiDigit(firstChar))
            {
                int deltaStartIndex = current;
                int deltaLength = HttpRuleParser.GetNumberLength(input, current, false);
 
                // The value must be in the range 0..2^31
                if ((deltaLength == 0) || (deltaLength > HttpRuleParser.MaxInt32Digits))
                {
                    return 0;
                }
 
                current += deltaLength;
                current += HttpRuleParser.GetWhitespaceLength(input, current);
 
                // RetryConditionHeaderValue only allows 1 value. There must be no delimiter/other chars after 'delta'
                if (current != input.Length)
                {
                    return 0;
                }
 
                if (!HeaderUtilities.TryParseInt32(input, deltaStartIndex, deltaLength, out deltaSeconds))
                {
                    return 0; // int.TryParse() may return 'false' if the value has 10 digits and is > Int32.MaxValue.
                }
            }
            else
            {
                if (!HttpDateParser.TryParse(input.AsSpan(current), out date))
                {
                    return 0;
                }
 
                // If we got a valid date, then the parser consumed the whole string (incl. trailing whitespace).
                current = input.Length;
            }
 
            if (deltaSeconds == -1) // we didn't change delta, so we must have found a date.
            {
                parsedValue = new RetryConditionHeaderValue(date);
            }
            else
            {
                parsedValue = new RetryConditionHeaderValue(new TimeSpan(0, 0, deltaSeconds));
            }
 
            return current - startIndex;
        }
 
        object ICloneable.Clone()
        {
            return new RetryConditionHeaderValue(this);
        }
    }
}