diff --git a/src/Framework.UnitTests/AbsolutePath_Tests.cs b/src/Framework.UnitTests/AbsolutePath_Tests.cs index 09a87b60b09..0c1f9478839 100644 --- a/src/Framework.UnitTests/AbsolutePath_Tests.cs +++ b/src/Framework.UnitTests/AbsolutePath_Tests.cs @@ -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(() => 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")] diff --git a/src/Framework/PathHelpers/AbsolutePath.cs b/src/Framework/PathHelpers/AbsolutePath.cs index 59d08dc102f..17cba39ceba 100644 --- a/src/Framework/PathHelpers/AbsolutePath.cs +++ b/src/Framework/PathHelpers/AbsolutePath.cs @@ -101,11 +101,26 @@ private static void ValidatePath(string path) /// /// The path to combine with the base path. /// The base path to combine with. - /// Thrown if is null or empty. + /// + /// Thrown if is null or empty, or if is whitespace-only on Windows. + /// + /// + /// On Windows, whitespace-only inputs are rejected to preserve the historical contract of + /// , which threw 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:\\proj\\ "), + /// which then trims back to the project directory, masking + /// the original error. On Unix whitespace is a valid filename character and the input is accepted. + /// 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. diff --git a/src/Framework/Resources/SR.resx b/src/Framework/Resources/SR.resx index ebcc88a74d5..a7c9a068529 100644 --- a/src/Framework/Resources/SR.resx +++ b/src/Framework/Resources/SR.resx @@ -153,6 +153,9 @@ Path must be rooted. + + Path cannot be null or whitespace on Windows. + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. diff --git a/src/Framework/Resources/xlf/SR.cs.xlf b/src/Framework/Resources/xlf/SR.cs.xlf index 32d075daa4f..8b3d2a4eadb 100644 --- a/src/Framework/Resources/xlf/SR.cs.xlf +++ b/src/Framework/Resources/xlf/SR.cs.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Cesta musí začínat kořenem. + + Path cannot be null or whitespace on Windows. + Path cannot be null or whitespace on Windows. + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Cesta: {0} překračuje maximální limit pro cestu k OS. Plně kvalifikovaný název souboru musí být kratší než {1} znaků. diff --git a/src/Framework/Resources/xlf/SR.de.xlf b/src/Framework/Resources/xlf/SR.de.xlf index 6b53f58a3fa..88df11aa712 100644 --- a/src/Framework/Resources/xlf/SR.de.xlf +++ b/src/Framework/Resources/xlf/SR.de.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Der Pfad muss einen Stamm besitzen. + + Path cannot be null or whitespace on Windows. + Path cannot be null or whitespace on Windows. + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Der Pfad "{0}" überschreitet das maximale Pfadlimit des Betriebssystems. Der vollqualifizierte Dateiname muss weniger als {1} Zeichen umfassen. diff --git a/src/Framework/Resources/xlf/SR.es.xlf b/src/Framework/Resources/xlf/SR.es.xlf index daf26188dba..cd563d9247b 100644 --- a/src/Framework/Resources/xlf/SR.es.xlf +++ b/src/Framework/Resources/xlf/SR.es.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Debe ser una ruta de acceso raíz. + + Path cannot be null or whitespace on Windows. + Path cannot be null or whitespace on Windows. + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. La ruta de acceso {0} supera el límite máximo para la ruta de acceso del sistema operativo. El nombre de archivo completo debe ser inferior a {1} caracteres. diff --git a/src/Framework/Resources/xlf/SR.fr.xlf b/src/Framework/Resources/xlf/SR.fr.xlf index 90d5bf48fa1..bd9b4181a6c 100644 --- a/src/Framework/Resources/xlf/SR.fr.xlf +++ b/src/Framework/Resources/xlf/SR.fr.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Le chemin doit être associé à une racine. + + Path cannot be null or whitespace on Windows. + Path cannot be null or whitespace on Windows. + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Le chemin {0} dépasse la limite maximale de chemin du système d'exploitation. Le nom du fichier qualifié complet doit contenir moins de {1} caractères. diff --git a/src/Framework/Resources/xlf/SR.it.xlf b/src/Framework/Resources/xlf/SR.it.xlf index 9f24bafe1ca..452040e67f4 100644 --- a/src/Framework/Resources/xlf/SR.it.xlf +++ b/src/Framework/Resources/xlf/SR.it.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Il percorso deve contenere una radice. + + Path cannot be null or whitespace on Windows. + Path cannot be null or whitespace on Windows. + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Il percorso {0} supera il limite massimo dei percorsi del sistema operativo. Il nome completo del file deve essere composto da meno di {1}. diff --git a/src/Framework/Resources/xlf/SR.ja.xlf b/src/Framework/Resources/xlf/SR.ja.xlf index 4490b72a34e..a4f39af9a27 100644 --- a/src/Framework/Resources/xlf/SR.ja.xlf +++ b/src/Framework/Resources/xlf/SR.ja.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ パスはルート指定パスである必要があります。 + + Path cannot be null or whitespace on Windows. + Path cannot be null or whitespace on Windows. + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. パス: {0} は OS のパスの上限を越えています。完全修飾のファイル名は {1} 文字以下にする必要があります。 diff --git a/src/Framework/Resources/xlf/SR.ko.xlf b/src/Framework/Resources/xlf/SR.ko.xlf index d4df3b1c552..bb5b1547d70 100644 --- a/src/Framework/Resources/xlf/SR.ko.xlf +++ b/src/Framework/Resources/xlf/SR.ko.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ 루트 경로로 지정해야 합니다. + + Path cannot be null or whitespace on Windows. + Path cannot be null or whitespace on Windows. + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. 경로: {0}은(는) OS 최대 경로 제한을 초과합니다. 정규화된 파일 이름은 {1}자 이하여야 합니다. diff --git a/src/Framework/Resources/xlf/SR.pl.xlf b/src/Framework/Resources/xlf/SR.pl.xlf index 8a0af80daaf..e311d4c520c 100644 --- a/src/Framework/Resources/xlf/SR.pl.xlf +++ b/src/Framework/Resources/xlf/SR.pl.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Ścieżka musi zaczynać się od katalogu głównego. + + Path cannot be null or whitespace on Windows. + Path cannot be null or whitespace on Windows. + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Ścieżka: {0} przekracza limit maksymalnej długości ścieżki w systemie operacyjnym. W pełni kwalifikowana nazwa pliku musi się składać z mniej niż {1} znaków. diff --git a/src/Framework/Resources/xlf/SR.pt-BR.xlf b/src/Framework/Resources/xlf/SR.pt-BR.xlf index 0cf2bf2f94b..53b26bc93d7 100644 --- a/src/Framework/Resources/xlf/SR.pt-BR.xlf +++ b/src/Framework/Resources/xlf/SR.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Caminho deve ter raiz. + + Path cannot be null or whitespace on Windows. + Path cannot be null or whitespace on Windows. + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Caminho: {0} excede o limite máximo do caminho do SO. O nome do arquivo totalmente qualificado deve ter menos de {1} caracteres. diff --git a/src/Framework/Resources/xlf/SR.ru.xlf b/src/Framework/Resources/xlf/SR.ru.xlf index 19c782d0903..ffd8f1d4b80 100644 --- a/src/Framework/Resources/xlf/SR.ru.xlf +++ b/src/Framework/Resources/xlf/SR.ru.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Путь должен иметь корень. + + Path cannot be null or whitespace on Windows. + Path cannot be null or whitespace on Windows. + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Длина пути {0} превышает максимально допустимую в ОС. Символов в полном имени файла должно быть не больше {1}. diff --git a/src/Framework/Resources/xlf/SR.tr.xlf b/src/Framework/Resources/xlf/SR.tr.xlf index fd1ff3fa370..ca0ce7ab02b 100644 --- a/src/Framework/Resources/xlf/SR.tr.xlf +++ b/src/Framework/Resources/xlf/SR.tr.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ Yol kökü belirtilmelidir. + + Path cannot be null or whitespace on Windows. + Path cannot be null or whitespace on Windows. + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. Yol: {0}, işletim sisteminin en yüksek yol sınırını aşıyor. Tam dosya adı en fazla {1} karakter olmalıdır. diff --git a/src/Framework/Resources/xlf/SR.zh-Hans.xlf b/src/Framework/Resources/xlf/SR.zh-Hans.xlf index 5dcdb7945bc..df6c2f13af5 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hans.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ 路径必须是根路径。 + + Path cannot be null or whitespace on Windows. + Path cannot be null or whitespace on Windows. + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. 路径: {0} 超过 OS 最大路径限制。完全限定的文件名必须少于 {1} 个字符。 diff --git a/src/Framework/Resources/xlf/SR.zh-Hant.xlf b/src/Framework/Resources/xlf/SR.zh-Hant.xlf index f70b3542096..d7bb1dc129a 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hant.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -107,6 +107,11 @@ 路徑必須為根路徑。 + + Path cannot be null or whitespace on Windows. + Path cannot be null or whitespace on Windows. + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. 路徑: {0} 超過 OS 路徑上限。完整檔案名稱必須少於 {1} 個字元。 diff --git a/src/Tasks/AddToWin32Manifest.cs b/src/Tasks/AddToWin32Manifest.cs index 53cc63a28f0..33921dba54b 100644 --- a/src/Tasks/AddToWin32Manifest.cs +++ b/src/Tasks/AddToWin32Manifest.cs @@ -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; diff --git a/src/Tasks/Move.cs b/src/Tasks/Move.cs index 52003eed64f..70bc6393e07 100644 --- a/src/Tasks/Move.cs +++ b/src/Tasks/Move.cs @@ -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); diff --git a/src/Utilities/ToolTask.cs b/src/Utilities/ToolTask.cs index 05ed6aca646..f3e85af25ea 100644 --- a/src/Utilities/ToolTask.cs +++ b/src/Utilities/ToolTask.cs @@ -678,9 +678,11 @@ protected virtual ProcessStartInfo GetProcessStartInfo( startInfo.RedirectStandardInput = true; } - // Generally we won't set a working directory, and it will use the current directory + // Generally we won't set a working directory, and it will use the current directory. + // Treat whitespace-only working directories the same as null/empty so AbsolutePath's + // Windows whitespace guard cannot turn a benign "no working directory" case into a throw. string workingDirectory = GetWorkingDirectory(); - if (!string.IsNullOrEmpty(workingDirectory)) + if (!string.IsNullOrWhiteSpace(workingDirectory)) { startInfo.WorkingDirectory = TaskEnvironment.GetAbsolutePath(workingDirectory); }