diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets index 1ff0d9030aea..e510aca0b463 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets @@ -850,8 +850,8 @@ Copyright (c) .NET Foundation. All rights reserved. %(Content.TargetPath) %(Content.Link) - $([MSBuild]::MakeRelative(%(Content.DefiningProjectDirectory), %(Content.FullPath))) - $([MSBuild]::MakeRelative($(MSBuildProjectDirectory), %(Content.FullPath))) + + $([MSBuild]::MakeRelative($(MSBuildProjectDirectory), %(Content.FullPath))) diff --git a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishWithIfDifferent.cs b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishWithIfDifferent.cs index 9ebb72c1b982..d234ec586284 100644 --- a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishWithIfDifferent.cs +++ b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishWithIfDifferent.cs @@ -381,5 +381,79 @@ public void It_handles_IfDifferent_with_self_contained_publish() publishDirectory.Should().HaveFile("appdata.txt"); File.ReadAllText(Path.Combine(publishDirectory.FullName, "appdata.txt")).Should().Be("Application data"); } + + [Fact] + public void It_publishes_content_from_imported_targets_with_correct_path() + { + // This test verifies that Content items introduced from imported .targets files + // (where DefiningProjectDirectory differs from MSBuildProjectDirectory) are + // published with the correct project-relative path and do not escape the publish directory. + // + // The bug scenario: when a .targets file OUTSIDE the project directory adds a Content item, + // using DefiningProjectDirectory to compute the relative path can result in paths with '..' + // segments that escape the publish directory. + + var testProject = new TestProject() + { + Name = "PublishImportedContent", + TargetFrameworks = ToolsetInfo.CurrentTargetFramework, + IsExe = true + }; + + testProject.SourceFiles["Program.cs"] = "class Program { static void Main() { } }"; + + var testAsset = _testAssetsManager.CreateTestProject(testProject); + + var projectDirectory = Path.Combine(testAsset.Path, testProject.Name); + + // Create the imported targets file OUTSIDE the project directory (sibling folder) + // This is the key difference - DefiningProjectDirectory will be different from MSBuildProjectDirectory + var externalImportsDir = Path.Combine(testAsset.Path, "ExternalImports"); + Directory.CreateDirectory(externalImportsDir); + + // Create the content file in the project directory (where we want it to be published from) + var contentFile = Path.Combine(projectDirectory, "project-content.txt"); + File.WriteAllText(contentFile, "Content defined by external targets"); + + // Create an imported .targets file OUTSIDE the project that adds a Content item + // pointing to a file in the project directory. The issue is that DefiningProjectDirectory + // will be ExternalImports/, not the project directory. + var importedTargetsFile = Path.Combine(externalImportsDir, "ImportedContent.targets"); + File.WriteAllText(importedTargetsFile, $@" + + + + +"); + + // Update the main project file to import the external targets + var projectFile = Path.Combine(projectDirectory, $"{testProject.Name}.csproj"); + var projectContent = File.ReadAllText(projectFile); + projectContent = projectContent.Replace("", @" + +"); + File.WriteAllText(projectFile, projectContent); + + var publishCommand = new PublishCommand(testAsset); + var publishResult = publishCommand.Execute(); + + publishResult.Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory(testProject.TargetFrameworks); + + // The content file should be published with a simple filename, not with path segments + // that could escape the publish directory (e.g., "..\PublishImportedContent\project-content.txt") + publishDirectory.Should().HaveFile("project-content.txt"); + + // Verify the content is correct + var publishedContentPath = Path.Combine(publishDirectory.FullName, "project-content.txt"); + File.ReadAllText(publishedContentPath).Should().Be("Content defined by external targets"); + + // Ensure no files escaped to parent directories + var parentDir = Directory.GetParent(publishDirectory.FullName); + var potentialEscapedFiles = Directory.GetFiles(parentDir.FullName, "project-content.txt", SearchOption.AllDirectories) + .Where(f => !f.StartsWith(publishDirectory.FullName)); + potentialEscapedFiles.Should().BeEmpty("Content file should not escape to directories outside publish folder"); + } } }