/
RetryConditionHeaderValue.cs
151 lines (120 loc) · 5.25 KB
/
RetryConditionHeaderValue.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// 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.Globalization;
using System.Diagnostics.CodeAnalysis;
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);
}
}
}