/
RangeConditionHeaderValue.cs
143 lines (113 loc) · 4.71 KB
/
RangeConditionHeaderValue.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
// 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;
namespace System.Net.Http.Headers
{
public class RangeConditionHeaderValue : ICloneable
{
// Exactly one of date and entityTag will be set.
private readonly DateTimeOffset _date;
private readonly EntityTagHeaderValue? _entityTag;
public DateTimeOffset? Date => _entityTag is null ? _date : null;
public EntityTagHeaderValue? EntityTag => _entityTag;
public RangeConditionHeaderValue(DateTimeOffset date)
{
_date = date;
}
public RangeConditionHeaderValue(EntityTagHeaderValue entityTag)
{
ArgumentNullException.ThrowIfNull(entityTag);
_entityTag = entityTag;
}
public RangeConditionHeaderValue(string entityTag)
: this(new EntityTagHeaderValue(entityTag))
{
}
private RangeConditionHeaderValue(RangeConditionHeaderValue source)
{
Debug.Assert(source != null);
_entityTag = source._entityTag;
_date = source._date;
}
public override string ToString() => _entityTag?.ToString() ?? _date.ToString("r");
public override bool Equals([NotNullWhen(true)] object? obj) =>
obj is RangeConditionHeaderValue other &&
(_entityTag is null ? other._entityTag is null : _entityTag.Equals(other._entityTag)) &&
_date == other._date;
public override int GetHashCode() => _entityTag?.GetHashCode() ?? _date.GetHashCode();
public static RangeConditionHeaderValue Parse(string input)
{
int index = 0;
return (RangeConditionHeaderValue)GenericHeaderParser.RangeConditionParser.ParseValue(
input, null, ref index);
}
public static bool TryParse([NotNullWhen(true)] string? input, [NotNullWhen(true)] out RangeConditionHeaderValue? parsedValue)
{
int index = 0;
parsedValue = null;
if (GenericHeaderParser.RangeConditionParser.TryParseValue(input, null, ref index, out object? output))
{
parsedValue = (RangeConditionHeaderValue)output!;
return true;
}
return false;
}
internal static int GetRangeConditionLength(string? input, int startIndex, out object? parsedValue)
{
Debug.Assert(startIndex >= 0);
parsedValue = null;
// Make sure we have at least 2 characters
if (string.IsNullOrEmpty(input) || (startIndex + 1 >= input.Length))
{
return 0;
}
int current = startIndex;
// Caller must remove leading whitespace.
DateTimeOffset date = DateTimeOffset.MinValue;
EntityTagHeaderValue? entityTag = null;
// Entity tags are quoted strings optionally preceded by "W/". By looking at the first two character we
// can determine whether the string is en entity tag or a date.
char firstChar = input[current];
char secondChar = input[current + 1];
if ((firstChar == '\"') || (((firstChar == 'w') || (firstChar == 'W')) && (secondChar == '/')))
{
// trailing whitespace is removed by GetEntityTagLength()
int entityTagLength = EntityTagHeaderValue.GetEntityTagLength(input, current, out entityTag);
if (entityTagLength == 0)
{
return 0;
}
current += entityTagLength;
// RangeConditionHeaderValue only allows 1 value. There must be no delimiter/other chars after an
// entity tag.
if (current != input.Length)
{
return 0;
}
}
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 (entityTag == null)
{
parsedValue = new RangeConditionHeaderValue(date);
}
else
{
parsedValue = new RangeConditionHeaderValue(entityTag);
}
return current - startIndex;
}
object ICloneable.Clone()
{
return new RangeConditionHeaderValue(this);
}
}
}