From 7b6539ff777e7db5dc04e5a12525521f56dc2a06 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 21 Dec 2021 18:28:46 +0100 Subject: [PATCH 01/12] Allow changing UmbracoMediaPath to an absolute path. Also ensure Imagesharp are handing requests outside of the wwwroot folder. --- .../Configuration/Models/GlobalSettings.cs | 10 ++- .../Constants-SystemDirectories.cs | 2 + .../Packaging/PackagesRepository.cs | 2 +- .../UmbracoBuilder.FileSystems.cs | 2 +- .../CreatedPackageSchemaRepository.cs | 2 +- .../Controllers/BackOfficeServerVariables.cs | 2 +- .../UmbracoApplicationBuilder.cs | 18 +++- .../UmbracoBuilder.ImageSharp.cs | 86 +++++++++++-------- .../IUmbracoMediaFileProvider.cs | 8 ++ .../UmbracoMediaFileProvider.cs | 15 ++++ .../UmbracoMediaPhysicalFileSystemProvider.cs | 53 ++++++++++++ 11 files changed, 156 insertions(+), 44 deletions(-) create mode 100644 src/Umbraco.Web.Common/ImageProcessors/IUmbracoMediaFileProvider.cs create mode 100644 src/Umbraco.Web.Common/ImageProcessors/UmbracoMediaFileProvider.cs create mode 100644 src/Umbraco.Web.Common/ImageProcessors/UmbracoMediaPhysicalFileSystemProvider.cs diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs index 97fb91b0ecbf..a19a7e2a8c28 100644 --- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs @@ -24,6 +24,7 @@ public class GlobalSettings internal const string StaticUmbracoCssPath = "~/css"; internal const string StaticUmbracoScriptsPath = "~/scripts"; internal const string StaticUmbracoMediaPath = "~/media"; + internal const string StaticUmbracoMediaUrl = "~/media"; internal const bool StaticInstallMissingDatabase = false; internal const bool StaticDisableElectionForSingleServer = false; internal const string StaticNoNodesViewPath = "~/umbraco/UmbracoWebsite/NoNodes.cshtml"; @@ -109,6 +110,13 @@ public class GlobalSettings [DefaultValue(StaticUmbracoMediaPath)] public string UmbracoMediaPath { get; set; } = StaticUmbracoMediaPath; + + /// + /// Gets or sets a value for the Umbraco media url. Starts with "~/". + /// + [DefaultValue(StaticUmbracoMediaUrl)] + public string UmbracoMediaUrl { get; set; } = StaticUmbracoMediaUrl; + /// /// Gets or sets a value indicating whether to install the database when it is missing. /// @@ -179,4 +187,4 @@ public class GlobalSettings [DefaultValue(StaticSqlWriteLockTimeOut)] public TimeSpan SqlWriteLockTimeOut { get; } = TimeSpan.Parse(StaticSqlWriteLockTimeOut); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Constants-SystemDirectories.cs b/src/Umbraco.Core/Constants-SystemDirectories.cs index 80b49781ecf9..bf34aab989c4 100644 --- a/src/Umbraco.Core/Constants-SystemDirectories.cs +++ b/src/Umbraco.Core/Constants-SystemDirectories.cs @@ -43,6 +43,8 @@ public static class SystemDirectories public const string AppPlugins = "/App_Plugins"; public static string AppPluginIcons => "/Backoffice/Icons"; + public const string CreatedPackages = "/created-packages"; + public const string MvcViews = "~/Views"; diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs index 331034e78758..d14ccc210aa1 100644 --- a/src/Umbraco.Core/Packaging/PackagesRepository.cs +++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs @@ -93,7 +93,7 @@ public PackagesRepository( _tempFolderPath = tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles"; _packagesFolderPath = packagesFolderPath ?? Constants.SystemDirectories.Packages; - _mediaFolderPath = mediaFolderPath ?? globalSettings.Value.UmbracoMediaPath + "/created-packages"; + _mediaFolderPath = mediaFolderPath ?? Path.Combine(globalSettings.Value.UmbracoMediaPath, Constants.SystemDirectories.CreatedPackages); _parser = new PackageDefinitionXmlParser(); _mediaService = mediaService; diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs index 6582cfb0c6e6..f80935ae7d72 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs @@ -50,7 +50,7 @@ internal static IUmbracoBuilder AddFileSystems(this IUmbracoBuilder builder) GlobalSettings globalSettings = factory.GetRequiredService>().Value; var rootPath = hostingEnvironment.MapPathWebRoot(globalSettings.UmbracoMediaPath); - var rootUrl = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaPath); + var rootUrl = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaUrl); return new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, rootPath, rootUrl); }); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs index 0c4f876bb182..9d78f8c124a2 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs @@ -76,7 +76,7 @@ public CreatedPackageSchemaRepository( _macroService = macroService; _contentTypeService = contentTypeService; _xmlParser = new PackageDefinitionXmlParser(); - _mediaFolderPath = mediaFolderPath ?? globalSettings.Value.UmbracoMediaPath + "/created-packages"; + _mediaFolderPath = mediaFolderPath ?? Path.Combine(globalSettings.Value.UmbracoMediaPath, Constants.SystemDirectories.CreatedPackages); _tempFolderPath = tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles"; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs index 43723207d39f..6d9118ffd9a8 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs @@ -384,7 +384,7 @@ internal async Task> GetServerVariablesAsync() "umbracoSettings", new Dictionary { {"umbracoPath", _globalSettings.GetBackOfficePath(_hostingEnvironment)}, - {"mediaPath", _hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaPath).TrimEnd(Constants.CharArrays.ForwardSlash)}, + {"mediaPath", _hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaUrl).TrimEnd(Constants.CharArrays.ForwardSlash)}, {"appPluginsPath", _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.AppPlugins).TrimEnd(Constants.CharArrays.ForwardSlash)}, { "imageFileTypes", diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs index 9d30551071e7..cba76a811b37 100644 --- a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs +++ b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs @@ -1,9 +1,13 @@ using System; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Options; using SixLabors.ImageSharp.Web.DependencyInjection; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.ImageProcessors; using Umbraco.Extensions; namespace Umbraco.Cms.Web.Common.ApplicationBuilder @@ -22,7 +26,7 @@ public UmbracoApplicationBuilder(IApplicationBuilder appBuilder) { AppBuilder = appBuilder ?? throw new ArgumentNullException(nameof(appBuilder)); ApplicationServices = appBuilder.ApplicationServices; - RuntimeState = appBuilder.ApplicationServices.GetRequiredService(); + RuntimeState = appBuilder.ApplicationServices.GetRequiredService(); _umbracoPipelineStartupOptions = ApplicationServices.GetRequiredService>(); } @@ -87,9 +91,19 @@ public void RegisterDefaultRequiredMiddleware() AppBuilder.UseStatusCodePages(); - // Important we handle image manipulations before the static files, otherwise the querystring is just ignored. + // Important we handle image manipulations before the static files, otherwise the querystring is just ignored. AppBuilder.UseImageSharp(); + + GlobalSettings globalSettings = AppBuilder.ApplicationServices.GetRequiredService>().Value; + IUmbracoMediaFileProvider umbracoMediaFileProvider = AppBuilder.ApplicationServices.GetRequiredService(); + + AppBuilder.UseStaticFiles(new StaticFileOptions() + { + FileProvider = umbracoMediaFileProvider, + RequestPath = globalSettings.UmbracoMediaUrl.TrimStart("~") + }); AppBuilder.UseStaticFiles(); + AppBuilder.UseUmbracoPluginsStaticFiles(); // UseRouting adds endpoint routing middleware, this means that middlewares registered after this one diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index 4d621d348cef..a058d729e24b 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -3,13 +3,17 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; +using SixLabors.ImageSharp.Web; using SixLabors.ImageSharp.Web.Caching; using SixLabors.ImageSharp.Web.Commands; using SixLabors.ImageSharp.Web.DependencyInjection; using SixLabors.ImageSharp.Web.Middleware; using SixLabors.ImageSharp.Web.Processors; +using SixLabors.ImageSharp.Web.Providers; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Web.Common.DependencyInjection; @@ -28,54 +32,62 @@ public static IServiceCollection AddUmbracoImageSharp(this IUmbracoBuilder build .Get() ?? new ImagingSettings(); builder.Services.AddImageSharp(options => - { - // options.Configuration is set using ImageSharpConfigurationOptions below - options.BrowserMaxAge = imagingSettings.Cache.BrowserMaxAge; - options.CacheMaxAge = imagingSettings.Cache.CacheMaxAge; - options.CachedNameLength = imagingSettings.Cache.CachedNameLength; - - // Use configurable maximum width and height (overwrite ImageSharps default) - options.OnParseCommandsAsync = context => { - if (context.Commands.Count == 0) - { - return Task.CompletedTask; - } + // options.Configuration is set using ImageSharpConfigurationOptions below + options.BrowserMaxAge = imagingSettings.Cache.BrowserMaxAge; + options.CacheMaxAge = imagingSettings.Cache.CacheMaxAge; + options.CachedNameLength = imagingSettings.Cache.CachedNameLength; - uint width = context.Parser.ParseValue(context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), context.Culture); - uint height = context.Parser.ParseValue(context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), context.Culture); - if (width > imagingSettings.Resize.MaxWidth || height > imagingSettings.Resize.MaxHeight) + // Use configurable maximum width and height (overwrite ImageSharps default) + options.OnParseCommandsAsync = context => { - context.Commands.Remove(ResizeWebProcessor.Width); - context.Commands.Remove(ResizeWebProcessor.Height); - } + if (context.Commands.Count == 0) + { + return Task.CompletedTask; + } - return Task.CompletedTask; - }; - options.OnBeforeSaveAsync = _ => Task.CompletedTask; - options.OnProcessedAsync = _ => Task.CompletedTask; - options.OnPrepareResponseAsync = context => - { - // Change Cache-Control header when cache buster value is present - if (context.Request.Query.ContainsKey("rnd")) + uint width = + context.Parser.ParseValue( + context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), context.Culture); + uint height = context.Parser.ParseValue( + context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), context.Culture); + if (width > imagingSettings.Resize.MaxWidth || height > imagingSettings.Resize.MaxHeight) + { + context.Commands.Remove(ResizeWebProcessor.Width); + context.Commands.Remove(ResizeWebProcessor.Height); + } + + return Task.CompletedTask; + }; + options.OnBeforeSaveAsync = _ => Task.CompletedTask; + options.OnProcessedAsync = _ => Task.CompletedTask; + options.OnPrepareResponseAsync = context => { - var headers = context.Response.GetTypedHeaders(); + // Change Cache-Control header when cache buster value is present + if (context.Request.Query.ContainsKey("rnd")) + { + var headers = context.Response.GetTypedHeaders(); - var cacheControl = headers.CacheControl; - cacheControl.MustRevalidate = false; - cacheControl.Extensions.Add(new NameValueHeaderValue("immutable")); + var cacheControl = headers.CacheControl; + cacheControl.MustRevalidate = false; + cacheControl.Extensions.Add(new NameValueHeaderValue("immutable")); - headers.CacheControl = cacheControl; - } + headers.CacheControl = cacheControl; + } - return Task.CompletedTask; - }; - }) - .Configure(options => options.CacheFolder = builder.BuilderHostingEnvironment.MapPathContentRoot(imagingSettings.Cache.CacheFolder)) - .AddProcessor(); + return Task.CompletedTask; + }; + }) + .Configure(options => + options.CacheFolder = + builder.BuilderHostingEnvironment.MapPathContentRoot(imagingSettings.Cache.CacheFolder)) + .AddProcessor() + .ClearProviders() + .AddProvider(); builder.Services.AddTransient, ImageSharpConfigurationOptions>(); + builder.Services.AddUnique(); return builder.Services; } } diff --git a/src/Umbraco.Web.Common/ImageProcessors/IUmbracoMediaFileProvider.cs b/src/Umbraco.Web.Common/ImageProcessors/IUmbracoMediaFileProvider.cs new file mode 100644 index 000000000000..3c86c5f9cb05 --- /dev/null +++ b/src/Umbraco.Web.Common/ImageProcessors/IUmbracoMediaFileProvider.cs @@ -0,0 +1,8 @@ +using Microsoft.Extensions.FileProviders; + +namespace Umbraco.Cms.Web.Common.ImageProcessors +{ + public interface IUmbracoMediaFileProvider : IFileProvider + { + } +} diff --git a/src/Umbraco.Web.Common/ImageProcessors/UmbracoMediaFileProvider.cs b/src/Umbraco.Web.Common/ImageProcessors/UmbracoMediaFileProvider.cs new file mode 100644 index 000000000000..717710ab42e5 --- /dev/null +++ b/src/Umbraco.Web.Common/ImageProcessors/UmbracoMediaFileProvider.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; + +namespace Umbraco.Cms.Web.Common.ImageProcessors +{ + internal class UmbracoMediaFileProvider : PhysicalFileProvider, IUmbracoMediaFileProvider + { + public UmbracoMediaFileProvider(IHostingEnvironment hostingEnvironment, IOptions globalSettings) + : base(hostingEnvironment.MapPathWebRoot(globalSettings.Value.UmbracoMediaPath)) + { + } + } +} diff --git a/src/Umbraco.Web.Common/ImageProcessors/UmbracoMediaPhysicalFileSystemProvider.cs b/src/Umbraco.Web.Common/ImageProcessors/UmbracoMediaPhysicalFileSystemProvider.cs new file mode 100644 index 000000000000..ebeb1eef8182 --- /dev/null +++ b/src/Umbraco.Web.Common/ImageProcessors/UmbracoMediaPhysicalFileSystemProvider.cs @@ -0,0 +1,53 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Options; +using SixLabors.ImageSharp.Web; +using SixLabors.ImageSharp.Web.Providers; +using SixLabors.ImageSharp.Web.Resolvers; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.ImageProcessors +{ + public class UmbracoImageProvider : IImageProvider + { + private readonly IFileProvider _fileProvider; + private readonly FormatUtilities _formatUtilities; + private readonly string _mediaUrlPrefix; + + public UmbracoImageProvider(IUmbracoMediaFileProvider fileProvider, FormatUtilities formatUtilities, IOptions globalSettings) + { + _fileProvider = fileProvider; + _formatUtilities = formatUtilities; + _mediaUrlPrefix = globalSettings.Value.UmbracoMediaUrl.TrimStart(Core.Constants.CharArrays.Tilde); + } + + /// + public bool IsValidRequest(HttpContext context) => _formatUtilities.GetExtensionFromUri(context.Request.GetDisplayUrl()) != null; + + /// + public Task GetAsync(HttpContext context) + { + // Path has already been correctly parsed before here. + IFileInfo fileInfo = _fileProvider.GetFileInfo(context.Request.Path.Value.TrimStart(_mediaUrlPrefix)); + + // Check to see if the file exists. + if (!fileInfo.Exists) + { + return Task.FromResult(null); + } + + var metadata = new ImageMetadata(fileInfo.LastModified.UtcDateTime, fileInfo.Length); + return Task.FromResult(new PhysicalFileSystemResolver(fileInfo, metadata)); + } + + /// + public ProcessingBehavior ProcessingBehavior { get; } = ProcessingBehavior.CommandOnly; + + /// + public Func Match { get; set; } = _ => true; + } +} From 8b2a3f4fa4155a65ca6bd28640bbd7a799938c35 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 28 Dec 2021 16:08:57 +0100 Subject: [PATCH 02/12] Let UmbracoMediaUrl fallback to UmbracoMediaPath when empty --- .../Configuration/Models/GlobalSettings.cs | 26 +++++++++---------- .../UmbracoBuilder.Configuration.cs | 26 ++++++++++++------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs index a19a7e2a8c28..65b1622137b6 100644 --- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs @@ -24,7 +24,6 @@ public class GlobalSettings internal const string StaticUmbracoCssPath = "~/css"; internal const string StaticUmbracoScriptsPath = "~/scripts"; internal const string StaticUmbracoMediaPath = "~/media"; - internal const string StaticUmbracoMediaUrl = "~/media"; internal const bool StaticInstallMissingDatabase = false; internal const bool StaticDisableElectionForSingleServer = false; internal const string StaticNoNodesViewPath = "~/umbraco/UmbracoWebsite/NoNodes.cshtml"; @@ -32,21 +31,19 @@ public class GlobalSettings internal const bool StaticSanitizeTinyMce = false; /// - /// Gets or sets a value for the reserved URLs. - /// It must end with a comma + /// Gets or sets a value for the reserved URLs (must end with a comma). /// [DefaultValue(StaticReservedUrls)] public string ReservedUrls { get; set; } = StaticReservedUrls; /// - /// Gets or sets a value for the reserved paths. - /// It must end with a comma + /// Gets or sets a value for the reserved paths (must end with a comma). /// [DefaultValue(StaticReservedPaths)] public string ReservedPaths { get; set; } = StaticReservedPaths; /// - /// Gets or sets a value for the timeout + /// Gets or sets a value for the back-office login timeout. /// [DefaultValue(StaticTimeOut)] public TimeSpan TimeOut { get; set; } = TimeSpan.Parse(StaticTimeOut); @@ -110,12 +107,10 @@ public class GlobalSettings [DefaultValue(StaticUmbracoMediaPath)] public string UmbracoMediaPath { get; set; } = StaticUmbracoMediaPath; - /// - /// Gets or sets a value for the Umbraco media url. Starts with "~/". + /// Gets or sets a value for the Umbraco media URL (falls back to when empty). /// - [DefaultValue(StaticUmbracoMediaUrl)] - public string UmbracoMediaUrl { get; set; } = StaticUmbracoMediaUrl; + public string UmbracoMediaUrl { get; set; } /// /// Gets or sets a value indicating whether to install the database when it is missing. @@ -139,6 +134,9 @@ public class GlobalSettings /// public string MainDomLock { get; set; } = string.Empty; + /// + /// Gets or sets the telemetry ID. + /// public string Id { get; set; } = string.Empty; /// @@ -172,18 +170,18 @@ public class GlobalSettings /// public bool IsPickupDirectoryLocationConfigured => !string.IsNullOrWhiteSpace(Smtp?.PickupDirectoryLocation); - /// Gets a value indicating whether TinyMCE scripting sanitization should be applied + /// + /// Gets a value indicating whether TinyMCE scripting sanitization should be applied. /// [DefaultValue(StaticSanitizeTinyMce)] public bool SanitizeTinyMce => StaticSanitizeTinyMce; /// - /// An int value representing the time in milliseconds to lock the database for a write operation + /// Gets a value representing the time in milliseconds to lock the database for a write operation. /// /// - /// The default value is 5000 milliseconds + /// The default value is 5000 milliseconds. /// - /// The timeout in milliseconds. [DefaultValue(StaticSqlWriteLockTimeOut)] public TimeSpan SqlWriteLockTimeOut { get; } = TimeSpan.Parse(StaticSqlWriteLockTimeOut); } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index 6ef87464e851..03b7194b53b2 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -13,23 +13,25 @@ namespace Umbraco.Cms.Core.DependencyInjection public static partial class UmbracoBuilderExtensions { - private static IUmbracoBuilder AddUmbracoOptions(this IUmbracoBuilder builder) + private static IUmbracoBuilder AddUmbracoOptions(this IUmbracoBuilder builder, Action> configure = null) where TOptions : class { var umbracoOptionsAttribute = typeof(TOptions).GetCustomAttribute(); - if (umbracoOptionsAttribute is null) { - throw new ArgumentException("typeof(TOptions) do not have the UmbracoOptionsAttribute"); + throw new ArgumentException($"{typeof(TOptions)} do not have the UmbracoOptionsAttribute."); } - - builder.Services.AddOptions() - .Bind(builder.Config.GetSection(umbracoOptionsAttribute.ConfigurationKey), - o => o.BindNonPublicProperties = umbracoOptionsAttribute.BindNonPublicProperties) + var optionsBuilder = builder.Services.AddOptions() + .Bind( + builder.Config.GetSection(umbracoOptionsAttribute.ConfigurationKey), + o => o.BindNonPublicProperties = umbracoOptionsAttribute.BindNonPublicProperties + ) .ValidateDataAnnotations(); - return builder; + configure?.Invoke(optionsBuilder); + + return builder; } /// @@ -52,7 +54,13 @@ public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder) .AddUmbracoOptions() .AddUmbracoOptions() .AddUmbracoOptions() - .AddUmbracoOptions() + .AddUmbracoOptions(optionsBuilder => optionsBuilder.PostConfigure(options => + { + if (string.IsNullOrEmpty(options.UmbracoMediaUrl)) + { + options.UmbracoMediaUrl = options.UmbracoMediaPath; + } + })) .AddUmbracoOptions() .AddUmbracoOptions() .AddUmbracoOptions() From b414ad5be642a74d3e14b261bd96c74d702b27da Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 28 Dec 2021 16:13:24 +0100 Subject: [PATCH 03/12] Add FileSystemFileProvider to expose an IFileSystem as IFileProvider --- .../IO/FileSystemDirectoryContents.cs | 59 ++++++++++++++++++ .../IO/FileSystemDirectoryInfo.cs | 48 ++++++++++++++ src/Umbraco.Core/IO/FileSystemFileInfo.cs | 48 ++++++++++++++ src/Umbraco.Core/IO/FileSystemFileProvider.cs | 62 +++++++++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 src/Umbraco.Core/IO/FileSystemDirectoryContents.cs create mode 100644 src/Umbraco.Core/IO/FileSystemDirectoryInfo.cs create mode 100644 src/Umbraco.Core/IO/FileSystemFileInfo.cs create mode 100644 src/Umbraco.Core/IO/FileSystemFileProvider.cs diff --git a/src/Umbraco.Core/IO/FileSystemDirectoryContents.cs b/src/Umbraco.Core/IO/FileSystemDirectoryContents.cs new file mode 100644 index 000000000000..5767ceb1834e --- /dev/null +++ b/src/Umbraco.Core/IO/FileSystemDirectoryContents.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.FileProviders; + +namespace Umbraco.Cms.Core.IO +{ + /// + /// Represents the directory contents in an . + /// + /// + public class FileSystemDirectoryContents : IDirectoryContents + { + private readonly IFileSystem _fileSystem; + private readonly string _subpath; + private IEnumerable _entries = null!; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The subpath. + /// + /// fileSystem + /// or + /// subpath + /// + public FileSystemDirectoryContents(IFileSystem fileSystem, string subpath) + { + _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); + _subpath = subpath ?? throw new ArgumentNullException(nameof(subpath)); + } + + /// + public bool Exists => true; + + /// + public IEnumerator GetEnumerator() + { + EnsureInitialized(); + return _entries.GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + EnsureInitialized(); + return _entries.GetEnumerator(); + } + + private void EnsureInitialized() + { + _entries = _fileSystem.GetDirectories(_subpath).Select(d => new FileSystemDirectoryInfo(_fileSystem, d)) + .Union(_fileSystem.GetFiles(_subpath).Select(f => new FileSystemFileInfo(_fileSystem, f))) + .ToList(); + } + } +} diff --git a/src/Umbraco.Core/IO/FileSystemDirectoryInfo.cs b/src/Umbraco.Core/IO/FileSystemDirectoryInfo.cs new file mode 100644 index 000000000000..fc233fa2b13c --- /dev/null +++ b/src/Umbraco.Core/IO/FileSystemDirectoryInfo.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using Microsoft.Extensions.FileProviders; + +namespace Umbraco.Cms.Core.IO +{ + /// + /// Represents a directory in an . + /// + /// + public class FileSystemDirectoryInfo : IFileInfo + { + private readonly IFileSystem _fileSystem; + private readonly string _subpath; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The subpath. + public FileSystemDirectoryInfo(IFileSystem fileSystem, string subpath) + { + _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); + _subpath = subpath ?? throw new ArgumentNullException(nameof(subpath)); + } + + /// + public bool Exists => true; + + /// + public bool IsDirectory => true; + + /// + public DateTimeOffset LastModified => _fileSystem.GetLastModified(_subpath); + + /// + public long Length => -1; + + /// + public string Name => _fileSystem.GetRelativePath(_subpath); + + /// + public string PhysicalPath => null!; + + /// + public Stream CreateReadStream() => throw new InvalidOperationException("Cannot create a stream for a directory."); + } +} diff --git a/src/Umbraco.Core/IO/FileSystemFileInfo.cs b/src/Umbraco.Core/IO/FileSystemFileInfo.cs new file mode 100644 index 000000000000..6762a5c696d8 --- /dev/null +++ b/src/Umbraco.Core/IO/FileSystemFileInfo.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using Microsoft.Extensions.FileProviders; + +namespace Umbraco.Cms.Core.IO +{ + /// + /// Represents a file in an . + /// + /// + public class FileSystemFileInfo : IFileInfo + { + private readonly IFileSystem _fileSystem; + private readonly string _subpath; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The subpath. + public FileSystemFileInfo(IFileSystem fileSystem, string subpath) + { + _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); + _subpath = subpath ?? throw new ArgumentNullException(nameof(subpath)); + } + + /// + public bool Exists => true; + + /// + public bool IsDirectory => false; + + /// + public DateTimeOffset LastModified => _fileSystem.GetLastModified(_subpath); + + /// + public long Length => _fileSystem.GetSize(_subpath); + + /// + public string Name => _fileSystem.GetRelativePath(_subpath); + + /// + public string PhysicalPath => null!; + + /// + public Stream CreateReadStream() => _fileSystem.OpenFile(_subpath); + } +} diff --git a/src/Umbraco.Core/IO/FileSystemFileProvider.cs b/src/Umbraco.Core/IO/FileSystemFileProvider.cs new file mode 100644 index 000000000000..313e55fdd63c --- /dev/null +++ b/src/Umbraco.Core/IO/FileSystemFileProvider.cs @@ -0,0 +1,62 @@ +using System; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Primitives; + +namespace Umbraco.Cms.Core.IO +{ + /// + /// Exposes an as an . + /// + /// + public class FileSystemFileProvider : IFileProvider + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The path prefix. + /// fileSystem + public FileSystemFileProvider(IFileSystem fileSystem, string pathPrefix = null) + { + FileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); + PathPrefix = pathPrefix; + } + + /// + /// Gets the file system. + /// + protected IFileSystem FileSystem { get; } + + /// + /// Gets the path prefix. + /// + protected string PathPrefix { get; } + + /// + public IDirectoryContents GetDirectoryContents(string subpath) + { + var path = PathPrefix + subpath; + if (path == null || !FileSystem.DirectoryExists(path)) + { + return NotFoundDirectoryContents.Singleton; + } + + return new FileSystemDirectoryContents(FileSystem, path); + } + + /// + public IFileInfo GetFileInfo(string subpath) + { + var path = PathPrefix + subpath; + if (path == null || !FileSystem.FileExists(path)) + { + return new NotFoundFileInfo(path); + } + + return new FileSystemFileInfo(FileSystem, path); + } + + /// + public IChangeToken Watch(string filter) => NullChangeToken.Singleton; + } +} From 918b3b8c0b3629722856ace0ceaa3ee1d61e3f7b Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 28 Dec 2021 16:17:24 +0100 Subject: [PATCH 04/12] Replace IUmbracoMediaFileProvider with IFileProviderFactory implementation --- src/Umbraco.Core/IO/IFileProviderFactory.cs | 18 ++++ src/Umbraco.Core/IO/MediaFileManager.cs | 40 ++++++--- src/Umbraco.Core/IO/PhysicalFileSystem.cs | 12 ++- src/Umbraco.Core/IO/ShadowWrapper.cs | 12 ++- src/Umbraco.Core/Umbraco.Core.csproj | 3 +- .../UmbracoApplicationBuilder.cs | 18 ++-- .../ImageSharpConfigurationOptions.cs | 4 +- .../UmbracoBuilder.ImageSharp.cs | 89 +++++++++---------- .../FileSystemImageProvider.cs | 80 +++++++++++++++++ .../IUmbracoMediaFileProvider.cs | 8 -- .../UmbracoMediaFileProvider.cs | 15 ---- .../UmbracoMediaPhysicalFileSystemProvider.cs | 53 ----------- 12 files changed, 204 insertions(+), 148 deletions(-) create mode 100644 src/Umbraco.Core/IO/IFileProviderFactory.cs create mode 100644 src/Umbraco.Web.Common/ImageProcessors/FileSystemImageProvider.cs delete mode 100644 src/Umbraco.Web.Common/ImageProcessors/IUmbracoMediaFileProvider.cs delete mode 100644 src/Umbraco.Web.Common/ImageProcessors/UmbracoMediaFileProvider.cs delete mode 100644 src/Umbraco.Web.Common/ImageProcessors/UmbracoMediaPhysicalFileSystemProvider.cs diff --git a/src/Umbraco.Core/IO/IFileProviderFactory.cs b/src/Umbraco.Core/IO/IFileProviderFactory.cs new file mode 100644 index 000000000000..9f4aed967d0c --- /dev/null +++ b/src/Umbraco.Core/IO/IFileProviderFactory.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.FileProviders; + +namespace Umbraco.Cms.Core.IO +{ + /// + /// Factory for creating instances. + /// + public interface IFileProviderFactory + { + /// + /// Creates a new instance. + /// + /// + /// The newly created instance. + /// + IFileProvider Create(); + } +} diff --git a/src/Umbraco.Core/IO/MediaFileManager.cs b/src/Umbraco.Core/IO/MediaFileManager.cs index 96680d3f845f..aa6dd0224f5f 100644 --- a/src/Umbraco.Core/IO/MediaFileManager.cs +++ b/src/Umbraco.Core/IO/MediaFileManager.cs @@ -4,11 +4,11 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; @@ -22,29 +22,49 @@ public sealed class MediaFileManager private readonly IShortStringHelper _shortStringHelper; private readonly IServiceProvider _serviceProvider; private MediaUrlGeneratorCollection _mediaUrlGenerators; - private readonly ContentSettings _contentSettings; - - /// - /// Gets the media filesystem. - /// - public IFileSystem FileSystem { get; } public MediaFileManager( IFileSystem fileSystem, IMediaPathScheme mediaPathScheme, ILogger logger, IShortStringHelper shortStringHelper, - IServiceProvider serviceProvider, - IOptions contentSettings) + IServiceProvider serviceProvider) { _mediaPathScheme = mediaPathScheme; _logger = logger; _shortStringHelper = shortStringHelper; _serviceProvider = serviceProvider; - _contentSettings = contentSettings.Value; FileSystem = fileSystem; } + [Obsolete("Use the ctr that doesn't include unused parameters.")] + public MediaFileManager( + IFileSystem fileSystem, + IMediaPathScheme mediaPathScheme, + ILogger logger, + IShortStringHelper shortStringHelper, + IServiceProvider serviceProvider, + IOptions contentSettings) + : this(fileSystem, mediaPathScheme, logger, shortStringHelper, serviceProvider) + { } + + /// + /// Gets the media filesystem. + /// + public IFileSystem FileSystem { get; } + + /// + /// Create an instance for the media file system. + /// + /// + /// The for the media file system. + /// + public IFileProvider CreateFileProvider() => FileSystem switch + { + IFileProviderFactory fileProviderFactory => fileProviderFactory.Create(), + var fileSystem => new FileSystemFileProvider(fileSystem) + }; + /// /// Delete media files. /// diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index db10cae4162a..a3cdd8ce10e7 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -3,14 +3,17 @@ using System.IO; using System.Linq; using System.Threading; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Hosting; using Umbraco.Extensions; namespace Umbraco.Cms.Core.IO { - public interface IPhysicalFileSystem : IFileSystem {} - public class PhysicalFileSystem : IPhysicalFileSystem + public interface IPhysicalFileSystem : IFileSystem + { } + + public class PhysicalFileSystem : IPhysicalFileSystem, IFileProviderFactory { private readonly IIOHelper _ioHelper; private readonly ILogger _logger; @@ -28,7 +31,7 @@ public class PhysicalFileSystem : IPhysicalFileSystem // eg "" or "/Views" or "/Media" or "//Media" in case of a virtual path private readonly string _rootUrl; - public PhysicalFileSystem(IIOHelper ioHelper,IHostingEnvironment hostingEnvironment, ILogger logger, string rootPath, string rootUrl) + public PhysicalFileSystem(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, ILogger logger, string rootPath, string rootUrl) { _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -450,6 +453,9 @@ protected void WithRetry(Action action) } } + /// + public IFileProvider Create() => new PhysicalFileProvider(_rootPath); + #endregion } } diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs index cda61cf7b50c..e99a247aad01 100644 --- a/src/Umbraco.Core/IO/ShadowWrapper.cs +++ b/src/Umbraco.Core/IO/ShadowWrapper.cs @@ -1,14 +1,15 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Hosting; using Umbraco.Extensions; namespace Umbraco.Cms.Core.IO { - internal class ShadowWrapper : IFileSystem + internal class ShadowWrapper : IFileSystem, IFileProviderFactory { private static readonly string ShadowFsPath = Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs"; @@ -220,5 +221,12 @@ public void AddFile(string path, string physicalPath, bool overrideIfExists = tr { FileSystem.AddFile(path, physicalPath, overrideIfExists, copy); } + + /// + public IFileProvider Create() => _innerFileSystem switch + { + IFileProviderFactory fileProviderFactory => fileProviderFactory.Create(), + var fileSystem => new FileSystemFileProvider(fileSystem) + }; } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index f36d7a8ee5c0..ee83d0677ac3 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -17,6 +17,7 @@ + diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs index cba76a811b37..8c4e1c243e0b 100644 --- a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs +++ b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs @@ -6,8 +6,8 @@ using SixLabors.ImageSharp.Web.DependencyInjection; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Web.Common.ImageProcessors; using Umbraco.Extensions; namespace Umbraco.Cms.Web.Common.ApplicationBuilder @@ -31,7 +31,9 @@ public UmbracoApplicationBuilder(IApplicationBuilder appBuilder) } public IServiceProvider ApplicationServices { get; } + public IRuntimeState RuntimeState { get; } + public IApplicationBuilder AppBuilder { get; } /// @@ -82,9 +84,8 @@ public void WithEndpoints(Action configureUmbrac } /// - /// Registers the default required middleware to run Umbraco + /// Registers the default required middleware to run Umbraco. /// - /// public void RegisterDefaultRequiredMiddleware() { UseUmbracoCoreMiddleware(); @@ -94,14 +95,19 @@ public void RegisterDefaultRequiredMiddleware() // Important we handle image manipulations before the static files, otherwise the querystring is just ignored. AppBuilder.UseImageSharp(); + // Get media file provider and request path/URL + IFileProvider mediaFileProvider = AppBuilder.ApplicationServices.GetRequiredService().CreateFileProvider(); + GlobalSettings globalSettings = AppBuilder.ApplicationServices.GetRequiredService>().Value; - IUmbracoMediaFileProvider umbracoMediaFileProvider = AppBuilder.ApplicationServices.GetRequiredService(); + IHostingEnvironment hostingEnvironment = AppBuilder.ApplicationServices.GetService(); + string mediaRequestPath = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaUrl); AppBuilder.UseStaticFiles(new StaticFileOptions() { - FileProvider = umbracoMediaFileProvider, - RequestPath = globalSettings.UmbracoMediaUrl.TrimStart("~") + FileProvider = mediaFileProvider, + RequestPath = mediaRequestPath }); + AppBuilder.UseStaticFiles(); AppBuilder.UseUmbracoPluginsStaticFiles(); diff --git a/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs b/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs index 628345dcd67a..f8897e522cd6 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs @@ -7,7 +7,7 @@ namespace Umbraco.Cms.Web.Common.DependencyInjection /// /// Configures the ImageSharp middleware options to use the registered configuration. /// - /// + /// public sealed class ImageSharpConfigurationOptions : IConfigureOptions { /// @@ -22,7 +22,7 @@ public sealed class ImageSharpConfigurationOptions : IConfigureOptions _configuration = configuration; /// - /// Invoked to configure a instance. + /// Invoked to configure an instance. /// /// The options instance to configure. public void Configure(ImageSharpMiddlewareOptions options) => options.Configuration = _configuration; diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index a058d729e24b..ff9dd9b1283b 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -3,11 +3,8 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; -using SixLabors.ImageSharp.Web; using SixLabors.ImageSharp.Web.Caching; using SixLabors.ImageSharp.Web.Commands; using SixLabors.ImageSharp.Web.DependencyInjection; @@ -32,62 +29,58 @@ public static IServiceCollection AddUmbracoImageSharp(this IUmbracoBuilder build .Get() ?? new ImagingSettings(); builder.Services.AddImageSharp(options => - { - // options.Configuration is set using ImageSharpConfigurationOptions below - options.BrowserMaxAge = imagingSettings.Cache.BrowserMaxAge; - options.CacheMaxAge = imagingSettings.Cache.CacheMaxAge; - options.CachedNameLength = imagingSettings.Cache.CachedNameLength; + { + // options.Configuration is set using ImageSharpConfigurationOptions below + options.BrowserMaxAge = imagingSettings.Cache.BrowserMaxAge; + options.CacheMaxAge = imagingSettings.Cache.CacheMaxAge; + options.CachedNameLength = imagingSettings.Cache.CachedNameLength; - // Use configurable maximum width and height (overwrite ImageSharps default) - options.OnParseCommandsAsync = context => + // Use configurable maximum width and height (overwrite ImageSharps default) + options.OnParseCommandsAsync = context => + { + if (context.Commands.Count == 0) { - if (context.Commands.Count == 0) - { - return Task.CompletedTask; - } + return Task.CompletedTask; + } - uint width = - context.Parser.ParseValue( - context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), context.Culture); - uint height = context.Parser.ParseValue( - context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), context.Culture); - if (width > imagingSettings.Resize.MaxWidth || height > imagingSettings.Resize.MaxHeight) - { - context.Commands.Remove(ResizeWebProcessor.Width); - context.Commands.Remove(ResizeWebProcessor.Height); - } + uint width = context.Parser.ParseValue(context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), context.Culture); + uint height = context.Parser.ParseValue(context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), context.Culture); + if (width > imagingSettings.Resize.MaxWidth || height > imagingSettings.Resize.MaxHeight) + { + context.Commands.Remove(ResizeWebProcessor.Width); + context.Commands.Remove(ResizeWebProcessor.Height); + } - return Task.CompletedTask; - }; - options.OnBeforeSaveAsync = _ => Task.CompletedTask; - options.OnProcessedAsync = _ => Task.CompletedTask; - options.OnPrepareResponseAsync = context => + return Task.CompletedTask; + }; + options.OnBeforeSaveAsync = _ => Task.CompletedTask; + options.OnProcessedAsync = _ => Task.CompletedTask; + options.OnPrepareResponseAsync = context => + { + // Change Cache-Control header when cache buster value is present + if (context.Request.Query.ContainsKey("rnd")) { - // Change Cache-Control header when cache buster value is present - if (context.Request.Query.ContainsKey("rnd")) - { - var headers = context.Response.GetTypedHeaders(); + var headers = context.Response.GetTypedHeaders(); - var cacheControl = headers.CacheControl; - cacheControl.MustRevalidate = false; - cacheControl.Extensions.Add(new NameValueHeaderValue("immutable")); + var cacheControl = headers.CacheControl; + cacheControl.MustRevalidate = false; + cacheControl.Extensions.Add(new NameValueHeaderValue("immutable")); - headers.CacheControl = cacheControl; - } + headers.CacheControl = cacheControl; + } - return Task.CompletedTask; - }; - }) - .Configure(options => - options.CacheFolder = - builder.BuilderHostingEnvironment.MapPathContentRoot(imagingSettings.Cache.CacheFolder)) - .AddProcessor() - .ClearProviders() - .AddProvider(); + return Task.CompletedTask; + }; + }) + .Configure(options => options.CacheFolder = builder.BuilderHostingEnvironment.MapPathContentRoot(imagingSettings.Cache.CacheFolder)) + .AddProcessor(); + // Configure middleware to use the registered/shared ImageSharp configuration builder.Services.AddTransient, ImageSharpConfigurationOptions>(); - builder.Services.AddUnique(); + // Add FileSystemImageProvider before default provider + builder.Services.Insert(0, ServiceDescriptor.Singleton()); + return builder.Services; } } diff --git a/src/Umbraco.Web.Common/ImageProcessors/FileSystemImageProvider.cs b/src/Umbraco.Web.Common/ImageProcessors/FileSystemImageProvider.cs new file mode 100644 index 000000000000..60fe9f41d9c0 --- /dev/null +++ b/src/Umbraco.Web.Common/ImageProcessors/FileSystemImageProvider.cs @@ -0,0 +1,80 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Options; +using SixLabors.ImageSharp.Web; +using SixLabors.ImageSharp.Web.Providers; +using SixLabors.ImageSharp.Web.Resolvers; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; + +namespace Umbraco.Cms.Web.Common.ImageProcessors +{ + /// + public class FileSystemImageProvider : IImageProvider + { + private readonly IFileProvider _fileProvider; + private string _rootPath; + private readonly FormatUtilities _formatUtilities; + + /// + /// A match function used by the resolver to identify itself as the correct resolver to use. + /// + private Func _match; + + /// + /// Initializes a new instance of the class. + /// + /// The media file manager. + /// The hosting environment. + /// The global settings options. + /// The format utilities. + public FileSystemImageProvider(MediaFileManager mediaFileManager, IHostingEnvironment hostingEnvironment, IOptionsMonitor globalSettings, FormatUtilities formatUtilities) + { + _fileProvider = mediaFileManager.CreateFileProvider(); + + GlobalSettingsOnChange(globalSettings.CurrentValue, hostingEnvironment); + globalSettings.OnChange(o => GlobalSettingsOnChange(o, hostingEnvironment)); + + _formatUtilities = formatUtilities; + } + + /// + public ProcessingBehavior ProcessingBehavior { get; } = ProcessingBehavior.CommandOnly; + + /// + public Func Match + { + get => _match ?? IsMatch; + set => _match = value; + } + + private void GlobalSettingsOnChange(GlobalSettings options, IHostingEnvironment hostingEnvironment) + => _rootPath = hostingEnvironment.ToAbsolute(options.UmbracoMediaUrl); + + private bool IsMatch(HttpContext context) + => context.Request.Path.StartsWithSegments(_rootPath, StringComparison.InvariantCultureIgnoreCase); + + /// + public bool IsValidRequest(HttpContext context) + => _formatUtilities.GetExtensionFromUri(context.Request.GetDisplayUrl()) != null; + + /// + public Task GetAsync(HttpContext context) + { + if (context.Request.Path.StartsWithSegments(_rootPath, StringComparison.InvariantCultureIgnoreCase, out PathString subpath) == false || + _fileProvider.GetFileInfo(subpath) is not IFileInfo fileInfo || + fileInfo.Exists == false) + { + return Task.FromResult(null); + } + + var metadata = new ImageMetadata(fileInfo.LastModified.UtcDateTime, fileInfo.Length); + + return Task.FromResult(new PhysicalFileSystemResolver(fileInfo, metadata)); + } + } +} diff --git a/src/Umbraco.Web.Common/ImageProcessors/IUmbracoMediaFileProvider.cs b/src/Umbraco.Web.Common/ImageProcessors/IUmbracoMediaFileProvider.cs deleted file mode 100644 index 3c86c5f9cb05..000000000000 --- a/src/Umbraco.Web.Common/ImageProcessors/IUmbracoMediaFileProvider.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.Extensions.FileProviders; - -namespace Umbraco.Cms.Web.Common.ImageProcessors -{ - public interface IUmbracoMediaFileProvider : IFileProvider - { - } -} diff --git a/src/Umbraco.Web.Common/ImageProcessors/UmbracoMediaFileProvider.cs b/src/Umbraco.Web.Common/ImageProcessors/UmbracoMediaFileProvider.cs deleted file mode 100644 index 717710ab42e5..000000000000 --- a/src/Umbraco.Web.Common/ImageProcessors/UmbracoMediaFileProvider.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Options; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Hosting; - -namespace Umbraco.Cms.Web.Common.ImageProcessors -{ - internal class UmbracoMediaFileProvider : PhysicalFileProvider, IUmbracoMediaFileProvider - { - public UmbracoMediaFileProvider(IHostingEnvironment hostingEnvironment, IOptions globalSettings) - : base(hostingEnvironment.MapPathWebRoot(globalSettings.Value.UmbracoMediaPath)) - { - } - } -} diff --git a/src/Umbraco.Web.Common/ImageProcessors/UmbracoMediaPhysicalFileSystemProvider.cs b/src/Umbraco.Web.Common/ImageProcessors/UmbracoMediaPhysicalFileSystemProvider.cs deleted file mode 100644 index ebeb1eef8182..000000000000 --- a/src/Umbraco.Web.Common/ImageProcessors/UmbracoMediaPhysicalFileSystemProvider.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Options; -using SixLabors.ImageSharp.Web; -using SixLabors.ImageSharp.Web.Providers; -using SixLabors.ImageSharp.Web.Resolvers; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Web.Common.ImageProcessors -{ - public class UmbracoImageProvider : IImageProvider - { - private readonly IFileProvider _fileProvider; - private readonly FormatUtilities _formatUtilities; - private readonly string _mediaUrlPrefix; - - public UmbracoImageProvider(IUmbracoMediaFileProvider fileProvider, FormatUtilities formatUtilities, IOptions globalSettings) - { - _fileProvider = fileProvider; - _formatUtilities = formatUtilities; - _mediaUrlPrefix = globalSettings.Value.UmbracoMediaUrl.TrimStart(Core.Constants.CharArrays.Tilde); - } - - /// - public bool IsValidRequest(HttpContext context) => _formatUtilities.GetExtensionFromUri(context.Request.GetDisplayUrl()) != null; - - /// - public Task GetAsync(HttpContext context) - { - // Path has already been correctly parsed before here. - IFileInfo fileInfo = _fileProvider.GetFileInfo(context.Request.Path.Value.TrimStart(_mediaUrlPrefix)); - - // Check to see if the file exists. - if (!fileInfo.Exists) - { - return Task.FromResult(null); - } - - var metadata = new ImageMetadata(fileInfo.LastModified.UtcDateTime, fileInfo.Length); - return Task.FromResult(new PhysicalFileSystemResolver(fileInfo, metadata)); - } - - /// - public ProcessingBehavior ProcessingBehavior { get; } = ProcessingBehavior.CommandOnly; - - /// - public Func Match { get; set; } = _ => true; - } -} From d53af53f2b6339d0f1445dbed4137dd089e5aa31 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 28 Dec 2021 16:18:06 +0100 Subject: [PATCH 05/12] Fix issue resolving relative paths when media URL has changed --- src/Umbraco.Core/IO/PhysicalFileSystem.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index a3cdd8ce10e7..3da09a499c66 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -273,7 +273,7 @@ public string GetRelativePath(string fullPathOrUrl) return path.Substring(_rootUrl.Length).TrimStart(Constants.CharArrays.ForwardSlash); // unchanged - what else? - return path; + return path.TrimStart(Constants.CharArrays.ForwardSlash); } /// @@ -288,7 +288,7 @@ public string GetRelativePath(string fullPathOrUrl) public string GetFullPath(string path) { // normalize - var opath = path; + var originalPath = path; path = EnsureDirectorySeparatorChar(path); // FIXME: this part should go! @@ -321,7 +321,7 @@ public string GetFullPath(string path) // nothing prevents us to reach the file, security-wise, yet it is outside // this filesystem's root - throw - throw new UnauthorizedAccessException($"File original: [{opath}] full: [{path}] is outside this filesystem's root."); + throw new UnauthorizedAccessException($"File original: [{originalPath}] full: [{path}] is outside this filesystem's root."); } /// From 9ab79d2fa364be22b7b6e5fabf8dbcfb949e6a67 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 4 Jan 2022 11:45:16 +0100 Subject: [PATCH 06/12] Remove FileSystemFileProvider and require explicitly implementing IFileProviderFactory --- .../IO/FileSystemDirectoryContents.cs | 59 ------------------ .../IO/FileSystemDirectoryInfo.cs | 48 -------------- src/Umbraco.Core/IO/FileSystemFileInfo.cs | 48 -------------- src/Umbraco.Core/IO/FileSystemFileProvider.cs | 62 ------------------- src/Umbraco.Core/IO/IFileProviderFactory.cs | 2 +- src/Umbraco.Core/IO/MediaFileManager.cs | 4 +- src/Umbraco.Core/IO/ShadowWrapper.cs | 2 +- .../UmbracoApplicationBuilder.cs | 21 ++++--- .../UmbracoBuilder.ImageSharp.cs | 2 +- ...er.cs => MediaFileManagerImageProvider.cs} | 13 ++-- 10 files changed, 24 insertions(+), 237 deletions(-) delete mode 100644 src/Umbraco.Core/IO/FileSystemDirectoryContents.cs delete mode 100644 src/Umbraco.Core/IO/FileSystemDirectoryInfo.cs delete mode 100644 src/Umbraco.Core/IO/FileSystemFileInfo.cs delete mode 100644 src/Umbraco.Core/IO/FileSystemFileProvider.cs rename src/Umbraco.Web.Common/ImageProcessors/{FileSystemImageProvider.cs => MediaFileManagerImageProvider.cs} (78%) diff --git a/src/Umbraco.Core/IO/FileSystemDirectoryContents.cs b/src/Umbraco.Core/IO/FileSystemDirectoryContents.cs deleted file mode 100644 index 5767ceb1834e..000000000000 --- a/src/Umbraco.Core/IO/FileSystemDirectoryContents.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.FileProviders; - -namespace Umbraco.Cms.Core.IO -{ - /// - /// Represents the directory contents in an . - /// - /// - public class FileSystemDirectoryContents : IDirectoryContents - { - private readonly IFileSystem _fileSystem; - private readonly string _subpath; - private IEnumerable _entries = null!; - - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The subpath. - /// - /// fileSystem - /// or - /// subpath - /// - public FileSystemDirectoryContents(IFileSystem fileSystem, string subpath) - { - _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); - _subpath = subpath ?? throw new ArgumentNullException(nameof(subpath)); - } - - /// - public bool Exists => true; - - /// - public IEnumerator GetEnumerator() - { - EnsureInitialized(); - return _entries.GetEnumerator(); - } - - /// - IEnumerator IEnumerable.GetEnumerator() - { - EnsureInitialized(); - return _entries.GetEnumerator(); - } - - private void EnsureInitialized() - { - _entries = _fileSystem.GetDirectories(_subpath).Select(d => new FileSystemDirectoryInfo(_fileSystem, d)) - .Union(_fileSystem.GetFiles(_subpath).Select(f => new FileSystemFileInfo(_fileSystem, f))) - .ToList(); - } - } -} diff --git a/src/Umbraco.Core/IO/FileSystemDirectoryInfo.cs b/src/Umbraco.Core/IO/FileSystemDirectoryInfo.cs deleted file mode 100644 index fc233fa2b13c..000000000000 --- a/src/Umbraco.Core/IO/FileSystemDirectoryInfo.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.IO; -using Microsoft.Extensions.FileProviders; - -namespace Umbraco.Cms.Core.IO -{ - /// - /// Represents a directory in an . - /// - /// - public class FileSystemDirectoryInfo : IFileInfo - { - private readonly IFileSystem _fileSystem; - private readonly string _subpath; - - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The subpath. - public FileSystemDirectoryInfo(IFileSystem fileSystem, string subpath) - { - _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); - _subpath = subpath ?? throw new ArgumentNullException(nameof(subpath)); - } - - /// - public bool Exists => true; - - /// - public bool IsDirectory => true; - - /// - public DateTimeOffset LastModified => _fileSystem.GetLastModified(_subpath); - - /// - public long Length => -1; - - /// - public string Name => _fileSystem.GetRelativePath(_subpath); - - /// - public string PhysicalPath => null!; - - /// - public Stream CreateReadStream() => throw new InvalidOperationException("Cannot create a stream for a directory."); - } -} diff --git a/src/Umbraco.Core/IO/FileSystemFileInfo.cs b/src/Umbraco.Core/IO/FileSystemFileInfo.cs deleted file mode 100644 index 6762a5c696d8..000000000000 --- a/src/Umbraco.Core/IO/FileSystemFileInfo.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.IO; -using Microsoft.Extensions.FileProviders; - -namespace Umbraco.Cms.Core.IO -{ - /// - /// Represents a file in an . - /// - /// - public class FileSystemFileInfo : IFileInfo - { - private readonly IFileSystem _fileSystem; - private readonly string _subpath; - - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The subpath. - public FileSystemFileInfo(IFileSystem fileSystem, string subpath) - { - _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); - _subpath = subpath ?? throw new ArgumentNullException(nameof(subpath)); - } - - /// - public bool Exists => true; - - /// - public bool IsDirectory => false; - - /// - public DateTimeOffset LastModified => _fileSystem.GetLastModified(_subpath); - - /// - public long Length => _fileSystem.GetSize(_subpath); - - /// - public string Name => _fileSystem.GetRelativePath(_subpath); - - /// - public string PhysicalPath => null!; - - /// - public Stream CreateReadStream() => _fileSystem.OpenFile(_subpath); - } -} diff --git a/src/Umbraco.Core/IO/FileSystemFileProvider.cs b/src/Umbraco.Core/IO/FileSystemFileProvider.cs deleted file mode 100644 index 313e55fdd63c..000000000000 --- a/src/Umbraco.Core/IO/FileSystemFileProvider.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Primitives; - -namespace Umbraco.Cms.Core.IO -{ - /// - /// Exposes an as an . - /// - /// - public class FileSystemFileProvider : IFileProvider - { - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The path prefix. - /// fileSystem - public FileSystemFileProvider(IFileSystem fileSystem, string pathPrefix = null) - { - FileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); - PathPrefix = pathPrefix; - } - - /// - /// Gets the file system. - /// - protected IFileSystem FileSystem { get; } - - /// - /// Gets the path prefix. - /// - protected string PathPrefix { get; } - - /// - public IDirectoryContents GetDirectoryContents(string subpath) - { - var path = PathPrefix + subpath; - if (path == null || !FileSystem.DirectoryExists(path)) - { - return NotFoundDirectoryContents.Singleton; - } - - return new FileSystemDirectoryContents(FileSystem, path); - } - - /// - public IFileInfo GetFileInfo(string subpath) - { - var path = PathPrefix + subpath; - if (path == null || !FileSystem.FileExists(path)) - { - return new NotFoundFileInfo(path); - } - - return new FileSystemFileInfo(FileSystem, path); - } - - /// - public IChangeToken Watch(string filter) => NullChangeToken.Singleton; - } -} diff --git a/src/Umbraco.Core/IO/IFileProviderFactory.cs b/src/Umbraco.Core/IO/IFileProviderFactory.cs index 9f4aed967d0c..742467ccc843 100644 --- a/src/Umbraco.Core/IO/IFileProviderFactory.cs +++ b/src/Umbraco.Core/IO/IFileProviderFactory.cs @@ -11,7 +11,7 @@ public interface IFileProviderFactory /// Creates a new instance. /// /// - /// The newly created instance. + /// The newly created instance (or null if not supported). /// IFileProvider Create(); } diff --git a/src/Umbraco.Core/IO/MediaFileManager.cs b/src/Umbraco.Core/IO/MediaFileManager.cs index aa6dd0224f5f..bc6e1681820f 100644 --- a/src/Umbraco.Core/IO/MediaFileManager.cs +++ b/src/Umbraco.Core/IO/MediaFileManager.cs @@ -57,12 +57,12 @@ public MediaFileManager( /// Create an instance for the media file system. /// /// - /// The for the media file system. + /// The for the media file system (or null if not supported). /// public IFileProvider CreateFileProvider() => FileSystem switch { IFileProviderFactory fileProviderFactory => fileProviderFactory.Create(), - var fileSystem => new FileSystemFileProvider(fileSystem) + _ => null }; /// diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs index e99a247aad01..5c307e361ac3 100644 --- a/src/Umbraco.Core/IO/ShadowWrapper.cs +++ b/src/Umbraco.Core/IO/ShadowWrapper.cs @@ -226,7 +226,7 @@ public void AddFile(string path, string physicalPath, bool overrideIfExists = tr public IFileProvider Create() => _innerFileSystem switch { IFileProviderFactory fileProviderFactory => fileProviderFactory.Create(), - var fileSystem => new FileSystemFileProvider(fileSystem) + _ => null }; } } diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs index 8c4e1c243e0b..8777b36caae4 100644 --- a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs +++ b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs @@ -97,16 +97,19 @@ public void RegisterDefaultRequiredMiddleware() // Get media file provider and request path/URL IFileProvider mediaFileProvider = AppBuilder.ApplicationServices.GetRequiredService().CreateFileProvider(); - - GlobalSettings globalSettings = AppBuilder.ApplicationServices.GetRequiredService>().Value; - IHostingEnvironment hostingEnvironment = AppBuilder.ApplicationServices.GetService(); - string mediaRequestPath = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaUrl); - - AppBuilder.UseStaticFiles(new StaticFileOptions() + if (mediaFileProvider is not null) { - FileProvider = mediaFileProvider, - RequestPath = mediaRequestPath - }); + GlobalSettings globalSettings = AppBuilder.ApplicationServices.GetRequiredService>().Value; + IHostingEnvironment hostingEnvironment = AppBuilder.ApplicationServices.GetService(); + string mediaRequestPath = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaUrl); + + // Configure custom file provider for media + AppBuilder.UseStaticFiles(new StaticFileOptions() + { + FileProvider = mediaFileProvider, + RequestPath = mediaRequestPath + }); + } AppBuilder.UseStaticFiles(); diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index ff9dd9b1283b..5c99ec14791e 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -79,7 +79,7 @@ public static IServiceCollection AddUmbracoImageSharp(this IUmbracoBuilder build builder.Services.AddTransient, ImageSharpConfigurationOptions>(); // Add FileSystemImageProvider before default provider - builder.Services.Insert(0, ServiceDescriptor.Singleton()); + builder.Services.Insert(0, ServiceDescriptor.Singleton()); return builder.Services; } diff --git a/src/Umbraco.Web.Common/ImageProcessors/FileSystemImageProvider.cs b/src/Umbraco.Web.Common/ImageProcessors/MediaFileManagerImageProvider.cs similarity index 78% rename from src/Umbraco.Web.Common/ImageProcessors/FileSystemImageProvider.cs rename to src/Umbraco.Web.Common/ImageProcessors/MediaFileManagerImageProvider.cs index 60fe9f41d9c0..ce13d1810d07 100644 --- a/src/Umbraco.Web.Common/ImageProcessors/FileSystemImageProvider.cs +++ b/src/Umbraco.Web.Common/ImageProcessors/MediaFileManagerImageProvider.cs @@ -14,7 +14,7 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors { /// - public class FileSystemImageProvider : IImageProvider + public class MediaFileManagerImageProvider : IImageProvider { private readonly IFileProvider _fileProvider; private string _rootPath; @@ -26,13 +26,13 @@ public class FileSystemImageProvider : IImageProvider private Func _match; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The media file manager. /// The hosting environment. /// The global settings options. /// The format utilities. - public FileSystemImageProvider(MediaFileManager mediaFileManager, IHostingEnvironment hostingEnvironment, IOptionsMonitor globalSettings, FormatUtilities formatUtilities) + public MediaFileManagerImageProvider(MediaFileManager mediaFileManager, IHostingEnvironment hostingEnvironment, IOptionsMonitor globalSettings, FormatUtilities formatUtilities) { _fileProvider = mediaFileManager.CreateFileProvider(); @@ -56,16 +56,17 @@ private void GlobalSettingsOnChange(GlobalSettings options, IHostingEnvironment => _rootPath = hostingEnvironment.ToAbsolute(options.UmbracoMediaUrl); private bool IsMatch(HttpContext context) - => context.Request.Path.StartsWithSegments(_rootPath, StringComparison.InvariantCultureIgnoreCase); + => _fileProvider is not null && context.Request.Path.StartsWithSegments(_rootPath, StringComparison.InvariantCultureIgnoreCase); /// public bool IsValidRequest(HttpContext context) - => _formatUtilities.GetExtensionFromUri(context.Request.GetDisplayUrl()) != null; + => _fileProvider is not null && _formatUtilities.GetExtensionFromUri(context.Request.GetDisplayUrl()) is not null; /// public Task GetAsync(HttpContext context) { - if (context.Request.Path.StartsWithSegments(_rootPath, StringComparison.InvariantCultureIgnoreCase, out PathString subpath) == false || + if (_fileProvider is null || + context.Request.Path.StartsWithSegments(_rootPath, StringComparison.InvariantCultureIgnoreCase, out PathString subpath) == false || _fileProvider.GetFileInfo(subpath) is not IFileInfo fileInfo || fileInfo.Exists == false) { From 9fbce556c6249cb143d23de4294a7c08270dca0f Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 4 Jan 2022 13:11:27 +0100 Subject: [PATCH 07/12] Update tests (UnauthorizedAccessException isn't thrown anymore for rooted files) --- .../Persistence/Repositories/PartialViewRepositoryTests.cs | 6 +++--- .../Persistence/Repositories/ScriptRepositoryTest.cs | 6 +++--- .../Persistence/Repositories/StylesheetRepositoryTest.cs | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs index 02709f7f84c2..721a806f6521 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs @@ -86,15 +86,15 @@ public void PathTests() Assert.AreEqual("path-2\\test-path-3.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path); Assert.AreEqual("/Views/Partials/path-2/test-path-3.cshtml", partialView.VirtualPath); - partialView = new PartialView(PartialViewType.PartialView, "\\test-path-4.cshtml") { Content = "// partialView" }; - Assert.Throws(() => // fixed in 7.3 - 7.2.8 used to strip the \ + partialView = new PartialView(PartialViewType.PartialView, "..\\test-path-4.cshtml") { Content = "// partialView" }; + Assert.Throws(() => repository.Save(partialView)); partialView = (PartialView)repository.Get("missing.cshtml"); Assert.IsNull(partialView); // fixed in 7.3 - 7.2.8 used to... - Assert.Throws(() => partialView = (PartialView)repository.Get("\\test-path-4.cshtml")); + Assert.Throws(() => partialView = (PartialView)repository.Get("..\\test-path-4.cshtml")); Assert.Throws(() => partialView = (PartialView)repository.Get("../../packages.config")); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs index 28f9a9eff15e..19e6a8303247 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs @@ -303,15 +303,15 @@ public void PathTests() Assert.AreEqual("path-2\\test-path-3.js".Replace("\\", $"{Path.DirectorySeparatorChar}"), script.Path); Assert.AreEqual("/scripts/path-2/test-path-3.js", script.VirtualPath); - script = new Script("\\test-path-4.js") { Content = "// script" }; - Assert.Throws(() => // fixed in 7.3 - 7.2.8 used to strip the \ + script = new Script("..\\test-path-4.js") { Content = "// script" }; + Assert.Throws(() => repository.Save(script)); script = repository.Get("missing.js"); Assert.IsNull(script); // fixed in 7.3 - 7.2.8 used to... - Assert.Throws(() => script = repository.Get("\\test-path-4.js")); + Assert.Throws(() => script = repository.Get("..\\test-path-4.js")); Assert.Throws(() => script = repository.Get("../packages.config")); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs index d9bde0bca2ec..9581ada1a948 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs @@ -300,8 +300,8 @@ public void PathTests() Assert.AreEqual("path-2\\test-path-3.css".Replace("\\", $"{Path.DirectorySeparatorChar}"), stylesheet.Path); Assert.AreEqual("/css/path-2/test-path-3.css", stylesheet.VirtualPath); - stylesheet = new Stylesheet("\\test-path-4.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; - Assert.Throws(() => // fixed in 7.3 - 7.2.8 used to strip the \ + stylesheet = new Stylesheet("..\\test-path-4.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; + Assert.Throws(() => repository.Save(stylesheet)); // fixed in 7.3 - 7.2.8 used to throw @@ -310,7 +310,7 @@ public void PathTests() // #7713 changes behaviour to return null when outside the filesystem // to accomodate changing the CSS path and not flooding the backoffice with errors - stylesheet = repository.Get("\\test-path-4.css"); // outside the filesystem, does not exist + stylesheet = repository.Get("..\\test-path-4.css"); // outside the filesystem, does not exist Assert.IsNull(stylesheet); stylesheet = repository.Get("../packages.config"); // outside the filesystem, exists From c0754c488832b995678df0579f7ed779b8463642 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 4 Jan 2022 13:49:39 +0100 Subject: [PATCH 08/12] Update test to use UmbracoMediaUrl --- .../Umbraco.Infrastructure/Scoping/ScopeFileSystemsTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeFileSystemsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeFileSystemsTests.cs index e30d7bbf5542..704919c3da7f 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeFileSystemsTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeFileSystemsTests.cs @@ -49,7 +49,7 @@ private void ClearFiles(IIOHelper ioHelper) public void MediaFileManager_does_not_write_to_physical_file_system_when_scoped_if_scope_does_not_complete() { string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPath); - string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaPath); + string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaUrl); var physMediaFileSystem = new PhysicalFileSystem(IOHelper, HostingEnvironment, GetRequiredService>(), rootPath, rootUrl); MediaFileManager mediaFileManager = MediaFileManager; @@ -78,7 +78,7 @@ public void MediaFileManager_does_not_write_to_physical_file_system_when_scoped_ public void MediaFileManager_writes_to_physical_file_system_when_scoped_and_scope_is_completed() { string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPath); - string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaPath); + string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaUrl); var physMediaFileSystem = new PhysicalFileSystem(IOHelper, HostingEnvironment, GetRequiredService>(), rootPath, rootUrl); MediaFileManager mediaFileManager = MediaFileManager; @@ -109,7 +109,7 @@ public void MediaFileManager_writes_to_physical_file_system_when_scoped_and_scop public void MultiThread() { string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPath); - string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaPath); + string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaUrl); var physMediaFileSystem = new PhysicalFileSystem(IOHelper, HostingEnvironment, GetRequiredService>(), rootPath, rootUrl); MediaFileManager mediaFileManager = MediaFileManager; var taskHelper = new TaskHelper(Mock.Of>()); From 08a68cd19c8212af6d233b23e03307dd0d39b9d4 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 4 Jan 2022 15:09:49 +0100 Subject: [PATCH 09/12] Add UmbracoMediaPhysicalRootPath global setting --- .../Configuration/Models/GlobalSettings.cs | 9 ++++++--- .../UmbracoBuilder.Configuration.cs | 4 ++-- src/Umbraco.Core/Packaging/PackagesRepository.cs | 2 +- .../Runtime/EssentialDirectoryCreator.cs | 2 +- .../UmbracoBuilder.FileSystems.cs | 4 ++-- .../Install/FilePermissionHelper.cs | 4 ++-- .../Implement/CreatedPackageSchemaRepository.cs | 2 +- .../Controllers/BackOfficeServerVariables.cs | 2 +- .../ApplicationBuilder/UmbracoApplicationBuilder.cs | 2 +- .../ImageProcessors/MediaFileManagerImageProvider.cs | 2 +- .../Scoping/ScopeFileSystemsTests.cs | 12 ++++++------ .../TestHelpers/TestHelper.cs | 4 ++-- 12 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs index 65b1622137b6..7e3e1a27006d 100644 --- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs @@ -102,15 +102,18 @@ public class GlobalSettings public string UmbracoScriptsPath { get; set; } = StaticUmbracoScriptsPath; /// - /// Gets or sets a value for the Umbraco media path. + /// Gets or sets a value for the Umbraco media request path. /// [DefaultValue(StaticUmbracoMediaPath)] public string UmbracoMediaPath { get; set; } = StaticUmbracoMediaPath; /// - /// Gets or sets a value for the Umbraco media URL (falls back to when empty). + /// Gets or sets a value for the physical Umbraco media root path (falls back to when empty). /// - public string UmbracoMediaUrl { get; set; } + /// + /// If the value is a virtual path, it's resolved relative to the webroot. + /// + public string UmbracoMediaPhysicalRootPath { get; set; } /// /// Gets or sets a value indicating whether to install the database when it is missing. diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index 03b7194b53b2..ce2e4f230404 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -56,9 +56,9 @@ public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder) .AddUmbracoOptions() .AddUmbracoOptions(optionsBuilder => optionsBuilder.PostConfigure(options => { - if (string.IsNullOrEmpty(options.UmbracoMediaUrl)) + if (string.IsNullOrEmpty(options.UmbracoMediaPhysicalRootPath)) { - options.UmbracoMediaUrl = options.UmbracoMediaPath; + options.UmbracoMediaPhysicalRootPath = options.UmbracoMediaPath; } })) .AddUmbracoOptions() diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs index d14ccc210aa1..36b7a5d5d5fe 100644 --- a/src/Umbraco.Core/Packaging/PackagesRepository.cs +++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs @@ -93,7 +93,7 @@ public PackagesRepository( _tempFolderPath = tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles"; _packagesFolderPath = packagesFolderPath ?? Constants.SystemDirectories.Packages; - _mediaFolderPath = mediaFolderPath ?? Path.Combine(globalSettings.Value.UmbracoMediaPath, Constants.SystemDirectories.CreatedPackages); + _mediaFolderPath = mediaFolderPath ?? Path.Combine(globalSettings.Value.UmbracoMediaPhysicalRootPath, Constants.SystemDirectories.CreatedPackages); _parser = new PackageDefinitionXmlParser(); _mediaService = mediaService; diff --git a/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs index a9564712c332..6c45e4d96915 100644 --- a/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs +++ b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs @@ -25,7 +25,7 @@ public void Handle(UmbracoApplicationStartingNotification notification) // ensure we have some essential directories // every other component can then initialize safely _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data)); - _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoMediaPath)); + _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoMediaPhysicalRootPath)); _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MvcViews)); _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.PartialViews)); _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials)); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs index f80935ae7d72..f66991fb6858 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs @@ -49,8 +49,8 @@ internal static IUmbracoBuilder AddFileSystems(this IUmbracoBuilder builder) ILogger logger = factory.GetRequiredService>(); GlobalSettings globalSettings = factory.GetRequiredService>().Value; - var rootPath = hostingEnvironment.MapPathWebRoot(globalSettings.UmbracoMediaPath); - var rootUrl = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaUrl); + var rootPath = hostingEnvironment.MapPathWebRoot(globalSettings.UmbracoMediaPhysicalRootPath); + var rootUrl = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaPath); return new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, rootPath, rootUrl); }); diff --git a/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs b/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs index 0ad2271d7e05..1d228ebf9883 100644 --- a/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs +++ b/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs @@ -44,7 +44,7 @@ public FilePermissionHelper(IOptions globalSettings, IIOHelper i hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoCssPath), hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Config), hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data), - hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoMediaPath), + hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoMediaPhysicalRootPath), hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Preview) }; _packagesPermissionsDirs = new[] @@ -70,7 +70,7 @@ public bool RunFilePermissionTestSuite(out Dictionary x.Value.Count()) == 0; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs index 9d78f8c124a2..be1a31c2c9d2 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs @@ -76,7 +76,7 @@ public CreatedPackageSchemaRepository( _macroService = macroService; _contentTypeService = contentTypeService; _xmlParser = new PackageDefinitionXmlParser(); - _mediaFolderPath = mediaFolderPath ?? Path.Combine(globalSettings.Value.UmbracoMediaPath, Constants.SystemDirectories.CreatedPackages); + _mediaFolderPath = mediaFolderPath ?? Path.Combine(globalSettings.Value.UmbracoMediaPhysicalRootPath, Constants.SystemDirectories.CreatedPackages); _tempFolderPath = tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles"; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs index 6d9118ffd9a8..43723207d39f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs @@ -384,7 +384,7 @@ internal async Task> GetServerVariablesAsync() "umbracoSettings", new Dictionary { {"umbracoPath", _globalSettings.GetBackOfficePath(_hostingEnvironment)}, - {"mediaPath", _hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaUrl).TrimEnd(Constants.CharArrays.ForwardSlash)}, + {"mediaPath", _hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaPath).TrimEnd(Constants.CharArrays.ForwardSlash)}, {"appPluginsPath", _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.AppPlugins).TrimEnd(Constants.CharArrays.ForwardSlash)}, { "imageFileTypes", diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs index 8777b36caae4..3f5129f855f0 100644 --- a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs +++ b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs @@ -101,7 +101,7 @@ public void RegisterDefaultRequiredMiddleware() { GlobalSettings globalSettings = AppBuilder.ApplicationServices.GetRequiredService>().Value; IHostingEnvironment hostingEnvironment = AppBuilder.ApplicationServices.GetService(); - string mediaRequestPath = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaUrl); + string mediaRequestPath = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaPath); // Configure custom file provider for media AppBuilder.UseStaticFiles(new StaticFileOptions() diff --git a/src/Umbraco.Web.Common/ImageProcessors/MediaFileManagerImageProvider.cs b/src/Umbraco.Web.Common/ImageProcessors/MediaFileManagerImageProvider.cs index ce13d1810d07..f7a20bd62cc5 100644 --- a/src/Umbraco.Web.Common/ImageProcessors/MediaFileManagerImageProvider.cs +++ b/src/Umbraco.Web.Common/ImageProcessors/MediaFileManagerImageProvider.cs @@ -53,7 +53,7 @@ public Func Match } private void GlobalSettingsOnChange(GlobalSettings options, IHostingEnvironment hostingEnvironment) - => _rootPath = hostingEnvironment.ToAbsolute(options.UmbracoMediaUrl); + => _rootPath = hostingEnvironment.ToAbsolute(options.UmbracoMediaPath); private bool IsMatch(HttpContext context) => _fileProvider is not null && context.Request.Path.StartsWithSegments(_rootPath, StringComparison.InvariantCultureIgnoreCase); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeFileSystemsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeFileSystemsTests.cs index 704919c3da7f..7ea8e65edaea 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeFileSystemsTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeFileSystemsTests.cs @@ -48,8 +48,8 @@ private void ClearFiles(IIOHelper ioHelper) [Test] public void MediaFileManager_does_not_write_to_physical_file_system_when_scoped_if_scope_does_not_complete() { - string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPath); - string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaUrl); + string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPhysicalRootPath); + string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaPath); var physMediaFileSystem = new PhysicalFileSystem(IOHelper, HostingEnvironment, GetRequiredService>(), rootPath, rootUrl); MediaFileManager mediaFileManager = MediaFileManager; @@ -77,8 +77,8 @@ public void MediaFileManager_does_not_write_to_physical_file_system_when_scoped_ [Test] public void MediaFileManager_writes_to_physical_file_system_when_scoped_and_scope_is_completed() { - string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPath); - string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaUrl); + string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPhysicalRootPath); + string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaPath); var physMediaFileSystem = new PhysicalFileSystem(IOHelper, HostingEnvironment, GetRequiredService>(), rootPath, rootUrl); MediaFileManager mediaFileManager = MediaFileManager; @@ -108,8 +108,8 @@ public void MediaFileManager_writes_to_physical_file_system_when_scoped_and_scop [Test] public void MultiThread() { - string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPath); - string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaUrl); + string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPhysicalRootPath); + string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaPath); var physMediaFileSystem = new PhysicalFileSystem(IOHelper, HostingEnvironment, GetRequiredService>(), rootPath, rootUrl); MediaFileManager mediaFileManager = MediaFileManager; var taskHelper = new TaskHelper(Mock.Of>()); diff --git a/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs b/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs index 8dee7db12d67..ba89c9775efa 100644 --- a/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs +++ b/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs @@ -134,9 +134,9 @@ public static MapperConfigurationStore CreateMaps() /// public static string MapPathForTestFiles(string relativePath) => s_testHelperInternal.MapPathForTestFiles(relativePath); - public static void InitializeContentDirectories() => CreateDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPath, Constants.SystemDirectories.AppPlugins }); + public static void InitializeContentDirectories() => CreateDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPhysicalRootPath, Constants.SystemDirectories.AppPlugins }); - public static void CleanContentDirectories() => CleanDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPath }); + public static void CleanContentDirectories() => CleanDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPhysicalRootPath }); public static void CreateDirectories(string[] directories) { From 58baf54c9806fcb0a1b3ee6387ccb41f19dd0660 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 5 Jan 2022 13:46:12 +0100 Subject: [PATCH 10/12] Remove MediaFileManagerImageProvider and use composited file providers --- .../UmbracoApplicationBuilder.cs | 11 ++- .../UmbracoBuilder.ImageSharp.cs | 4 - .../ApplicationBuilderExtensions.cs | 29 ++++--- .../Extensions/FileProviderExtensions.cs | 19 +++++ .../MediaFileManagerImageProvider.cs | 81 ------------------- 5 files changed, 38 insertions(+), 106 deletions(-) create mode 100644 src/Umbraco.Web.Common/Extensions/FileProviderExtensions.cs delete mode 100644 src/Umbraco.Web.Common/ImageProcessors/MediaFileManagerImageProvider.cs diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs index 3f5129f855f0..d59f082547cd 100644 --- a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs +++ b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs @@ -1,14 +1,16 @@ using System; +using Dazinator.Extensions.FileProviders.PrependBasePath; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Options; using SixLabors.ImageSharp.Web.DependencyInjection; using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; +using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; namespace Umbraco.Cms.Web.Common.ApplicationBuilder { @@ -104,11 +106,8 @@ public void RegisterDefaultRequiredMiddleware() string mediaRequestPath = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaPath); // Configure custom file provider for media - AppBuilder.UseStaticFiles(new StaticFileOptions() - { - FileProvider = mediaFileProvider, - RequestPath = mediaRequestPath - }); + IWebHostEnvironment webHostEnvironment = AppBuilder.ApplicationServices.GetService(); + webHostEnvironment.WebRootFileProvider = webHostEnvironment.WebRootFileProvider.ConcatComposite(new PrependBasePathFileProvider(mediaRequestPath, mediaFileProvider)); } AppBuilder.UseStaticFiles(); diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index 5c99ec14791e..30331fd812fd 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -10,7 +10,6 @@ using SixLabors.ImageSharp.Web.DependencyInjection; using SixLabors.ImageSharp.Web.Middleware; using SixLabors.ImageSharp.Web.Processors; -using SixLabors.ImageSharp.Web.Providers; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Web.Common.DependencyInjection; @@ -78,9 +77,6 @@ public static IServiceCollection AddUmbracoImageSharp(this IUmbracoBuilder build // Configure middleware to use the registered/shared ImageSharp configuration builder.Services.AddTransient, ImageSharpConfigurationOptions>(); - // Add FileSystemImageProvider before default provider - builder.Services.Insert(0, ServiceDescriptor.Singleton()); - return builder.Services; } } diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs index cc035d116b8d..fc6a2904dfbe 100644 --- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs @@ -1,18 +1,20 @@ using System; using System.IO; +using Dazinator.Extensions.FileProviders.PrependBasePath; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Serilog.Context; using StackExchange.Profiling; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Logging.Serilog.Enrichers; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.ApplicationBuilder; using Umbraco.Cms.Web.Common.Middleware; using Umbraco.Cms.Web.Common.Plugins; +using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; namespace Umbraco.Extensions { @@ -94,7 +96,8 @@ public static IApplicationBuilder UseUmbracoRequestLogging(this IApplicationBuil throw new ArgumentNullException(nameof(app)); } - if (!app.UmbracoCanBoot()) return app; + if (!app.UmbracoCanBoot()) + return app; app.UseMiddleware(); @@ -109,25 +112,21 @@ public static IApplicationBuilder UseUmbracoRequestLogging(this IApplicationBuil public static IApplicationBuilder UseUmbracoPluginsStaticFiles(this IApplicationBuilder app) { var hostingEnvironment = app.ApplicationServices.GetRequiredService(); - var umbracoPluginSettings = app.ApplicationServices.GetRequiredService>(); var pluginFolder = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins); + if (Directory.Exists(pluginFolder)) + { + var umbracoPluginSettings = app.ApplicationServices.GetRequiredService>(); - // Ensure the plugin folder exists - Directory.CreateDirectory(pluginFolder); - - var fileProvider = new UmbracoPluginPhysicalFileProvider( - pluginFolder, - umbracoPluginSettings); + var pluginFileProvider = new UmbracoPluginPhysicalFileProvider( + pluginFolder, + umbracoPluginSettings); - app.UseStaticFiles(new StaticFileOptions - { - FileProvider = fileProvider, - RequestPath = Constants.SystemDirectories.AppPlugins - }); + IWebHostEnvironment webHostEnvironment = app.ApplicationServices.GetService(); + webHostEnvironment.WebRootFileProvider = webHostEnvironment.WebRootFileProvider.ConcatComposite(new PrependBasePathFileProvider(Constants.SystemDirectories.AppPlugins, pluginFileProvider)); + } return app; } } - } diff --git a/src/Umbraco.Web.Common/Extensions/FileProviderExtensions.cs b/src/Umbraco.Web.Common/Extensions/FileProviderExtensions.cs new file mode 100644 index 000000000000..35a2882bdd5d --- /dev/null +++ b/src/Umbraco.Web.Common/Extensions/FileProviderExtensions.cs @@ -0,0 +1,19 @@ +using System.Linq; +using Microsoft.Extensions.FileProviders; + +namespace Umbraco.Extensions +{ + internal static class FileProviderExtensions + { + public static IFileProvider ConcatComposite(this IFileProvider fileProvider, params IFileProvider[] fileProviders) + { + var existingFileProviders = fileProvider switch + { + CompositeFileProvider compositeFileProvider => compositeFileProvider.FileProviders, + _ => new[] { fileProvider } + }; + + return new CompositeFileProvider(existingFileProviders.Concat(fileProviders)); + } + } +} diff --git a/src/Umbraco.Web.Common/ImageProcessors/MediaFileManagerImageProvider.cs b/src/Umbraco.Web.Common/ImageProcessors/MediaFileManagerImageProvider.cs deleted file mode 100644 index f7a20bd62cc5..000000000000 --- a/src/Umbraco.Web.Common/ImageProcessors/MediaFileManagerImageProvider.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Options; -using SixLabors.ImageSharp.Web; -using SixLabors.ImageSharp.Web.Providers; -using SixLabors.ImageSharp.Web.Resolvers; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.IO; - -namespace Umbraco.Cms.Web.Common.ImageProcessors -{ - /// - public class MediaFileManagerImageProvider : IImageProvider - { - private readonly IFileProvider _fileProvider; - private string _rootPath; - private readonly FormatUtilities _formatUtilities; - - /// - /// A match function used by the resolver to identify itself as the correct resolver to use. - /// - private Func _match; - - /// - /// Initializes a new instance of the class. - /// - /// The media file manager. - /// The hosting environment. - /// The global settings options. - /// The format utilities. - public MediaFileManagerImageProvider(MediaFileManager mediaFileManager, IHostingEnvironment hostingEnvironment, IOptionsMonitor globalSettings, FormatUtilities formatUtilities) - { - _fileProvider = mediaFileManager.CreateFileProvider(); - - GlobalSettingsOnChange(globalSettings.CurrentValue, hostingEnvironment); - globalSettings.OnChange(o => GlobalSettingsOnChange(o, hostingEnvironment)); - - _formatUtilities = formatUtilities; - } - - /// - public ProcessingBehavior ProcessingBehavior { get; } = ProcessingBehavior.CommandOnly; - - /// - public Func Match - { - get => _match ?? IsMatch; - set => _match = value; - } - - private void GlobalSettingsOnChange(GlobalSettings options, IHostingEnvironment hostingEnvironment) - => _rootPath = hostingEnvironment.ToAbsolute(options.UmbracoMediaPath); - - private bool IsMatch(HttpContext context) - => _fileProvider is not null && context.Request.Path.StartsWithSegments(_rootPath, StringComparison.InvariantCultureIgnoreCase); - - /// - public bool IsValidRequest(HttpContext context) - => _fileProvider is not null && _formatUtilities.GetExtensionFromUri(context.Request.GetDisplayUrl()) is not null; - - /// - public Task GetAsync(HttpContext context) - { - if (_fileProvider is null || - context.Request.Path.StartsWithSegments(_rootPath, StringComparison.InvariantCultureIgnoreCase, out PathString subpath) == false || - _fileProvider.GetFileInfo(subpath) is not IFileInfo fileInfo || - fileInfo.Exists == false) - { - return Task.FromResult(null); - } - - var metadata = new ImageMetadata(fileInfo.LastModified.UtcDateTime, fileInfo.Length); - - return Task.FromResult(new PhysicalFileSystemResolver(fileInfo, metadata)); - } - } -} From 41e8a2ac91ca0997969348adc56e38b0bdad6ce3 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 5 Jan 2022 14:16:38 +0100 Subject: [PATCH 11/12] Move CreateFileProvider to IFileSystem extension method --- src/Umbraco.Core/IO/FileSystemExtensions.cs | 20 +++++++++++++++++++ src/Umbraco.Core/IO/MediaFileManager.cs | 13 ------------ src/Umbraco.Core/IO/ShadowWrapper.cs | 6 +----- .../UmbracoApplicationBuilder.cs | 4 ++-- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Core/IO/FileSystemExtensions.cs b/src/Umbraco.Core/IO/FileSystemExtensions.cs index 23be195e4b16..c95d37e1c3c2 100644 --- a/src/Umbraco.Core/IO/FileSystemExtensions.cs +++ b/src/Umbraco.Core/IO/FileSystemExtensions.cs @@ -3,6 +3,7 @@ using System.Security.Cryptography; using System.Text; using System.Threading; +using Microsoft.Extensions.FileProviders; using Umbraco.Cms.Core.IO; namespace Umbraco.Extensions @@ -87,5 +88,24 @@ public static void CreateFolder(this IFileSystem fs, string folderPath) } fs.DeleteFile(tempFile); } + + /// + /// Creates a new from the file system. + /// + /// The file system. + /// When this method returns, contains an created from the file system. + /// + /// true if the was successfully created; otherwise, false. + /// + public static bool TryCreateFileProvider(this IFileSystem fileSystem, out IFileProvider fileProvider) + { + fileProvider = fileSystem switch + { + IFileProviderFactory fileProviderFactory => fileProviderFactory.Create(), + _ => null + }; + + return fileProvider != null; + } } } diff --git a/src/Umbraco.Core/IO/MediaFileManager.cs b/src/Umbraco.Core/IO/MediaFileManager.cs index bc6e1681820f..c769b9801e41 100644 --- a/src/Umbraco.Core/IO/MediaFileManager.cs +++ b/src/Umbraco.Core/IO/MediaFileManager.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; @@ -53,18 +52,6 @@ public MediaFileManager( /// public IFileSystem FileSystem { get; } - /// - /// Create an instance for the media file system. - /// - /// - /// The for the media file system (or null if not supported). - /// - public IFileProvider CreateFileProvider() => FileSystem switch - { - IFileProviderFactory fileProviderFactory => fileProviderFactory.Create(), - _ => null - }; - /// /// Delete media files. /// diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs index 5c307e361ac3..7bf315a5756f 100644 --- a/src/Umbraco.Core/IO/ShadowWrapper.cs +++ b/src/Umbraco.Core/IO/ShadowWrapper.cs @@ -223,10 +223,6 @@ public void AddFile(string path, string physicalPath, bool overrideIfExists = tr } /// - public IFileProvider Create() => _innerFileSystem switch - { - IFileProviderFactory fileProviderFactory => fileProviderFactory.Create(), - _ => null - }; + public IFileProvider Create() => _innerFileSystem.TryCreateFileProvider(out IFileProvider fileProvider) ? fileProvider : null; } } diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs index d59f082547cd..58f66a6fb8c5 100644 --- a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs +++ b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs @@ -98,8 +98,8 @@ public void RegisterDefaultRequiredMiddleware() AppBuilder.UseImageSharp(); // Get media file provider and request path/URL - IFileProvider mediaFileProvider = AppBuilder.ApplicationServices.GetRequiredService().CreateFileProvider(); - if (mediaFileProvider is not null) + var mediaFileManager = AppBuilder.ApplicationServices.GetRequiredService(); + if (mediaFileManager.FileSystem.TryCreateFileProvider(out IFileProvider mediaFileProvider)) { GlobalSettings globalSettings = AppBuilder.ApplicationServices.GetRequiredService>().Value; IHostingEnvironment hostingEnvironment = AppBuilder.ApplicationServices.GetService(); From 143b345dc7f648840eca38fa2a083acad03b6908 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 6 Jan 2022 10:40:19 +0100 Subject: [PATCH 12/12] Add rooted path tests --- .../PartialViewRepositoryTests.cs | 25 ++++++++++++------- .../Repositories/ScriptRepositoryTest.cs | 9 ++++++- .../Repositories/StylesheetRepositoryTest.cs | 11 ++++++-- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs index 721a806f6521..4c6204ee4edb 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs @@ -53,7 +53,7 @@ public void PathTests() { var repository = new PartialViewRepository(fileSystems); - var partialView = new PartialView(PartialViewType.PartialView, "test-path-1.cshtml") { Content = "// partialView" }; + IPartialView partialView = new PartialView(PartialViewType.PartialView, "test-path-1.cshtml") { Content = "// partialView" }; repository.Save(partialView); Assert.IsTrue(_fileSystem.FileExists("test-path-1.cshtml")); Assert.AreEqual("test-path-1.cshtml", partialView.Path); @@ -62,10 +62,10 @@ public void PathTests() partialView = new PartialView(PartialViewType.PartialView, "path-2/test-path-2.cshtml") { Content = "// partialView" }; repository.Save(partialView); Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-2.cshtml")); - Assert.AreEqual("path-2\\test-path-2.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path); // fixed in 7.3 - 7.2.8 does not update the path + Assert.AreEqual("path-2\\test-path-2.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path); Assert.AreEqual("/Views/Partials/path-2/test-path-2.cshtml", partialView.VirtualPath); - partialView = (PartialView)repository.Get("path-2/test-path-2.cshtml"); + partialView = repository.Get("path-2/test-path-2.cshtml"); Assert.IsNotNull(partialView); Assert.AreEqual("path-2\\test-path-2.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path); Assert.AreEqual("/Views/Partials/path-2/test-path-2.cshtml", partialView.VirtualPath); @@ -76,12 +76,12 @@ public void PathTests() Assert.AreEqual("path-2\\test-path-3.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path); Assert.AreEqual("/Views/Partials/path-2/test-path-3.cshtml", partialView.VirtualPath); - partialView = (PartialView)repository.Get("path-2/test-path-3.cshtml"); + partialView = repository.Get("path-2/test-path-3.cshtml"); Assert.IsNotNull(partialView); Assert.AreEqual("path-2\\test-path-3.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path); Assert.AreEqual("/Views/Partials/path-2/test-path-3.cshtml", partialView.VirtualPath); - partialView = (PartialView)repository.Get("path-2\\test-path-3.cshtml"); + partialView = repository.Get("path-2\\test-path-3.cshtml"); Assert.IsNotNull(partialView); Assert.AreEqual("path-2\\test-path-3.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path); Assert.AreEqual("/Views/Partials/path-2/test-path-3.cshtml", partialView.VirtualPath); @@ -90,12 +90,19 @@ public void PathTests() Assert.Throws(() => repository.Save(partialView)); - partialView = (PartialView)repository.Get("missing.cshtml"); + partialView = new PartialView(PartialViewType.PartialView, "\\test-path-5.cshtml") { Content = "// partialView" }; + repository.Save(partialView); + + partialView = repository.Get("\\test-path-5.cshtml"); + Assert.IsNotNull(partialView); + Assert.AreEqual("test-path-5.cshtml", partialView.Path); + Assert.AreEqual("/Views/Partials/test-path-5.cshtml", partialView.VirtualPath); + + partialView = repository.Get("missing.cshtml"); Assert.IsNull(partialView); - // fixed in 7.3 - 7.2.8 used to... - Assert.Throws(() => partialView = (PartialView)repository.Get("..\\test-path-4.cshtml")); - Assert.Throws(() => partialView = (PartialView)repository.Get("../../packages.config")); + Assert.Throws(() => partialView = repository.Get("..\\test-path-4.cshtml")); + Assert.Throws(() => partialView = repository.Get("../../packages.config")); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs index 19e6a8303247..4721af14e171 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs @@ -307,10 +307,17 @@ public void PathTests() Assert.Throws(() => repository.Save(script)); + script = new Script("\\test-path-5.js") { Content = "// script" }; + repository.Save(script); + + script = repository.Get("\\test-path-5.js"); + Assert.IsNotNull(script); + Assert.AreEqual("test-path-5.js", script.Path); + Assert.AreEqual("/scripts/test-path-5.js", script.VirtualPath); + script = repository.Get("missing.js"); Assert.IsNull(script); - // fixed in 7.3 - 7.2.8 used to... Assert.Throws(() => script = repository.Get("..\\test-path-4.js")); Assert.Throws(() => script = repository.Get("../packages.config")); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs index 9581ada1a948..a32638ed4dba 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs @@ -275,7 +275,7 @@ public void PathTests() repository.Save(stylesheet); Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-2.css")); - Assert.AreEqual("path-2\\test-path-2.css".Replace("\\", $"{Path.DirectorySeparatorChar}"), stylesheet.Path); // fixed in 7.3 - 7.2.8 does not update the path + Assert.AreEqual("path-2\\test-path-2.css".Replace("\\", $"{Path.DirectorySeparatorChar}"), stylesheet.Path); Assert.AreEqual("/css/path-2/test-path-2.css", stylesheet.VirtualPath); stylesheet = repository.Get("path-2/test-path-2.css"); @@ -304,7 +304,14 @@ public void PathTests() Assert.Throws(() => repository.Save(stylesheet)); - // fixed in 7.3 - 7.2.8 used to throw + stylesheet = new Stylesheet("\\test-path-5.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; + repository.Save(stylesheet); + + stylesheet = repository.Get("\\test-path-5.css"); + Assert.IsNotNull(stylesheet); + Assert.AreEqual("test-path-5.css", stylesheet.Path); + Assert.AreEqual("/css/test-path-5.css", stylesheet.VirtualPath); + stylesheet = repository.Get("missing.css"); Assert.IsNull(stylesheet);