# Conflicts: # build/NuSpecs/UmbracoCms.Core.nuspec # build/NuSpecs/UmbracoCms.Web.nuspec # src/SolutionInfo.cs # src/Umbraco.Core/Cache/CacheKeys.cs # src/Umbraco.Core/Composing/TypeFinder.cs # src/Umbraco.Core/Configuration/GlobalSettings.cs # src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs # src/Umbraco.Core/Configuration/IGlobalSettings.cs # src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs # src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs # src/Umbraco.Core/Constants-AppSettings.cs # src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs # src/Umbraco.Core/Extensions/StringExtensions.cs # src/Umbraco.Core/Extensions/UriExtensions.cs # src/Umbraco.Core/IO/IOHelper.cs # src/Umbraco.Core/IO/PhysicalFileSystem.cs # src/Umbraco.Core/Media/Exif/MathEx.cs # src/Umbraco.Core/Media/UploadAutoFillProperties.cs # src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs # src/Umbraco.Core/Models/Membership/User.cs # src/Umbraco.Core/Models/UserExtensions.cs # src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs # src/Umbraco.Core/PropertyEditors/ListViewConfiguration.cs # src/Umbraco.Core/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs # src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs # src/Umbraco.Core/Routing/AliasUrlProvider.cs # src/Umbraco.Core/Routing/DefaultUrlProvider.cs # src/Umbraco.Core/Routing/UriUtility.cs # src/Umbraco.Core/Routing/UrlProviderExtensions.cs # src/Umbraco.Core/Runtime/CoreRuntime.cs # src/Umbraco.Core/RuntimeOptions.cs # src/Umbraco.Core/RuntimeState.cs # src/Umbraco.Core/Security/BackOfficeUserStore.cs # src/Umbraco.Core/Security/ContentPermissions.cs # src/Umbraco.Core/Sync/ApplicationUrlHelper.cs # src/Umbraco.Core/Trees/TreeNode.cs # src/Umbraco.Core/Udi.cs # src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs # src/Umbraco.Examine/Umbraco.Examine.csproj # src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs # src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs # src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs # src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs # src/Umbraco.Infrastructure/Scoping/Scope.cs # src/Umbraco.Infrastructure/Search/ExamineComponent.cs # src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs # src/Umbraco.Infrastructure/Services/Implement/ContentService.cs # src/Umbraco.Infrastructure/Services/Implement/MediaService.cs # src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs # src/Umbraco.Persistence.SqlCe/SqlCeSyntaxProvider.cs # src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs # src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserExtensionsTests.cs # src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Editors/UserEditorAuthorizationHelperTests.cs # src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/UmbracoContentValueSetValidatorTests.cs # src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs # src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config # src/Umbraco.Tests/TestHelpers/SettingsForTests.cs # src/Umbraco.Tests/Testing/TestDatabase.cs # src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs # src/Umbraco.Tests/Web/Controllers/FilterAllowedOutgoingContentAttributeTests.cs # src/Umbraco.Tests/Web/Controllers/MediaControllerUnitTests.cs # src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs # src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs # src/Umbraco.Web.BackOffice/Controllers/ContentController.cs # src/Umbraco.Web.BackOffice/Controllers/EntityController.cs # src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs # src/Umbraco.Web.BackOffice/Controllers/MediaController.cs # src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs # src/Umbraco.Web.BackOffice/Controllers/TourController.cs # src/Umbraco.Web.BackOffice/Controllers/UserGroupEditorAuthorizationHelper.cs # src/Umbraco.Web.BackOffice/Filters/FilterAllowedOutgoingContentAttribute.cs # src/Umbraco.Web.BackOffice/Filters/FilterAllowedOutgoingMediaAttribute.cs # src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs # src/Umbraco.Web.BackOffice/Services/IconService.cs # src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs # src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs # src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs # src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs # src/Umbraco.Web.Common/Extensions/FormCollectionExtensions.cs # src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js # src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html # src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml # src/Umbraco.Web.UI/config/umbracoSettings.Release.config # src/Umbraco.Web/Cache/MemberCacheRefresher.cs # src/Umbraco.Web/Composing/ModuleInjector.cs # src/Umbraco.Web/Editors/BackOfficeController.cs # src/Umbraco.Web/Editors/Binders/ContentModelBinderHelper.cs # src/Umbraco.Web/Editors/ContentTypeController.cs # src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs # src/Umbraco.Web/Editors/Filters/MediaItemSaveValidationAttribute.cs # src/Umbraco.Web/Editors/Filters/UserGroupAuthorizationAttribute.cs # src/Umbraco.Web/Editors/TinyMceController.cs # src/Umbraco.Web/Editors/UserGroupsController.cs # src/Umbraco.Web/Editors/UsersController.cs # src/Umbraco.Web/ImageCropperTemplateExtensions.cs # src/Umbraco.Web/Logging/WebProfiler.cs # src/Umbraco.Web/Logging/WebProfilerProvider.cs # src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs # src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs # src/Umbraco.Web/Mvc/JsonNetResult.cs # src/Umbraco.Web/Mvc/MemberAuthorizeAttribute.cs # src/Umbraco.Web/Mvc/RenderRouteHandler.cs # src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs # src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs # src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs # src/Umbraco.Web/RoutableDocumentFilter.cs # src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs # src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs # src/Umbraco.Web/Routing/PublishedRouter.cs # src/Umbraco.Web/Runtime/WebInitialComposer.cs # src/Umbraco.Web/Scheduling/KeepAlive.cs # src/Umbraco.Web/Security/AppBuilderExtensions.cs # src/Umbraco.Web/Security/BackOfficeClaimsIdentityFactory.cs # src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs # src/Umbraco.Web/Trees/DictionaryTreeController.cs # src/Umbraco.Web/Trees/LanguageTreeController.cs # src/Umbraco.Web/Trees/LogViewerTreeController.cs # src/Umbraco.Web/Trees/PackagesTreeController.cs # src/Umbraco.Web/UmbracoApplication.cs # src/Umbraco.Web/UmbracoApplicationBase.cs # src/Umbraco.Web/UmbracoInjectedModule.cs # src/Umbraco.Web/WebApi/Filters/AdminUsersAuthorizeAttribute.cs # src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs # src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs # src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs # src/Umbraco.Web/WebApi/MemberAuthorizeAttribute.cs
373 lines
18 KiB
C#
373 lines
18 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Umbraco.Cms.Core.PublishedCache;
|
|
using Umbraco.Cms.Core.Web;
|
|
using Umbraco.Extensions;
|
|
|
|
namespace Umbraco.Cms.Core.Routing
|
|
{
|
|
/// <summary>
|
|
/// Provides utilities to handle domains.
|
|
/// </summary>
|
|
public static class DomainUtilities
|
|
{
|
|
#region Document Culture
|
|
|
|
/// <summary>
|
|
/// Gets the culture assigned to a document by domains, in the context of a current Uri.
|
|
/// </summary>
|
|
/// <param name="contentId">The document identifier.</param>
|
|
/// <param name="contentPath">The document path.</param>
|
|
/// <param name="current">An optional current Uri.</param>
|
|
/// <param name="umbracoContext">An Umbraco context.</param>
|
|
/// <param name="siteDomainHelper">The site domain helper.</param>
|
|
/// <returns>The culture assigned to the document by domains.</returns>
|
|
/// <remarks>
|
|
/// <para>In 1:1 multilingual setup, a document contains several cultures (there is not
|
|
/// one document per culture), and domains, withing the context of a current Uri, assign
|
|
/// a culture to that document.</para>
|
|
/// </remarks>
|
|
public static string GetCultureFromDomains(int contentId, string contentPath, Uri current, IUmbracoContext umbracoContext, ISiteDomainHelper siteDomainHelper)
|
|
{
|
|
if (umbracoContext == null)
|
|
throw new InvalidOperationException("A current UmbracoContext is required.");
|
|
|
|
if (current == null)
|
|
current = umbracoContext.CleanedUmbracoUrl;
|
|
|
|
// get the published route, else the preview route
|
|
// if both are null then the content does not exist
|
|
var route = umbracoContext.Content.GetRouteById(contentId) ??
|
|
umbracoContext.Content.GetRouteById(true, contentId);
|
|
|
|
if (route == null)
|
|
return null;
|
|
|
|
var pos = route.IndexOf('/');
|
|
var domain = pos == 0
|
|
? null
|
|
: DomainForNode(umbracoContext.Domains, siteDomainHelper, int.Parse(route.Substring(0, pos)), current);
|
|
|
|
var rootContentId = domain?.ContentId ?? -1;
|
|
var wcDomain = FindWildcardDomainInPath(umbracoContext.Domains.GetAll(true), contentPath, rootContentId);
|
|
|
|
if (wcDomain != null) return wcDomain.Culture;
|
|
if (domain != null) return domain.Culture;
|
|
return umbracoContext.Domains.DefaultCulture;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Domain for Document
|
|
|
|
/// <summary>
|
|
/// Finds the domain for the specified node, if any, that best matches a specified uri.
|
|
/// </summary>
|
|
/// <param name="domainCache">A domain cache.</param>
|
|
/// <param name="siteDomainHelper">The site domain helper.</param>
|
|
/// <param name="nodeId">The node identifier.</param>
|
|
/// <param name="current">The uri, or null.</param>
|
|
/// <param name="culture">The culture, or null.</param>
|
|
/// <returns>The domain and its uri, if any, that best matches the specified uri and culture, else null.</returns>
|
|
/// <remarks>
|
|
/// <para>If at least a domain is set on the node then the method returns the domain that
|
|
/// best matches the specified uri and culture, else it returns null.</para>
|
|
/// <para>If culture is null, uses the default culture for the installation instead. Otherwise,
|
|
/// will try with the specified culture, else return null.</para>
|
|
/// </remarks>
|
|
internal static DomainAndUri DomainForNode(IDomainCache domainCache, ISiteDomainHelper siteDomainHelper, int nodeId, Uri current, string culture = null)
|
|
{
|
|
// be safe
|
|
if (nodeId <= 0)
|
|
return null;
|
|
|
|
// get the domains on that node
|
|
var domains = domainCache.GetAssigned(nodeId).ToArray();
|
|
|
|
// none?
|
|
if (domains.Length == 0)
|
|
return null;
|
|
|
|
// else filter
|
|
// it could be that none apply (due to culture)
|
|
return SelectDomain(domains, current, culture, domainCache.DefaultCulture, siteDomainHelper.MapDomain);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find the domains for the specified node, if any, that match a specified uri.
|
|
/// </summary>
|
|
/// <param name="domainCache">A domain cache.</param>
|
|
/// <param name="siteDomainHelper">The site domain helper.</param>
|
|
/// <param name="nodeId">The node identifier.</param>
|
|
/// <param name="current">The uri, or null.</param>
|
|
/// <param name="excludeDefault">A value indicating whether to exclude the current/default domain. True by default.</param>
|
|
/// <returns>The domains and their uris, that match the specified uri, else null.</returns>
|
|
/// <remarks>If at least a domain is set on the node then the method returns the domains that
|
|
/// best match the specified uri, else it returns null.</remarks>
|
|
internal static IEnumerable<DomainAndUri> DomainsForNode(IDomainCache domainCache, ISiteDomainHelper siteDomainHelper, int nodeId, Uri current, bool excludeDefault = true)
|
|
{
|
|
// be safe
|
|
if (nodeId <= 0)
|
|
return null;
|
|
|
|
// get the domains on that node
|
|
var domains = domainCache.GetAssigned(nodeId).ToArray();
|
|
|
|
// none?
|
|
if (domains.Length == 0)
|
|
return null;
|
|
|
|
// get the domains and their uris
|
|
var domainAndUris = SelectDomains(domains, current).ToArray();
|
|
|
|
// filter
|
|
return siteDomainHelper.MapDomains(domainAndUris, current, excludeDefault, null, domainCache.DefaultCulture).ToArray();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Selects Domain(s)
|
|
|
|
/// <summary>
|
|
/// Selects the domain that best matches a specified uri and cultures, from a set of domains.
|
|
/// </summary>
|
|
/// <param name="domains">The group of domains.</param>
|
|
/// <param name="uri">An optional uri.</param>
|
|
/// <param name="culture">An optional culture.</param>
|
|
/// <param name="defaultCulture">An optional default culture.</param>
|
|
/// <param name="filter">An optional function to filter the list of domains, if more than one applies.</param>
|
|
/// <returns>The domain and its normalized uri, that best matches the specified uri and cultures.</returns>
|
|
/// <remarks>
|
|
/// TODO: must document and explain this all
|
|
/// <para>If <paramref name="uri"/> is null, pick the first domain that matches <paramref name="culture"/>,
|
|
/// else the first that matches <paramref name="defaultCulture"/>, else the first one (ordered by id), else null.</para>
|
|
/// <para>If <paramref name="uri"/> is not null, look for domains that would be a base uri of the current uri,</para>
|
|
/// <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>
|
|
public static DomainAndUri SelectDomain(IEnumerable<Domain> domains, Uri uri, string culture = null, string defaultCulture = null, Func<IReadOnlyCollection<DomainAndUri>, Uri, string, string, 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 domainsAndUris = domains
|
|
.Where(d => d.IsWildcard == false)
|
|
.Select(d => new DomainAndUri(d, uri))
|
|
.OrderByDescending(d => d.Uri.ToString())
|
|
.ToList();
|
|
|
|
// nothing = no magic, return null
|
|
if (domainsAndUris.Count == 0)
|
|
return null;
|
|
|
|
// sanitize cultures
|
|
culture = culture.NullOrWhiteSpaceAsNull();
|
|
defaultCulture = defaultCulture.NullOrWhiteSpaceAsNull();
|
|
|
|
if (uri == null)
|
|
{
|
|
// no uri - will only rely on culture
|
|
return GetByCulture(domainsAndUris, culture, defaultCulture);
|
|
}
|
|
|
|
// else we have a uri,
|
|
// try to match that uri, else filter
|
|
|
|
// if a culture is specified, then try to get domains for that culture
|
|
// (else cultureDomains will be null)
|
|
// do NOT specify a default culture, else it would pick those domains
|
|
var cultureDomains = SelectByCulture(domainsAndUris, culture, defaultCulture: null);
|
|
IReadOnlyCollection<DomainAndUri> considerForBaseDomains = domainsAndUris;
|
|
if (cultureDomains != null)
|
|
{
|
|
if (cultureDomains.Count == 1) // only 1, return
|
|
return cultureDomains.First();
|
|
|
|
// else restrict to those domains, for base lookup
|
|
considerForBaseDomains = cultureDomains;
|
|
}
|
|
|
|
// look for domains that would be the base of the uri
|
|
var baseDomains = SelectByBase(considerForBaseDomains, uri);
|
|
if (baseDomains.Count > 0) // found, return
|
|
return baseDomains.First();
|
|
|
|
// if nothing works, then try to run the filter to select a domain
|
|
// either restricting on cultureDomains, or on all domains
|
|
if (filter != null)
|
|
{
|
|
var domainAndUri = filter(cultureDomains ?? domainsAndUris, uri, culture, defaultCulture);
|
|
// 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;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static bool IsBaseOf(DomainAndUri domain, Uri uri)
|
|
=> domain.Uri.EndPathWithSlash().IsBaseOf(uri);
|
|
|
|
private static IReadOnlyCollection<DomainAndUri> SelectByBase(IReadOnlyCollection<DomainAndUri> domainsAndUris, Uri uri)
|
|
{
|
|
// look for domains that would be the base of the uri
|
|
// ie current is www.example.com/foo/bar, look for domain www.example.com
|
|
var currentWithSlash = uri.EndPathWithSlash();
|
|
var baseDomains = domainsAndUris.Where(d => IsBaseOf(d, currentWithSlash)).ToList();
|
|
|
|
// if none matches, try again without the port
|
|
// ie current is www.example.com:1234/foo/bar, look for domain www.example.com
|
|
var currentWithoutPort = currentWithSlash.WithoutPort();
|
|
if (baseDomains.Count == 0)
|
|
baseDomains = domainsAndUris.Where(d => IsBaseOf(d, currentWithoutPort)).ToList();
|
|
|
|
return baseDomains;
|
|
}
|
|
|
|
private static IReadOnlyCollection<DomainAndUri> SelectByCulture(IReadOnlyCollection<DomainAndUri> domainsAndUris, string culture, string defaultCulture)
|
|
{
|
|
// we try our best to match cultures, but may end with a bogus domain
|
|
|
|
if (culture != null) // try the supplied culture
|
|
{
|
|
var cultureDomains = domainsAndUris.Where(x => x.Culture.InvariantEquals(culture)).ToList();
|
|
if (cultureDomains.Count > 0) return cultureDomains;
|
|
}
|
|
|
|
if (defaultCulture != null) // try the defaultCulture culture
|
|
{
|
|
var cultureDomains = domainsAndUris.Where(x => x.Culture.InvariantEquals(defaultCulture)).ToList();
|
|
if (cultureDomains.Count > 0) return cultureDomains;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static DomainAndUri GetByCulture(IReadOnlyCollection<DomainAndUri> domainsAndUris, string culture, string defaultCulture)
|
|
{
|
|
DomainAndUri domainAndUri;
|
|
|
|
// we try our best to match cultures, but may end with a bogus domain
|
|
|
|
if (culture != null) // try the supplied culture
|
|
{
|
|
domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture));
|
|
if (domainAndUri != null) return domainAndUri;
|
|
}
|
|
|
|
if (defaultCulture != null) // try the defaultCulture culture
|
|
{
|
|
domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture));
|
|
if (domainAndUri != null) return domainAndUri;
|
|
}
|
|
|
|
return domainsAndUris.First(); // what else?
|
|
}
|
|
|
|
/// <summary>
|
|
/// Selects the domains that match a specified uri, from a set of domains.
|
|
/// </summary>
|
|
/// <param name="domains">The domains.</param>
|
|
/// <param name="uri">The uri, or null.</param>
|
|
/// <returns>The domains and their normalized uris, that match the specified uri.</returns>
|
|
internal static IEnumerable<DomainAndUri> SelectDomains(IEnumerable<Domain> domains, Uri uri)
|
|
{
|
|
// TODO: where are we matching ?!!?
|
|
return domains
|
|
.Where(d => d.IsWildcard == false)
|
|
.Select(d => new DomainAndUri(d, uri))
|
|
.OrderByDescending(d => d.Uri.ToString());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a domain name into a URI.
|
|
/// </summary>
|
|
/// <param name="domainName">The domain name to parse</param>
|
|
/// <param name="currentUri">The currently requested URI. If the domain name is relative, the authority of URI will be used.</param>
|
|
/// <returns>The domain name as a URI</returns>
|
|
public static Uri ParseUriFromDomainName(string domainName, Uri currentUri)
|
|
{
|
|
// turn "/en" into "http://whatever.com/en" so it becomes a parseable uri
|
|
var name = domainName.StartsWith("/") && currentUri != null
|
|
? currentUri.GetLeftPart(UriPartial.Authority) + domainName
|
|
: domainName;
|
|
var scheme = currentUri?.Scheme ?? Uri.UriSchemeHttp;
|
|
return new Uri(UriUtilityCore.TrimPathEndSlash(UriUtilityCore.StartWithScheme(name, scheme)));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Utilities
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether there is another domain defined down in the path to a node under the current domain's root node.
|
|
/// </summary>
|
|
/// <param name="domains">The domains.</param>
|
|
/// <param name="path">The path to a node under the current domain's root node eg '-1,1234,5678'.</param>
|
|
/// <param name="rootNodeId">The current domain root node identifier, or null.</param>
|
|
/// <returns>A value indicating if there is another domain defined down in the path.</returns>
|
|
/// <remarks>Looks _under_ rootNodeId but not _at_ rootNodeId.</remarks>
|
|
internal static bool ExistsDomainInPath(IEnumerable<Domain> domains, string path, int? rootNodeId)
|
|
{
|
|
return FindDomainInPath(domains, path, rootNodeId) != null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the deepest non-wildcard Domain, if any, from a group of Domains, in a node path.
|
|
/// </summary>
|
|
/// <param name="domains">The domains.</param>
|
|
/// <param name="path">The node path eg '-1,1234,5678'.</param>
|
|
/// <param name="rootNodeId">The current domain root node identifier, or null.</param>
|
|
/// <returns>The deepest non-wildcard Domain in the path, or null.</returns>
|
|
/// <remarks>Looks _under_ rootNodeId but not _at_ rootNodeId.</remarks>
|
|
internal static Domain FindDomainInPath(IEnumerable<Domain> domains, string path, int? rootNodeId)
|
|
{
|
|
var stopNodeId = rootNodeId ?? -1;
|
|
|
|
return path.Split(Constants.CharArrays.Comma)
|
|
.Reverse()
|
|
.Select(int.Parse)
|
|
.TakeWhile(id => id != stopNodeId)
|
|
.Select(id => domains.FirstOrDefault(d => d.ContentId == id && d.IsWildcard == false))
|
|
.SkipWhile(domain => domain == null)
|
|
.FirstOrDefault();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the deepest wildcard Domain, if any, from a group of Domains, in a node path.
|
|
/// </summary>
|
|
/// <param name="domains">The domains.</param>
|
|
/// <param name="path">The node path eg '-1,1234,5678'.</param>
|
|
/// <param name="rootNodeId">The current domain root node identifier, or null.</param>
|
|
/// <returns>The deepest wildcard Domain in the path, or null.</returns>
|
|
/// <remarks>Looks _under_ rootNodeId but not _at_ rootNodeId.</remarks>
|
|
public static Domain FindWildcardDomainInPath(IEnumerable<Domain> domains, string path, int? rootNodeId)
|
|
{
|
|
var stopNodeId = rootNodeId ?? -1;
|
|
|
|
return path.Split(Constants.CharArrays.Comma)
|
|
.Reverse()
|
|
.Select(int.Parse)
|
|
.TakeWhile(id => id != stopNodeId)
|
|
.Select(id => domains.FirstOrDefault(d => d.ContentId == id && d.IsWildcard))
|
|
.FirstOrDefault(domain => domain != null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the part of a path relative to the uri of a domain.
|
|
/// </summary>
|
|
/// <param name="domainUri">The normalized uri of the domain.</param>
|
|
/// <param name="path">The full path of the uri.</param>
|
|
/// <returns>The path part relative to the uri of the domain.</returns>
|
|
/// <remarks>Eg the relative part of <c>/foo/bar/nil</c> to domain <c>example.com/foo</c> is <c>/bar/nil</c>.</remarks>
|
|
public static string PathRelativeToDomain(Uri domainUri, string path)
|
|
=> path.Substring(domainUri.GetAbsolutePathDecoded().Length).EnsureStartsWith('/');
|
|
|
|
#endregion
|
|
}
|
|
}
|