1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Diagnostics;
6 using System.Globalization;
7 
8 namespace System.Net.Http.Headers
9 {
10     public class StringWithQualityHeaderValue : ICloneable
11     {
12         private string _value;
13         private double? _quality;
14 
15         public string Value
16         {
17             get { return _value; }
18         }
19 
20         public double? Quality
21         {
22             get { return _quality; }
23         }
24 
StringWithQualityHeaderValue(string value)25         public StringWithQualityHeaderValue(string value)
26         {
27             HeaderUtilities.CheckValidToken(value, nameof(value));
28 
29             _value = value;
30         }
31 
StringWithQualityHeaderValue(string value, double quality)32         public StringWithQualityHeaderValue(string value, double quality)
33         {
34             HeaderUtilities.CheckValidToken(value, nameof(value));
35 
36             if ((quality < 0) || (quality > 1))
37             {
38                 throw new ArgumentOutOfRangeException(nameof(quality));
39             }
40 
41             _value = value;
42             _quality = quality;
43         }
44 
StringWithQualityHeaderValue(StringWithQualityHeaderValue source)45         private StringWithQualityHeaderValue(StringWithQualityHeaderValue source)
46         {
47             Debug.Assert(source != null);
48 
49             _value = source._value;
50             _quality = source._quality;
51         }
52 
StringWithQualityHeaderValue()53         private StringWithQualityHeaderValue()
54         {
55         }
56 
ToString()57         public override string ToString()
58         {
59             if (_quality.HasValue)
60             {
61                 return _value + "; q=" + _quality.Value.ToString("0.0##", NumberFormatInfo.InvariantInfo);
62             }
63 
64             return _value;
65         }
66 
Equals(object obj)67         public override bool Equals(object obj)
68         {
69             StringWithQualityHeaderValue other = obj as StringWithQualityHeaderValue;
70 
71             if (other == null)
72             {
73                 return false;
74             }
75 
76             if (!string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase))
77             {
78                 return false;
79             }
80 
81             if (_quality.HasValue)
82             {
83                 // Note that we don't consider double.Epsilon here. We really consider two values equal if they're
84                 // actually equal. This makes sure that we also get the same hashcode for two values considered equal
85                 // by Equals().
86                 return other._quality.HasValue && (_quality.Value == other._quality.Value);
87             }
88 
89             // If we don't have a quality value, then 'other' must also have no quality assigned in order to be
90             // considered equal.
91             return !other._quality.HasValue;
92         }
93 
GetHashCode()94         public override int GetHashCode()
95         {
96             int result = StringComparer.OrdinalIgnoreCase.GetHashCode(_value);
97 
98             if (_quality.HasValue)
99             {
100                 result = result ^ _quality.Value.GetHashCode();
101             }
102 
103             return result;
104         }
105 
Parse(string input)106         public static StringWithQualityHeaderValue Parse(string input)
107         {
108             int index = 0;
109             return (StringWithQualityHeaderValue)GenericHeaderParser.SingleValueStringWithQualityParser.ParseValue(
110                 input, null, ref index);
111         }
112 
TryParse(string input, out StringWithQualityHeaderValue parsedValue)113         public static bool TryParse(string input, out StringWithQualityHeaderValue parsedValue)
114         {
115             int index = 0;
116             object output;
117             parsedValue = null;
118 
119             if (GenericHeaderParser.SingleValueStringWithQualityParser.TryParseValue(
120                 input, null, ref index, out output))
121             {
122                 parsedValue = (StringWithQualityHeaderValue)output;
123                 return true;
124             }
125             return false;
126         }
127 
GetStringWithQualityLength(string input, int startIndex, out object parsedValue)128         internal static int GetStringWithQualityLength(string input, int startIndex, out object parsedValue)
129         {
130             Debug.Assert(startIndex >= 0);
131 
132             parsedValue = null;
133 
134             if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
135             {
136                 return 0;
137             }
138 
139             // Parse the value string: <value> in '<value>; q=<quality>'
140             int valueLength = HttpRuleParser.GetTokenLength(input, startIndex);
141 
142             if (valueLength == 0)
143             {
144                 return 0;
145             }
146 
147             StringWithQualityHeaderValue result = new StringWithQualityHeaderValue();
148             result._value = input.Substring(startIndex, valueLength);
149             int current = startIndex + valueLength;
150             current = current + HttpRuleParser.GetWhitespaceLength(input, current);
151 
152             if ((current == input.Length) || (input[current] != ';'))
153             {
154                 parsedValue = result;
155                 return current - startIndex; // we have a valid token, but no quality.
156             }
157 
158             current++; // skip ';' separator
159             current = current + HttpRuleParser.GetWhitespaceLength(input, current);
160 
161             // If we found a ';' separator, it must be followed by a quality information
162             if (!TryReadQuality(input, result, ref current))
163             {
164                 return 0;
165             }
166 
167             parsedValue = result;
168             return current - startIndex;
169         }
170 
TryReadQuality(string input, StringWithQualityHeaderValue result, ref int index)171         private static bool TryReadQuality(string input, StringWithQualityHeaderValue result, ref int index)
172         {
173             int current = index;
174 
175             // See if we have a quality value by looking for "q"
176             if ((current == input.Length) || ((input[current] != 'q') && (input[current] != 'Q')))
177             {
178                 return false;
179             }
180 
181             current++; // skip 'q' identifier
182             current = current + HttpRuleParser.GetWhitespaceLength(input, current);
183 
184             // If we found "q" it must be followed by "="
185             if ((current == input.Length) || (input[current] != '='))
186             {
187                 return false;
188             }
189 
190             current++; // skip '=' separator
191             current = current + HttpRuleParser.GetWhitespaceLength(input, current);
192 
193             if (current == input.Length)
194             {
195                 return false;
196             }
197 
198             int qualityLength = HttpRuleParser.GetNumberLength(input, current, true);
199 
200             if (qualityLength == 0)
201             {
202                 return false;
203             }
204 
205             double quality = 0;
206             if (!double.TryParse(input.Substring(current, qualityLength), NumberStyles.AllowDecimalPoint,
207                 NumberFormatInfo.InvariantInfo, out quality))
208             {
209                 return false;
210             }
211 
212             if ((quality < 0) || (quality > 1))
213             {
214                 return false;
215             }
216 
217             result._quality = quality;
218 
219             current = current + qualityLength;
220             current = current + HttpRuleParser.GetWhitespaceLength(input, current);
221 
222             index = current;
223             return true;
224         }
225 
ICloneable.Clone()226         object ICloneable.Clone()
227         {
228             return new StringWithQualityHeaderValue(this);
229         }
230     }
231 }
232