First pass at introducing kestrel and refactoring to make the RESTAPI be its own package.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user