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 = "";