Skip to content

Commit 7b6539f

Browse files
committed
Allow changing UmbracoMediaPath to an absolute path. Also ensure Imagesharp are handing requests outside of the wwwroot folder.
1 parent 76cf503 commit 7b6539f

File tree

11 files changed

+156
-44
lines changed

11 files changed

+156
-44
lines changed

src/Umbraco.Core/Configuration/Models/GlobalSettings.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class GlobalSettings
2424
internal const string StaticUmbracoCssPath = "~/css";
2525
internal const string StaticUmbracoScriptsPath = "~/scripts";
2626
internal const string StaticUmbracoMediaPath = "~/media";
27+
internal const string StaticUmbracoMediaUrl = "~/media";
2728
internal const bool StaticInstallMissingDatabase = false;
2829
internal const bool StaticDisableElectionForSingleServer = false;
2930
internal const string StaticNoNodesViewPath = "~/umbraco/UmbracoWebsite/NoNodes.cshtml";
@@ -109,6 +110,13 @@ public class GlobalSettings
109110
[DefaultValue(StaticUmbracoMediaPath)]
110111
public string UmbracoMediaPath { get; set; } = StaticUmbracoMediaPath;
111112

113+
114+
/// <summary>
115+
/// Gets or sets a value for the Umbraco media url. Starts with "~/".
116+
/// </summary>
117+
[DefaultValue(StaticUmbracoMediaUrl)]
118+
public string UmbracoMediaUrl { get; set; } = StaticUmbracoMediaUrl;
119+
112120
/// <summary>
113121
/// Gets or sets a value indicating whether to install the database when it is missing.
114122
/// </summary>
@@ -179,4 +187,4 @@ public class GlobalSettings
179187
[DefaultValue(StaticSqlWriteLockTimeOut)]
180188
public TimeSpan SqlWriteLockTimeOut { get; } = TimeSpan.Parse(StaticSqlWriteLockTimeOut);
181189
}
182-
}
190+
}

src/Umbraco.Core/Constants-SystemDirectories.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public static class SystemDirectories
4343

4444
public const string AppPlugins = "/App_Plugins";
4545
public static string AppPluginIcons => "/Backoffice/Icons";
46+
public const string CreatedPackages = "/created-packages";
47+
4648

4749
public const string MvcViews = "~/Views";
4850

src/Umbraco.Core/Packaging/PackagesRepository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public PackagesRepository(
9393

9494
_tempFolderPath = tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles";
9595
_packagesFolderPath = packagesFolderPath ?? Constants.SystemDirectories.Packages;
96-
_mediaFolderPath = mediaFolderPath ?? globalSettings.Value.UmbracoMediaPath + "/created-packages";
96+
_mediaFolderPath = mediaFolderPath ?? Path.Combine(globalSettings.Value.UmbracoMediaPath, Constants.SystemDirectories.CreatedPackages);
9797

9898
_parser = new PackageDefinitionXmlParser();
9999
_mediaService = mediaService;

src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ internal static IUmbracoBuilder AddFileSystems(this IUmbracoBuilder builder)
5050
GlobalSettings globalSettings = factory.GetRequiredService<IOptions<GlobalSettings>>().Value;
5151

5252
var rootPath = hostingEnvironment.MapPathWebRoot(globalSettings.UmbracoMediaPath);
53-
var rootUrl = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaPath);
53+
var rootUrl = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaUrl);
5454
return new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, rootPath, rootUrl);
5555
});
5656

src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public CreatedPackageSchemaRepository(
7676
_macroService = macroService;
7777
_contentTypeService = contentTypeService;
7878
_xmlParser = new PackageDefinitionXmlParser();
79-
_mediaFolderPath = mediaFolderPath ?? globalSettings.Value.UmbracoMediaPath + "/created-packages";
79+
_mediaFolderPath = mediaFolderPath ?? Path.Combine(globalSettings.Value.UmbracoMediaPath, Constants.SystemDirectories.CreatedPackages);
8080
_tempFolderPath =
8181
tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles";
8282
}

