Split IOHelper into platform specific versions

This commit is contained in:
Benjamin Carleski
2020-11-18 04:49:03 -08:00
parent 64609475d3
commit d51677d5ed
8 changed files with 152 additions and 27 deletions

View File

@@ -21,6 +21,14 @@ namespace Umbraco.Core.IO
/// <returns></returns>
string MapPath(string path);
/// <summary>
/// Returns true if the path has a root, and is considered fully qualified for the OS it is on
/// See https://github.com/dotnet/runtime/blob/30769e8f31b20be10ca26e27ec279cd4e79412b9/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs#L281 for the .NET Standard 2.1 version of this
/// </summary>
/// <param name="path">The path to check</param>
/// <returns>True if the path is fully qualified, false otherwise</returns>
bool IsPathFullyQualified(string path);
/// <summary>
/// Verifies that the current filepath matches a directory where the user is allowed to edit a file.
/// </summary>
@@ -45,7 +53,7 @@ namespace Umbraco.Core.IO
/// <returns>A value indicating whether the filepath is valid.</returns>
bool VerifyFileExtension(string filePath, IEnumerable<string> validFileExtensions);
bool PathStartsWith(string path, string root, char separator);
bool PathStartsWith(string path, string root, params char[] separators);
void EnsurePathExists(string path);

View File

