| File: src\runtime\src\libraries\Common\src\System\Net\CredentialCacheKey.cs | Web Access |
| Project: System.Net.Http.csproj (System.Net.Http) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; namespace System.Net { internal sealed class CredentialCacheKey : IEquatable<CredentialCacheKey?> { public readonly Uri UriPrefix; public readonly int UriPrefixLength = -1; public readonly string AuthenticationType; internal CredentialCacheKey(Uri uriPrefix, string authenticationType) { Debug.Assert(uriPrefix != null); Debug.Assert(authenticationType != null); UriPrefix = uriPrefix; UriPrefixLength = UriPrefix.AbsolutePath.LastIndexOf('/'); AuthenticationType = authenticationType; } internal bool Match(Uri uri, int prefixLen, string authenticationType) { if (uri == null || authenticationType == null) { return false; } // If the protocols don't match, this credential is not applicable for the given Uri. if (!string.Equals(authenticationType, AuthenticationType, StringComparison.OrdinalIgnoreCase)) { return false; } if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Match({UriPrefix} & {uri})"); return IsPrefix(uri, prefixLen); } // IsPrefix (Uri) // // Determines whether <this> is a prefix of this URI. A prefix // match is defined as: // // scheme match // + host match // + port match, if any // + <prefix> path is a prefix of <URI> path, if any // // Returns: // True if <prefixUri> is a prefix of this URI private bool IsPrefix(Uri uri, int prefixLen) { Debug.Assert(uri != null); Uri uriPrefix = UriPrefix; if (uriPrefix.Scheme != uri.Scheme || uriPrefix.Host != uri.Host || uriPrefix.Port != uri.Port) { return false; } if (UriPrefixLength > prefixLen) { return false; } // Compare the path prefix including the trailing '/' delimiter (UriPrefixLength points at // the last '/' in the prefix path). Including that delimiter ensures the prefix only matches // at a path-segment boundary, so a prefix such as "/admin/" does not incorrectly match a // sibling path like "/administrator/...". // // The path on the server may be case-sensitive, so the path portion is compared // case-sensitively (scheme, host, and port are already matched above). int compareLength = UriPrefixLength + 1; return uri.AbsolutePath.AsSpan(0, compareLength).Equals(uriPrefix.AbsolutePath.AsSpan(0, compareLength), StringComparison.Ordinal); } public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(AuthenticationType) ^ UriPrefix.GetHashCode(); public bool Equals([NotNullWhen(true)] CredentialCacheKey? other) { if (other == null) { return false; } bool equals = string.Equals(AuthenticationType, other.AuthenticationType, StringComparison.OrdinalIgnoreCase) && UriPrefix.Equals(other.UriPrefix); if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Equals({this},{other}) returns {equals}"); return equals; } public override bool Equals([NotNullWhen(true)] object? obj) => Equals(obj as CredentialCacheKey); public override string ToString() => string.Create(CultureInfo.InvariantCulture, $"[{UriPrefixLength}]:{UriPrefix}:{AuthenticationType}"); } internal static class CredentialCacheHelper { public static bool TryGetCredential(Dictionary<CredentialCacheKey, NetworkCredential> cache, Uri uriPrefix, string authType, [NotNullWhen(true)] out Uri? mostSpecificMatchUri, [NotNullWhen(true)] out NetworkCredential? mostSpecificMatch) { int longestMatchPrefix = -1; mostSpecificMatch = null; mostSpecificMatchUri = null; if (cache.Count == 0) { return false; } // precompute the length of the prefix int uriPrefixLength = uriPrefix.AbsolutePath.LastIndexOf('/'); // Enumerate through every credential in the cache, get match with longest prefix foreach ((CredentialCacheKey key, NetworkCredential value) in cache) { int prefixLen = key.UriPrefixLength; if (prefixLen <= longestMatchPrefix) { // this credential can't provide a longer prefix match continue; } // Determine if this credential is applicable to the current Uri/AuthType if (key.Match(uriPrefix, uriPrefixLength, authType)) { // update the information about currently preferred match longestMatchPrefix = prefixLen; mostSpecificMatch = value; mostSpecificMatchUri = key.UriPrefix; if (uriPrefixLength == prefixLen) { // we can't get any better than this break; } } } return mostSpecificMatch != null; } } }