src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ internal async Task<Dictionary<string, object>> GetServerVariablesAsync()
384384
"umbracoSettings", new Dictionary<string, object>
385385
{
386386
{"umbracoPath", _globalSettings.GetBackOfficePath(_hostingEnvironment)},
387-
{"mediaPath", _hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaPath).TrimEnd(Constants.CharArrays.ForwardSlash)},
387+
{"mediaPath", _hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaUrl).TrimEnd(Constants.CharArrays.ForwardSlash)},
388388
{"appPluginsPath", _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.AppPlugins).TrimEnd(Constants.CharArrays.ForwardSlash)},
389389
{
390390
"imageFileTypes",

src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
using System;
22
using Microsoft.AspNetCore.Builder;
33
using Microsoft.Extensions.DependencyInjection;
4+
using Microsoft.Extensions.FileProviders;
45
using Microsoft.Extensions.Options;
56
using SixLabors.ImageSharp.Web.DependencyInjection;
7+
using Umbraco.Cms.Core.Configuration.Models;
8+
using Umbraco.Cms.Core.Hosting;
69
using Umbraco.Cms.Core.Services;
10+
using Umbraco.Cms.Web.Common.ImageProcessors;
711
using Umbraco.Extensions;
812

913
namespace Umbraco.Cms.Web.Common.ApplicationBuilder
@@ -22,7 +26,7 @@ public UmbracoApplicationBuilder(IApplicationBuilder appBuilder)
2226
{
2327
AppBuilder = appBuilder ?? throw new ArgumentNullException(nameof(appBuilder));
2428
ApplicationServices = appBuilder.ApplicationServices;
25-
RuntimeState = appBuilder.ApplicationServices.GetRequiredService<IRuntimeState>();
29+
RuntimeState = appBuilder.ApplicationServices.GetRequiredService<IRuntimeState>();
2630
_umbracoPipelineStartupOptions = ApplicationServices.GetRequiredService<IOptions<UmbracoPipelineOptions>>();
2731
}
2832

@@ -87,9 +91,19 @@ public void RegisterDefaultRequiredMiddleware()
8791

8892
AppBuilder.UseStatusCodePages();
8993

90-
// Important we handle image manipulations before the static files, otherwise the querystring is just ignored.
94+
// Important we handle image manipulations before the static files, otherwise the querystring is just ignored.
9195
AppBuilder.UseImageSharp();
96+
97+
GlobalSettings globalSettings = AppBuilder.ApplicationServices.GetRequiredService<IOptions<GlobalSettings>>().Value;
98+
IUmbracoMediaFileProvider umbracoMediaFileProvider = AppBuilder.ApplicationServices.GetRequiredService<IUmbracoMediaFileProvider>();
99+
100+
AppBuilder.UseStaticFiles(new StaticFileOptions()
101+
{
102+
FileProvider = umbracoMediaFileProvider,
103+
RequestPath = globalSettings.UmbracoMediaUrl.TrimStart("~")
104+
});
92105
AppBuilder.UseStaticFiles();
106+
93107
AppBuilder.UseUmbracoPluginsStaticFiles();
94108

95109
// UseRouting adds endpoint routing middleware, this means that middlewares registered after this one

src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
using Microsoft.AspNetCore.Http;
44
using Microsoft.Extensions.Configuration;
55
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.DependencyInjection.Extensions;
7+
using Microsoft.Extensions.FileProviders;
68
using Microsoft.Extensions.Options;
79
using Microsoft.Net.Http.Headers;
10+
using SixLabors.ImageSharp.Web;
811
using SixLabors.ImageSharp.Web.Caching;
912
using SixLabors.ImageSharp.Web.Commands;
1013
using SixLabors.ImageSharp.Web.DependencyInjection;
1114
using SixLabors.ImageSharp.Web.Middleware;
1215
using SixLabors.ImageSharp.Web.Processors;
16+
using SixLabors.ImageSharp.Web.Providers;
1317
using Umbraco.Cms.Core.Configuration.Models;
1418
using Umbraco.Cms.Core.DependencyInjection;
1519
using Umbraco.Cms.Web.Common.DependencyInjection;
@@ -28,54 +32,62 @@ public static IServiceCollection AddUmbracoImageSharp(this IUmbracoBuilder build
2832
.Get<ImagingSettings>() ?? new ImagingSettings();
2933

3034
builder.Services.AddImageSharp(options =>
31-
{
32-
// options.Configuration is set using ImageSharpConfigurationOptions below
33-
options.BrowserMaxAge = imagingSettings.Cache.BrowserMaxAge;
34-
options.CacheMaxAge = imagingSettings.Cache.CacheMaxAge;
35-
options.CachedNameLength = imagingSettings.Cache.CachedNameLength;
36-
37-
// Use configurable maximum width and height (overwrite ImageSharps default)
38-
options.OnParseCommandsAsync = context =>
3935
{
40-
if (context.Commands.Count == 0)
41-
{
42-
return Task.CompletedTask;
43-
}
36+
// options.Configuration is set using ImageSharpConfigurationOptions below
37+
options.BrowserMaxAge = imagingSettings.Cache.BrowserMaxAge;
38+
options.CacheMaxAge = imagingSettings.Cache.CacheMaxAge;
39+
options.CachedNameLength = imagingSettings.Cache.CachedNameLength;
4440

45-
uint width = context.Parser.ParseValue<uint>(context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), context.Culture);
46-
uint height = context.Parser.ParseValue<uint>(context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), context.Culture);
47-
if (width > imagingSettings.Resize.MaxWidth || height > imagingSettings.Resize.MaxHeight)
41+
// Use configurable maximum width and height (overwrite ImageSharps default)
42+
options.OnParseCommandsAsync = context =>
4843
{
49-
context.Commands.Remove(ResizeWebProcessor.Width);
50-
context.Commands.Remove(ResizeWebProcessor.Height);
51-
}
44+
if (context.Commands.Count == 0)
45+
{
46+
return Task.CompletedTask;
47+
}
5248

53-
return Task.CompletedTask;
54-
};
55-
options.OnBeforeSaveAsync = _ => Task.CompletedTask;
56-
options.OnProcessedAsync = _ => Task.CompletedTask;
57-
options.OnPrepareResponseAsync = context =>
58-
{
59-
// Change Cache-Control header when cache buster value is present
60-
if (context.Request.Query.ContainsKey("rnd"))
49+
uint width =
50+
context.Parser.ParseValue<uint>(
51+
context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), context.Culture);
52+
uint height = context.Parser.ParseValue<uint>(
53+
context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), context.Culture);
54+
if (width > imagingSettings.Resize.MaxWidth || height > imagingSettings.Resize.MaxHeight)
55+
{
56+
context.Commands.Remove(ResizeWebProcessor.Width);
57+
context.Commands.Remove(ResizeWebProcessor.Height);
58+
}
59+
60+
return Task.CompletedTask;
61+
};
62+
options.OnBeforeSaveAsync = _ => Task.CompletedTask;
63+
options.OnProcessedAsync = _ => Task.CompletedTask;
64+
options.OnPrepareResponseAsync = context =>
6165
{
62-
var headers = context.Response.GetTypedHeaders();
66+
// Change Cache-Control header when cache buster value is present
67+
if (context.Request.Query.ContainsKey("rnd"))
68+
{
69+
var headers = context.Response.GetTypedHeaders();
6370

64-
var cacheControl = headers.CacheControl;
65-
cacheControl.MustRevalidate = false;
66-
cacheControl.Extensions.Add(new NameValueHeaderValue("immutable"));
71+
var cacheControl = headers.CacheControl;
72+
cacheControl.MustRevalidate = false;
73+
cacheControl.Extensions.Add(new NameValueHeaderValue("immutable"));
6774

68-
headers.CacheControl = cacheControl;
69-
}
75+
headers.CacheControl = cacheControl;
76+
}
7077

