Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -850,8 +850,8 @@ Copyright (c) .NET Foundation. All rights reserved.
<!-- Preserve existing TargetPath if already set, otherwise compute it -->
<TargetPath Condition="'%(Content.TargetPath)' != ''">%(Content.TargetPath)</TargetPath>
<TargetPath Condition="'%(Content.TargetPath)' == '' and '%(Content.Link)' != ''">%(Content.Link)</TargetPath>
<TargetPath Condition="'%(Content.TargetPath)' == '' and '%(Content.Link)' == '' and '%(Content.DefiningProjectDirectory)' != '' and '%(Content.DefiningProjectFullPath)' != ''">$([MSBuild]::MakeRelative(%(Content.DefiningProjectDirectory), %(Content.FullPath)))</TargetPath>
<TargetPath Condition="'%(Content.TargetPath)' == '' and '%(Content.Link)' == '' and ('%(Content.DefiningProjectDirectory)' == '' or '%(Content.DefiningProjectFullPath)' == '')">$([MSBuild]::MakeRelative($(MSBuildProjectDirectory), %(Content.FullPath)))</TargetPath>
<!-- Use MSBuildProjectDirectory as the base for relative path computation. DefiningProjectDirectory can point to sdk targets folder and lead to unexpected paths. -->
<TargetPath Condition="'%(Content.TargetPath)' == '' and '%(Content.Link)' == ''">$([MSBuild]::MakeRelative($(MSBuildProjectDirectory), %(Content.FullPath)))</TargetPath>
</_ContentWithPublishMetadata>

<ContentWithTargetPath Include="@(_ContentWithPublishMetadata)" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, $@"<Project>
<ItemGroup>
<!-- This Content item points to a file in the main project directory, but is defined in external .targets -->
<Content Include=""$(MSBuildProjectDirectory)\project-content.txt"" CopyToPublishDirectory=""IfDifferent"" />
</ItemGroup>
</Project>");

// 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("</Project>", @"
<Import Project=""..\ExternalImports\ImportedContent.targets"" />
</Project>");
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");
}
}
}
Loading