Skip to content
Closed
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
32 changes: 32 additions & 0 deletions src/Framework.UnitTests/AbsolutePath_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,38 @@ public void AbsolutePath_NullOrEmptyWithBasePath_ShouldThrowOnEmpty()
exception.Message.ShouldStartWith("The value cannot be an empty string.");
}

[WindowsOnlyTheory]
[InlineData(" ")]
[InlineData(" ")]
[InlineData("\t")]
[InlineData(" \t ")]
public void AbsolutePath_WhitespaceWithBasePath_ShouldThrowOnWindows(string whitespace)
{
// Before the multi-threaded migration, Path.GetFullPath(" ") threw ArgumentException on Windows.
// The "absolutize-at-boundary" pattern combines the input with a base path first, so without this
// guard Path.GetFullPath would silently trim the trailing whitespace and return the base directory,
// masking the original error. The constructor explicitly rejects whitespace-only input on Windows.
var basePath = GetTestBasePath();

var exception = Should.Throw<ArgumentException>(() => new AbsolutePath(whitespace, basePath));
exception.Message.ShouldContain("Path cannot be null or whitespace on Windows");
}

[UnixOnlyTheory]
[InlineData(" ")]
[InlineData(" ")]
[InlineData("\t")]
public void AbsolutePath_WhitespaceWithBasePath_ShouldNotThrowOnUnix(string whitespace)
{
// Whitespace is a legal filename character on Unix; preserve the historical accepting behavior.
var basePath = GetTestBasePath();

var absolutePath = new AbsolutePath(whitespace, basePath);

absolutePath.Value.ShouldBe(Path.Combine(basePath.Value, whitespace));
absolutePath.OriginalValue.ShouldBe(whitespace);
}

[Theory]
[InlineData("subfolder")]
[InlineData("deep/nested/path")]
Expand Down
17 changes: 16 additions & 1 deletion src/Framework/PathHelpers/AbsolutePath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,26 @@ private static void ValidatePath(string path)
/// </summary>
/// <param name="path">The path to combine with the base path.</param>
/// <param name="basePath">The base path to combine with.</param>
/// <exception cref="ArgumentException">Thrown if <paramref name="path"/> is null or empty.</exception>
/// <exception cref="ArgumentException">
/// Thrown if <paramref name="path"/> is null or empty, or if <paramref name="path"/> is whitespace-only on Windows.
/// </exception>
/// <remarks>
/// On Windows, whitespace-only inputs are rejected to preserve the historical contract of
/// <see cref="System.IO.Path.GetFullPath(string)"/>, which threw <see cref="ArgumentException"/> for
/// whitespace-only paths. Without this guard the "absolutize-at-boundary" pattern used by multi-threadable
/// tasks would silently combine a whitespace input with the project directory (e.g. <c>"C:\\proj\\ "</c>),
/// which <see cref="System.IO.Path.GetFullPath(string)"/> then trims back to the project directory, masking
/// the original error. On Unix whitespace is a valid filename character and the input is accepted.
/// </remarks>
public AbsolutePath(string path, AbsolutePath basePath)
{
ArgumentException.ThrowIfNullOrEmpty(path);

if (NativeMethods.IsWindows && string.IsNullOrWhiteSpace(path))
{
throw new ArgumentException(SR.WhitespacePathNotAllowedOnWindows, nameof(path));
}

// This function should not throw when path has illegal characters.
// For .NET Framework, Microsoft.IO.Path.Combine should be used instead of System.IO.Path.Combine to achieve it.
// For .NET Core, System.IO.Path.Combine already does not throw in this case.
Expand Down
3 changes: 3 additions & 0 deletions src/Framework/Resources/SR.resx
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@
<data name="PathMustBeRooted" xml:space="preserve">
<value>Path must be rooted.</value>
</data>
<data name="WhitespacePathNotAllowedOnWindows" xml:space="preserve">
<value>Path cannot be null or whitespace on Windows.</value>
</data>
<data name="PathTooLong" xml:space="preserve">
<value>Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters.</value>
</data>
Expand Down
7 changes: 6 additions & 1 deletion src/Framework/Resources/xlf/SR.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/Framework/Resources/xlf/SR.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/Framework/Resources/xlf/SR.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/Framework/Resources/xlf/SR.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/Framework/Resources/xlf/SR.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/Framework/Resources/xlf/SR.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/Framework/Resources/xlf/SR.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/Framework/Resources/xlf/SR.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/Framework/Resources/xlf/SR.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/Framework/Resources/xlf/SR.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/Framework/Resources/xlf/SR.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/Framework/Resources/xlf/SR.zh-Hans.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/Framework/Resources/xlf/SR.zh-Hant.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/Tasks/AddToWin32Manifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public string ManifestPath
{
if (ApplicationManifest != null)
{
if (string.IsNullOrEmpty(ApplicationManifest.ItemSpec))
if (string.IsNullOrWhiteSpace(ApplicationManifest.ItemSpec))
{
Log.LogErrorWithCodeFromResources(null, ApplicationManifest.ItemSpec, 0, 0, 0, 0, "AddToWin32Manifest.SpecifiedApplicationManifestCanNotBeFound");
return null;
Expand Down
2 changes: 1 addition & 1 deletion src/Tasks/Move.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ public override bool Execute()
success = false;
}
}
catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e) || e is ArgumentException)
{
string lockedFileMessage = LockCheck.GetLockedFileMessage(sourceFile?.OriginalValue ?? sourceSpec);
Log.LogErrorWithCodeFromResources("Move.Error", sourceSpec, destinationSpec, e.Message, lockedFileMessage);
Expand Down
Loading