Escape Windows commandline percent expansion

This commit is contained in:
JamBalaya56562
2026-07-07 01:45:37 +00:00
parent 9b4afb4384
commit b5f6fcf39f
2 changed files with 79 additions and 8 deletions
+18 -8
View File
@@ -41,6 +41,8 @@ using System.Threading.Tasks;
using Duplicati.Library.Common.IO;
using Duplicati.StreamUtil;
[assembly: InternalsVisibleTo("Duplicati.UnitTest")]
namespace Duplicati.Library.Utility
{
public static class Utility
@@ -1466,11 +1468,22 @@ namespace Duplicati.Library.Utility
/// <param name="allowEnvExpansion">A flag indicating if environment variables are allowed to be expanded</param>
[return: NotNullIfNotNull("arg")]
public static string? WrapCommandLineElement(string? arg, bool allowEnvExpansion)
=> WrapCommandLineElement(arg, allowEnvExpansion, OperatingSystem.IsWindows());
/// <summary>
/// Wraps a single argument in quotes suitable for passing on a commandline.
/// </summary>
/// <returns>The wrapped commandline element.</returns>
/// <param name="arg">The argument to wrap.</param>
/// <param name="allowEnvExpansion">A flag indicating if environment variables are allowed to be expanded.</param>
/// <param name="isWindows">A flag indicating if Windows commandline escaping should be used.</param>
[return: NotNullIfNotNull("arg")]
internal static string? WrapCommandLineElement(string? arg, bool allowEnvExpansion, bool isWindows)
{
if (string.IsNullOrWhiteSpace(arg))
return arg;
if (!OperatingSystem.IsWindows())
if (!isWindows)
{
// We could consider using single quotes that prevents all expansions
//if (!allowEnvExpansion)
@@ -1490,16 +1503,13 @@ namespace Duplicati.Library.Utility
}
else
{
// Windows needs only needs " replaced with "",
// but is prone to %var% expansion when used in
// immediate mode (i.e. from command prompt)
// Windows needs " replaced with "", but is prone to %var%
// expansion when used in immediate mode (i.e. from command prompt)
// Fortunately it does not expand when processes
// are started from within .Net
if (!allowEnvExpansion)
arg = arg.Replace("%", "%%");
// TODO: I have not found a way to avoid escaping %varname%,
// and sadly it expands only if the variable exists
// making it even rarer and harder to diagnose when
// it happens
arg = arg.Replace(@"""", @"""""");
// Also fix the case where the argument ends with a slash
@@ -0,0 +1,61 @@
// Copyright (C) 2026, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using Duplicati.Library.Utility;
using NUnit.Framework;
using Assert = NUnit.Framework.Legacy.ClassicAssert;
namespace Duplicati.UnitTest
{
public class CommandLineWrappingTests
{
[Test]
[Category("Utility")]
public static void WrapCommandLineElementEscapesWindowsPercentWhenEnvironmentExpansionIsDisabled()
{
Assert.AreEqual("\"abc%%TEMP%%def\"", Utility.WrapCommandLineElement("abc%TEMP%def", false, true));
Assert.AreEqual("\"100%%\"", Utility.WrapCommandLineElement("100%", false, true));
}
[Test]
[Category("Utility")]
public static void WrapCommandLineElementKeepsWindowsPercentWhenEnvironmentExpansionIsEnabled()
{
Assert.AreEqual("\"abc%TEMP%def\"", Utility.WrapCommandLineElement("abc%TEMP%def", true, true));
}
[Test]
[Category("Utility")]
public static void WrapCommandLineElementKeepsExistingWindowsQuotingBehavior()
{
Assert.AreEqual("\"has\"\"quote\"", Utility.WrapCommandLineElement("has\"quote", false, true));
Assert.AreEqual("\"C:\\Temp\\folder\\\\\"", Utility.WrapCommandLineElement("C:\\Temp\\folder\\", false, true));
}
[Test]
[Category("Utility")]
public static void WrapCommandLineElementRespectsUnixEnvironmentExpansionFlag()
{
Assert.AreEqual("\"$HOME/file\"", Utility.WrapCommandLineElement("$HOME/file", true, false));
Assert.AreEqual("\"\\$HOME/file\"", Utility.WrapCommandLineElement("$HOME/file", false, false));
}
}
}