diff --git a/Duplicati/WebserverCore/Endpoints/V1/Commandline.cs b/Duplicati/WebserverCore/Endpoints/V1/Commandline.cs index 157dff3bf..9c430c151 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Commandline.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Commandline.cs @@ -18,6 +18,8 @@ // 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.Server; +using Duplicati.Server.Database; using Duplicati.WebserverCore.Abstractions; using Duplicati.WebserverCore.Exceptions; using Microsoft.AspNetCore.Mvc; @@ -38,8 +40,8 @@ public class Commandline : IEndpointV1 => ExecuteAbortCommand(commandlineRunService, runid)) .RequireAuthorization(); - group.MapPost("/commandline", ([FromServices] ICommandlineRunService commandlineRunService, [FromBody] string[] input) - => ExecuteRunCommand(commandlineRunService, input)) + group.MapPost("/commandline", ([FromServices] Connection connection, [FromServices] ICommandlineRunService commandlineRunService, [FromBody] string[] input) + => ExecuteRunCommand(connection, commandlineRunService, input)) .RequireAuthorization(); } @@ -87,12 +89,73 @@ public class Commandline : IEndpointV1 activeRun.Abort(); } - private static Dto.CommandlineTaskStartedDto ExecuteRunCommand(ICommandlineRunService commandlineRunService, string[] arguments) + private static Dto.CommandlineTaskStartedDto ExecuteRunCommand(Connection connection, ICommandlineRunService commandlineRunService, string[] arguments) { if (arguments.Length == 0) throw new BadRequestException("No arguments provided"); if (CommandLine.Program.SupportedCommands.Contains(arguments[0]) == false) throw new BadRequestException("Command not supported"); + + // Parse arguments into args + options, so we can replace any password placeholders + var args = new List(); + var options = new List>(); + var inArgParse = false; + + foreach (var n in arguments) + { + if (n.StartsWith("--", StringComparison.Ordinal)) + { + var eq = n.IndexOf('='); + if (eq > 2) + { + var key = n.Substring(2, eq - 2); + var value = n.Substring(eq + 1); + if (value.StartsWith("\"") && value.EndsWith("\"") && value.Length >= 2) + value = value.Substring(1, value.Length - 2); + options.Add(new KeyValuePair(key, value)); + inArgParse = false; + } + else + { + var key = n.Substring(2); + options.Add(new KeyValuePair(key, null)); + inArgParse = true; + } + } + // Support "--key value" format + else if (inArgParse) + { + var last = options[^1]; + options[^1] = new KeyValuePair(last.Key, n); + inArgParse = false; + } + else + { + args.Add(n); + } + } + + var backupId = options.FirstOrDefault(x => x.Key.Equals("backup-id", StringComparison.OrdinalIgnoreCase)).Value; + if (backupId != null) + { + var backup = connection.GetBackup(backupId); + if (backup != null) + { + if (args.Count > 1) + args[1] = QuerystringMasking.Unmask(args[1], backup.TargetURL); + + var settings = backup.Settings.ToDictionary(x => x.Name, x => x.Value, StringComparer.OrdinalIgnoreCase); + for (int i = 0; i < options.Count; i++) + { + var opt = options[i]; + if (settings.TryGetValue(opt.Key, out var val) && Connection.IsPasswordPlaceholder(opt.Value)) + options[i] = new KeyValuePair(opt.Key, val); + } + } + } + + arguments = args.Concat(options.Select(x => x.Value == null ? $"--{x.Key}" : $"--{x.Key}={x.Value}")).ToArray(); + return new Dto.CommandlineTaskStartedDto("OK", commandlineRunService.StartTask(arguments)); } diff --git a/Duplicati/WebserverCore/Services/CommandlineRunService.cs b/Duplicati/WebserverCore/Services/CommandlineRunService.cs index 670fcc496..063869262 100644 --- a/Duplicati/WebserverCore/Services/CommandlineRunService.cs +++ b/Duplicati/WebserverCore/Services/CommandlineRunService.cs @@ -99,7 +99,6 @@ public class CommandlineRunService(IQueueRunnerService queueRunnerService, ILogW public bool Finished { get; set; } = false; public bool Started { get; set; } = false; public bool IsLogDisposed { get; set; } = false; - public Thread? Thread; public IEnumerable GetLog() => Log; @@ -108,10 +107,6 @@ public class CommandlineRunService(IQueueRunnerService queueRunnerService, ILogW var tt = this.Task; if (tt != null) tt.Abort(); - - var tr = this.Thread; - if (tr != null) - tr.Interrupt(); } } @@ -143,7 +138,6 @@ public class CommandlineRunService(IQueueRunnerService queueRunnerService, ILogW { try { - k.Thread = Thread.CurrentThread; k.Started = true; // Expand sources for supporting %DOCUMENTS% and similar @@ -191,7 +185,6 @@ public class CommandlineRunService(IQueueRunnerService queueRunnerService, ILogW finally { k.Finished = true; - k.Thread = null; } });