| | | 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 | | |
| | | 4 | | using System; |
| | | 5 | | using System.Collections.Generic; |
| | | 6 | | using System.Collections.Specialized; |
| | | 7 | | using System.Diagnostics; |
| | | 8 | | using CoreWCF.Runtime; |
| | | 9 | | |
| | | 10 | | namespace CoreWCF |
| | | 11 | | { |
| | | 12 | | internal static class UriTemplateHelpers |
| | | 13 | | { |
| | 0 | 14 | | private static readonly UriTemplateQueryComparer s_queryComparer = new UriTemplateQueryComparer(); |
| | 0 | 15 | | private static readonly UriTemplateQueryKeyComparer s_queryKeyComperar = new UriTemplateQueryKeyComparer(); |
| | | 16 | | |
| | | 17 | | [Conditional("DEBUG")] |
| | | 18 | | public static void AssertCanonical(string s) |
| | | 19 | | { |
| | | 20 | | Fx.Assert(s == s.ToUpperInvariant(), "non-canonicalized"); |
| | 0 | 21 | | } |
| | | 22 | | |
| | | 23 | | public static bool CanMatchQueryInterestingly(UriTemplate ut, NameValueCollection query, bool mustBeEspeciallyIn |
| | | 24 | | { |
| | 1 | 25 | | if (ut._queries.Count == 0) |
| | | 26 | | { |
| | 0 | 27 | | return false; // trivial, not interesting |
| | | 28 | | } |
| | | 29 | | |
| | 1 | 30 | | string[] queryKeys = query.AllKeys; |
| | 4 | 31 | | foreach (KeyValuePair<string, UriTemplateQueryValue> kvp in ut._queries) |
| | | 32 | | { |
| | 1 | 33 | | string queryKeyName = kvp.Key; |
| | 1 | 34 | | if (kvp.Value.Nature == UriTemplatePartType.Literal) |
| | | 35 | | { |
| | 0 | 36 | | bool queryKeysContainsQueryVarName = false; |
| | 0 | 37 | | for (int i = 0; i < queryKeys.Length; ++i) |
| | | 38 | | { |
| | 0 | 39 | | if (StringComparer.OrdinalIgnoreCase.Equals(queryKeys[i], queryKeyName)) |
| | | 40 | | { |
| | 0 | 41 | | queryKeysContainsQueryVarName = true; |
| | 0 | 42 | | break; |
| | | 43 | | } |
| | | 44 | | } |
| | | 45 | | |
| | 0 | 46 | | if (!queryKeysContainsQueryVarName) |
| | | 47 | | { |
| | 0 | 48 | | return false; |
| | | 49 | | } |
| | | 50 | | |
| | 0 | 51 | | if (kvp.Value == UriTemplateQueryValue.Empty) |
| | | 52 | | { |
| | 0 | 53 | | if (!string.IsNullOrEmpty(query[queryKeyName])) |
| | | 54 | | { |
| | 0 | 55 | | return false; |
| | | 56 | | } |
| | | 57 | | } |
| | | 58 | | else |
| | | 59 | | { |
| | 0 | 60 | | if (((UriTemplateLiteralQueryValue)(kvp.Value)).AsRawUnescapedString() != query[queryKeyName]) |
| | | 61 | | { |
| | 0 | 62 | | return false; |
| | | 63 | | } |
| | | 64 | | } |
| | | 65 | | } |
| | | 66 | | else |
| | | 67 | | { |
| | 1 | 68 | | if (mustBeEspeciallyInteresting && Array.IndexOf(queryKeys, queryKeyName) == -1) |
| | | 69 | | { |
| | 0 | 70 | | return false; |
| | | 71 | | } |
| | | 72 | | } |
| | | 73 | | } |
| | | 74 | | |
| | 1 | 75 | | return true; |
| | 0 | 76 | | } |
| | | 77 | | |
| | 170 | 78 | | public static bool CanMatchQueryTrivially(UriTemplate ut) => ut._queries.Count == 0; |
| | | 79 | | |
| | | 80 | | public static void DisambiguateSamePath(UriTemplate[] array, int a, int b, bool allowDuplicateEquivalentUriTempl |
| | | 81 | | { |
| | | 82 | | // [a,b) all have same path |
| | | 83 | | // ensure queries make them unambiguous |
| | | 84 | | Fx.Assert(b > a, "array bug"); |
| | | 85 | | |
| | | 86 | | // sort empty queries to front |
| | 0 | 87 | | Array.Sort(array, a, b - a, s_queryComparer); |
| | 0 | 88 | | if (b - a == 1) |
| | | 89 | | { |
| | 0 | 90 | | return; // if only one, cannot be ambiguous |
| | | 91 | | } |
| | | 92 | | |
| | 0 | 93 | | if (!allowDuplicateEquivalentUriTemplates) |
| | | 94 | | { |
| | | 95 | | // ensure at most one empty query and ignore it |
| | 0 | 96 | | if (array[a]._queries.Count == 0) |
| | | 97 | | { |
| | 0 | 98 | | a++; |
| | | 99 | | } |
| | | 100 | | |
| | 0 | 101 | | if (array[a]._queries.Count == 0) |
| | | 102 | | { |
| | 0 | 103 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.Format( |
| | 0 | 104 | | SR.UTTDuplicate, array[a].ToString(), array[a - 1].ToString()))); |
| | | 105 | | } |
| | | 106 | | |
| | 0 | 107 | | if (b - a == 1) |
| | | 108 | | { |
| | 0 | 109 | | return; // if only one, cannot be ambiguous |
| | | 110 | | } |
| | | 111 | | } |
| | | 112 | | else |
| | | 113 | | { |
| | 0 | 114 | | while (a < b && array[a]._queries.Count == 0) // all equivalent |
| | | 115 | | { |
| | 0 | 116 | | a++; |
| | | 117 | | } |
| | | 118 | | |
| | 0 | 119 | | if (b - a <= 1) |
| | | 120 | | { |
| | 0 | 121 | | return; |
| | | 122 | | } |
| | | 123 | | } |
| | | 124 | | |
| | | 125 | | Fx.Assert(b > a, "array bug"); |
| | | 126 | | |
| | | 127 | | // now consider non-empty queries |
| | | 128 | | // more than one, so enforce that |
| | | 129 | | // forall |
| | | 130 | | // exist set of querystringvars S where |
| | | 131 | | // every op has literal value foreach var in S, and |
| | | 132 | | // those literal tuples are different |
| | 0 | 133 | | EnsureQueriesAreDistinct(array, a, b, allowDuplicateEquivalentUriTemplates); |
| | 0 | 134 | | } |
| | | 135 | | |
| | 0 | 136 | | public static IEqualityComparer<string> GetQueryKeyComparer() => s_queryKeyComperar; |
| | | 137 | | |
| | 198 | 138 | | public static string GetUriPath(Uri uri) => uri.GetComponents(UriComponents.Path | UriComponents.KeepDelimiter, |
| | | 139 | | |
| | | 140 | | public static bool HasQueryLiteralRequirements(UriTemplate ut) |
| | | 141 | | { |
| | 4 | 142 | | foreach (UriTemplateQueryValue utqv in ut._queries.Values) |
| | | 143 | | { |
| | 1 | 144 | | if (utqv.Nature == UriTemplatePartType.Literal) |
| | | 145 | | { |
| | 0 | 146 | | return true; |
| | | 147 | | } |
| | | 148 | | } |
| | | 149 | | |
| | 1 | 150 | | return false; |
| | 0 | 151 | | } |
| | | 152 | | |
| | | 153 | | public static UriTemplatePartType IdentifyPartType(string part) |
| | | 154 | | { |
| | | 155 | | // Identifying the nature of a string - Literal|Compound|Variable |
| | | 156 | | // Algorithem is based on the following steps: |
| | | 157 | | // - Finding the position of the first open curlly brace ('{') and close curlly brace ('}') |
| | | 158 | | // in the string |
| | | 159 | | // - If we don't find any this is a Literal |
| | | 160 | | // - otherwise, we validate that position of the close brace is at least two characters from |
| | | 161 | | // the position of the open brace |
| | | 162 | | // - Then we identify if we are dealing with a compound string or a single variable string |
| | | 163 | | // + var name is not at the string start --> Compound |
| | | 164 | | // + var name is shorter then the entire string (End < Length-2 or End==Length-2 |
| | | 165 | | // and string ends with '/') --> Compound |
| | | 166 | | // + otherwise --> Variable |
| | 1154 | 167 | | int varStartIndex = part.IndexOf("{", StringComparison.Ordinal); |
| | 1154 | 168 | | int varEndIndex = part.IndexOf("}", StringComparison.Ordinal); |
| | 1154 | 169 | | if (varStartIndex == -1) |
| | | 170 | | { |
| | 951 | 171 | | if (varEndIndex != -1) |
| | | 172 | | { |
| | 0 | 173 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException( |
| | 0 | 174 | | SR.Format(SR.UTInvalidFormatSegmentOrQueryPart, part))); |
| | | 175 | | } |
| | | 176 | | |
| | 951 | 177 | | return UriTemplatePartType.Literal; |
| | | 178 | | } |
| | | 179 | | else |
| | | 180 | | { |
| | 203 | 181 | | if (varEndIndex < varStartIndex + 2) |
| | | 182 | | { |
| | 0 | 183 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException( |
| | 0 | 184 | | SR.Format(SR.UTInvalidFormatSegmentOrQueryPart, part))); |
| | | 185 | | } |
| | | 186 | | |
| | 203 | 187 | | if (varStartIndex > 0) |
| | | 188 | | { |
| | 0 | 189 | | return UriTemplatePartType.Compound; |
| | | 190 | | } |
| | 203 | 191 | | else if ((varEndIndex < part.Length - 2) || |
| | 203 | 192 | | ((varEndIndex == part.Length - 2) && !part.EndsWith("/", StringComparison.Ordinal))) |
| | | 193 | | { |
| | 48 | 194 | | return UriTemplatePartType.Compound; |
| | | 195 | | } |
| | | 196 | | else |
| | | 197 | | { |
| | 155 | 198 | | return UriTemplatePartType.Variable; |
| | | 199 | | } |
| | | 200 | | } |
| | | 201 | | } |
| | | 202 | | |
| | | 203 | | public static bool IsWildcardPath(string path) |
| | | 204 | | { |
| | 163 | 205 | | if (path.IndexOf('/') != -1) |
| | | 206 | | { |
| | 155 | 207 | | return false; |
| | | 208 | | } |
| | | 209 | | |
| | 8 | 210 | | return IsWildcardSegment(path, out UriTemplatePartType partType); |
| | | 211 | | } |
| | | 212 | | |
| | | 213 | | public static bool IsWildcardSegment(string segment, out UriTemplatePartType type) |
| | | 214 | | { |
| | 520 | 215 | | type = IdentifyPartType(segment); |
| | 520 | 216 | | switch (type) |
| | | 217 | | { |
| | | 218 | | case UriTemplatePartType.Literal: |
| | 422 | 219 | | return (string.Compare(segment, UriTemplate.WildcardPath, StringComparison.Ordinal) == 0); |
| | | 220 | | |
| | | 221 | | case UriTemplatePartType.Compound: |
| | 24 | 222 | | return false; |
| | | 223 | | |
| | | 224 | | case UriTemplatePartType.Variable: |
| | 74 | 225 | | return ((segment.IndexOf(UriTemplate.WildcardPath, StringComparison.Ordinal) == 1) && |
| | 74 | 226 | | !segment.EndsWith("/", StringComparison.Ordinal) && |
| | 74 | 227 | | (segment.Length > UriTemplate.WildcardPath.Length + 2)); |
| | | 228 | | |
| | | 229 | | default: |
| | | 230 | | Fx.Assert("Bad part type identification !"); |
| | 0 | 231 | | return false; |
| | | 232 | | } |
| | | 233 | | } |
| | | 234 | | |
| | | 235 | | public static NameValueCollection ParseQueryString(string query) |
| | | 236 | | { |
| | | 237 | | // We are adjusting the parsing of UrlUtility.ParseQueryString, which identify |
| | | 238 | | // ?wsdl as a null key with wsdl as a value |
| | 1 | 239 | | NameValueCollection result = UrlUtility.ParseQueryString(query); |
| | 1 | 240 | | string nullKeyValuesString = result[null]; |
| | 1 | 241 | | if (!string.IsNullOrEmpty(nullKeyValuesString)) |
| | | 242 | | { |
| | 0 | 243 | | result.Remove(null); |
| | 0 | 244 | | string[] nullKeyValues = nullKeyValuesString.Split(','); |
| | 0 | 245 | | for (int i = 0; i < nullKeyValues.Length; i++) |
| | | 246 | | { |
| | 0 | 247 | | result.Add(nullKeyValues[i], null); |
| | | 248 | | } |
| | | 249 | | } |
| | | 250 | | |
| | 1 | 251 | | return result; |
| | | 252 | | } |
| | | 253 | | |
| | | 254 | | private static bool AllTemplatesAreEquivalent(IList<UriTemplate> array, int a, int b) |
| | | 255 | | { |
| | 0 | 256 | | for (int i = a; i < b - 1; ++i) |
| | | 257 | | { |
| | 0 | 258 | | if (!array[i].IsEquivalentTo(array[i + 1])) |
| | | 259 | | { |
| | 0 | 260 | | return false; |
| | | 261 | | } |
| | | 262 | | } |
| | | 263 | | |
| | 0 | 264 | | return true; |
| | | 265 | | } |
| | | 266 | | |
| | | 267 | | private static void EnsureQueriesAreDistinct(UriTemplate[] array, int a, int b, bool allowDuplicateEquivalentUri |
| | | 268 | | { |
| | 0 | 269 | | Dictionary<string, byte> queryVarNamesWithLiteralVals = new Dictionary<string, byte>(StringComparer.OrdinalI |
| | 0 | 270 | | for (int i = a; i < b; ++i) |
| | | 271 | | { |
| | 0 | 272 | | foreach (KeyValuePair<string, UriTemplateQueryValue> kvp in array[i]._queries) |
| | | 273 | | { |
| | 0 | 274 | | if (kvp.Value.Nature == UriTemplatePartType.Literal) |
| | | 275 | | { |
| | 0 | 276 | | if (!queryVarNamesWithLiteralVals.ContainsKey(kvp.Key)) |
| | | 277 | | { |
| | 0 | 278 | | queryVarNamesWithLiteralVals.Add(kvp.Key, 0); |
| | | 279 | | } |
| | | 280 | | } |
| | | 281 | | } |
| | | 282 | | } |
| | | 283 | | |
| | | 284 | | // now we have set of possibilities: |
| | | 285 | | // further refine to only those for whom all templates have literals |
| | 0 | 286 | | Dictionary<string, byte> queryVarNamesAllLiterals = new Dictionary<string, byte>(queryVarNamesWithLiteralVal |
| | 0 | 287 | | for (int i = a; i < b; ++i) |
| | | 288 | | { |
| | 0 | 289 | | foreach (string s in queryVarNamesWithLiteralVals.Keys) |
| | | 290 | | { |
| | 0 | 291 | | if (!array[i]._queries.ContainsKey(s) || (array[i]._queries[s].Nature != UriTemplatePartType.Literal |
| | | 292 | | { |
| | 0 | 293 | | queryVarNamesAllLiterals.Remove(s); |
| | | 294 | | } |
| | | 295 | | } |
| | | 296 | | } |
| | | 297 | | |
| | 0 | 298 | | queryVarNamesWithLiteralVals = null; // ensure we don't reference this variable any more |
| | | 299 | | // now we have the set of names that every operation has as a literal |
| | 0 | 300 | | if (queryVarNamesAllLiterals.Count == 0) |
| | | 301 | | { |
| | 0 | 302 | | if (allowDuplicateEquivalentUriTemplates && AllTemplatesAreEquivalent(array, a, b)) |
| | | 303 | | { |
| | | 304 | | // we're ok, do nothing |
| | | 305 | | } |
| | | 306 | | else |
| | | 307 | | { |
| | 0 | 308 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.Format( |
| | 0 | 309 | | SR.UTTOtherAmbiguousQueries, array[a].ToString()))); |
| | | 310 | | } |
| | | 311 | | } |
| | | 312 | | |
| | | 313 | | // now just ensure that each template has a unique tuple of values for the names |
| | 0 | 314 | | string[][] upsLits = new string[b - a][]; |
| | 0 | 315 | | for (int i = 0; i < b - a; ++i) |
| | | 316 | | { |
| | 0 | 317 | | upsLits[i] = GetQueryLiterals(array[i + a], queryVarNamesAllLiterals); |
| | | 318 | | } |
| | | 319 | | |
| | 0 | 320 | | for (int i = 0; i < b - a; ++i) |
| | | 321 | | { |
| | 0 | 322 | | for (int j = i + 1; j < b - a; ++j) |
| | | 323 | | { |
| | 0 | 324 | | if (Same(upsLits[i], upsLits[j])) |
| | | 325 | | { |
| | 0 | 326 | | if (!array[i + a].IsEquivalentTo(array[j + a])) |
| | | 327 | | { |
| | 0 | 328 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.F |
| | 0 | 329 | | SR.UTTAmbiguousQueries, array[a + i].ToString(), array[j + a].ToString()))); |
| | | 330 | | } |
| | | 331 | | |
| | | 332 | | Fx.Assert(array[i + a].IsEquivalentTo(array[j + a]), "bad equiv logic"); |
| | 0 | 333 | | if (!allowDuplicateEquivalentUriTemplates) |
| | | 334 | | { |
| | 0 | 335 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.F |
| | 0 | 336 | | SR.UTTDuplicate, array[a + i].ToString(), array[j + a].ToString()))); |
| | | 337 | | } |
| | | 338 | | } |
| | | 339 | | } |
| | | 340 | | } |
| | | 341 | | // we're good. whew! |
| | 0 | 342 | | } |
| | | 343 | | |
| | | 344 | | private static string[] GetQueryLiterals(UriTemplate up, Dictionary<string, byte> queryVarNames) |
| | | 345 | | { |
| | 0 | 346 | | string[] queryLitVals = new string[queryVarNames.Count]; |
| | 0 | 347 | | int i = 0; |
| | 0 | 348 | | foreach (string queryVarName in queryVarNames.Keys) |
| | | 349 | | { |
| | | 350 | | Fx.Assert(up._queries.ContainsKey(queryVarName), "query doesn't have name"); |
| | | 351 | | |
| | 0 | 352 | | UriTemplateQueryValue utqv = up._queries[queryVarName]; |
| | | 353 | | Fx.Assert(utqv.Nature == UriTemplatePartType.Literal, "query for name is not literal"); |
| | 0 | 354 | | if (utqv == UriTemplateQueryValue.Empty) |
| | | 355 | | { |
| | 0 | 356 | | queryLitVals[i] = null; |
| | | 357 | | } |
| | | 358 | | else |
| | | 359 | | { |
| | 0 | 360 | | queryLitVals[i] = ((UriTemplateLiteralQueryValue)(utqv)).AsRawUnescapedString(); |
| | | 361 | | } |
| | 0 | 362 | | ++i; |
| | | 363 | | } |
| | | 364 | | |
| | 0 | 365 | | return queryLitVals; |
| | | 366 | | } |
| | | 367 | | |
| | | 368 | | private static bool Same(string[] a, string[] b) |
| | | 369 | | { |
| | | 370 | | Fx.Assert(a.Length == b.Length, "arrays not same length"); |
| | | 371 | | |
| | 0 | 372 | | for (int i = 0; i < a.Length; ++i) |
| | | 373 | | { |
| | 0 | 374 | | if (a[i] != b[i]) |
| | | 375 | | { |
| | 0 | 376 | | return false; |
| | | 377 | | } |
| | | 378 | | } |
| | | 379 | | |
| | 0 | 380 | | return true; |
| | | 381 | | } |
| | | 382 | | |
| | | 383 | | internal class UriTemplateQueryComparer : IComparer<UriTemplate> |
| | | 384 | | { |
| | | 385 | | public int Compare(UriTemplate x, UriTemplate y) |
| | | 386 | | { |
| | | 387 | | // sort the empty queries to the front |
| | 0 | 388 | | return Comparer<int>.Default.Compare(x._queries.Count, y._queries.Count); |
| | | 389 | | } |
| | | 390 | | } |
| | | 391 | | |
| | | 392 | | internal class UriTemplateQueryKeyComparer : IEqualityComparer<string> |
| | | 393 | | { |
| | | 394 | | public bool Equals(string x, string y) |
| | | 395 | | { |
| | 0 | 396 | | return (string.Compare(x, y, StringComparison.OrdinalIgnoreCase) == 0); |
| | | 397 | | } |
| | | 398 | | |
| | | 399 | | public int GetHashCode(string obj) |
| | | 400 | | { |
| | 0 | 401 | | if (obj == null) |
| | | 402 | | { |
| | 0 | 403 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(obj)); |
| | | 404 | | } |
| | | 405 | | |
| | 0 | 406 | | return obj.ToUpperInvariant().GetHashCode(); |
| | | 407 | | } |
| | | 408 | | } |
| | | 409 | | } |
| | | 410 | | } |