First pass at introducing kestrel and refactoring to make the RESTAPI be its own package.

This commit is contained in:
Thomas Suckow
2022-12-30 10:59:29 -08:00
parent 83cc35664b
commit 235568dade
79 changed files with 11412 additions and 11074 deletions
@@ -0,0 +1,53 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Collections.Generic;
using Duplicati.Library.Common.IO;
using Duplicati.Library.Utility;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class Acknowledgements : IRESTMethodGET, IRESTMethodDocumented
{
private class GetResponse
{
public string Status;
public string Acknowledgements;
}
public void GET(string key, RequestInfo info)
{
var path = SystemIO.IO_OS.PathCombine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "acknowledgements.txt");
info.OutputOK(new GetResponse() {
Status = "OK",
Acknowledgements = System.IO.File.ReadAllText(path)
});
}
public string Description { get { return "Gets all acknowledgements"; } }
public IEnumerable<KeyValuePair<string, Type>> Types
{
get
{
return new KeyValuePair<string, Type>[] {
new KeyValuePair<string, Type>(HttpServer.Method.Get, typeof(GetResponse)),
};
}
}
}
}
@@ -0,0 +1,733 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Collections.Generic;
using Duplicati.Server.Serialization;
using System.IO;
using System.Linq;
using Duplicati.Server.Serialization.Interface;
using Duplicati.Library.RestAPI;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class Backup : IRESTMethodGET, IRESTMethodPUT, IRESTMethodPOST, IRESTMethodDELETE, IRESTMethodDocumented
{
public class GetResponse
{
public class GetResponseData
{
public Serialization.Interface.ISchedule Schedule;
public Serialization.Interface.IBackup Backup;
public Dictionary<string, string> DisplayNames;
}
public bool success;
public GetResponseData data;
}
private void SearchFiles(IBackup backup, string filterstring, RequestInfo info)
{
var filter = filterstring;
var timestring = info.Request.QueryString["time"].Value;
var allversion = Duplicati.Library.Utility.Utility.ParseBool(info.Request.QueryString["all-versions"].Value, false);
if (string.IsNullOrWhiteSpace(timestring) && !allversion)
{
info.ReportClientError("Invalid or missing time", System.Net.HttpStatusCode.BadRequest);
return;
}
var prefixonly = Duplicati.Library.Utility.Utility.ParseBool(info.Request.QueryString["prefix-only"].Value, false);
var foldercontents = Duplicati.Library.Utility.Utility.ParseBool(info.Request.QueryString["folder-contents"].Value, false);
var time = new DateTime();
if (!allversion)
time = Duplicati.Library.Utility.Timeparser.ParseTimeInterval(timestring, DateTime.Now);
var r = Runner.Run(Runner.CreateListTask(backup, new string[] { filter }, prefixonly, allversion, foldercontents, time), false) as Duplicati.Library.Interface.IListResults;
var result = new Dictionary<string, object>();
foreach(HttpServer.HttpInputItem n in info.Request.QueryString)
result[n.Name] = n.Value;
result["Filesets"] = r.Filesets;
result["Files"] = r.Files
// Group directories first - support either directory separator here as we may be restoring data from an alternate platform
.OrderByDescending(f => (f.Path.StartsWith("/", StringComparison.Ordinal) && f.Path.EndsWith("/", StringComparison.Ordinal)) || (!f.Path.StartsWith("/", StringComparison.Ordinal) && f.Path.EndsWith("\\", StringComparison.Ordinal)))
// Sort both groups (directories and files) alphabetically
.ThenBy(f => f.Path);
info.OutputOK(result);
}
private void ListFileSets(IBackup backup, RequestInfo info)
{
var input = info.Request.QueryString;
var extra = new Dictionary<string, string>
{
["list-sets-only"] = "true"
};
if (input["include-metadata"].Value != null)
extra["list-sets-only"] = (!Library.Utility.Utility.ParseBool(input["include-metadata"].Value, false)).ToString();
if (input["from-remote-only"].Value != null)
extra["no-local-db"] = Library.Utility.Utility.ParseBool(input["from-remote-only"].Value, false).ToString();
var r = Runner.Run(Runner.CreateTask(DuplicatiOperation.List, backup, extra), false) as Duplicati.Library.Interface.IListResults;
if (r.EncryptedFiles && backup.Settings.Any(x => string.Equals("--no-encryption", x.Name, StringComparison.OrdinalIgnoreCase)))
info.ReportServerError("encrypted-storage");
else
info.OutputOK(r.Filesets);
}
private void FetchLogData(IBackup backup, RequestInfo info)
{
using(var con = Duplicati.Library.SQLiteHelper.SQLiteLoader.LoadConnection(backup.DBPath))
using(var cmd = con.CreateCommand())
info.OutputOK(LogData.DumpTable(cmd, "LogData", "ID", info.Request.QueryString["offset"].Value, info.Request.QueryString["pagesize"].Value));
}
private void FetchRemoteLogData(IBackup backup, RequestInfo info)
{
using(var con = Duplicati.Library.SQLiteHelper.SQLiteLoader.LoadConnection(backup.DBPath))
using(var cmd = con.CreateCommand())
{
var dt = LogData.DumpTable(cmd, "RemoteOperation", "ID", info.Request.QueryString["offset"].Value, info.Request.QueryString["pagesize"].Value);
// Unwrap raw data to a string
foreach(var n in dt)
try { n["Data"] = System.Text.Encoding.UTF8.GetString((byte[])n["Data"]); }
catch { }
info.OutputOK(dt);
}
}
private void IsDBUsedElseWhere(IBackup backup, RequestInfo info)
{
info.OutputOK(new { inuse = Library.Main.DatabaseLocator.IsDatabasePathInUse(backup.DBPath) });
}
public static void RemovePasswords(IBackup backup)
{
backup.SanitizeSettings();
backup.SanitizeTargetUrl();
}
private void Export(IBackup backup, RequestInfo info)
{
var cmdline = Library.Utility.Utility.ParseBool(info.Request.QueryString["cmdline"].Value, false);
var argsonly = Library.Utility.Utility.ParseBool(info.Request.QueryString["argsonly"].Value, false);
var exportPasswords = Library.Utility.Utility.ParseBool(info.Request.QueryString["export-passwords"].Value, false);
if (!exportPasswords)
{
Backup.RemovePasswords(backup);
}
if (cmdline)
{
info.OutputOK(new { Command = Runner.GetCommandLine(Runner.CreateTask(DuplicatiOperation.Backup, backup)) });
}
else if (argsonly)
{
var parts = Runner.GetCommandLineParts(Runner.CreateTask(DuplicatiOperation.Backup, backup));
info.OutputOK(new {
Backend = parts.First(),
Arguments = parts.Skip(1).Where(x => !x.StartsWith("--", StringComparison.Ordinal)),
Options = parts.Skip(1).Where(x => x.StartsWith("--", StringComparison.Ordinal))
});
}
else
{
var passphrase = info.Request.QueryString["passphrase"].Value;
byte[] data = Backup.ExportToJSON(backup, passphrase);
string filename = Library.Utility.Uri.UrlEncode(backup.Name) + "-duplicati-config.json";
if (!string.IsNullOrWhiteSpace(passphrase))
{
filename += ".aes";
}
info.Response.ContentLength = data.Length;
info.Response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}", filename));
info.Response.ContentType = "application/octet-stream";
info.BodyWriter.SetOK();
info.Response.SendHeaders();
info.Response.SendBody(data);
}
}
public static byte[] ExportToJSON(IBackup backup, string passphrase)
{
Serializable.ImportExportStructure ipx = FIXMEGlobal.DataConnection.PrepareBackupForExport(backup);
byte[] data;
using (MemoryStream ms = new System.IO.MemoryStream())
{
using (StreamWriter sw = new System.IO.StreamWriter(ms))
{
Serializer.SerializeJson(sw, ipx, true);
if (!string.IsNullOrWhiteSpace(passphrase))
{
ms.Position = 0;
using (MemoryStream ms2 = new System.IO.MemoryStream())
{
using (Library.Encryption.AESEncryption m = new Duplicati.Library.Encryption.AESEncryption(passphrase, new Dictionary<string, string>()))
{
m.Encrypt(ms, ms2);
data = ms2.ToArray();
}
}
}
else
{
data = ms.ToArray();
}
}
}
return data;
}
private void RestoreFiles(IBackup backup, RequestInfo info)
{
var input = info.Request.Form;
string[] filters = parsePaths(input["paths"].Value ?? string.Empty);
var passphrase = string.IsNullOrEmpty(input["passphrase"].Value) ? null : input["passphrase"].Value;
var time = Duplicati.Library.Utility.Timeparser.ParseTimeInterval(input["time"].Value, DateTime.Now);
var restoreTarget = input["restore-path"].Value;
var overwrite = Duplicati.Library.Utility.Utility.ParseBool(input["overwrite"].Value, false);
var permissions = Duplicati.Library.Utility.Utility.ParseBool(input["permissions"].Value, false);
var skip_metadata = Duplicati.Library.Utility.Utility.ParseBool(input["skip-metadata"].Value, false);
var task = Runner.CreateRestoreTask(backup, filters, time, restoreTarget, overwrite, permissions, skip_metadata, passphrase);
FIXMEGlobal.WorkThread.AddTask(task);
info.OutputOK(new { TaskID = task.TaskID });
}
private void CreateReport(IBackup backup, RequestInfo info)
{
var task = Runner.CreateTask(DuplicatiOperation.CreateReport, backup);
FIXMEGlobal.WorkThread.AddTask(task);
FIXMEGlobal.StatusEventNotifyer.SignalNewEvent();
info.OutputOK(new { Status = "OK", ID = task.TaskID });
}
private void ReportRemoteSize(IBackup backup, RequestInfo info)
{
var task = Runner.CreateTask(DuplicatiOperation.ListRemote, backup);
FIXMEGlobal.WorkThread.AddTask(task);
FIXMEGlobal.StatusEventNotifyer.SignalNewEvent();
info.OutputOK(new { Status = "OK", ID = task.TaskID });
}
private void Repair(IBackup backup, RequestInfo info)
{
DoRepair(backup, info, false);
}
private void RepairUpdate(IBackup backup, RequestInfo info)
{
DoRepair(backup, info, true);
}
private void Vacuum(IBackup backup, RequestInfo info)
{
var task = Runner.CreateTask(DuplicatiOperation.Vacuum, backup);
FIXMEGlobal.WorkThread.AddTask(task);
FIXMEGlobal.StatusEventNotifyer.SignalNewEvent();
info.OutputOK(new { Status = "OK", ID = task.TaskID });
}
private void Verify(IBackup backup, RequestInfo info)
{
var task = Runner.CreateTask(DuplicatiOperation.Verify, backup);
FIXMEGlobal.WorkThread.AddTask(task);
FIXMEGlobal.StatusEventNotifyer.SignalNewEvent();
info.OutputOK(new {Status = "OK", ID = task.TaskID});
}
private void Compact(IBackup backup, RequestInfo info)
{
var task = Runner.CreateTask(DuplicatiOperation.Compact, backup);
FIXMEGlobal.WorkThread.AddTask(task);
FIXMEGlobal.StatusEventNotifyer.SignalNewEvent();
info.OutputOK(new { Status = "OK", ID = task.TaskID });
}
private string[] parsePaths(string paths)
{
string[] filters;
var rawpaths = (paths ?? string.Empty).Trim();
// We send the file list as a JSON array to avoid encoding issues with the path separator
// as it is an allowed character in file and path names.
// We also accept the old way, for compatibility with the greeno theme
if (!string.IsNullOrWhiteSpace(rawpaths) && rawpaths.StartsWith("[", StringComparison.Ordinal) && rawpaths.EndsWith("]", StringComparison.Ordinal))
filters = Newtonsoft.Json.JsonConvert.DeserializeObject<string[]>(rawpaths);
else
filters = paths.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries);
return filters;
}
private void DoRepair(IBackup backup, RequestInfo info, bool repairUpdate)
{
var input = info.Request.Form;
string[] filters = null;
var extra = new Dictionary<string, string>();
if (input["only-paths"].Value != null)
extra["repair-only-paths"] = (Library.Utility.Utility.ParseBool(input["only-paths"].Value, false)).ToString();
if (input["time"].Value != null)
extra["time"] = input["time"].Value;
if (input["version"].Value != null)
extra["version"] = input["version"].Value;
if (input["paths"].Value != null)
filters = parsePaths(input["paths"].Value);
var task = Runner.CreateTask(repairUpdate ? DuplicatiOperation.RepairUpdate : DuplicatiOperation.Repair, backup, extra, filters);
FIXMEGlobal.WorkThread.AddTask(task);
FIXMEGlobal.StatusEventNotifyer.SignalNewEvent();
info.OutputOK(new {Status = "OK", ID = task.TaskID});
}
private void RunBackup(IBackup backup, RequestInfo info)
{
var t = FIXMEGlobal.WorkThread.CurrentTask;
var bt = t == null ? null : t.Backup;
if (bt != null && backup.ID == bt.ID)
{
// Already running
}
else if (FIXMEGlobal.WorkThread.CurrentTasks.Any(x => {
var bn = x?.Backup;
return bn == null || bn.ID == backup.ID;
}))
{
// Already in queue
}
else
{
FIXMEGlobal.WorkThread.AddTask(Runner.CreateTask(DuplicatiOperation.Backup, backup), true);
FIXMEGlobal.StatusEventNotifyer.SignalNewEvent();
}
info.OutputOK();
}
private void IsActive(IBackup backup, RequestInfo info)
{
var t = FIXMEGlobal.WorkThread.CurrentTask;
var bt = t?.Backup;
if (bt != null && backup.ID == bt.ID)
{
info.OutputOK(new { Status = "OK", Active = true });
return;
}
else if (FIXMEGlobal.WorkThread.CurrentTasks.Any(x =>
{
var bn = x?.Backup;
return bn == null || bn.ID == backup.ID;
}))
{
info.OutputOK(new { Status = "OK", Active = true });
return;
}
else
{
info.OutputOK(new { Status = "OK", Active = false });
return;
}
}
private void UpdateDatabasePath(IBackup backup, RequestInfo info, bool move)
{
var np = info.Request.Form["path"].Value;
if (string.IsNullOrWhiteSpace(np))
info.ReportClientError("No target path supplied", System.Net.HttpStatusCode.BadRequest);
else if (!Path.IsPathRooted(np))
info.ReportClientError("Target path is relative, please supply a fully qualified path", System.Net.HttpStatusCode.BadRequest);
else
{
if (move && (File.Exists(np) || Directory.Exists(np)))
info.ReportClientError("A file already exists at the new location", System.Net.HttpStatusCode.Conflict);
else
{
if (move)
File.Move(backup.DBPath, np);
FIXMEGlobal.DataConnection.UpdateBackupDBPath(backup, np);
}
}
}
public void GET(string key, RequestInfo info)
{
var parts = (key ?? "").Split(new char[] { '/' }, 2);
var bk = FIXMEGlobal.DataConnection.GetBackup(parts.First());
if (bk == null)
info.ReportClientError("Invalid or missing backup id", System.Net.HttpStatusCode.NotFound);
else
{
if (parts.Length > 1)
{
var operation = parts.Last().Split(new char[] {'/'}).First().ToLowerInvariant();
switch (operation)
{
case "files":
var filter = parts.Last().Split(new char[] { '/' }, 2).Skip(1).FirstOrDefault();
if (!string.IsNullOrWhiteSpace(info.Request.QueryString["filter"].Value))
filter = info.Request.QueryString["filter"].Value;
SearchFiles(bk, filter, info);
return;
case "log":
FetchLogData(bk, info);
return;
case "remotelog":
FetchRemoteLogData(bk, info);
return;
case "filesets":
ListFileSets(bk, info);
return;
case "export":
Export(bk, info);
return;
case "isdbusedelsewhere":
IsDBUsedElseWhere(bk, info);
return;
case "isactive":
IsActive(bk, info);
return;
default:
info.ReportClientError(string.Format("Invalid component: {0}", operation), System.Net.HttpStatusCode.BadRequest);
return;
}
}
var scheduleId = FIXMEGlobal.DataConnection.GetScheduleIDsFromTags(new string[] { "ID=" + bk.ID });
var schedule = scheduleId.Any() ? FIXMEGlobal.DataConnection.GetSchedule(scheduleId.First()) : null;
var sourcenames = SpecialFolders.GetSourceNames(bk);
//TODO: Filter out the password in both settings and the target url
info.OutputOK(new GetResponse()
{
success = true,
data = new GetResponse.GetResponseData {
Schedule = schedule,
Backup = bk,
DisplayNames = sourcenames
}
});
}
}
public void POST(string key, RequestInfo info)
{
var parts = (key ?? "").Split(new char[] { '/' }, 2);
var bk = FIXMEGlobal.DataConnection.GetBackup(parts.First());
if (bk == null)
info.ReportClientError("Invalid or missing backup id", System.Net.HttpStatusCode.NotFound);
else
{
if (parts.Length > 1)
{
var operation = parts.Last().Split(new char[] { '/' }).First().ToLowerInvariant();
switch (operation)
{
case "deletedb":
System.IO.File.Delete(bk.DBPath);
info.OutputOK();
return;
case "movedb":
UpdateDatabasePath(bk, info, true);
return;
case "updatedb":
UpdateDatabasePath(bk, info, false);
return;
case "restore":
RestoreFiles(bk, info);
return;
case "createreport":
CreateReport(bk, info);
return;
case "repair":
Repair(bk, info);
return;
case "repairupdate":
RepairUpdate(bk, info);
return;
case "vacuum":
Vacuum(bk, info);
return;
case "verify":
Verify(bk, info);
return;
case "compact":
Compact(bk, info);
return;
case "start":
case "run":
RunBackup(bk, info);
return;
case "report-remote-size":
ReportRemoteSize(bk, info);
return;
case "copytotemp":
var ipx = Serializer.Deserialize<Database.Backup>(new StringReader(Newtonsoft.Json.JsonConvert.SerializeObject(bk)));
using(var tf = new Duplicati.Library.Utility.TempFile())
ipx.DBPath = tf;
ipx.ID = null;
info.OutputOK(new { status = "OK", ID = FIXMEGlobal.DataConnection.RegisterTemporaryBackup(ipx) });
return;
}
}
info.ReportClientError("Invalid request", System.Net.HttpStatusCode.BadRequest);
}
}
public void PUT(string key, RequestInfo info)
{
string str = info.Request.Form["data"].Value;
if (string.IsNullOrWhiteSpace(str))
str = new StreamReader(info.Request.Body, System.Text.Encoding.UTF8).ReadToEnd();
if (string.IsNullOrWhiteSpace(str))
{
info.ReportClientError("Missing backup object", System.Net.HttpStatusCode.BadRequest);
return;
}
Backups.AddOrUpdateBackupData data = null;
try
{
data = Serializer.Deserialize<Backups.AddOrUpdateBackupData>(new StringReader(str));
if (data.Backup == null)
{
info.ReportClientError("Data object had no backup entry", System.Net.HttpStatusCode.BadRequest);
return;
}
if (!string.IsNullOrEmpty(key))
data.Backup.ID = key;
if (string.IsNullOrEmpty(data.Backup.ID))
{
info.ReportClientError("Invalid or missing backup id", System.Net.HttpStatusCode.BadRequest);
return;
}
if (data.Backup.IsTemporary)
{
var backup = FIXMEGlobal.DataConnection.GetBackup(data.Backup.ID);
if (backup.IsTemporary)
throw new InvalidDataException("External is temporary but internal is not?");
FIXMEGlobal.DataConnection.UpdateTemporaryBackup(backup);
info.OutputOK();
}
else
{
lock(FIXMEGlobal.DataConnection.m_lock)
{
var backup = FIXMEGlobal.DataConnection.GetBackup(data.Backup.ID);
if (backup == null)
{
info.ReportClientError("Invalid or missing backup id", System.Net.HttpStatusCode.NotFound);
return;
}
if (FIXMEGlobal.DataConnection.Backups.Any(x => x.Name.Equals(data.Backup.Name, StringComparison.OrdinalIgnoreCase) && x.ID != data.Backup.ID))
{
info.ReportClientError("There already exists a backup with the name: " + data.Backup.Name, System.Net.HttpStatusCode.Conflict);
return;
}
var err = FIXMEGlobal.DataConnection.ValidateBackup(data.Backup, data.Schedule);
if (!string.IsNullOrWhiteSpace(err))
{
info.ReportClientError(err, System.Net.HttpStatusCode.BadRequest);
return;
}
//TODO: Merge in real passwords where the placeholder is found
FIXMEGlobal.DataConnection.AddOrUpdateBackupAndSchedule(data.Backup, data.Schedule);
}
info.OutputOK();
}
}
catch (Exception ex)
{
if (data == null)
info.ReportClientError(string.Format("Unable to parse backup or schedule object: {0}", ex.Message), System.Net.HttpStatusCode.BadRequest);
else
info.ReportClientError(string.Format("Unable to save backup or schedule: {0}", ex.Message), System.Net.HttpStatusCode.InternalServerError);
}
}
public void DELETE(string key, RequestInfo info)
{
var backup = FIXMEGlobal.DataConnection.GetBackup(key);
if (backup == null)
{
info.ReportClientError("Invalid or missing backup id", System.Net.HttpStatusCode.NotFound);
return;
}
var delete_remote_files = Library.Utility.Utility.ParseBool(info.Request.Param["delete-remote-files"].Value, false);
if (delete_remote_files)
{
var captcha_token = info.Request.Param["captcha-token"].Value;
var captcha_answer = info.Request.Param["captcha-answer"].Value;
if (string.IsNullOrWhiteSpace(captcha_token) || string.IsNullOrWhiteSpace(captcha_answer))
{
info.ReportClientError("Missing captcha", System.Net.HttpStatusCode.Unauthorized);
return;
}
if (!Captcha.SolvedCaptcha(captcha_token, "DELETE /backup/" + backup.ID, captcha_answer))
{
info.ReportClientError("Invalid captcha", System.Net.HttpStatusCode.Forbidden);
return;
}
}
if (FIXMEGlobal.WorkThread.Active)
{
try
{
//TODO: It's not safe to access the values like this,
//because the runner thread might interfere
var nt = FIXMEGlobal.WorkThread.CurrentTask;
if (backup.Equals(nt == null ? null : nt.Backup))
{
bool force;
if (!bool.TryParse(info.Request.QueryString["force"].Value, out force))
force = false;
if (!force)
{
info.OutputError(new { status = "failed", reason = "backup-in-progress" });
return;
}
bool hasPaused = FIXMEGlobal.LiveControl.State == LiveControls.LiveControlState.Paused;
FIXMEGlobal.LiveControl.Pause();
for(int i = 0; i < 10; i++)
if (FIXMEGlobal.WorkThread.Active)
{
var t = FIXMEGlobal.WorkThread.CurrentTask;
if (backup.Equals(t == null ? null : t.Backup))
System.Threading.Thread.Sleep(1000);
else
break;
}
else
break;
if (FIXMEGlobal.WorkThread.Active)
{
var t = FIXMEGlobal.WorkThread.CurrentTask;
if (backup.Equals(t == null ? null : t.Backup))
{
if (hasPaused)
FIXMEGlobal.LiveControl.Resume();
info.OutputError(new { status = "failed", reason = "backup-unstoppable" });
return;
}
}
if (hasPaused)
FIXMEGlobal.LiveControl.Resume();
}
}
catch (Exception ex)
{
info.OutputError(new { status = "error", message = ex.Message });
return;
}
}
var extra = new Dictionary<string, string>();
if (!string.IsNullOrWhiteSpace(info.Request.Param["delete-local-db"].Value))
extra["delete-local-db"] = info.Request.Param["delete-local-db"].Value;
if (delete_remote_files)
extra["delete-remote-files"] = "true";
var task = Runner.CreateTask(DuplicatiOperation.Delete, backup, extra);
FIXMEGlobal.WorkThread.AddTask(task);
FIXMEGlobal.StatusEventNotifyer.SignalNewEvent();
info.OutputOK(new { Status = "OK", ID = task.TaskID });
}
public string Description { get { return "Retrieves, updates or deletes an existing backup and schedule"; } }
public IEnumerable<KeyValuePair<string, Type>> Types
{
get
{
return new KeyValuePair<string, Type>[] {
new KeyValuePair<string, Type>(HttpServer.Method.Get, typeof(GetResponse)),
new KeyValuePair<string, Type>(HttpServer.Method.Put, typeof(Backups.AddOrUpdateBackupData)),
new KeyValuePair<string, Type>(HttpServer.Method.Delete, typeof(long))
};
}
}
}
}
@@ -0,0 +1,137 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Linq;
using Duplicati.Library.Common.IO;
using Duplicati.Library.RestAPI;
using Duplicati.Library.Utility;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class BackupDefaults : IRESTMethodGET
{
private static readonly string LOGTAG = Library.Logging.Log.LogTagFromType<BackupDefaults>();
public void GET(string key, RequestInfo info)
{
// Start with a scratch object
var o = new Newtonsoft.Json.Linq.JObject();
// Add application wide settings
o.Add("ApplicationOptions", new Newtonsoft.Json.Linq.JArray(
from n in FIXMEGlobal.DataConnection.Settings
select Newtonsoft.Json.Linq.JObject.FromObject(n)
));
try
{
// Add built-in defaults
Newtonsoft.Json.Linq.JObject n;
using(var s = new System.IO.StreamReader(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(typeof(FIXMEGlobal), "newbackup.json")))
n = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(s.ReadToEnd());
MergeJsonObjects(o, n);
}
catch (Exception e)
{
Library.Logging.Log.WriteErrorMessage(LOGTAG, "BackupDefaultsError", e, "Failed to locate embeded backup defaults");
}
try
{
// Add install defaults/overrides, if present
var path = SystemIO.IO_OS.PathCombine(Library.AutoUpdater.UpdaterManager.InstalledBaseDir, "newbackup.json");
if (System.IO.File.Exists(path))
{
Newtonsoft.Json.Linq.JObject n;
n = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(System.IO.File.ReadAllText(path));
MergeJsonObjects(o, n);
}
}
catch (Exception e)
{
Library.Logging.Log.WriteErrorMessage(LOGTAG, "BackupDefaultsError", e, "Failed to process newbackup.json");
}
info.OutputOK(new
{
success = true,
data = o
});
}
private static void MergeJsonObjects(Newtonsoft.Json.Linq.JObject self, Newtonsoft.Json.Linq.JObject other)
{
foreach(var p in other.Properties())
{
var sp = self.Property(p.Name);
if (sp == null)
self.Add(p);
else
{
switch (p.Type)
{
// Primitives override
case Newtonsoft.Json.Linq.JTokenType.Boolean:
case Newtonsoft.Json.Linq.JTokenType.Bytes:
case Newtonsoft.Json.Linq.JTokenType.Comment:
case Newtonsoft.Json.Linq.JTokenType.Constructor:
case Newtonsoft.Json.Linq.JTokenType.Date:
case Newtonsoft.Json.Linq.JTokenType.Float:
case Newtonsoft.Json.Linq.JTokenType.Guid:
case Newtonsoft.Json.Linq.JTokenType.Integer:
case Newtonsoft.Json.Linq.JTokenType.String:
case Newtonsoft.Json.Linq.JTokenType.TimeSpan:
case Newtonsoft.Json.Linq.JTokenType.Uri:
case Newtonsoft.Json.Linq.JTokenType.None:
case Newtonsoft.Json.Linq.JTokenType.Null:
case Newtonsoft.Json.Linq.JTokenType.Undefined:
self.Replace(p);
break;
// Arrays merge
case Newtonsoft.Json.Linq.JTokenType.Array:
if (sp.Type == Newtonsoft.Json.Linq.JTokenType.Array)
sp.Value = new Newtonsoft.Json.Linq.JArray(((Newtonsoft.Json.Linq.JArray)sp.Value).Union((Newtonsoft.Json.Linq.JArray)p.Value));
else
{
var a = new Newtonsoft.Json.Linq.JArray(sp.Value);
sp.Value = new Newtonsoft.Json.Linq.JArray(a.Union((Newtonsoft.Json.Linq.JArray)p.Value));
}
break;
// Objects merge
case Newtonsoft.Json.Linq.JTokenType.Object:
if (sp.Type == Newtonsoft.Json.Linq.JTokenType.Object)
MergeJsonObjects((Newtonsoft.Json.Linq.JObject)sp.Value, (Newtonsoft.Json.Linq.JObject)p.Value);
else
sp.Value = p.Value;
break;
// Ignore other stuff
default:
break;
}
}
}
}
}
}
@@ -0,0 +1,300 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Linq;
using System.Collections.Generic;
using Duplicati.Server.Serialization;
using System.IO;
using Duplicati.Library.RestAPI;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class Backups : IRESTMethodGET, IRESTMethodPOST, IRESTMethodDocumented
{
public class AddOrUpdateBackupData
{
public Boolean IsUnencryptedOrPassphraseStored { get; set;}
public Database.Schedule Schedule { get; set;}
public Database.Backup Backup { get; set;}
}
public void GET(string key, RequestInfo info)
{
var schedules = FIXMEGlobal.DataConnection.Schedules;
var backups = FIXMEGlobal.DataConnection.Backups;
var all = from n in backups
select new AddOrUpdateBackupData {
IsUnencryptedOrPassphraseStored = FIXMEGlobal.DataConnection.IsUnencryptedOrPassphraseStored(long.Parse(n.ID)),
Backup = (Database.Backup)n,
Schedule =
(from x in schedules
where x.Tags != null && x.Tags.Contains("ID=" + n.ID)
select (Database.Schedule)x).FirstOrDefault()
};
info.BodyWriter.OutputOK(all.ToArray());
}
private void ImportBackup(RequestInfo info)
{
var output_template = "<html><body><script type=\"text/javascript\">var jso = 'JSO'; var rp = null; try { rp = parent['CBM']; } catch (e) {}; if (rp) { rp('MSG', jso); } else { alert; rp('MSG'); };</script></body></html>";
//output_template = "<html><body><script type=\"text/javascript\">alert('MSG');</script></body></html>";
try
{
var input = info.Request.Form;
var cmdline = Library.Utility.Utility.ParseBool(input["cmdline"].Value, false);
var import_metadata = Library.Utility.Utility.ParseBool(input["import_metadata"].Value, false);
var direct = Library.Utility.Utility.ParseBool(input["direct"].Value, false);
output_template = output_template.Replace("CBM", input["callback"].Value);
if (cmdline)
{
info.Response.ContentType = "text/html";
info.BodyWriter.Write(output_template.Replace("MSG", "Import from commandline not yet implemented"));
}
else
{
var file = info.Request.Form.GetFile("config");
if (file == null)
throw new Exception("No file uploaded");
Serializable.ImportExportStructure ipx = Backups.LoadConfiguration(file.Filename, import_metadata, () => input["passphrase"].Value);
if (direct)
{
lock (FIXMEGlobal.DataConnection.m_lock)
{
var basename = ipx.Backup.Name;
var c = 0;
while (c++ < 100 && FIXMEGlobal.DataConnection.Backups.Any(x => x.Name.Equals(ipx.Backup.Name, StringComparison.OrdinalIgnoreCase)))
ipx.Backup.Name = basename + " (" + c.ToString() + ")";
if (FIXMEGlobal.DataConnection.Backups.Any(x => x.Name.Equals(ipx.Backup.Name, StringComparison.OrdinalIgnoreCase)))
{
info.BodyWriter.SetOK();
info.Response.ContentType = "text/html";
info.BodyWriter.Write(output_template.Replace("MSG", "There already exists a backup with the name: " + basename.Replace("\'", "\\'")));
}
var err = FIXMEGlobal.DataConnection.ValidateBackup(ipx.Backup, ipx.Schedule);
if (!string.IsNullOrWhiteSpace(err))
{
info.ReportClientError(err, System.Net.HttpStatusCode.BadRequest);
return;
}
FIXMEGlobal.DataConnection.AddOrUpdateBackupAndSchedule(ipx.Backup, ipx.Schedule);
}
info.Response.ContentType = "text/html";
info.BodyWriter.Write(output_template.Replace("MSG", "OK"));
}
else
{
using (var sw = new StringWriter())
{
Serializer.SerializeJson(sw, ipx, true);
output_template = output_template.Replace("'JSO'", sw.ToString());
}
info.BodyWriter.Write(output_template.Replace("MSG", "Import completed, but a browser issue prevents loading the contents. Try using the direct import method instead."));
}
}
}
catch (Exception ex)
{
FIXMEGlobal.DataConnection.LogError("", "Failed to import backup", ex);
info.Response.ContentType = "text/html";
info.BodyWriter.Write(output_template.Replace("MSG", ex.Message.Replace("\'", "\\'").Replace("\r", "\\r").Replace("\n", "\\n")));
}
}
public static Serializable.ImportExportStructure ImportBackup(string configurationFile, bool importMetadata, Func<string> getPassword, Dictionary<string, string> advancedOptions)
{
// This removes the ID and DBPath from the backup configuration.
Serializable.ImportExportStructure importedStructure = Backups.LoadConfiguration(configurationFile, importMetadata, getPassword);
// This will create the Duplicati-server.sqlite database file if it doesn't exist.
using (Duplicati.Server.Database.Connection connection = FIXMEGlobal.GetDatabaseConnection(advancedOptions))
{
if (connection.Backups.Any(x => x.Name.Equals(importedStructure.Backup.Name, StringComparison.OrdinalIgnoreCase)))
{
throw new InvalidOperationException($"A backup with the name {importedStructure.Backup.Name} already exists.");
}
string error = connection.ValidateBackup(importedStructure.Backup, importedStructure.Schedule);
if (!string.IsNullOrWhiteSpace(error))
{
throw new InvalidOperationException(error);
}
// This creates a new ID and DBPath.
connection.AddOrUpdateBackupAndSchedule(importedStructure.Backup, importedStructure.Schedule);
}
return importedStructure;
}
private static Serializable.ImportExportStructure LoadConfiguration(string filename, bool importMetadata, Func<string> getPassword)
{
Serializable.ImportExportStructure ipx;
var buf = new byte[3];
using (var fs = System.IO.File.OpenRead(filename))
{
Duplicati.Library.Utility.Utility.ForceStreamRead(fs, buf, buf.Length);
fs.Position = 0;
if (buf[0] == 'A' && buf[1] == 'E' && buf[2] == 'S')
{
using (var m = new Duplicati.Library.Encryption.AESEncryption(getPassword(), new Dictionary<string, string>()))
{
using (var m2 = m.Decrypt(fs))
{
using (var sr = new System.IO.StreamReader(m2))
{
ipx = Serializer.Deserialize<Serializable.ImportExportStructure>(sr);
}
}
}
}
else
{
using (var sr = new System.IO.StreamReader(fs))
{
ipx = Serializer.Deserialize<Serializable.ImportExportStructure>(sr);
}
}
}
if (ipx.Backup == null)
{
throw new Exception("No backup found in document");
}
if (ipx.Backup.Metadata == null)
{
ipx.Backup.Metadata = new Dictionary<string, string>();
}
if (!importMetadata)
{
ipx.Backup.Metadata.Clear();
}
ipx.Backup.ID = null;
ipx.Backup.DBPath = null;
if (ipx.Schedule != null)
{
ipx.Schedule.ID = -1;
}
return ipx;
}
public void POST(string key, RequestInfo info)
{
if ("import".Equals(key, StringComparison.OrdinalIgnoreCase))
{
ImportBackup(info);
return;
}
AddOrUpdateBackupData data = null;
try
{
var str = info.Request.Form["data"].Value;
if (string.IsNullOrWhiteSpace(str))
str = new StreamReader(info.Request.Body, System.Text.Encoding.UTF8).ReadToEnd();
data = Serializer.Deserialize<AddOrUpdateBackupData>(new StringReader(str));
if (data.Backup == null)
{
info.ReportClientError("Data object had no backup entry", System.Net.HttpStatusCode.BadRequest);
return;
}
data.Backup.ID = null;
if (Duplicati.Library.Utility.Utility.ParseBool(info.Request.Form["temporary"].Value, false))
{
using(var tf = new Duplicati.Library.Utility.TempFile())
data.Backup.DBPath = tf;
data.Backup.Filters = data.Backup.Filters ?? new Duplicati.Server.Serialization.Interface.IFilter[0];
data.Backup.Settings = data.Backup.Settings ?? new Duplicati.Server.Serialization.Interface.ISetting[0];
FIXMEGlobal.DataConnection.RegisterTemporaryBackup(data.Backup);
info.OutputOK(new { status = "OK", ID = data.Backup.ID });
}
else
{
if (Library.Utility.Utility.ParseBool(info.Request.Form["existing_db"].Value, false))
{
data.Backup.DBPath = Library.Main.DatabaseLocator.GetDatabasePath(data.Backup.TargetURL, null, false, false);
if (string.IsNullOrWhiteSpace(data.Backup.DBPath))
throw new Exception("Unable to find remote db path?");
}
lock(FIXMEGlobal.DataConnection.m_lock)
{
if (FIXMEGlobal.DataConnection.Backups.Any(x => x.Name.Equals(data.Backup.Name, StringComparison.OrdinalIgnoreCase)))
{
info.ReportClientError("There already exists a backup with the name: " + data.Backup.Name, System.Net.HttpStatusCode.Conflict);
return;
}
var err = FIXMEGlobal.DataConnection.ValidateBackup(data.Backup, data.Schedule);
if (!string.IsNullOrWhiteSpace(err))
{
info.ReportClientError(err, System.Net.HttpStatusCode.BadRequest);
return;
}
FIXMEGlobal.DataConnection.AddOrUpdateBackupAndSchedule(data.Backup, data.Schedule);
}
info.OutputOK(new { status = "OK", ID = data.Backup.ID });
}
}
catch (Exception ex)
{
if (data == null)
info.ReportClientError(string.Format("Unable to parse backup or schedule object: {0}", ex.Message), System.Net.HttpStatusCode.BadRequest);
else
info.ReportClientError(string.Format("Unable to save schedule or backup object: {0}", ex.Message), System.Net.HttpStatusCode.InternalServerError);
}
}
public string Description { get { return "Return a list of current backups and their schedules"; } }
public IEnumerable<KeyValuePair<string, Type>> Types
{
get
{
return new KeyValuePair<string, Type>[] {
new KeyValuePair<string, Type>(HttpServer.Method.Get, typeof(AddOrUpdateBackupData[])),
new KeyValuePair<string, Type>(HttpServer.Method.Post, typeof(AddOrUpdateBackupData))
};
}
}
}
}
@@ -0,0 +1,58 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using Duplicati.Library.RestAPI;
using System;
using System.Linq;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class BugReport : IRESTMethodGET
{
public void GET(string key, RequestInfo info)
{
long id;
long.TryParse(key, out id);
var tf = FIXMEGlobal.DataConnection.GetTempFiles().FirstOrDefault(x => x.ID == id);
if (tf == null)
{
info.ReportClientError("Invalid or missing bugreport id", System.Net.HttpStatusCode.NotFound);
return;
}
if (!System.IO.File.Exists(tf.Path))
{
info.ReportClientError("File is missing", System.Net.HttpStatusCode.NotFound);
return;
}
var filename = "bugreport.zip";
using(var fs = System.IO.File.OpenRead(tf.Path))
{
info.Response.ContentLength = fs.Length;
info.Response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}", filename));
info.Response.ContentType = "application/octet-stream";
info.BodyWriter.SetOK();
info.Response.SendHeaders();
fs.CopyTo(info.Response.Body);
info.Response.Send();
}
}
}
}
@@ -0,0 +1,168 @@
// Copyright (C) 2016, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Linq;
using System.Collections.Generic;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class Captcha : IRESTMethodGET, IRESTMethodPOST
{
private class CaptchaEntry
{
public readonly string Answer;
public readonly string Target;
public int Attempts;
public readonly DateTime Expires;
public CaptchaEntry(string answer, string target)
{
Answer = answer;
Target = target;
Attempts = 4;
Expires = DateTime.Now.AddMinutes(2);
}
}
private static readonly object m_lock = new object();
private static readonly Dictionary<string, CaptchaEntry> m_captchas = new Dictionary<string, CaptchaEntry>();
public static bool SolvedCaptcha(string token, string target, string answer)
{
lock(m_lock)
{
CaptchaEntry tp;
m_captchas.TryGetValue(token ?? string.Empty, out tp);
if (tp == null)
return false;
if (tp.Attempts > 0)
tp.Attempts--;
return tp.Attempts >= 0 && string.Equals(tp.Answer, answer, StringComparison.OrdinalIgnoreCase) && tp.Target == target && tp.Expires >= DateTime.Now;
}
}
public void GET(string key, RequestInfo info)
{
if (string.IsNullOrWhiteSpace(key))
{
info.ReportClientError("Missing token value", System.Net.HttpStatusCode.Unauthorized);
return;
}
else
{
string answer = null;
lock (m_lock)
{
CaptchaEntry tp;
m_captchas.TryGetValue(key, out tp);
if (tp != null && tp.Expires > DateTime.Now)
answer = tp.Answer;
}
if (string.IsNullOrWhiteSpace(answer))
{
info.ReportClientError("No such entry", System.Net.HttpStatusCode.NotFound);
return;
}
using (var bmp = CaptchaUtil.CreateCaptcha(answer))
using (var ms = new System.IO.MemoryStream())
{
info.Response.ContentType = "image/jpeg";
info.Response.ContentLength = ms.Length;
bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
ms.Position = 0;
info.Response.ContentType = "image/jpeg";
info.Response.ContentLength = ms.Length;
info.Response.SendHeaders();
ms.CopyTo(info.Response.Body);
info.Response.Send();
}
}
}
public void POST(string key, RequestInfo info)
{
if (string.IsNullOrWhiteSpace(key))
{
var target = info.Request.Param["target"].Value;
if (string.IsNullOrWhiteSpace(target))
{
info.ReportClientError("Missing target parameter", System.Net.HttpStatusCode.BadRequest);
return;
}
var answer = CaptchaUtil.CreateRandomAnswer(minlength: 6, maxlength: 6);
var nonce = Guid.NewGuid().ToString();
string token;
using (var ms = new System.IO.MemoryStream())
{
var bytes = System.Text.Encoding.UTF8.GetBytes(answer + nonce);
ms.Write(bytes, 0, bytes.Length);
ms.Position = 0;
using(var hasher = Library.Utility.HashFactory.CreateHasher(Library.Utility.HashFactory.SHA256)){
token = Library.Utility.Utility.Base64PlainToBase64Url(Convert.ToBase64String(hasher.ComputeHash(ms)));
}
}
lock (m_lock)
{
var expired = m_captchas.Where(x => x.Value.Expires < DateTime.Now).Select(x => x.Key).ToArray();
foreach (var x in expired)
m_captchas.Remove(x);
if (m_captchas.Count > 3)
{
info.ReportClientError("Too many captchas, wait 2 minutes and try again", System.Net.HttpStatusCode.ServiceUnavailable);
return;
}
m_captchas[token] = new CaptchaEntry(answer, target);
}
info.OutputOK(new
{
token = token
});
}
else
{
var answer = info.Request.Param["answer"].Value;
var target = info.Request.Param["target"].Value;
if (string.IsNullOrWhiteSpace(answer))
{
info.ReportClientError("Missing answer parameter", System.Net.HttpStatusCode.BadRequest);
return;
}
if (string.IsNullOrWhiteSpace(target))
{
info.ReportClientError("Missing target parameter", System.Net.HttpStatusCode.BadRequest);
return;
}
if (SolvedCaptcha(key, target, answer))
info.OutputOK();
else
info.ReportClientError("Incorrect", System.Net.HttpStatusCode.Forbidden);
}
}
}
}
@@ -0,0 +1,75 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using Duplicati.Library.RestAPI;
using System;
using System.Collections.Generic;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class Changelog : IRESTMethodGET, IRESTMethodDocumented
{
private class GetResponse
{
public string Status;
public string Version;
public string Changelog;
}
public void GET(string key, RequestInfo info)
{
var fromUpdate = info.Request.QueryString["from-update"].Value;
if (!Library.Utility.Utility.ParseBool(fromUpdate, false))
{
var path = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "changelog.txt");
info.OutputOK(new GetResponse() {
Status = "OK",
Version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(),
Changelog = System.IO.File.ReadAllText(path)
});
}
else
{
var updateInfo = FIXMEGlobal.DataConnection.ApplicationSettings.UpdatedVersion;
if (updateInfo == null)
{
info.ReportClientError("No update found", System.Net.HttpStatusCode.NotFound);
}
else
{
info.OutputOK(new GetResponse() {
Status = "OK",
Version = updateInfo.Version,
Changelog = updateInfo.ChangeInfo
});
}
}
}
public string Description { get { return "Gets the current changelog"; } }
public IEnumerable<KeyValuePair<string, Type>> Types
{
get
{
return new KeyValuePair<string, Type>[] {
new KeyValuePair<string, Type>(HttpServer.Method.Get, typeof(GetResponse)),
};
}
}
}
}
@@ -0,0 +1,284 @@
// Copyright (C) 2017, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using Duplicati.Library.RestAPI;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class CommandLine : IRESTMethodGET, IRESTMethodPOST
{
private static readonly string LOGTAG = Library.Logging.Log.LogTagFromType<CommandLine>();
private class LogWriter : System.IO.TextWriter
{
private readonly ActiveRun m_target;
private readonly StringBuilder m_sb = new StringBuilder();
private int m_newlinechars = 0;
public LogWriter(ActiveRun target)
{
m_target = target;
}
public override Encoding Encoding { get { return System.Text.Encoding.UTF8; } }
public override void Write(char value)
{
lock(m_target.Lock)
{
m_sb.Append(value);
if (NewLine[m_newlinechars] == value)
{
m_newlinechars++;
if (m_newlinechars == NewLine.Length)
WriteLine(string.Empty);
}
else
m_newlinechars = 0;
}
}
public override void WriteLine(string value)
{
value = value ?? string.Empty;
lock(m_target.Lock)
{
m_target.LastAccess = DateTime.Now;
//Avoid writing the log if it does not exist
if (m_target.IsLogDisposed)
{
FIXMEGlobal.LogHandler.WriteMessage(new Library.Logging.LogEntry("Attempted to write message after closing: {0}", new object[] { value }, Library.Logging.LogMessageType.Warning, LOGTAG, "CommandLineOutputAfterLogClosed", null));
return;
}
try
{
if (m_sb.Length != 0)
{
m_target.Log.Add(m_sb + value);
m_sb.Length = 0;
m_newlinechars = 0;
}
else
{
m_target.Log.Add(value);
}
}
catch (Exception ex)
{
// This can happen on a very unlucky race where IsLogDisposed is set right after the check
FIXMEGlobal.LogHandler.WriteMessage(new Library.Logging.LogEntry("Failed to forward commandline message: {0}", new object[] { value }, Library.Logging.LogMessageType.Warning, LOGTAG, "CommandLineOutputAfterLogClosed", ex));
}
}
}
}
private class ActiveRun
{
public readonly string ID = Guid.NewGuid().ToString();
public DateTime LastAccess = DateTime.Now;
public readonly Library.Utility.FileBackedStringList Log = new Library.Utility.FileBackedStringList();
public Runner.IRunnerData Task;
public LogWriter Writer;
public readonly object Lock = new object();
public bool Finished = false;
public bool Started = false;
public bool IsLogDisposed = false;
public System.Threading.Thread Thread;
}
private readonly Dictionary<string, ActiveRun> m_activeItems = new Dictionary<string, ActiveRun>();
private System.Threading.Tasks.Task m_cleanupTask;
public void POST(string key, RequestInfo info)
{
if (string.IsNullOrWhiteSpace(key))
{
string[] args;
using (var sr = new StreamReader(info.Request.Body, System.Text.Encoding.UTF8, true))
args = Newtonsoft.Json.JsonConvert.DeserializeObject<string[]>(sr.ReadToEnd());
var k = new ActiveRun();
k.Writer = new LogWriter(k);
m_activeItems[k.ID] = k;
StartCleanupTask();
k.Task = Runner.CreateCustomTask((sink) =>
{
try
{
k.Thread = System.Threading.Thread.CurrentThread;
k.Started = true;
var code = Duplicati.CommandLine.Program.RunCommandLine(k.Writer, k.Writer, c => {
k.Task.SetController(c);
c.AppendSink(sink);
}, args);
k.Writer.WriteLine("Return code: {0}", code);
}
catch (Exception ex)
{
var rx = ex;
if (rx is System.Reflection.TargetInvocationException)
rx = rx.InnerException;
if (rx is Library.Interface.UserInformationException)
k.Log.Add(rx.Message);
else
k.Log.Add(rx.ToString());
throw rx;
}
finally
{
k.Finished = true;
k.Thread = null;
}
});
FIXMEGlobal.WorkThread.AddTask(k.Task);
info.OutputOK(new
{
ID = k.ID
});
}
else
{
if (!key.EndsWith("/abort", StringComparison.OrdinalIgnoreCase))
{
info.ReportClientError("Only abort commands are allowed", System.Net.HttpStatusCode.BadRequest);
return;
}
key = key.Substring(0, key.Length - "/abort".Length);
if (string.IsNullOrWhiteSpace(key))
{
info.ReportClientError("No task key found", System.Net.HttpStatusCode.BadRequest);
return;
}
ActiveRun t;
if (!m_activeItems.TryGetValue(key, out t))
{
info.OutputError(code: System.Net.HttpStatusCode.NotFound);
return;
}
var tt = t.Task;
if (tt != null)
tt.Abort();
var tr = t.Thread;
if (tr != null)
tr.Interrupt();
info.OutputOK();
}
}
private void StartCleanupTask()
{
if (m_cleanupTask == null || m_cleanupTask.IsCompleted || m_cleanupTask.IsFaulted || m_cleanupTask.IsCanceled)
m_cleanupTask = RunCleanupAsync();
}
private async System.Threading.Tasks.Task RunCleanupAsync()
{
while (m_activeItems.Count > 0)
{
var oldest = m_activeItems.Values
.OrderBy(x => x.LastAccess)
.FirstOrDefault();
if (oldest != null)
{
// If the task has finished, we just wait a little to allow the UI to pick it up
var timeout = oldest.Finished ? TimeSpan.FromMinutes(5) : TimeSpan.FromDays(1);
if (DateTime.Now - oldest.LastAccess > timeout)
{
oldest.IsLogDisposed = true;
m_activeItems.Remove(oldest.ID);
oldest.Log.Dispose();
// Fix all expired, or stop running
continue;
}
}
await System.Threading.Tasks.Task.Delay(TimeSpan.FromMinutes(1)).ConfigureAwait(false);
}
}
public void GET(string key, RequestInfo info)
{
if (string.IsNullOrWhiteSpace(key))
{
info.OutputOK(
Duplicati.CommandLine.Program.SupportedCommands
);
}
else
{
ActiveRun t;
if (!m_activeItems.TryGetValue(key, out t))
{
info.OutputError(code: System.Net.HttpStatusCode.NotFound);
return;
}
int pagesize;
int offset;
int.TryParse(info.Request.QueryString["pagesize"].Value, out pagesize);
int.TryParse(info.Request.QueryString["offset"].Value, out offset);
pagesize = Math.Max(10, Math.Min(500, pagesize));
offset = Math.Max(0, offset);
var items = new List<string>();
long count;
bool started;
bool finished;
lock(t.Lock)
{
t.LastAccess = DateTime.Now;
count = t.Log.Count;
offset = Math.Min((int)count, offset);
items.AddRange(t.Log.Skip(offset).Take(pagesize));
finished = t.Finished;
started = t.Started;
}
info.OutputOK(new
{
Pagesize = pagesize,
Offset = offset,
Count = count,
Items = items,
Finished = finished,
Started = started
});
}
}
}
}
@@ -0,0 +1,273 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Duplicati.Library.Snapshots;
using Duplicati.Library.Common.IO;
using Duplicati.Library.Common;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class Filesystem : IRESTMethodGET, IRESTMethodPOST, IRESTMethodDocumented
{
public void GET(string key, RequestInfo info)
{
var parts = (key ?? "").Split(new char[] { '/' });
var path = Duplicati.Library.Utility.Uri.UrlDecode((parts.Length == 2 ? parts.FirstOrDefault() : key ?? ""));
var command = parts.Length == 2 ? parts.Last() : null;
if (string.IsNullOrEmpty(path))
path = info.Request.QueryString["path"].Value;
Process(command, path, info);
}
private void Process(string command, string path, RequestInfo info)
{
if (string.IsNullOrEmpty(path))
{
info.ReportClientError("No path parameter was found", System.Net.HttpStatusCode.BadRequest);
return;
}
bool skipFiles = Library.Utility.Utility.ParseBool(info.Request.QueryString["onlyfolders"].Value, false);
bool showHidden = Library.Utility.Utility.ParseBool(info.Request.QueryString["showhidden"].Value, false);
string specialpath = null;
string specialtoken = null;
if (path.StartsWith("%", StringComparison.Ordinal))
{
var ix = path.IndexOf("%", 1, StringComparison.Ordinal);
if (ix > 0)
{
var tk = path.Substring(0, ix + 1);
var node = SpecialFolders.Nodes.FirstOrDefault(x => x.id.Equals(tk, StringComparison.OrdinalIgnoreCase));
if (node != null)
{
specialpath = node.resolvedpath;
specialtoken = node.id;
}
}
}
path = SpecialFolders.ExpandEnvironmentVariables(path);
if (Platform.IsClientPosix && !path.StartsWith("/", StringComparison.Ordinal))
{
info.ReportClientError("The path parameter must start with a forward-slash", System.Net.HttpStatusCode.BadRequest);
return;
}
if (!string.IsNullOrWhiteSpace(command))
{
if ("validate".Equals(command, StringComparison.OrdinalIgnoreCase))
{
try
{
if (System.IO.Path.IsPathRooted(path) && (System.IO.Directory.Exists(path) || System.IO.File.Exists(path)))
{
info.OutputOK();
return;
}
}
catch
{
}
info.ReportServerError("File or folder not found", System.Net.HttpStatusCode.NotFound);
return;
}
else
{
info.ReportClientError(string.Format("No such operation found: {0}", command), System.Net.HttpStatusCode.NotFound);
return;
}
}
try
{
if (path != "" && path != "/")
path = Util.AppendDirSeparator(path);
IEnumerable<Serializable.TreeNode> res;
if (!Platform.IsClientPosix && (path.Equals("/") || path.Equals("")))
{
res = DriveInfo.GetDrives()
.Where(di =>
(di.DriveType == DriveType.Fixed || di.DriveType == DriveType.Network || di.DriveType == DriveType.Removable)
&& di.IsReady // Only try to create TreeNode entries for drives who were ready 'now'
)
.Select(TryCreateTreeNodeForDrive) // This will try to create a TreeNode for selected drives
.Where(tn => tn != null); // This filters out such entries that could not be created
}
else
{
res = ListFolderAsNodes(path, skipFiles, showHidden);
}
if ((path.Equals("/") || path.Equals("")) && specialtoken == null)
{
// Prepend special folders
res = SpecialFolders.Nodes.Union(res);
}
if (specialtoken != null)
{
res = res.Select(x => {
x.resolvedpath = x.id;
x.id = specialtoken + x.id.Substring(specialpath.Length);
return x;
});
}
// We have to resolve the query before giving it to OutputOK
// If we do not do this, and the query throws an exception when OutputOK resolves it,
// the exception would not be handled properly
res = res.ToList();
info.OutputOK(res);
}
catch (Exception ex)
{
info.ReportClientError("Failed to process the path: " + ex.Message, System.Net.HttpStatusCode.InternalServerError);
}
}
/// <summary>
/// Try to create a new TreeNode instance for the given DriveInfo instance.
///
/// <remarks>
/// If an exception occurs during creation (most likely the device became unavailable), a null is returned instead.
/// </remarks>
/// </summary>
/// <param name="driveInfo">DriveInfo to try create a TreeNode for. Cannot be null.</param>
/// <returns>A new TreeNode instance on success; null if an exception occurred during creation.</returns>
private static Serializable.TreeNode TryCreateTreeNodeForDrive(DriveInfo driveInfo)
{
if (driveInfo == null) throw new ArgumentNullException(nameof(driveInfo));
try
{
// Try to create the TreeNode
// This may still fail as the drive might become unavailable in the meanwhile
return new Serializable.TreeNode
{
id = driveInfo.RootDirectory.FullName,
text =
(
string.IsNullOrWhiteSpace(driveInfo.VolumeLabel)
? driveInfo.RootDirectory.FullName.Replace('\\', ' ')
: driveInfo.VolumeLabel + " - " + driveInfo.RootDirectory.FullName.Replace('\\', ' ')
) + "(" + driveInfo.DriveType + ")",
iconCls = "x-tree-icon-drive"
};
}
catch
{
// Drive became unavailable in the meanwhile or another exception occurred
// Return a null as fall back
return null;
}
}
private static IEnumerable<Serializable.TreeNode> ListFolderAsNodes(string entrypath, bool skipFiles, bool showHidden)
{
//Helper function for finding out if a folder has sub elements
Func<string, bool> hasSubElements = (p) => skipFiles ? Directory.EnumerateDirectories(p).Any() : Directory.EnumerateFileSystemEntries(p).Any();
//Helper function for dealing with exceptions when accessing off-limits folders
Func<string, bool> isEmptyFolder = (p) =>
{
try { return !hasSubElements(p); }
catch { }
return true;
};
//Helper function for dealing with exceptions when accessing off-limits folders
Func<string, bool> canAccess = (p) =>
{
try { hasSubElements(p); return true; }
catch { }
return false;
};
foreach (var s in SystemIO.IO_OS.EnumerateFileSystemEntries(entrypath)
// Group directories first
.OrderByDescending(f => SystemIO.IO_OS.GetFileAttributes(f) & FileAttributes.Directory)
// Sort both groups (directories and files) alphabetically
.ThenBy(f => f))
{
Serializable.TreeNode tn = null;
try
{
var attr = SystemIO.IO_OS.GetFileAttributes(s);
var isSymlink = SystemIO.IO_OS.IsSymlink(s, attr);
var isFolder = (attr & FileAttributes.Directory) != 0;
var isFile = !isFolder;
var isHidden = (attr & FileAttributes.Hidden) != 0;
var accessible = isFile || canAccess(s);
var isLeaf = isFile || !accessible || isEmptyFolder(s);
var rawid = isFolder ? Util.AppendDirSeparator(s) : s;
if (skipFiles && !isFolder)
continue;
if (!showHidden && isHidden)
continue;
tn = new Serializable.TreeNode()
{
id = rawid,
text = SystemIO.IO_OS.PathGetFileName(s),
hidden = isHidden,
symlink = isSymlink,
iconCls = isFolder ? (accessible ? (isSymlink ? "x-tree-icon-symlink" : "x-tree-icon-parent") : "x-tree-icon-locked") : "x-tree-icon-leaf",
leaf = isLeaf
};
}
catch
{
}
if (tn != null)
yield return tn;
}
}
public void POST(string key, RequestInfo info)
{
Process(key, info.Request.Form["path"].Value, info);
}
public string Description { get { return "Enumerates the server filesystem"; } }
public IEnumerable<KeyValuePair<string, Type>> Types
{
get
{
return new KeyValuePair<string, Type>[] {
new KeyValuePair<string, Type>(HttpServer.Method.Get, typeof(string[])),
};
}
}
}
}
@@ -0,0 +1,100 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class Help : IRESTMethodGET
{
public void GET(string key, RequestInfo info)
{
var sb = new StringBuilder();
if (string.IsNullOrWhiteSpace(key))
{
foreach(var m in RESTHandler.Modules.Keys.OrderBy(x => x))
{
var mod = RESTHandler.Modules[m];
if (mod == this)
continue;
var desc = mod.GetType().Name;
if (mod is IRESTMethodDocumented documented)
desc = documented.Description;
sb.AppendFormat(ITEM_TEMPLATE, RESTHandler.API_URI_PATH, m, mod.GetType().Name, desc);
}
var data = Encoding.UTF8.GetBytes(string.Format(TEMPLATE, "API Information", "", sb));
info.Response.ContentType = "text/html";
info.Response.ContentLength = data.Length;
info.Response.Body.Write(data, 0, data.Length);
info.Response.Send();
}
else
{
IRESTMethod m;
RESTHandler.Modules.TryGetValue(key, out m);
if (m == null)
{
info.Response.Status = System.Net.HttpStatusCode.NotFound;
info.Response.Reason = "Module not found";
}
else
{
var desc = "";
if (m is IRESTMethodDocumented doc)
{
desc = doc.Description;
foreach(var t in doc.Types)
sb.AppendFormat(METHOD_TEMPLATE, t.Key, JsonConvert.SerializeObject(t.Value)); //TODO: Format the type
}
var data = Encoding.UTF8.GetBytes(string.Format(TEMPLATE, m.GetType().Name, desc, sb));
info.Response.ContentType = "text/html";
info.Response.ContentLength = data.Length;
info.Response.Body.Write(data, 0, data.Length);
info.Response.Send();
}
}
}
private const string TEMPLATE = @"
<html><head><title>{0}</title></head>
<body>
<h1>{0}</h1>
<p>{1}</p>
<ul>
{2}
</ul>
</body>
";
private const string ITEM_TEMPLATE = @"
<li><a href=""{0}/help/{1}"">{2}</a>: {3}</li>
";
private const string METHOD_TEMPLATE = @"
<b>{0}:</b><br><code>{1}</code><br>
";
}
}
@@ -0,0 +1,89 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Collections.Generic;
using Duplicati.Library.Interface;
using System.Linq;
using System.Security.Principal;
using Duplicati.Library.Snapshots;
using Duplicati.Library.Common;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class HyperV : IRESTMethodGET, IRESTMethodDocumented
{
public void GET(string key, RequestInfo info)
{
// Early exit in case we are non-windows to prevent attempting to load Windows-only components
if (Platform.IsClientWindows)
RealGET(key, info);
else
info.OutputOK(new string[0]);
}
// Make sure the JIT does not attempt to inline this call and thus load
// referenced types from System.Management here
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
private void RealGET(string key, RequestInfo info)
{
var hypervUtility = new HyperVUtility();
if (!hypervUtility.IsHyperVInstalled || !new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator))
{
info.OutputOK(new string[0]);
return;
}
try
{
if (string.IsNullOrEmpty(key))
{
hypervUtility.QueryHyperVGuestsInfo();
info.OutputOK(hypervUtility.Guests.Select(x => new { id = x.ID, name = x.Name }).ToList());
}
else
{
hypervUtility.QueryHyperVGuestsInfo(true);
var foundVMs = hypervUtility.Guests.FindAll(x => x.ID.Equals(new Guid(key)));
if (foundVMs.Count == 1)
info.OutputOK(foundVMs[0].DataPaths.Select(x => new { text = x, id = x, cls = "folder", iconCls = "x-tree-icon-leaf", check = "false", leaf = "true" }).ToList());
else
info.ReportClientError(string.Format("Cannot find VM with ID {0}.", key), System.Net.HttpStatusCode.NotFound);
}
}
catch (Exception ex)
{
info.ReportServerError("Failed to enumerate Hyper-V virtual machines: " + ex.Message);
}
}
public string Description { get { return "Return a list of Hyper-V virtual machines"; } }
public IEnumerable<KeyValuePair<string, Type>> Types
{
get
{
return new KeyValuePair<string, Type>[] {
new KeyValuePair<string, Type>(HttpServer.Method.Get, typeof(ICommandLineArgument[]))
};
}
}
}
}
@@ -0,0 +1,56 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Collections.Generic;
namespace Duplicati.Server.WebServer.RESTMethods
{
public interface IRESTMethod
{
}
public interface IRESTMethodDocumented
{
string Description { get; }
IEnumerable<KeyValuePair<string, Type>> Types { get; }
}
public interface IRESTMethodGET : IRESTMethod
{
void GET(string key, RequestInfo info);
}
public interface IRESTMethodPUT : IRESTMethod
{
void PUT(string key, RequestInfo info);
}
public interface IRESTMethodPOST : IRESTMethod
{
void POST(string key, RequestInfo info);
}
public interface IRESTMethodDELETE : IRESTMethod
{
void DELETE(string key, RequestInfo info);
}
public interface IRESTMethodPATCH : IRESTMethod
{
void PATCH(string key, RequestInfo info);
}
}
@@ -0,0 +1,30 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class Licenses : IRESTMethodGET
{
public void GET(string key, RequestInfo info)
{
var path = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(Duplicati.Library.Utility.Utility.getEntryAssembly().Location), "licenses");
info.OutputOK(Duplicati.License.LicenseReader.ReadLicenses(path));
}
}
}
@@ -0,0 +1,118 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Collections.Generic;
using Duplicati.Library.RestAPI;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class LogData : IRESTMethodGET, IRESTMethodDocumented
{
public void GET(string key, RequestInfo info)
{
if ("poll".Equals(key, StringComparison.OrdinalIgnoreCase))
{
var input = info.Request.QueryString;
var level_str = input["level"].Value ?? "";
var id_str = input["id"].Value ?? "";
int pagesize;
if (!int.TryParse(info.Request.QueryString["pagesize"].Value, out pagesize))
pagesize = 100;
pagesize = Math.Max(1, Math.Min(500, pagesize));
Library.Logging.LogMessageType level;
long id;
long.TryParse(id_str, out id);
Enum.TryParse(level_str, true, out level);
info.OutputOK(FIXMEGlobal.LogHandler.AfterID(id, level, pagesize));
}
else
{
List<Dictionary<string, object>> res = null;
FIXMEGlobal.DataConnection.ExecuteWithCommand(x =>
{
res = DumpTable(x, "ErrorLog", "Timestamp", info.Request.QueryString["offset"].Value, info.Request.QueryString["pagesize"].Value);
});
info.OutputOK(res);
}
}
public static List<Dictionary<string, object>> DumpTable(System.Data.IDbCommand cmd, string tablename, string pagingfield, string offset_str, string pagesize_str)
{
var result = new List<Dictionary<string, object>>();
long pagesize;
if (!long.TryParse(pagesize_str, out pagesize))
pagesize = 100;
pagesize = Math.Max(10, Math.Min(500, pagesize));
cmd.CommandText = "SELECT * FROM \"" + tablename + "\"";
long offset = 0;
if (!string.IsNullOrWhiteSpace(offset_str) && long.TryParse(offset_str, out offset) && !string.IsNullOrEmpty(pagingfield))
{
var p = cmd.CreateParameter();
p.Value = offset;
cmd.Parameters.Add(p);
cmd.CommandText += " WHERE \"" + pagingfield + "\" < ?";
}
if (!string.IsNullOrEmpty(pagingfield))
cmd.CommandText += " ORDER BY \"" + pagingfield + "\" DESC";
cmd.CommandText += " LIMIT " + pagesize.ToString();
using(var rd = cmd.ExecuteReader())
{
var names = new List<string>();
for(var i = 0; i < rd.FieldCount; i++)
names.Add(rd.GetName(i));
while (rd.Read())
{
var dict = new Dictionary<string, object>();
for(int i = 0; i < names.Count; i++)
dict[names[i]] = rd.GetValue(i);
result.Add(dict);
}
}
return result;
}
public string Description { get { return "Retrieves system log data"; } }
public IEnumerable<KeyValuePair<string, Type>> Types
{
get
{
return new KeyValuePair<string, Type>[] {
new KeyValuePair<string, Type>(HttpServer.Method.Get, typeof(Dictionary<string, string>[])),
};
}
}
}
}
@@ -0,0 +1,87 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Collections.Generic;
using Duplicati.Library.Interface;
using System.Linq;
using System.Security.Principal;
using Duplicati.Library.Snapshots;
using Duplicati.Library.Common;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class MSSQL : IRESTMethodGET, IRESTMethodDocumented
{
public void GET(string key, RequestInfo info)
{
// Early exit in case we are non-windows to prevent attempting to load Windows-only components
if (Platform.IsClientWindows)
RealGET(key, info);
else
info.OutputOK(new string[0]);
}
// Make sure the JIT does not attempt to inline this call and thus load
// referenced types from System.Management here
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
private void RealGET(string key, RequestInfo info)
{
var mssqlUtility = new MSSQLUtility();
if (!mssqlUtility.IsMSSQLInstalled || !new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator))
{
info.OutputOK(new string[0]);
return;
}
try
{
mssqlUtility.QueryDBsInfo();
if (string.IsNullOrEmpty(key))
info.OutputOK(mssqlUtility.DBs.Select(x => new { id = x.ID, name = x.Name }).ToList());
else
{
var foundDBs = mssqlUtility.DBs.FindAll(x => x.ID.Equals(key, StringComparison.OrdinalIgnoreCase));
if (foundDBs.Count == 1)
info.OutputOK(foundDBs[0].DataPaths.Select(x => new { text = x, id = x, cls = "folder", iconCls = "x-tree-icon-leaf", check = "false", leaf = "true" }).ToList());
else
info.ReportClientError(string.Format("Cannot find DB with ID {0}.", key), System.Net.HttpStatusCode.NotFound);
}
}
catch (Exception ex)
{
info.ReportServerError("Failed to enumerate Microsoft SQL Server databases: " + ex.Message);
}
}
public string Description { get { return "Return a list of Microsoft SQL Server databases"; } }
public IEnumerable<KeyValuePair<string, Type>> Types
{
get
{
return new KeyValuePair<string, Type>[] {
new KeyValuePair<string, Type>(HttpServer.Method.Get, typeof(ICommandLineArgument[]))
};
}
}
}
}
@@ -0,0 +1,61 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using Duplicati.Library.RestAPI;
using System;
using System.Linq;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class Notification : IRESTMethodGET, IRESTMethodDELETE
{
public void GET(string key, RequestInfo info)
{
long id;
if (!long.TryParse(key, out id))
{
info.ReportClientError("Invalid ID", System.Net.HttpStatusCode.BadRequest);
return;
}
var el = FIXMEGlobal.DataConnection.GetNotifications().FirstOrDefault(x => x.ID == id);
if (el == null)
info.ReportClientError("No such notification", System.Net.HttpStatusCode.NotFound);
else
info.OutputOK(el);
}
public void DELETE(string key, RequestInfo info)
{
long id;
if (!long.TryParse(key, out id))
{
info.ReportClientError("Invalid ID", System.Net.HttpStatusCode.BadRequest);
return;
}
var el = FIXMEGlobal.DataConnection.GetNotifications().FirstOrDefault(x => x.ID == id);
if (el == null)
info.ReportClientError("No such notification", System.Net.HttpStatusCode.NotFound);
else
{
FIXMEGlobal.DataConnection.DismissNotification(id);
info.OutputOK();
}
}
}
}
@@ -0,0 +1,30 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using Duplicati.Library.RestAPI;
using System;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class Notifications : IRESTMethodGET
{
public void GET(string key, RequestInfo info)
{
info.OutputOK(FIXMEGlobal.DataConnection.GetNotifications());
}
}
}
@@ -0,0 +1,46 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using Duplicati.Library.RestAPI;
using System;
using System.Collections.Generic;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class ProgressState : IRESTMethodGET, IRESTMethodDocumented
{
public void GET(string key, RequestInfo info)
{
if (FIXMEGlobal.GenerateProgressState == null)
info.ReportClientError("No active backup", System.Net.HttpStatusCode.NotFound);
else
info.OutputOK(FIXMEGlobal.GenerateProgressState());
}
public string Description { get { return "Return the progress of the currently running operation."; } }
public IEnumerable<KeyValuePair<string, Type>> Types
{
get
{
return new KeyValuePair<string, Type>[] {
new KeyValuePair<string, Type>(HttpServer.Method.Get, typeof(Serialization.Interface.IProgressEventData))
};
}
}
}
}
@@ -0,0 +1,187 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class RemoteOperation : IRESTMethodGET, IRESTMethodPOST
{
private void LocateDbUri(string uri, RequestInfo info)
{
var path = Library.Main.DatabaseLocator.GetDatabasePath(uri, null, false, false);
info.OutputOK(new {
Exists = !string.IsNullOrWhiteSpace(path),
Path = path
});
}
private void CreateFolder(string uri, RequestInfo info)
{
using(var b = Duplicati.Library.DynamicLoader.BackendLoader.GetBackend(uri, new Dictionary<string, string>()))
b.CreateFolder();
info.OutputOK();
}
private void UploadFile(string uri, RequestInfo info)
{
var data = info.Request.QueryString["data"].Value;
var remotename = info.Request.QueryString["filename"].Value;
using(var ms = new System.IO.MemoryStream())
using(var b = Library.DynamicLoader.BackendLoader.GetBackend(uri, new Dictionary<string, string>()))
{
using(var tf = new Library.Utility.TempFile())
{
System.IO.File.WriteAllText(tf, data);
b.PutAsync(remotename, tf, CancellationToken.None).Wait();
}
}
info.OutputOK();
}
private void ListFolder(string uri, RequestInfo info)
{
using(var b = Duplicati.Library.DynamicLoader.BackendLoader.GetBackend(uri, new Dictionary<string, string>()))
info.OutputOK(b.List());
}
private void TestConnection(string url, RequestInfo info)
{
var modules = (from n in Library.DynamicLoader.GenericLoader.Modules
where n is Library.Interface.IConnectionModule
select n).ToArray();
try
{
var uri = new Library.Utility.Uri(url);
var qp = uri.QueryParameters;
var opts = new Dictionary<string, string>();
foreach (var k in qp.Keys.Cast<string>())
opts[k] = qp[k];
foreach (var n in modules)
n.Configure(opts);
using (var b = Duplicati.Library.DynamicLoader.BackendLoader.GetBackend(url, new Dictionary<string, string>()))
b.Test();
info.OutputOK();
}
catch (Duplicati.Library.Interface.FolderMissingException)
{
info.ReportServerError("missing-folder");
}
catch (Duplicati.Library.Utility.SslCertificateValidator.InvalidCertificateException icex)
{
if (string.IsNullOrWhiteSpace(icex.Certificate))
info.ReportServerError(icex.Message);
else
info.ReportServerError("incorrect-cert:" + icex.Certificate);
}
catch (Duplicati.Library.Utility.HostKeyException hex)
{
if (string.IsNullOrWhiteSpace(hex.ReportedHostKey))
info.ReportServerError(hex.Message);
else
{
info.ReportServerError(string.Format(
@"incorrect-host-key:""{0}"", accepted-host-key:""{1}""",
hex.ReportedHostKey,
hex.AcceptedHostKey
));
}
}
finally
{
foreach (var n in modules)
if (n is IDisposable disposable)
disposable.Dispose();
}
}
public void GET(string key, RequestInfo info)
{
var parts = (key ?? "").Split(new char[] { '/' }, 2);
if (parts.Length <= 1)
{
info.ReportClientError("No url or operation supplied", System.Net.HttpStatusCode.BadRequest);
return;
}
var url = Library.Utility.Uri.UrlDecode(parts.First());
var operation = parts.Last().ToLowerInvariant();
switch (operation)
{
case "dbpath":
LocateDbUri(url, info);
return;
case "list":
ListFolder(url, info);
return;
case "create":
CreateFolder(url, info);
return;
case "test":
TestConnection(url, info);
return;
default:
info.ReportClientError("No such method", System.Net.HttpStatusCode.BadRequest);
return;
}
}
public void POST(string key, RequestInfo info)
{
string url;
using(var sr = new System.IO.StreamReader(info.Request.Body, System.Text.Encoding.UTF8, true))
url = sr.ReadToEnd();
switch (key)
{
case "dbpath":
LocateDbUri(url, info);
return;
case "list":
ListFolder(url, info);
return;
case "create":
CreateFolder(url, info);
return;
case "put":
UploadFile(url, info);
return;
case "test":
TestConnection(url, info);
return;
default:
info.ReportClientError("No such method", System.Net.HttpStatusCode.BadRequest);
return;
}
}
}
}
@@ -0,0 +1,109 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class RequestInfo : IDisposable
{
public HttpServer.IHttpRequest Request { get; private set; }
public HttpServer.IHttpResponse Response { get; private set; }
public HttpServer.Sessions.IHttpSession Session { get; private set; }
public BodyWriter BodyWriter { get; private set; }
public RequestInfo(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session)
{
Request = request;
Response = response;
Session = session;
BodyWriter = new BodyWriter(response, request);
}
public void ReportServerError(string message, System.Net.HttpStatusCode code = System.Net.HttpStatusCode.InternalServerError)
{
Response.Status = code;
Response.Reason = message;
BodyWriter.WriteJsonObject(new { Error = message });
}
public void ReportClientError(string message, System.Net.HttpStatusCode code = System.Net.HttpStatusCode.BadRequest)
{
ReportServerError(message, code);
}
public bool LongPollCheck(EventPollNotify poller, ref long id, out bool isError)
{
HttpServer.HttpInput input = String.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase) ? Request.Form : Request.QueryString;
if (Library.Utility.Utility.ParseBool(input["longpoll"].Value, false))
{
long lastEventId;
if (!long.TryParse(input["lasteventid"].Value, out lastEventId))
{
ReportClientError("When activating long poll, the request must include the last event id", System.Net.HttpStatusCode.BadRequest);
isError = true;
return false;
}
TimeSpan ts;
try { ts = Library.Utility.Timeparser.ParseTimeSpan(input["duration"].Value); }
catch (Exception ex)
{
ReportClientError("Invalid duration: " + ex.Message, System.Net.HttpStatusCode.BadRequest);
isError = true;
return false;
}
if (ts <= TimeSpan.FromSeconds(10) || ts.TotalMilliseconds > int.MaxValue)
{
ReportClientError("Invalid duration, must be at least 10 seconds, and less than " + int.MaxValue + " milliseconds", System.Net.HttpStatusCode.BadRequest);
isError = true;
return false;
}
isError = false;
id = poller.Wait(lastEventId, (int)ts.TotalMilliseconds);
return true;
}
isError = false;
return false;
}
public void OutputOK(object item = null)
{
BodyWriter.OutputOK(item);
}
public void OutputError(object item = null, System.Net.HttpStatusCode code = System.Net.HttpStatusCode.InternalServerError, string reason = null)
{
Response.Status = code;
Response.Reason = reason ?? "Error";
BodyWriter.WriteJsonObject(item);
}
public void Dispose()
{
if (BodyWriter != null)
{
var bw = BodyWriter;
BodyWriter = null;
bw.Dispose();
}
}
}
}
@@ -0,0 +1,112 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using Duplicati.Library.RestAPI;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class ServerSetting : IRESTMethodGET, IRESTMethodPUT, IRESTMethodDocumented
{
public void GET(string key, RequestInfo info)
{
if (string.IsNullOrWhiteSpace(key))
{
info.OutputError(null, System.Net.HttpStatusCode.BadRequest, "Key is missing");
return;
}
if (key.Equals("server-ssl-certificate", StringComparison.OrdinalIgnoreCase) || key.Equals("ServerSSLCertificate", StringComparison.OrdinalIgnoreCase))
{
info.OutputOK(FIXMEGlobal.DataConnection.ApplicationSettings.ServerSSLCertificate == null ? "False" : "True");
return;
}
if (key.StartsWith("--", StringComparison.Ordinal))
{
var prop = FIXMEGlobal.DataConnection.Settings.FirstOrDefault(x => string.Equals(key, x.Name, StringComparison.OrdinalIgnoreCase));
info.OutputOK(prop == null ? null : prop.Value);
}
else
{
var prop = typeof(Database.ServerSettings).GetProperty(key);
if (prop == null)
info.OutputError(null, System.Net.HttpStatusCode.NotFound, "Not found");
else
info.OutputOK(prop.GetValue(FIXMEGlobal.DataConnection.ApplicationSettings));
}
}
public void PUT(string key, RequestInfo info)
{
if (string.IsNullOrWhiteSpace(key))
{
info.OutputError(null, System.Net.HttpStatusCode.BadRequest, "Key is missing");
return;
}
if (key.Equals("server-ssl-certificate", StringComparison.OrdinalIgnoreCase) || key.Equals("ServerSSLCertificate", StringComparison.OrdinalIgnoreCase))
{
info.OutputError(null, System.Net.HttpStatusCode.BadRequest, "Can only update SSL certificate from commandline");
return;
}
if (key.StartsWith("--", StringComparison.Ordinal))
{
var settings = FIXMEGlobal.DataConnection.Settings.ToList();
var prop = settings.FirstOrDefault(x => string.Equals(key, x.Name, StringComparison.OrdinalIgnoreCase));
if (prop == null)
settings.Add(prop = new Database.Setting() { Name = key, Value = info.Request.Form["data"].Value });
else
prop.Value = info.Request.Form["data"].Value;
FIXMEGlobal.DataConnection.Settings = settings.ToArray();
info.OutputOK(prop == null ? null : prop.Value);
}
else
{
var prop = typeof(Database.ServerSettings).GetProperty(key);
if (prop == null)
info.OutputError(null, System.Net.HttpStatusCode.NotFound, "Not found");
else
{
var dict = new Dictionary<string, string>();
dict[key] = info.Request.Form["data"].Value;
FIXMEGlobal.DataConnection.ApplicationSettings.UpdateSettings(dict, false);
info.OutputOK();
}
}
}
public string Description { get { return "Return a list of settings for the server"; } }
public IEnumerable<KeyValuePair<string, Type>> Types
{
get
{
return new KeyValuePair<string, Type>[] {
new KeyValuePair<string, Type>(HttpServer.Method.Get, typeof(string)),
new KeyValuePair<string, Type>(HttpServer.Method.Put, typeof(string))
};
}
}
}
}
@@ -0,0 +1,127 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Linq;
using System.Collections.Generic;
using Duplicati.Server.Database;
using System.IO;
using Duplicati.Server.Serialization;
using Duplicati.Library.RestAPI;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class ServerSettings : IRESTMethodGET, IRESTMethodPATCH, IRESTMethodDocumented
{
public void GET(string key, RequestInfo info)
{
// Join server settings and global settings
var adv_props =
FIXMEGlobal.DataConnection.GetSettings(Database.Connection.SERVER_SETTINGS_ID)
.Where(x => !string.IsNullOrWhiteSpace(x.Name))
.Union(
FIXMEGlobal.DataConnection.Settings
.Where(x => !string.IsNullOrWhiteSpace(x.Name) && x.Name.StartsWith("--", StringComparison.Ordinal))
);
var dict = new Dictionary<string, string>();
foreach (var n in adv_props)
dict[n.Name] = n.Value;
string sslcert;
dict.TryGetValue("server-ssl-certificate", out sslcert);
dict["server-ssl-certificate"] = (!string.IsNullOrWhiteSpace(sslcert)).ToString();
info.OutputOK(dict);
}
public void PATCH(string key, RequestInfo info)
{
string str = info.Request.Form["data"].Value;
if (string.IsNullOrWhiteSpace(str))
str = new StreamReader(info.Request.Body, System.Text.Encoding.UTF8).ReadToEnd();
if (string.IsNullOrWhiteSpace(str))
{
info.ReportClientError("Missing data object", System.Net.HttpStatusCode.BadRequest);
return;
}
Dictionary<string, string> data = null;
try
{
data = Serializer.Deserialize<Dictionary<string, string>>(new StringReader(str));
if (data == null)
{
info.ReportClientError("Data object had no entry", System.Net.HttpStatusCode.BadRequest);
return;
}
// Split into server settings and global settings
var serversettings = data.Where(x => !string.IsNullOrWhiteSpace(x.Key)).ToDictionary(x => x.Key, x => x.Key.StartsWith("--", StringComparison.Ordinal) ? null : x.Value);
var globalsettings = data.Where(x => !string.IsNullOrWhiteSpace(x.Key) && x.Key.StartsWith("--", StringComparison.Ordinal));
serversettings.Remove("server-ssl-certificate");
serversettings.Remove("ServerSSLCertificate");
if (serversettings.Any())
FIXMEGlobal.DataConnection.ApplicationSettings.UpdateSettings(serversettings, false);
if (globalsettings.Any())
{
// Update based on inputs
var existing = FIXMEGlobal.DataConnection.Settings.ToDictionary(x => x.Name, x => x);
foreach (var g in globalsettings)
if (g.Value == null)
existing.Remove(g.Key);
else
{
if (existing.ContainsKey(g.Key))
existing[g.Key].Value = g.Value;
else
existing[g.Key] = new Setting() { Name = g.Key, Value = g.Value };
}
FIXMEGlobal.DataConnection.Settings = existing.Select(x => x.Value).ToArray();
}
info.OutputOK();
}
catch (Exception ex)
{
if (data == null)
info.ReportClientError(string.Format("Unable to parse data object: {0}", ex.Message), System.Net.HttpStatusCode.BadRequest);
else
info.ReportClientError(string.Format("Unable to save settings: {0}", ex.Message), System.Net.HttpStatusCode.InternalServerError);
}
}
public string Description { get { return "Return a list of settings for the server"; } }
public IEnumerable<KeyValuePair<string, Type>> Types
{
get
{
return new KeyValuePair<string, Type>[] {
new KeyValuePair<string, Type>(HttpServer.Method.Get, typeof(Database.ServerSettings))
};
}
}
}
}
@@ -0,0 +1,100 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using Duplicati.Library.RestAPI;
using System;
using System.Collections.Generic;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class ServerState : IRESTMethodGET, IRESTMethodPOST, IRESTMethodDocumented
{
public void GET(string key, RequestInfo info)
{
bool isError;
long id = 0;
long.TryParse(key, out id);
if (info.LongPollCheck(FIXMEGlobal.StatusEventNotifyer, ref id, out isError))
{
//Make sure we do not report a higher number than the eventnotifier says
var st = new Serializable.ServerStatus();
st.LastEventID = id;
info.OutputOK(st);
}
else if (!isError)
{
info.OutputOK(new Serializable.ServerStatus());
}
}
public void POST(string key, RequestInfo info)
{
var input = info.Request.Form;
switch ((key ?? "").ToLowerInvariant())
{
case "pause":
if (input.Contains("duration") && !string.IsNullOrWhiteSpace(input["duration"].Value))
{
TimeSpan ts;
try
{
ts = Library.Utility.Timeparser.ParseTimeSpan(input["duration"].Value);
}
catch (Exception ex)
{
info.ReportClientError(ex.Message, System.Net.HttpStatusCode.BadRequest);
return;
}
if (ts.TotalMilliseconds > 0)
FIXMEGlobal.LiveControl.Pause(ts);
else
FIXMEGlobal.LiveControl.Pause();
}
else
{
FIXMEGlobal.LiveControl.Pause();
}
info.OutputOK();
return;
case "resume":
FIXMEGlobal.LiveControl.Resume();
info.OutputOK();
return;
default:
info.ReportClientError("No such action", System.Net.HttpStatusCode.NotFound);
return;
}
}
public string Description { get { return "Return the state of the server. This method can be long-polled."; } }
public IEnumerable<KeyValuePair<string, Type>> Types
{
get
{
return new KeyValuePair<string, Type>[] {
new KeyValuePair<string, Type>(HttpServer.Method.Get, typeof(Serializable.ServerStatus)),
new KeyValuePair<string, Type>(HttpServer.Method.Post, typeof(Serializable.ServerStatus))
};
}
}
}
}
@@ -0,0 +1,127 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Linq;
using System.Collections.Generic;
using Duplicati.Library.Interface;
using Duplicati.Library.Common;
using Duplicati.Library.RestAPI;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class SystemInfo : IRESTMethodGET, IRESTMethodPOST, IRESTMethodDocumented
{
public string Description { get { return "Gets various system properties"; } }
public IEnumerable<KeyValuePair<string, Type>> Types
{
get
{
return new KeyValuePair<string, Type>[] {
new KeyValuePair<string, Type>(HttpServer.Method.Get, SystemData(null).GetType())
};
}
}
public void GET(string key, RequestInfo info)
{
info.BodyWriter.OutputOK(SystemData(info));
}
public void POST(string key, RequestInfo info)
{
switch ((key ?? "").ToLowerInvariant())
{
case "suppressdonationmessages":
Library.Main.Utility.SuppressDonationMessages = true;
info.OutputOK();
return;
case "showdonationmessages":
Library.Main.Utility.SuppressDonationMessages = false;
info.OutputOK();
return;
default:
info.ReportClientError("No such action", System.Net.HttpStatusCode.NotFound);
return;
}
}
private static object SystemData(RequestInfo info)
{
var browserlanguage = RESTHandler.ParseDefaultRequestCulture(info) ?? System.Globalization.CultureInfo.InvariantCulture;
return new
{
APIVersion = 1,
PasswordPlaceholder = Duplicati.Server.WebServer.Server.PASSWORD_PLACEHOLDER,
ServerVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(),
ServerVersionName = Duplicati.License.VersionNumbers.Version,
ServerVersionType = Duplicati.Library.AutoUpdater.UpdaterManager.SelfVersion.ReleaseType,
StartedBy = FIXMEGlobal.Origin,
BaseVersionName = Duplicati.Library.AutoUpdater.UpdaterManager.BaseVersion.Displayname,
DefaultUpdateChannel = Duplicati.Library.AutoUpdater.AutoUpdateSettings.DefaultUpdateChannel,
DefaultUsageReportLevel = Duplicati.Library.UsageReporter.Reporter.DefaultReportLevel,
ServerTime = DateTime.Now,
OSType = Platform.IsClientPosix ? (Platform.IsClientOSX ? "OSX" : "Linux") : "Windows",
DirectorySeparator = System.IO.Path.DirectorySeparatorChar,
PathSeparator = System.IO.Path.PathSeparator,
CaseSensitiveFilesystem = Duplicati.Library.Utility.Utility.IsFSCaseSensitive,
MonoVersion = Duplicati.Library.Utility.Utility.IsMono ? Duplicati.Library.Utility.Utility.MonoVersion.ToString() : null,
MachineName = System.Environment.MachineName,
UserName = System.Environment.UserName,
NewLine = System.Environment.NewLine,
CLRVersion = System.Environment.Version.ToString(),
CLROSInfo = new
{
Platform = System.Environment.OSVersion.Platform.ToString(),
ServicePack = System.Environment.OSVersion.ServicePack,
Version = System.Environment.OSVersion.Version.ToString(),
VersionString = System.Environment.OSVersion.VersionString
},
Options = Serializable.ServerSettings.Options,
CompressionModules = Serializable.ServerSettings.CompressionModules,
EncryptionModules = Serializable.ServerSettings.EncryptionModules,
BackendModules = Serializable.ServerSettings.BackendModules,
GenericModules = Serializable.ServerSettings.GenericModules,
WebModules = Serializable.ServerSettings.WebModules,
ConnectionModules = Serializable.ServerSettings.ConnectionModules,
ServerModules = Serializable.ServerSettings.ServerModules,
UsingAlternateUpdateURLs = Duplicati.Library.AutoUpdater.AutoUpdateSettings.UsesAlternateURLs,
LogLevels = Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType)),
SuppressDonationMessages = Duplicati.Library.Main.Utility.SuppressDonationMessages,
SpecialFolders = from n in SpecialFolders.Nodes select new { ID = n.id, Path = n.resolvedpath },
BrowserLocale = new
{
Code = browserlanguage.Name,
EnglishName = browserlanguage.EnglishName,
DisplayName = browserlanguage.NativeName
},
SupportedLocales =
Library.Localization.LocalizationService.SupportedCultures
.Select(x => new {
Code = x,
EnglishName = new System.Globalization.CultureInfo(x).EnglishName,
DisplayName = new System.Globalization.CultureInfo(x).NativeName
}
),
BrowserLocaleSupported = Library.Localization.LocalizationService.isCultureSupported(browserlanguage)
};
}
}
}
@@ -0,0 +1,44 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Collections.Generic;
using Duplicati.Library.Interface;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class SystemWideSettings : IRESTMethodGET, IRESTMethodDocumented
{
public void GET(string key, RequestInfo info)
{
info.OutputOK(new Duplicati.Library.Main.Options(new Dictionary<string, string>()).SupportedCommands);
}
public string Description { get { return "Return a list of settings that can be applied to all backups on a system-wide basis"; } }
public IEnumerable<KeyValuePair<string, Type>> Types
{
get
{
return new KeyValuePair<string, Type>[] {
new KeyValuePair<string, Type>(HttpServer.Method.Get, typeof(ICommandLineArgument[]))
};
}
}
}
}
@@ -0,0 +1,55 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using Duplicati.Library.RestAPI;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class Tags : IRESTMethodGET, IRESTMethodDocumented
{
public void GET(string key, RequestInfo info)
{
var r =
from n in
Serializable.ServerSettings.CompressionModules
.Union(Serializable.ServerSettings.EncryptionModules)
.Union(Serializable.ServerSettings.BackendModules)
.Union(Serializable.ServerSettings.GenericModules)
select n.Key.ToLower(CultureInfo.InvariantCulture);
// Append all known tags
r = r.Union(from n in FIXMEGlobal.DataConnection.Backups select n.Tags into p from x in p select x.ToLower(CultureInfo.InvariantCulture));
info.OutputOK(r);
}
public string Description { get { return "Gets the list of tags"; } }
public IEnumerable<KeyValuePair<string, Type>> Types
{
get
{
return new KeyValuePair<string, Type>[] {
new KeyValuePair<string, Type>(HttpServer.Method.Get, typeof(string[])),
};
}
}
}
}
@@ -0,0 +1,112 @@
#region Disclaimer / License
// Copyright (C) 2019, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
using System;
using System.Linq;
using System.Collections.Generic;
using Duplicati.Library.RestAPI;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class Task : IRESTMethodGET, IRESTMethodPOST
{
public void GET(string key, RequestInfo info)
{
var parts = (key ?? "").Split(new char[] { '/' }, 2);
long taskid;
if (long.TryParse(parts.FirstOrDefault(), out taskid))
{
var task = FIXMEGlobal.WorkThread.CurrentTask;
var tasks = FIXMEGlobal.WorkThread.CurrentTasks;
if (task != null && task.TaskID == taskid)
{
info.OutputOK(new { Status = "Running" });
return;
}
if (tasks.FirstOrDefault(x => x.TaskID == taskid) == null)
{
KeyValuePair<long, Exception>[] matches;
lock(FIXMEGlobal.MainLock)
matches = FIXMEGlobal.TaskResultCache.Where(x => x.Key == taskid).ToArray();
if (matches.Length == 0)
info.ReportClientError("No such task found", System.Net.HttpStatusCode.NotFound);
else
info.OutputOK(new {
Status = matches[0].Value == null ? "Completed" : "Failed",
ErrorMessage = matches[0].Value == null ? null : matches[0].Value.Message,
Exception = matches[0].Value == null ? null : matches[0].Value.ToString()
});
}
else
{
info.OutputOK(new { Status = "Waiting" });
}
}
else
{
info.ReportClientError("Invalid request", System.Net.HttpStatusCode.BadRequest);
}
}
public void POST(string key, RequestInfo info)
{
var parts = (key ?? "").Split(new char[] { '/' }, 2);
long taskid;
if (parts.Length == 2 && long.TryParse(parts.First(), out taskid))
{
var task = FIXMEGlobal.WorkThread.CurrentTask;
var tasks = FIXMEGlobal.WorkThread.CurrentTasks;
if (task != null)
tasks.Insert(0, task);
task = tasks.FirstOrDefault(x => x.TaskID == taskid);
if (task == null)
{
info.ReportClientError("No such task", System.Net.HttpStatusCode.NotFound);
return;
}
switch (parts.Last().ToLowerInvariant())
{
case "stopaftercurrentfile":
task.Stop(allowCurrentFileToFinish: true);
info.OutputOK();
return;
case "stopnow":
task.Stop(allowCurrentFileToFinish: false);
info.OutputOK();
return;
case "abort":
task.Abort();
info.OutputOK();
return;
}
}
info.ReportClientError("Invalid or missing task id", System.Net.HttpStatusCode.NotFound);
}
}
}
@@ -0,0 +1,37 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using Duplicati.Library.RestAPI;
using System;
using System.Collections.Generic;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class Tasks : IRESTMethodGET
{
public void GET(string key, RequestInfo info)
{
var cur = FIXMEGlobal.WorkThread.CurrentTask;
var n = FIXMEGlobal.WorkThread.CurrentTasks;
if (cur != null)
n.Insert(0, cur);
info.OutputOK(n);
}
}
}
@@ -0,0 +1,78 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Collections.Generic;
using Duplicati.Server.Serialization;
using System.IO;
using Duplicati.Library.RestAPI;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class UISettings : IRESTMethodGET, IRESTMethodPOST, IRESTMethodPATCH
{
public void GET(string key, RequestInfo info)
{
if (string.IsNullOrWhiteSpace(key))
{
info.OutputOK(FIXMEGlobal.DataConnection.GetUISettingsSchemes());
}
else
{
info.OutputOK(FIXMEGlobal.DataConnection.GetUISettings(key));
}
}
public void POST(string key, RequestInfo info)
{
PATCH(key, info);
}
public void PATCH(string key, RequestInfo info)
{
if (string.IsNullOrWhiteSpace(key))
{
info.ReportClientError("Scheme is missing", System.Net.HttpStatusCode.BadRequest);
return;
}
IDictionary<string, string> data;
try
{
data = Serializer.Deserialize<Dictionary<string, string>>(new StreamReader(info.Request.Body));
}
catch (Exception ex)
{
info.ReportClientError(string.Format("Unable to parse settings object: {0}", ex.Message), System.Net.HttpStatusCode.BadRequest);
return;
}
if (data == null)
{
info.ReportClientError("Unable to parse settings object", System.Net.HttpStatusCode.BadRequest);
return;
}
if (info.Request.Method == "POST")
FIXMEGlobal.DataConnection.SetUISettings(key, data);
else
FIXMEGlobal.DataConnection.UpdateUISettings(key, data);
info.OutputOK();
}
}
}
@@ -0,0 +1,57 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using Duplicati.Library.RestAPI;
using System;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class Updates : IRESTMethodPOST
{
public void POST(string key, RequestInfo info)
{
switch ((key ?? "").ToLowerInvariant())
{
case "check":
FIXMEGlobal.UpdatePoller.CheckNow();
info.OutputOK();
return;
case "install":
FIXMEGlobal.UpdatePoller.InstallUpdate();
info.OutputOK();
return;
case "activate":
if (FIXMEGlobal.WorkThread.CurrentTask != null || FIXMEGlobal.WorkThread.CurrentTasks.Count != 0)
{
info.ReportServerError("Cannot activate update while task is running or scheduled");
}
else
{
FIXMEGlobal.UpdatePoller.ActivateUpdate();
info.OutputOK();
}
return;
default:
info.ReportClientError("No such action", System.Net.HttpStatusCode.NotFound);
return;
}
}
}
}
@@ -0,0 +1,41 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Linq;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class WebModule : IRESTMethodPOST
{
public void POST(string key, RequestInfo info)
{
var m = Duplicati.Library.DynamicLoader.WebLoader.Modules.FirstOrDefault(x => x.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
if (m == null)
{
info.ReportClientError(string.Format("No such command {0}", key), System.Net.HttpStatusCode.NotFound);
return;
}
info.OutputOK(new {
Status = "OK",
Result = m.Execute(info.Request.Form.Where(x => !x.Name.Equals("command", StringComparison.OrdinalIgnoreCase)
).ToDictionary(x => x.Name, x => x.Value))
});
}
}
}
@@ -0,0 +1,30 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Linq;
namespace Duplicati.Server.WebServer.RESTMethods
{
public class WebModules : IRESTMethodGET
{
public void GET(string key, RequestInfo info)
{
info.OutputOK(Duplicati.Library.DynamicLoader.WebLoader.Modules);
}
}
}