fix: canonicalize legacy drive aliases in resolved paths
This commit is contained in:
@@ -36,6 +36,12 @@ public sealed class PathResolver(IReadOnlyDictionary<string, string?>? environme
|
|||||||
{
|
{
|
||||||
var resolved = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
|
var resolved = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
|
||||||
foreach (var name in variables.Values.Keys) resolved[name] = ResolveVariable(name, variables, resolved, []);
|
foreach (var name in variables.Values.Keys) resolved[name] = ResolveVariable(name, variables, resolved, []);
|
||||||
|
foreach (var name in resolved.Keys.ToArray())
|
||||||
|
if (resolved[name] is { } value && value.StartsWith("A:\\", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var corporate = ResolveVariable(LauncherPathVariableNames.CorporateDfsRoot, variables, resolved, []) ?? throw new InvalidOperationException("PATH_VARIABLE_EMPTY:CorporateDfsRoot");
|
||||||
|
resolved[name] = Path.Combine(corporate, value[3..]);
|
||||||
|
}
|
||||||
foreach (var name in required ?? [])
|
foreach (var name in required ?? [])
|
||||||
if (!resolved.TryGetValue(name, out var value) || string.IsNullOrWhiteSpace(value))
|
if (!resolved.TryGetValue(name, out var value) || string.IsNullOrWhiteSpace(value))
|
||||||
throw new InvalidOperationException($"PATH_VARIABLE_REQUIRED:{name}");
|
throw new InvalidOperationException($"PATH_VARIABLE_REQUIRED:{name}");
|
||||||
@@ -47,12 +53,12 @@ public sealed class PathResolver(IReadOnlyDictionary<string, string?>? environme
|
|||||||
var resolved = Expand(value, variables, new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase), []);
|
var resolved = Expand(value, variables, new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase), []);
|
||||||
if (string.IsNullOrWhiteSpace(resolved) || !Path.IsPathFullyQualified(resolved))
|
if (string.IsNullOrWhiteSpace(resolved) || !Path.IsPathFullyQualified(resolved))
|
||||||
throw new InvalidOperationException("PATH_ABSOLUTE_REQUIRED");
|
throw new InvalidOperationException("PATH_ABSOLUTE_REQUIRED");
|
||||||
var full = Path.GetFullPath(resolved);
|
if (resolved.StartsWith("A:\\", StringComparison.OrdinalIgnoreCase))
|
||||||
if (full.StartsWith("A:\\", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
var corporate = Resolve(variables, ["CorporateDfsRoot"]).Require("CorporateDfsRoot");
|
var corporate = Resolve(variables, [LauncherPathVariableNames.CorporateDfsRoot]).Require(LauncherPathVariableNames.CorporateDfsRoot);
|
||||||
full = Path.Combine(corporate, full[3..]);
|
resolved = Path.Combine(corporate, resolved[3..]);
|
||||||
}
|
}
|
||||||
|
var full = Path.GetFullPath(resolved);
|
||||||
if (full.StartsWith("A:\\", StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("LEGACY_DRIVE_A_UNRESOLVED");
|
if (full.StartsWith("A:\\", StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("LEGACY_DRIVE_A_UNRESOLVED");
|
||||||
if (!string.IsNullOrWhiteSpace(constrainedRoot))
|
if (!string.IsNullOrWhiteSpace(constrainedRoot))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public sealed class PathAndLaunchTests
|
|||||||
[Fact] public void PathResolver_NormalizesSeparators() => Assert.DoesNotContain('/', Resolver().Resolve(new(Paths)).Require(LauncherPathVariableNames.DisciplineRoot));
|
[Fact] public void PathResolver_NormalizesSeparators() => Assert.DoesNotContain('/', Resolver().Resolve(new(Paths)).Require(LauncherPathVariableNames.DisciplineRoot));
|
||||||
[Fact] public void PathResolver_NormalizesLegacyDriveAToCorporateDfs() => Assert.Equal("\\\\phimproject.com\\a\\Project\\Plant", Resolver().ResolvePath("A:\\Project\\Plant", new(Paths)));
|
[Fact] public void PathResolver_NormalizesLegacyDriveAToCorporateDfs() => Assert.Equal("\\\\phimproject.com\\a\\Project\\Plant", Resolver().ResolvePath("A:\\Project\\Plant", new(Paths)));
|
||||||
[Fact] public void PathResolver_DoesNotEmitDriveA() => Assert.False(Resolver().ResolvePath("A:\\Project", new(Paths)).StartsWith("A:\\", StringComparison.OrdinalIgnoreCase));
|
[Fact] public void PathResolver_DoesNotEmitDriveA() => Assert.False(Resolver().ResolvePath("A:\\Project", new(Paths)).StartsWith("A:\\", StringComparison.OrdinalIgnoreCase));
|
||||||
|
[Fact] public void PathResolver_CanonicalVariablesDoNotEmitDriveA() => Assert.False(Resolver().Resolve(new(new Dictionary<string, string?> { [LauncherPathVariableNames.CorporateDfsRoot] = "\\\\phimproject.com\\a", ["Legacy"] = "A:\\Project" })).Require("Legacy").StartsWith("A:\\", StringComparison.OrdinalIgnoreCase));
|
||||||
[Fact] public void PathResolver_RejectsTraversalOutsideRoot() => Assert.Throws<InvalidOperationException>(() => Resolver().ResolvePath("${LocalSessionRoot}\\..\\escape", new(Paths), "${LocalSessionRoot}"));
|
[Fact] public void PathResolver_RejectsTraversalOutsideRoot() => Assert.Throws<InvalidOperationException>(() => Resolver().ResolvePath("${LocalSessionRoot}\\..\\escape", new(Paths), "${LocalSessionRoot}"));
|
||||||
[Fact] public void PathResolver_RequiresAbsolutePath() => Assert.Throws<InvalidOperationException>(() => Resolver().ResolvePath("relative\\file", new(Paths)));
|
[Fact] public void PathResolver_RequiresAbsolutePath() => Assert.Throws<InvalidOperationException>(() => Resolver().ResolvePath("relative\\file", new(Paths)));
|
||||||
[Fact] public void ProjectRoot_UsesProjectAndDisciplineVariables() => Assert.Equal("\\\\phimproject.com\\a\\Проект", Resolver().Resolve(new(Paths)).Require(LauncherPathVariableNames.ProjectRoot));
|
[Fact] public void ProjectRoot_UsesProjectAndDisciplineVariables() => Assert.Equal("\\\\phimproject.com\\a\\Проект", Resolver().Resolve(new(Paths)).Require(LauncherPathVariableNames.ProjectRoot));
|
||||||
|
|||||||
Reference in New Issue
Block a user