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