diff --git a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs index 5a82d860bb..7e82fe2e95 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs @@ -262,4 +262,9 @@ public class ContentSettings /// [DefaultValue(StaticDisallowedUploadFiles)] public string[] DisallowedUploadedFileExtensions { get; set; } = StaticDisallowedUploadFiles.Split(','); + + /// + /// Gets or sets the allowed external host for media. If empty only relative paths are allowed. + /// + public string[] AllowedMediaHosts { get; set; } = Array.Empty(); } diff --git a/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs b/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs index 71c0929e39..e843d7954b 100644 --- a/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs +++ b/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs @@ -169,7 +169,10 @@ public class BackOfficeExamineSearcher : IBackOfficeExamineSearcher var allLangs = _languageService.GetAllLanguages().Select(x => x.IsoCode.ToLowerInvariant()).ToList(); // the chars [*-_] in the query will mess everything up so let's remove those - query = Regex.Replace(query, "[\\*\\-_]", string.Empty); + // However we cannot just remove - and _ since these signify a space, so we instead replace them with that. + query = Regex.Replace(query, "[\\*]", string.Empty); + query = Regex.Replace(query, "[\\-_]", " "); + //check if text is surrounded by single or double quotes, if so, then exact match var surroundedByQuotes = Regex.IsMatch(query, "^\".*?\"$") diff --git a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs index 8f7901b2b4..e718696ae3 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs @@ -1,10 +1,14 @@ using System.Web; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Web.BackOffice.Controllers; @@ -17,13 +21,31 @@ public class ImagesController : UmbracoAuthorizedApiController { private readonly IImageUrlGenerator _imageUrlGenerator; private readonly MediaFileManager _mediaFileManager; + private ContentSettings _contentSettings; + [Obsolete("Use non obsolete-constructor. Scheduled for removal in Umbraco 13.")] public ImagesController( MediaFileManager mediaFileManager, IImageUrlGenerator imageUrlGenerator) + : this(mediaFileManager, + imageUrlGenerator, + StaticServiceProvider.Instance.GetRequiredService>()) + { + + } + + [ActivatorUtilitiesConstructor] + public ImagesController( + MediaFileManager mediaFileManager, + IImageUrlGenerator imageUrlGenerator, + IOptionsMonitor contentSettingsMonitor) { _mediaFileManager = mediaFileManager; _imageUrlGenerator = imageUrlGenerator; + _contentSettings = contentSettingsMonitor.CurrentValue; + + contentSettingsMonitor.OnChange(x => _contentSettings = x); + } /// @@ -58,7 +80,7 @@ public class ImagesController : UmbracoAuthorizedApiController var ext = Path.GetExtension(encodedImagePath); // check if imagePath is local to prevent open redirect - if (!Uri.IsWellFormedUriString(encodedImagePath, UriKind.Relative)) + if (!IsAllowed(encodedImagePath)) { return Unauthorized(); } @@ -90,12 +112,33 @@ public class ImagesController : UmbracoAuthorizedApiController ImageCropMode = ImageCropMode.Max, CacheBusterValue = rnd }); - if (Url.IsLocalUrl(imageUrl)) + + if (imageUrl is not null) { - return new LocalRedirectResult(imageUrl, false); + return new RedirectResult(imageUrl, false); } - return Unauthorized(); + return NotFound(); + } + + private bool IsAllowed(string encodedImagePath) + { + if(Uri.IsWellFormedUriString(encodedImagePath, UriKind.Relative)) + { + return true; + } + + var builder = new UriBuilder(encodedImagePath); + + foreach (var allowedMediaHost in _contentSettings.AllowedMediaHosts) + { + if (string.Equals(builder.Host, allowedMediaHost, StringComparison.InvariantCultureIgnoreCase)) + { + return true; + } + } + + return false; } /// diff --git a/src/Umbraco.Web.Common/Controllers/PublishedRequestFilterAttribute.cs b/src/Umbraco.Web.Common/Controllers/PublishedRequestFilterAttribute.cs index 10986d3882..9cd0a975a1 100644 --- a/src/Umbraco.Web.Common/Controllers/PublishedRequestFilterAttribute.cs +++ b/src/Umbraco.Web.Common/Controllers/PublishedRequestFilterAttribute.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Web.Common.ActionsResults; using Umbraco.Cms.Web.Common.Routing; namespace Umbraco.Cms.Web.Common.Controllers; @@ -15,9 +16,11 @@ internal class PublishedRequestFilterAttribute : ResultFilterAttribute /// public override void OnResultExecuting(ResultExecutingContext context) { - if (context.Result is not null) + if (context.Result is MaintenanceResult) { - // If the result is already set, we just skip the execution + // If the result is already set to a maintenance result we can't do anything + // Since the umbraco pipeline has not run. + // Fortunately we don't need to either. return; } diff --git a/src/Umbraco.Web.Common/Routing/UmbracoVirtualPageRoute.cs b/src/Umbraco.Web.Common/Routing/UmbracoVirtualPageRoute.cs index f0d0427438..cff5a589f6 100644 --- a/src/Umbraco.Web.Common/Routing/UmbracoVirtualPageRoute.cs +++ b/src/Umbraco.Web.Common/Routing/UmbracoVirtualPageRoute.cs @@ -61,8 +61,9 @@ public class UmbracoVirtualPageRoute : IUmbracoVirtualPageRoute if (controllerType != null) { - // Get the controller for the endpoint - var controller = httpContext.RequestServices.GetRequiredService(controllerType); + // Get the controller for the endpoint. We need to fallback to ActivatorUtilities if the controller is not registered in DI. + var controller = httpContext.RequestServices.GetService(controllerType) + ?? ActivatorUtilities.CreateInstance(httpContext.RequestServices, controllerType); // Try and find the content if this is a virtual page IPublishedContent? publishedContent = FindContent( diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js index 28a781a8ec..8b42bdbe27 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js @@ -3,7 +3,7 @@ angular.module("umbraco.install").controller("Umbraco.Install.UserController", f $scope.majorVersion = Umbraco.Sys.ServerVariables.application.version; $scope.passwordPattern = /.*/; $scope.installer.current.model.subscribeToNewsLetter = $scope.installer.current.model.subscribeToNewsLetter || false; - setTelemetryLevelAndDescription($scope.installer.current.model.telemetryIndex ?? 1); + setTelemetryLevelAndDescription($scope.installer.current.model.telemetryIndex ?? 2); if ($scope.installer.current.model.minNonAlphaNumericLength > 0) { var exp = "";