71-
return Task.CompletedTask;
72-
};
73-
})
74-
.Configure<PhysicalFileSystemCacheOptions>(options => options.CacheFolder = builder.BuilderHostingEnvironment.MapPathContentRoot(imagingSettings.Cache.CacheFolder))
75-
.AddProcessor<CropWebProcessor>();
78+
return Task.CompletedTask;
79+
};
80+
})
81+
.Configure<PhysicalFileSystemCacheOptions>(options =>
82+
options.CacheFolder =
83+
builder.BuilderHostingEnvironment.MapPathContentRoot(imagingSettings.Cache.CacheFolder))
84+
.AddProcessor<CropWebProcessor>()
85+
.ClearProviders()
86+
.AddProvider<UmbracoImageProvider>();
7687

7788
builder.Services.AddTransient<IConfigureOptions<ImageSharpMiddlewareOptions>, ImageSharpConfigurationOptions>();
7889

90+
builder.Services.AddUnique<IUmbracoMediaFileProvider, UmbracoMediaFileProvider>();
7991
return builder.Services;
8092
}
8193
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using Microsoft.Extensions.FileProviders;
2+
3+
namespace Umbraco.Cms.Web.Common.ImageProcessors
4+
{
5+
public interface IUmbracoMediaFileProvider : IFileProvider
6+
{
7+
}
8+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Microsoft.Extensions.FileProviders;
2+
using Microsoft.Extensions.Options;
3+
using Umbraco.Cms.Core.Configuration.Models;
4+
using Umbraco.Cms.Core.Hosting;
5+
6+
namespace Umbraco.Cms.Web.Common.ImageProcessors
7+
{
8+
internal class UmbracoMediaFileProvider : PhysicalFileProvider, IUmbracoMediaFileProvider
9+
{
10+
public UmbracoMediaFileProvider(IHostingEnvironment hostingEnvironment, IOptions<GlobalSettings> globalSettings)
11+
: base(hostingEnvironment.MapPathWebRoot(globalSettings.Value.UmbracoMediaPath))
12+
{
13+
}
14+
}
15+
}

0 commit comments

Comments
 (0)