Escape Windows commandline percent expansion
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user