// Copyright (c) DEMA Consulting
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
namespace DemaConsulting.TemplateDotNetTool.Utilities;
///
/// Helper utilities for safe path operations.
///
internal static class PathHelpers
{
///
/// Safely combines two paths, ensuring the resolved combined path stays within the base directory.
///
/// The base path.
/// The relative path to combine.
/// The combined path.
/// Thrown when or is .
///
/// Thrown when the resolved combined path escapes the base directory, or when a supplied path is invalid.
///
/// Thrown when a supplied path contains an unsupported format.
/// Thrown when the combined or resolved path exceeds the system-defined maximum length.
internal static string SafePathCombine(string basePath, string relativePath)
{
// Validate inputs
ArgumentNullException.ThrowIfNull(basePath);
ArgumentNullException.ThrowIfNull(relativePath);
// Combine the paths (preserves the caller's relative/absolute style)
var combinedPath = Path.Combine(basePath, relativePath);
// Security check: resolve both paths to absolute form and verify the combined
// path is still inside the base directory. Path.GetRelativePath handles root
// paths, platform case-sensitivity, and directory-separator normalization natively.
var absoluteBase = Path.GetFullPath(basePath);
var absoluteCombined = Path.GetFullPath(combinedPath);
var checkRelative = Path.GetRelativePath(absoluteBase, absoluteCombined);
if (string.Equals(checkRelative, "..", StringComparison.Ordinal)
|| checkRelative.StartsWith(".." + Path.DirectorySeparatorChar, StringComparison.Ordinal)
|| checkRelative.StartsWith(".." + Path.AltDirectorySeparatorChar, StringComparison.Ordinal)
|| Path.IsPathRooted(checkRelative))
{
throw new ArgumentException($"Invalid path component: {relativePath}", nameof(relativePath));
}
return combinedPath;
}
}