@@ -4,12 +4,13 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using Umbraco.Core.Hosting;
using Umbraco.Core.Strings;
namespace Umbraco.Core.IO
{
public class IOHelper : IIOHelper
public abstract class IOHelper : IIOHelper
{
private readonly IHostingEnvironment _hostingEnvironment;
@@ -29,7 +30,7 @@ namespace Umbraco.Core.IO
if (virtualPath.StartsWith("~"))
retval = virtualPath.Replace("~", _hostingEnvironment.ApplicationVirtualPath);
if (virtualPath.StartsWith("/") && virtualPath.StartsWith(_hostingEnvironment.ApplicationVirtualPath) == false)
if (virtualPath.StartsWith("/") && !PathStartsWith(virtualPath, _hostingEnvironment.ApplicationVirtualPath))
retval = _hostingEnvironment.ApplicationVirtualPath + "/" + virtualPath.TrimStart('/');
return retval;
@@ -64,19 +65,18 @@ namespace Umbraco.Core.IO
{
if (path == null) throw new ArgumentNullException(nameof(path));
// Check if the path is already mapped
if ((path.Length >= 2 && path[1] == Path.VolumeSeparatorChar)
|| path.StartsWith(@"\\")) //UNC Paths start with "\\". If the site is running off a network drive mapped paths will look like "\\Whatever\Boo\Bar"
// Check if the path is already mapped - TODO: This should be switched to Path.IsPathFullyQualified once we are on Net Standard 2.1
if (Path.IsPathRooted(path) &&
(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || // Linux paths are fully qualified as long as they are rooted, Windows paths can be rooted but not fully qualified
((path.Length >= 2 && path[1] == Path.VolumeSeparatorChar) || path.StartsWith(@"\\") //UNC Paths start with "\\". If the site is running off a network drive mapped paths will look like "\\Whatever\Boo\Bar"
)))
{
return path;
}
// Check that we even have an HttpContext! otherwise things will fail anyways
// http://umbraco.codeplex.com/workitem/30946
if (_hostingEnvironment.IsHosted)
{
var result = (!string.IsNullOrEmpty(path) && (path.StartsWith("~") || path.StartsWith(_hostingEnvironment.ApplicationVirtualPath)))
var result = (!string.IsNullOrEmpty(path) && (path.StartsWith("~") || PathStartsWith(path, _hostingEnvironment.ApplicationVirtualPath)))
? _hostingEnvironment.MapPathWebRoot(path)
: _hostingEnvironment.MapPathWebRoot("~/" + path.TrimStart('/'));
@@ -91,6 +91,14 @@ namespace Umbraco.Core.IO
return retval;
}
/// <summary>
/// Returns true if the path has a root, and is considered fully qualified for the OS it is on
/// See https://github.com/dotnet/runtime/blob/30769e8f31b20be10ca26e27ec279cd4e79412b9/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs#L281 for the .NET Standard 2.1 version of this
/// </summary>
/// <param name="path">The path to check</param>
/// <returns>True if the path is fully qualified, false otherwise</returns>
public abstract bool IsPathFullyQualified(string path);
/// <summary>
/// Verifies that the current filepath matches a directory where the user is allowed to edit a file.
@@ -121,7 +129,7 @@ namespace Umbraco.Core.IO
// not going to fix everything today
var mappedRoot = MapPath(_hostingEnvironment.ApplicationVirtualPath);
if (filePath.StartsWith(mappedRoot) == false)
if (!PathStartsWith(filePath, mappedRoot))
filePath = _hostingEnvironment.MapPathContentRoot(filePath);
// yes we can (see above)
@@ -131,10 +139,10 @@ namespace Umbraco.Core.IO
foreach (var dir in validDirs)
{
var validDir = dir;
if (validDir.StartsWith(mappedRoot) == false)
if (!PathStartsWith(validDir, mappedRoot))
validDir = _hostingEnvironment.MapPathContentRoot(validDir);
if (PathStartsWith(filePath, validDir, Path.DirectorySeparatorChar))
if (PathStartsWith(filePath, validDir))
return true;
}
@@ -153,16 +161,7 @@ namespace Umbraco.Core.IO
return ext != null && validFileExtensions.Contains(ext.TrimStart('.'));
}
public bool PathStartsWith(string path, string root, char separator)
{
// either it is identical to root,
// or it is root + separator + anything
if (path.StartsWith(root, StringComparison.OrdinalIgnoreCase) == false) return false;
if (path.Length == root.Length) return true;
if (path.Length < root.Length) return false;
return path[root.Length] == separator;
}
public abstract bool PathStartsWith(string path, string root, params char[] separators);
public void EnsurePathExists(string path)
{
@@ -181,7 +180,7 @@ namespace Umbraco.Core.IO
if (path.IsFullPath())
{
var rootDirectory = MapPath("~");
var relativePath = path.ToLowerInvariant().Replace(rootDirectory.ToLowerInvariant(), string.Empty);
var relativePath = PathStartsWith(path, rootDirectory) ? path.Substring(rootDirectory.Length) : path;
path = relativePath;
}

View File

@@ -0,0 +1,28 @@
using System;
using System.IO;
using System.Linq;
using Umbraco.Core.Hosting;
namespace Umbraco.Core.IO
{
public class IOHelperLinux : IOHelper
{
public IOHelperLinux(IHostingEnvironment hostingEnvironment) : base(hostingEnvironment)
{
}
public override bool IsPathFullyQualified(string path) => Path.IsPathRooted(path);
public override bool PathStartsWith(string path, string root, params char[] separators)
{
// either it is identical to root,
// or it is root + separator + anything
if (separators == null || separators.Length == 0) separators = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
if (!path.StartsWith(root, StringComparison.Ordinal)) return false;
if (path.Length == root.Length) return true;
if (path.Length < root.Length) return false;
return separators.Contains(path[root.Length]);
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.IO;
using System.Linq;
using Umbraco.Core.Hosting;
namespace Umbraco.Core.IO
{
public class IOHelperOSX : IOHelper
{
public IOHelperOSX(IHostingEnvironment hostingEnvironment) : base(hostingEnvironment)
{
}
public override bool IsPathFullyQualified(string path) => Path.IsPathRooted(path);
public override bool PathStartsWith(string path, string root, params char[] separators)
{
// either it is identical to root,
// or it is root + separator + anything
if (separators == null || separators.Length == 0) separators = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
if (!path.StartsWith(root, StringComparison.OrdinalIgnoreCase)) return false;
if (path.Length == root.Length) return true;
if (path.Length < root.Length) return false;
return separators.Contains(path[root.Length]);
}
}
}

View File

@@ -0,0 +1,55 @@
using System;
using System.IO;
using System.Linq;
using Umbraco.Core.Hosting;
namespace Umbraco.Core.IO
{
public class IOHelperWindows : IOHelper
{
public IOHelperWindows(IHostingEnvironment hostingEnvironment) : base(hostingEnvironment)
{
}
public override bool IsPathFullyQualified(string path)
{
// TODO: This implementation is taken from the .NET Standard 2.1 implementation. We should switch to using Path.IsPathFullyQualified once we are on .NET Standard 2.1
if (path.Length < 2)
{
// It isn't fixed, it must be relative. There is no way to specify a fixed
// path with one character (or less).
return false;
}
if (path[0] == Path.DirectorySeparatorChar || path[0] == Path.AltDirectorySeparatorChar)
{
// There is no valid way to specify a relative path with two initial slashes or
// \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\
return path[1] == '?' || path[1] == Path.DirectorySeparatorChar || path[1] == Path.AltDirectorySeparatorChar;
}
// The only way to specify a fixed path that doesn't begin with two slashes
// is the drive, colon, slash format- i.e. C:\
return (path.Length >= 3)
&& (path[1] == Path.VolumeSeparatorChar)
&& (path[2] == Path.DirectorySeparatorChar || path[2] == Path.AltDirectorySeparatorChar)
// To match old behavior we'll check the drive character for validity as the path is technically
// not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream.
&& ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z'));
}
public override bool PathStartsWith(string path, string root, params char[] separators)
{
// either it is identical to root,
// or it is root + separator + anything
if (separators == null || separators.Length == 0) separators = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
if (!path.StartsWith(root, StringComparison.OrdinalIgnoreCase)) return false;
if (path.Length == root.Length) return true;
if (path.Length < root.Length) return false;
return separators.Contains(path[root.Length]);
}
}
}

View File

@@ -23,6 +23,7 @@ using Umbraco.Core.Strings;
using Umbraco.Web;
using Umbraco.Web.Routing;
using Umbraco.Tests.Common.Builders;
using System.Runtime.InteropServices;
namespace Umbraco.Tests.Common
{
@@ -86,7 +87,11 @@ namespace Umbraco.Tests.Common
get
{
if (_ioHelper == null)
_ioHelper = new IOHelper(GetHostingEnvironment());
{
var hostingEnvironment = GetHostingEnvironment();
_ioHelper = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? new IOHelperLinux(hostingEnvironment)
: (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? (IIOHelper)new IOHelperOSX(hostingEnvironment) : new IOHelperWindows(hostingEnvironment));
}
return _ioHelper;
}
}

View File

@@ -373,7 +373,8 @@ namespace Umbraco.Extensions
throw new InvalidOperationException($"Could not resolve type {typeof(GlobalSettings)} from the container, ensure {nameof(AddUmbracoConfiguration)} is called before calling {nameof(AddUmbracoCore)}");
hostingEnvironment = new AspNetCoreHostingEnvironment(hostingSettings, webHostEnvironment);
ioHelper = new IOHelper(hostingEnvironment);
ioHelper = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? new IOHelperLinux(hostingEnvironment)
: (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? (IIOHelper)new IOHelperOSX(hostingEnvironment) : new IOHelperWindows(hostingEnvironment));
AddLogger(services, hostingEnvironment, loggingConfiguration, configuration);
backOfficeInfo = new AspNetCoreBackOfficeInfo(globalSettings);
profiler = GetWebProfiler(hostingEnvironment);

View File

@@ -58,7 +58,7 @@ namespace Umbraco.Web
var hostingEnvironment = new AspNetHostingEnvironment(Options.Create(hostingSettings));
var loggingConfiguration = new LoggingConfiguration(
Path.Combine(hostingEnvironment.ApplicationPhysicalPath, "App_Data\\Logs"));
var ioHelper = new IOHelper(hostingEnvironment);
var ioHelper = new IOHelperWindows(hostingEnvironment);
var logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment, loggingConfiguration, new ConfigurationRoot(new List<IConfigurationProvider>()));
var backOfficeInfo = new AspNetBackOfficeInfo(globalSettings, ioHelper, _loggerFactory.CreateLogger<AspNetBackOfficeInfo>(), Options.Create(webRoutingSettings));