/// <summary> /// Finds the domain that best matches the current uri, into an enumeration of domains. /// </summary> /// <param name="domains">The enumeration of Umbraco domains.</param> /// <param name="current">The uri of the current request, or null.</param> /// <param name="defaultToFirst">A value indicating whether to return the first domain of the list when no domain matches.</param> /// <returns>The domain and its normalized uri, that best matches the current uri, else the first domain (if <c>defaultToFirst</c> is <c>true</c>), else null.</returns> public static DomainAndUri DomainMatch(IEnumerable<Domain> domains, Uri current, bool defaultToFirst) { if (!domains.Any()) return null; // sanitize the list to have proper uris for comparison (scheme, path end with /) // we need to end with / because example.com/foo cannot match example.com/foobar // we need to order so example.com/foo matches before example.com/ var scheme = current == null ? Uri.UriSchemeHttp : current.Scheme; var domainsAndUris = domains .Select(d => new { Domain = d, UriString = UriUtility.EndPathWithSlash(UriUtility.StartWithScheme(d.Name, scheme)) }) .OrderByDescending(t => t.UriString) .Select(t => new DomainAndUri { Domain = t.Domain, Uri = new Uri(t.UriString) }); DomainAndUri domainAndUri; if (current == null) { // take the first one by default domainAndUri = domainsAndUris.First(); } else { // look for a domain that would be the base of the hint // else take the first one by default var hintWithSlash = current.EndPathWithSlash(); domainAndUri = domainsAndUris .FirstOrDefault(t => t.Uri.IsBaseOf(hintWithSlash)); if (domainAndUri == null && defaultToFirst) domainAndUri = domainsAndUris.First(); } if (domainAndUri != null) domainAndUri.Uri = domainAndUri.Uri.TrimPathEndSlash(); return domainAndUri; }
/// <summary> /// Filters a list of <c>DomainAndUri</c> to pick those that best matches the current request. /// </summary> /// <param name="current">The Uri of the current request.</param> /// <param name="domainAndUris">The list of <c>DomainAndUri</c> to filter.</param> /// <param name="excludeDefault">A value indicating whether to exclude the current/default domain.</param> /// <returns>The selected <c>DomainAndUri</c> items.</returns> /// <remarks>The filter must return something, even empty, else an exception will be thrown.</remarks> public virtual IEnumerable<DomainAndUri> MapDomains(Uri current, DomainAndUri[] domainAndUris, bool excludeDefault) { var currentAuthority = current.GetLeftPart(UriPartial.Authority); KeyValuePair<string, string[]>[] candidateSites = null; IEnumerable<DomainAndUri> ret = domainAndUris; using (ConfigReadLock) // so nothing changes between GetQualifiedSites and access to bindings { var qualifiedSites = GetQualifiedSitesInsideLock(current); if (excludeDefault) { // exclude the current one (avoid producing the absolute equivalent of what GetUrl returns) var hintWithSlash = current.EndPathWithSlash(); var hinted = domainAndUris.FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(hintWithSlash)); if (hinted != null) ret = ret.Where(d => d != hinted); // exclude the default one (avoid producing a possible duplicate of what GetUrl returns) // only if the default one cannot be the current one ie if hinted is not null if (hinted == null && domainAndUris.Any()) { // it is illegal to call MapDomain if domainAndUris is empty // also, domainAndUris should NOT contain current, hence the test on hinted var mainDomain = MapDomain(domainAndUris, qualifiedSites, currentAuthority); // what GetUrl would get ret = ret.Where(d => d != mainDomain); } } // we do our best, but can't do the impossible if (qualifiedSites == null) return ret; // find a site that contains the current authority var currentSite = qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority)); // if current belongs to a site, pick every element from domainAndUris that also belong // to that site -- or to any site bound to that site if (!currentSite.Equals(default(KeyValuePair<string, string[]>))) { candidateSites = new[] { currentSite }; if (_bindings != null && _bindings.ContainsKey(currentSite.Key)) { var boundSites = qualifiedSites.Where(site => _bindings[currentSite.Key].Contains(site.Key)); candidateSites = candidateSites.Union(boundSites).ToArray(); // .ToArray ensures it is evaluated before the configuration lock is exited } } } // if we are able to filter, then filter, else return the whole lot return candidateSites == null ? ret : ret.Where(d => { var authority = d.Uri.GetLeftPart(UriPartial.Authority); return candidateSites.Any(site => site.Value.Contains(authority)); }); }
public void EndPathWithSlash(string input, string expected) { var source = new Uri(input); var output = source.EndPathWithSlash(); Assert.AreEqual(expected, output.ToString()); }
/// <summary> /// Finds the domain that best matches a specified uri, into a group of domains. /// </summary> /// <param name="domains">The group of domains.</param> /// <param name="current">The uri, or null.</param> /// <param name="filter">A function to filter the list of domains, if more than one applies, or <c>null</c>.</param> /// <returns>The domain and its normalized uri, that best matches the specified uri.</returns> /// <remarks> /// <para>If more than one domain matches, then the <paramref name="filter"/> function is used to pick /// the right one, unless it is <c>null</c>, in which case the method returns <c>null</c>.</para> /// <para>The filter, if any, will be called only with a non-empty argument, and _must_ return something.</para> /// </remarks> internal static DomainAndUri DomainForUri(Domain[] domains, Uri current, Func<DomainAndUri[], DomainAndUri> filter = null) { // sanitize the list to have proper uris for comparison (scheme, path end with /) // we need to end with / because example.com/foo cannot match example.com/foobar // we need to order so example.com/foo matches before example.com/ var scheme = current == null ? Uri.UriSchemeHttp : current.Scheme; var domainsAndUris = domains .Where(d => !d.IsWildcard) .Select(SanitizeForBackwardCompatibility) .Select(d => new DomainAndUri(d, scheme)) .OrderByDescending(d => d.Uri.ToString()) .ToArray(); if (!domainsAndUris.Any()) return null; DomainAndUri domainAndUri; if (current == null) { // take the first one by default (what else can we do?) domainAndUri = domainsAndUris.First(); // .First() protected by .Any() above } else { // look for the first domain that would be the base of the hint var hintWithSlash = current.EndPathWithSlash(); domainAndUri = domainsAndUris .FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(hintWithSlash)); // if none matches, then try to run the filter to pick a domain if (domainAndUri == null && filter != null) { domainAndUri = filter(domainsAndUris); // if still nothing, pick the first one? // no: move that constraint to the filter, but check if (domainAndUri == null) throw new InvalidOperationException("The filter returned null."); } } return domainAndUri; }