();
+ options.TryGetValue(OPTION_PORT, out var portstring);
+ if (!string.IsNullOrEmpty(portstring))
+ ports =
+ from n in portstring.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+ where int.TryParse(n, out _)
+ select int.Parse(n);
+
+ if (ports == null || !ports.Any())
+ ports = [DEFAULT_OPTION_PORT];
+
+ options.TryGetValue(OPTION_INTERFACE, out var interfacestring);
+
+ if (string.IsNullOrWhiteSpace(interfacestring))
+ interfacestring = FIXMEGlobal.DataConnection.ApplicationSettings.ServerListenInterface;
+ if (string.IsNullOrWhiteSpace(interfacestring))
+ interfacestring = DEFAULT_OPTION_INTERFACE;
+
+ var listenInterface = System.Net.IPAddress.Loopback;
+ interfacestring = interfacestring.Trim();
+ if (new[] { "*", "all", "any" }.Any(x => x.Equals(interfacestring, StringComparison.OrdinalIgnoreCase)))
+ listenInterface = System.Net.IPAddress.Any;
+ else if (interfacestring != "loopback")
+ listenInterface = System.Net.IPAddress.Parse(interfacestring);
+
+ options.TryGetValue(OPTION_SSLCERTIFICATEFILE, out var certificateFile);
+ options.TryGetValue(OPTION_SSLCERTIFICATEFILEPASSWORD, out var certificateFilePassword);
+ certificateFilePassword = certificateFilePassword?.Trim() ?? "";
+
+ X509Certificate2? cert = null;
+ if (certificateFile == null)
+ {
+ try
+ {
+ cert = FIXMEGlobal.DataConnection.ApplicationSettings.ServerSSLCertificate;
+ }
+ catch (Exception ex)
+ {
+ Duplicati.Library.Logging.Log.WriteWarningMessage(LOGTAG, "DefectStoredSSLCert", ex, Duplicati.Server.Strings.Server.DefectSSLCertInDatabase);
+ }
+ }
+ else if (certificateFile.Length == 0)
+ {
+ FIXMEGlobal.DataConnection.ApplicationSettings.ServerSSLCertificate = null;
+ }
+ else
+ {
+ try
+ {
+ cert = new X509Certificate2(certificateFile, certificateFilePassword, X509KeyStorageFlags.Exportable);
+ }
+ catch (Exception ex)
+ {
+ throw new Exception(Duplicati.Server.Strings.Server.SSLCertificateFailure(ex.Message), ex);
+ }
+ }
+
+ var webroot = Library.AutoUpdater.UpdaterManager.INSTALLATIONDIR;
+
+#if DEBUG
+ //For debug we go "../../../../../.." to get out of "Executables/net8/Duplicati.GUI.TrayIcon/bin/debug/net8.0"
+ string tmpwebroot = System.IO.Path.GetFullPath(System.IO.Path.Combine(webroot, "..", "..", "..", "..", "..", ".."));
+ tmpwebroot = System.IO.Path.Combine(tmpwebroot, "Duplicati", "Server");
+ if (System.IO.Directory.Exists(System.IO.Path.Combine(tmpwebroot, "webroot")))
+ webroot = tmpwebroot;
+#endif
+
+ webroot = System.IO.Path.Combine(webroot, "webroot");
+
+ if (options.ContainsKey(OPTION_WEBROOT))
+ {
+ string userroot = options[OPTION_WEBROOT];
+#if DEBUG
+ //In debug mode we do not care where the path points
+#else
+ //In release mode we check that the user supplied path is located
+ // in the same folders as the running application, to avoid users
+ // that inadvertently expose top level folders
+ if (!string.IsNullOrWhiteSpace(userroot) && userroot.StartsWith(Util.AppendDirSeparator(Duplicati.Library.Utility.Utility.getEntryAssembly().Location), Library.Utility.Utility.ClientFilenameStringComparison))
+ webroot = userroot;
+#endif
+ }
+
+ var certValid = cert != null && cert.HasPrivateKey;
+ var settings = new ParsedWebserverSettings(
+ webroot,
+ -1,
+ listenInterface,
+ cert,
+ string.Format("{0} v{1}", Duplicati.Library.AutoUpdater.AutoUpdateSettings.AppName, System.Reflection.Assembly.GetExecutingAssembly().GetName().Version),
+ options.GetValueOrDefault(OPTION_WEBSERVICE_PASSWORD, ""),
+ options.GetValueOrDefault(OPTION_WEBSERVICE_ALLOWEDHOSTNAMES, "").Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
+ );
+
+ // If we are in hosted mode with no specified port,
+ // then try different ports
+ foreach (var p in ports)
+ try
+ {
+ settings = settings with { Port = p };
+
+ var server = await createServer(settings);
+ if (interfacestring != FIXMEGlobal.DataConnection.ApplicationSettings.ServerListenInterface)
+ FIXMEGlobal.DataConnection.ApplicationSettings.ServerListenInterface = interfacestring;
+
+ if (certValid && cert != FIXMEGlobal.DataConnection.ApplicationSettings.ServerSSLCertificate)
+ FIXMEGlobal.DataConnection.ApplicationSettings.ServerSSLCertificate = cert;
+
+ Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "ServerListening", Duplicati.Server.Strings.Server.StartedServer(listenInterface.ToString(), p));
+
+ return server;
+ }
+ catch (System.Net.Sockets.SocketException)
+ {
+ }
+
+ throw new Exception(Duplicati.Server.Strings.Server.ServerStartFailure(ports));
+ }
+}
\ No newline at end of file
diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/CaptchaController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/CaptchaController.js
index 2238e66ff..59dc2fe32 100644
--- a/Duplicati/Server/webroot/ngax/scripts/controllers/CaptchaController.js
+++ b/Duplicati/Server/webroot/ngax/scripts/controllers/CaptchaController.js
@@ -4,7 +4,7 @@ backupApp.controller('CaptchaController', function($scope, CaptchaService, Dialo
function refreshImage() {
entry.imageurl = null;
- AppService.post('/captcha', { 'target': entry.target}).then(function(resp) {
+ AppService.postJson('/captcha', { 'target': entry.target}).then(function(resp) {
entry.token = resp.data.token;
entry.imageurl = AppService.apiurl + '/captcha/' + entry.token;
diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/CommandlineController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/CommandlineController.js
index 326ca2e65..63b89844f 100644
--- a/Duplicati/Server/webroot/ngax/scripts/controllers/CommandlineController.js
+++ b/Duplicati/Server/webroot/ngax/scripts/controllers/CommandlineController.js
@@ -69,11 +69,10 @@ backupApp.controller('CommandlineController', function($scope, $routeParams, $lo
}
options = {
- headers: {'Content-Type': 'application/json; charset=utf-8'},
responseType: 'text'
};
- AppService.post('/commandline', combined, options).then(
+ AppService.postJson('/commandline', combined, options).then(
function(resp) {
$location.path('/commandline/view/' + resp.data.ID);
},
@@ -162,7 +161,7 @@ backupApp.controller('CommandlineController', function($scope, $routeParams, $lo
}
if ($routeParams.backupid != null) {
- AppService.get('/backup/' + $routeParams.backupid + '/export?argsonly=true&export-passwords=true').then(
+ AppService.get('/backup/' + $routeParams.backupid + '/export-argsonly?export-passwords=true').then(
function(resp) {
$scope.TargetURL = resp.data.Backend;
$scope.Arguments = resp.data.Arguments;
diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/DeleteController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/DeleteController.js
index c7729edb5..c93d7e30a 100644
--- a/Duplicati/Server/webroot/ngax/scripts/controllers/DeleteController.js
+++ b/Duplicati/Server/webroot/ngax/scripts/controllers/DeleteController.js
@@ -16,7 +16,7 @@ backupApp.controller('DeleteController', function($scope, $routeParams, $locatio
$scope.DBPath = $scope.Backup.Backup.DBPath;
if ($scope.DBPath != prev || force)
- AppService.post('/filesystem/validate', {path: $scope.DBPath}).then(function(resp) {
+ AppService.postJson('/filesystem/validate', {path: $scope.DBPath}).then(function(resp) {
$scope.NoLocalDB = false;
}, function() {
$scope.NoLocalDB = true;
diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/EditBackupController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/EditBackupController.js
index 0d14a25f5..696b91eda 100644
--- a/Duplicati/Server/webroot/ngax/scripts/controllers/EditBackupController.js
+++ b/Duplicati/Server/webroot/ngax/scripts/controllers/EditBackupController.js
@@ -144,7 +144,7 @@ backupApp.controller('EditBackupController', function ($rootScope, $scope, $rout
function continuation() {
scope.validatingSourcePath = true;
- AppService.post('/filesystem/validate', {path: scope.manualSourcePath}).then(function() {
+ AppService.postJson('/filesystem/validate', {path: scope.manualSourcePath}).then(function() {
scope.validatingSourcePath = false;
scope.Backup.Sources.push(scope.manualSourcePath);
scope.manualSourcePath = null;
@@ -439,13 +439,13 @@ backupApp.controller('EditBackupController', function ($rootScope, $scope, $rout
if ($routeParams.backupid == null) {
function postDb() {
- AppService.post('/backups', result, {'headers': {'Content-Type': 'application/json'}}).then(function() {
+ AppService.postJson('/backups', result).then(function() {
$location.path('/');
}, AppUtils.connectionError);
};
function checkForExistingDb(continuation) {
- AppService.post('/remoteoperation/dbpath', $scope.Backup.TargetURL, {'headers': {'Content-Type': 'application/text'}}).then(
+ AppService.postJson('/remoteoperation/dbpath', { path: $scope.Backup.TargetURL }).then(
function(resp) {
if (resp.data.Exists) {
DialogService.dialog(gettextCatalog.getString('Use existing database?'), gettextCatalog.getString('An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?'), [gettextCatalog.getString('Cancel'), gettextCatalog.getString('Yes'), gettextCatalog.getString('No')], function(ix) {
@@ -696,7 +696,7 @@ backupApp.controller('EditBackupController', function ($rootScope, $scope, $rout
AppService.get('/backupdefaults').then(function(data) {
- $scope.rawddata = data.data.data;
+ $scope.rawddata = data.data;
if ($location.$$path.indexOf('/add-import') == 0 && $rootScope.importConfig != null)
angular.merge($scope.rawddata, $rootScope.importConfig);
@@ -711,7 +711,7 @@ backupApp.controller('EditBackupController', function ($rootScope, $scope, $rout
AppService.get('/backup/' + $routeParams.backupid).then(function(data) {
- $scope.rawddata = data.data.data;
+ $scope.rawddata = data.data;
setupScope($scope.rawddata);
}, function() {
diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js
index 2e75e8aca..e17649da3 100644
--- a/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js
+++ b/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js
@@ -27,7 +27,7 @@ backupApp.controller("ExportController", function($scope, $routeParams, AppServi
function getExport() {
if ($scope.ExportType === "commandline") {
$scope.Connecting = true;
- AppService.get("/backup/" + $scope.BackupID + "/export?cmdline=true&export-passwords=" + encodeURIComponent($scope.ExportPasswords)).then(
+ AppService.get("/backup/" + $scope.BackupID + "/export-cmdline?export-passwords=" + encodeURIComponent($scope.ExportPasswords)).then(
function(resp) {
$scope.Connecting = false;
$scope.Completed = true;
diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/ImportController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/ImportController.js
index 8f2557785..e9476399f 100644
--- a/Duplicati/Server/webroot/ngax/scripts/controllers/ImportController.js
+++ b/Duplicati/Server/webroot/ngax/scripts/controllers/ImportController.js
@@ -2,6 +2,7 @@ backupApp.controller('ImportController', function($rootScope, $scope, $timeout,
$scope.Connecting = false;
$scope.Completed = false;
$scope.ImportURL = AppService.get_import_url();
+ $scope.XSRFToken = AppService.get_xsrf_token();
$scope.restoremode = $location.$$path.indexOf('/restore-import') == 0;
diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/LocalDatabaseController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/LocalDatabaseController.js
index 3af527afc..e30e12499 100644
--- a/Duplicati/Server/webroot/ngax/scripts/controllers/LocalDatabaseController.js
+++ b/Duplicati/Server/webroot/ngax/scripts/controllers/LocalDatabaseController.js
@@ -13,7 +13,7 @@ backupApp.controller('LocalDatabaseController', function($scope, $routeParams, $
$scope.DBPath = $scope.Backup.Backup.DBPath;
if ($scope.DBPath != prev || force)
- AppService.post('/filesystem/validate', {path: $scope.DBPath}).then(function(resp) {
+ AppService.postJson('/filesystem/validate', {path: $scope.DBPath}).then(function(resp) {
$scope.NoLocalDB = false;
}, function() {
$scope.NoLocalDB = true;
@@ -42,7 +42,7 @@ backupApp.controller('LocalDatabaseController', function($scope, $routeParams, $
};
$scope.doRepair = function() {
- AppService.post('/backup/' + $scope.BackupID + '/repair');
+ AppService.postJson('/backup/' + $scope.BackupID + '/repair');
$location.path('/');
};
@@ -55,7 +55,7 @@ backupApp.controller('LocalDatabaseController', function($scope, $routeParams, $
$scope.doSave = function(continuation, move) {
function doUpdate() {
- AppService.post('/backup/' + $scope.BackupID + '/' + (move ? 'movedb' : 'updatedb'), {path: $scope.DBPath}).then(
+ AppService.postJson('/backup/' + $scope.BackupID + '/' + (move ? 'movedb' : 'updatedb'), {path: $scope.DBPath}).then(
function(resp) {
$scope.Backup.Backup.DBPath = $scope.DBPath;
resetBackupItem(true);
@@ -68,7 +68,7 @@ backupApp.controller('LocalDatabaseController', function($scope, $routeParams, $
};
function doCheckTarget() {
- AppService.post('/filesystem/validate', {path: $scope.DBPath}).then(function(resp) {
+ AppService.postJson('/filesystem/validate', {path: $scope.DBPath}).then(function(resp) {
DialogService.dialog(gettextCatalog.getString('Existing file found'), gettextCatalog.getString('An existing file was found at the new location\nAre you sure you want the database to point to an existing file?'), [gettextCatalog.getString('Cancel'), gettextCatalog.getString('No'), gettextCatalog.getString('Yes')], function(ix) {
if (ix == 2) {
doUpdate();
@@ -99,7 +99,7 @@ backupApp.controller('LocalDatabaseController', function($scope, $routeParams, $
};
$scope.doMove = function() {
- AppService.post('/filesystem/validate', {path: $scope.DBPath}).then(function(resp) {
+ AppService.postJson('/filesystem/validate', {path: $scope.DBPath}).then(function(resp) {
DialogService.dialog(gettextCatalog.getString('Cannot move to existing file'), gettextCatalog.getString('An existing file was found at the new location'));
}, function() {
$scope.doSave(null, true);
diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/RestoreController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/RestoreController.js
index a837d1acd..d30e0512f 100644
--- a/Duplicati/Server/webroot/ngax/scripts/controllers/RestoreController.js
+++ b/Duplicati/Server/webroot/ngax/scripts/controllers/RestoreController.js
@@ -120,7 +120,7 @@ backupApp.controller('RestoreController', function ($rootScope, $scope, $routePa
$scope.ConnectionProgress = gettextCatalog.getString('Fetching path information …');
inProgress[version] = true;
- AppService.post('/backup/' + $scope.BackupID + '/repairupdate', { 'only-paths': true, 'time': filesetStamps[version + '']}).then(
+ AppService.postJson('/backup/' + $scope.BackupID + '/repairupdate', { 'only-paths': true, 'time': filesetStamps[version + '']}).then(
function(resp) {
var taskid = resp.data.ID;
@@ -159,7 +159,7 @@ backupApp.controller('RestoreController', function ($rootScope, $scope, $routePa
$scope.ConnectionProgress = gettextCatalog.getString('Fetching path information …');
inProgress[version] = true;
- AppService.get('/backup/' + $scope.BackupID + '/files/*?prefix-only=true&folder-contents=false&time=' + encodeURIComponent(stamp)).then(
+ AppService.get('/backup/' + $scope.BackupID + '/files?prefix-only=true&folder-contents=false&time=' + encodeURIComponent(stamp)).then(
function(resp) {
delete inProgress[version];
$scope.connecting = false;
@@ -219,7 +219,7 @@ backupApp.controller('RestoreController', function ($rootScope, $scope, $routePa
var version = $scope.RestoreVersion + '';
var stamp = filesetStamps[version];
- AppService.get('/backup/' + $scope.BackupID + '/files/*' + $scope.SearchFilter + '*?prefix-only=false&time=' + encodeURIComponent(stamp) + '&filter=*' + encodeURIComponent($scope.SearchFilter) + '*').then(
+ AppService.get('/backup/' + $scope.BackupID + '/files?prefix-only=false&time=' + encodeURIComponent(stamp) + '&filter=*' + encodeURIComponent($scope.SearchFilter) + '*').then(
function(resp) {
$scope.Searching = false;
var searchNodes = [];
diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/RestoreDirectController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/RestoreDirectController.js
index 8be1ab41d..651af1ac6 100644
--- a/Duplicati/Server/webroot/ngax/scripts/controllers/RestoreDirectController.js
+++ b/Duplicati/Server/webroot/ngax/scripts/controllers/RestoreDirectController.js
@@ -58,7 +58,7 @@ backupApp.controller('RestoreDirectController', function ($rootScope, $scope, $l
});
}
- AppService.post('/backups?temporary=true', obj, {'headers': {'Content-Type': 'application/json'}}).then(
+ AppService.postJson('/backups?temporary=true', obj).then(
function(resp) {
$scope.ConnectionProgress = gettextCatalog.getString('Listing backup dates …');
diff --git a/Duplicati/Server/webroot/ngax/scripts/directives/backupEditUri.js b/Duplicati/Server/webroot/ngax/scripts/directives/backupEditUri.js
index f8597cf61..e7dd6e14d 100644
--- a/Duplicati/Server/webroot/ngax/scripts/directives/backupEditUri.js
+++ b/Duplicati/Server/webroot/ngax/scripts/directives/backupEditUri.js
@@ -41,7 +41,7 @@ backupApp.directive('backupEditUri', function(gettextCatalog) {
dlg.dismiss();
dlg = DialogService.dialog(gettextCatalog.getString('Testing ...'), gettextCatalog.getString('Testing connection ...'), [], null, function() {
- AppService.post('/remoteoperation/test', uri).then(function() {
+ AppService.postJson('/remoteoperation/test', { path: uri }).then(function() {
scope.Testing = false;
dlg.dismiss();
@@ -60,7 +60,7 @@ backupApp.directive('backupEditUri', function(gettextCatalog) {
var createFolder = function() {
hasTriedCreate = true;
scope.Testing = true;
- AppService.post('/remoteoperation/create', uri).then(testConnection, handleError);
+ AppService.postJson('/remoteoperation/create', { path: uri }).then(testConnection, handleError);
};
var appendApprovedCert = function(hash)
diff --git a/Duplicati/Server/webroot/ngax/scripts/directives/restoreFilePicker.js b/Duplicati/Server/webroot/ngax/scripts/directives/restoreFilePicker.js
index c5a6b026a..4c341c30c 100644
--- a/Duplicati/Server/webroot/ngax/scripts/directives/restoreFilePicker.js
+++ b/Duplicati/Server/webroot/ngax/scripts/directives/restoreFilePicker.js
@@ -39,7 +39,7 @@ backupApp.directive('restoreFilePicker', function() {
// Prefix filter with "@" to prevent Duplicati from
// mistaking literal '*' and '?' characters in paths
// for glob wildcard characters
- AppService.get('/backup/' + $scope.ngBackupId + '/files/' + encodeURIComponent(node.id) + '?prefix-only=false&folder-contents=true&time=' + encodeURIComponent($scope.ngTimestamp) + '&filter=' + encodeURIComponent('@' + node.id)).then(function(data) {
+ AppService.get('/backup/' + $scope.ngBackupId + '/files?prefix-only=false&folder-contents=true&time=' + encodeURIComponent($scope.ngTimestamp) + '&filter=' + encodeURIComponent('@' + node.id)).then(function(data) {
var children = []
for(var n in data.data.Files)
diff --git a/Duplicati/Server/webroot/ngax/scripts/directives/sourceFolderPicker.js b/Duplicati/Server/webroot/ngax/scripts/directives/sourceFolderPicker.js
index 52d405a84..6baa53f19 100644
--- a/Duplicati/Server/webroot/ngax/scripts/directives/sourceFolderPicker.js
+++ b/Duplicati/Server/webroot/ngax/scripts/directives/sourceFolderPicker.js
@@ -294,7 +294,7 @@ backupApp.directive('sourceFolderPicker', function() {
if (p.substr(0, 1) == '%' && p.substr(p.length - 1, 1) == '%')
p += scope.dirsep;
- AppService.post('/filesystem/validate', {path: p}).then(function(data) {
+ AppService.postJson('/filesystem/validate', {path: p}).then(function(data) {
defunctmap[compareablePath(data.config.data.path)] = false;
}, function(data) {
@@ -428,7 +428,7 @@ backupApp.directive('sourceFolderPicker', function() {
if (!node.children && !node.loading) {
node.loading = true;
- AppService.post('/filesystem?onlyfolders=false&showhidden=true', {path: node.id}).then(function(data) {
+ AppService.postJson('/filesystem?onlyfolders=false&showhidden=true', {path: node.id}).then(function(data) {
node.children = data.data;
node.loading = false;
@@ -520,7 +520,7 @@ backupApp.directive('sourceFolderPicker', function() {
// Load filter groups
AppUtils.loadFilterGroups();
- AppService.post('/filesystem?onlyfolders=false&showhidden=true', {path: '/'}).then(function(data) {
+ AppService.postJson('/filesystem?onlyfolders=false&showhidden=true', {path: '/'}).then(function(data) {
var usernode = {
text: gettextCatalog.getString('User data'),
@@ -584,7 +584,7 @@ backupApp.directive('sourceFolderPicker', function() {
var node = {
leaf: true,
id: "%HYPERV%\\" + data.data[i].id,
- text: data.data[i].name};
+ text: data.data[i].text};
cp = compareablePath(node.id);
displayMap[cp] = gettextCatalog.getString('Hyper-V Machine:') + " " + node.text;
@@ -616,7 +616,7 @@ backupApp.directive('sourceFolderPicker', function() {
var node = {
leaf: true,
id: "%MSSQL%\\" + data.data[i].id,
- text: data.data[i].name
+ text: data.data[i].text
};
cp = compareablePath(node.id);
diff --git a/Duplicati/Server/webroot/ngax/scripts/directives/targetFolderPicker.js b/Duplicati/Server/webroot/ngax/scripts/directives/targetFolderPicker.js
index bee49af6a..20608905b 100644
--- a/Duplicati/Server/webroot/ngax/scripts/directives/targetFolderPicker.js
+++ b/Duplicati/Server/webroot/ngax/scripts/directives/targetFolderPicker.js
@@ -63,7 +63,8 @@ backupApp.directive('destinationFolderPicker', function() {
if (!node.children && !node.loading) {
node.loading = true;
- AppService.post('/filesystem?onlyfolders=true&showhidden=true', {path: node.id}).then(function(data) {
+ AppService.postJson('/filesystem?onlyfolders=true&showhidden=true', {path: node.id}).then(function(data) {
+ console.log(data);
node.children = data.data;
node.loading = false;
@@ -102,7 +103,7 @@ backupApp.directive('destinationFolderPicker', function() {
$scope.$watch('ngHideUserNode', updateHideUserNode);
- AppService.post('/filesystem?onlyfolders=true&showhidden=true', {path: '/'}).then(function(data) {
+ AppService.postJson('/filesystem?onlyfolders=true&showhidden=true', {path: '/'}).then(function(data) {
var usernode = {
text: 'User data',
diff --git a/Duplicati/Server/webroot/ngax/scripts/services/AppService.js b/Duplicati/Server/webroot/ngax/scripts/services/AppService.js
index c13043c72..330e2f48f 100644
--- a/Duplicati/Server/webroot/ngax/scripts/services/AppService.js
+++ b/Duplicati/Server/webroot/ngax/scripts/services/AppService.js
@@ -1,6 +1,8 @@
backupApp.service('AppService', function ($http, $cookies, $q, $cookies, DialogService, appConfig) {
this.apiurl = '../api/v1';
this.proxy_url = null;
+ this.xsrftoken = null;
+ this.xsrfpromise = null;
var self = this;
@@ -71,38 +73,94 @@ backupApp.service('AppService', function ($http, $cookies, $q, $cookies, DialogS
return deferred.promise;
};
+ var chainPromise = function (deferred, callback) {
+ callback().then(function (response) {
+ deferred.resolve(response);
+ }, function (response) {
+ deferred.reject(response);
+ });
+ };
+
+ this.injectXsrf = function (options, callback) {
+ options.headers = options.headers || {};
+ if (this.xsrftoken != null) {
+ options.headers['X-XSRF-Token'] = this.xsrftoken;
+ return callback();
+ }
+
+ var deferred = $q.defer();
+ var self = this;
+ if (this.xsrfpromise == null) {
+ this.xsrfpromise = deferred.promise;
+ var url = this.apiurl + '/antiforgery/token';
+ installResponseHook(() => $http.get(this.proxy_url == null ? url : this.proxy_url, setupConfig('GET', {'responseType': 'text'}, null, url)))
+ .then(function (response) {
+ self.xsrftoken = response.data;
+ options.headers['X-XSRF-Token'] = self.xsrftoken;
+ chainPromise(deferred, callback);
+ }, function (response) {
+ deferred.reject(response);
+ });
+
+ } else {
+ this.xsrfpromise.then(() => {
+ options.headers['X-XSRF-Token'] = self.xsrftoken;
+ chainPromise(deferred, callback);
+ }, (response) => deferred.reject(response));
+ }
+ return deferred.promise;
+ };
+
this.get = function (url, options) {
let rurl = url;
if (!url.startsWith('http')) {
rurl = this.apiurl + url;
}
- return installResponseHook(() => $http.get(this.proxy_url == null ? rurl : this.proxy_url, setupConfig('GET', options, null, rurl)));
+ options = options || {};
+ return this.injectXsrf(options, () => installResponseHook(() => $http.get(this.proxy_url == null ? rurl : this.proxy_url, setupConfig('GET', options, null, rurl))));
};
this.patch = function (url, data, options) {
var rurl = this.apiurl + url;
- return installResponseHook(() => $http.patch(this.proxy_url == null ? rurl : this.proxy_url, data, setupConfig('PATCH', options, data, rurl)));
+ options = options || {};
+ return this.injectXsrf(options, () => installResponseHook(() => $http.patch(this.proxy_url == null ? rurl : this.proxy_url, data, setupConfig('PATCH', options, data, rurl))));
};
this.post = function (url, data, options) {
var rurl = this.apiurl + url;
- return installResponseHook(() => $http.post(this.proxy_url == null ? rurl : this.proxy_url, data, setupConfig('POST', options, data, rurl)));
+ options = options || {};
+ return this.injectXsrf(options, () => installResponseHook(() => $http.post(this.proxy_url == null ? rurl : this.proxy_url, data, setupConfig('POST', options, data, rurl))));
+ };
+
+ this.postJson = function (url, data, options) {
+
+ var rurl = this.apiurl + url;
+ options = options || {};
+ options.headers = options.headers || {};
+ options.headers['Content-Type'] = 'application/json; charset=utf-8';
+ return this.injectXsrf(options, () => installResponseHook(() => $http.post(this.proxy_url == null ? rurl : this.proxy_url, data, setupConfig('POST', options, data, rurl))));
};
this.put = function (url, data, options) {
var rurl = this.apiurl + url;
- return installResponseHook(() => $http.put(this.proxy_url == null ? rurl : this.proxy_url, data, setupConfig('PUT', options, data, rurl)));
+ options = options || {};
+ return this.injectXsrf(options, () => installResponseHook(() => $http.put(this.proxy_url == null ? rurl : this.proxy_url, data, setupConfig('PUT', options, data, rurl))));
};
this.delete = function (url, options) {
var rurl = this.apiurl + url;
- return installResponseHook(() => $http.delete(this.proxy_url == null ? rurl : this.proxy_url, setupConfig('DELETE', options, null, rurl)));
+ options = options || {};
+ return this.injectXsrf(options, () => installResponseHook(() => $http.delete(this.proxy_url == null ? rurl : this.proxy_url, setupConfig('DELETE', options, null, rurl))));
};
+ this.get_xsrf_token = function () {
+ return this.xsrftoken;
+ }
+
this.get_export_url = function (backupid, passphrase, exportPasswords) {
- var rurl = this.apiurl + '/backup/' + backupid + '/export?x-xsrf-token=' + encodeURIComponent($cookies.get('xsrf-token'));
- rurl += '&export-passwords=' + encodeURIComponent(exportPasswords);
+ var rurl = this.apiurl + '/backup/' + backupid + '/export';
+ rurl += '?export-passwords=' + encodeURIComponent(exportPasswords);
if ((passphrase || '').trim().length > 0)
rurl += '&passphrase=' + encodeURIComponent(passphrase);
@@ -112,7 +170,7 @@ backupApp.service('AppService', function ($http, $cookies, $q, $cookies, DialogS
};
this.get_import_url = function (passphrase) {
- var rurl = this.apiurl + '/backups/import?x-xsrf-token=' + encodeURIComponent($cookies.get('xsrf-token'));
+ var rurl = this.apiurl + '/backups/import';
if ((passphrase || '').trim().length > 0)
rurl += '&passphrase=' + encodeURIComponent(passphrase);
diff --git a/Duplicati/Server/webroot/ngax/scripts/services/CaptchaService.js b/Duplicati/Server/webroot/ngax/scripts/services/CaptchaService.js
index 413b1ae95..1eb9e90a8 100644
--- a/Duplicati/Server/webroot/ngax/scripts/services/CaptchaService.js
+++ b/Duplicati/Server/webroot/ngax/scripts/services/CaptchaService.js
@@ -31,7 +31,7 @@ backupApp.service('CaptchaService', function(DialogService, AppService, AppUtils
DialogService.dialog(gettextCatalog.getString('Verifying answer'), gettextCatalog.getString('Verifying ...'), [], function() {}, function() {
- AppService.post('/captcha/' + encodeURIComponent(cb.token), {'answer': cb.answer, 'target': cb.target}).then(function(resp) {
+ AppService.postJson('/captcha/' + encodeURIComponent(cb.token), {'answer': cb.answer, 'target': cb.target}).then(function(resp) {
DialogService.dismissCurrent();
self.active = null;
cb.callback(cb.token, cb.answer);
diff --git a/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js b/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js
index 949036edb..2348851ed 100644
--- a/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js
+++ b/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js
@@ -47,7 +47,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
var dlg = null;
dlg = DialogService.dialog(gettextCatalog.getString('Testing permissions...'), gettextCatalog.getString('Testing permissions …'), [], null, function () {
- AppService.post('/webmodule/s3-iamconfig', {
+ AppService.postJson('/webmodule/s3-iamconfig', {
's3-operation': 'CanCreateUser',
's3-username': scope.Username,
's3-password': scope.Password
@@ -80,7 +80,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
// Loaders are a way for backends to request extra data from the server
EditUriBackendConfig.loaders['s3'] = function (scope) {
if (scope.s3_providers == null) {
- AppService.post('/webmodule/s3-getconfig', {'s3-config': 'Providers'}).then(function (data) {
+ AppService.postJson('/webmodule/s3-getconfig', {'s3-config': 'Providers'}).then(function (data) {
scope.s3_providers = data.data.Result;
if (scope.s3_server == undefined && scope.s3_server_custom == undefined)
scope.s3_server = 's3.amazonaws.com';
@@ -92,7 +92,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
}
if (scope.s3_regions == null) {
- AppService.post('/webmodule/s3-getconfig', {'s3-config': 'Regions'}).then(function (data) {
+ AppService.postJson('/webmodule/s3-getconfig', {'s3-config': 'Regions'}).then(function (data) {
scope.s3_regions = data.data.Result;
if (scope.s3_region == null && scope.s3_region_custom == undefined)
scope.s3_region = '';
@@ -103,7 +103,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
}
if (scope.s3_storageclasses == null) {
- AppService.post('/webmodule/s3-getconfig', {'s3-config': 'StorageClasses'}).then(function (data) {
+ AppService.postJson('/webmodule/s3-getconfig', {'s3-config': 'StorageClasses'}).then(function (data) {
scope.s3_storageclasses = data.data.Result;
if (scope.s3_storageclass == null && scope.s3_storageclass_custom == undefined)
scope.s3_storageclass = '';
@@ -123,7 +123,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
dlg = DialogService.dialog(gettextCatalog.getString('Creating user...'), gettextCatalog.getString('Creating new user with limited access …'), [], null, function () {
path = (scope.Server || '') + '/' + (scope.Path || '');
- AppService.post('/webmodule/s3-iamconfig', {
+ AppService.postJson('/webmodule/s3-iamconfig', {
's3-operation': 'CreateIAMUser',
's3-path': path,
's3-username': scope.Username,
@@ -147,7 +147,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
EditUriBackendConfig.validaters['s3'](scope, function () {
path = (scope.Server || '') + '/' + (scope.Path || '');
- AppService.post('/webmodule/s3-iamconfig', {
+ AppService.postJson('/webmodule/s3-iamconfig', {
's3-operation': 'GetPolicyDoc',
's3-path': path
}).then(function (data) {
@@ -162,7 +162,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
EditUriBackendConfig.loaders['storj'] = function (scope) {
if (scope.storj_satellites == null) {
- AppService.post('/webmodule/storj-getconfig', {'storj-config': 'Satellites'}).then(function (data) {
+ AppService.postJson('/webmodule/storj-getconfig', {'storj-config': 'Satellites'}).then(function (data) {
scope.storj_satellites = data.data.Result;
if (scope.storj_satellite == undefined && scope.storj_satellite_custom == undefined)
scope.storj_satellite = 'us1.storj.io:7777';
@@ -174,7 +174,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
}
if (scope.storj_auth_methods == null) {
- AppService.post('/webmodule/storj-getconfig', {'storj-config': 'AuthenticationMethods'}).then(function (data) {
+ AppService.postJson('/webmodule/storj-getconfig', {'storj-config': 'AuthenticationMethods'}).then(function (data) {
scope.storj_auth_methods = data.data.Result;
if (scope.storj_auth_method == undefined)
scope.storj_auth_method = 'API key';
@@ -188,7 +188,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
EditUriBackendConfig.loaders['tardigrade'] = function (scope) {
if (scope.tardigrade_satellites == null) {
- AppService.post('/webmodule/storj-getconfig', {'storj-config': 'Satellites'}).then(function (data) {
+ AppService.postJson('/webmodule/storj-getconfig', {'storj-config': 'Satellites'}).then(function (data) {
scope.tardigrade_satellites = data.data.Result;
if (scope.tardigrade_satellite == undefined && scope.tardigrade_satellite_custom == undefined)
scope.tardigrade_satellite = 'us1.storj.io:7777';
@@ -200,7 +200,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
}
if (scope.tardigrade_auth_methods == null) {
- AppService.post('/webmodule/storj-getconfig', {'storj-config': 'AuthenticationMethods'}).then(function (data) {
+ AppService.postJson('/webmodule/storj-getconfig', {'storj-config': 'AuthenticationMethods'}).then(function (data) {
scope.tardigrade_auth_methods = data.data.Result;
if (scope.tardigrade_auth_method == undefined)
scope.tardigrade_auth_method = 'API key';
@@ -306,7 +306,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
EditUriBackendConfig.loaders['openstack'] = function (scope) {
if (scope.openstack_providers == null) {
- AppService.post('/webmodule/openstack-getconfig', {'openstack-config': 'Providers'}).then(function (data) {
+ AppService.postJson('/webmodule/openstack-getconfig', {'openstack-config': 'Providers'}).then(function (data) {
scope.openstack_providers = data.data.Result;
if (scope.openstack_server == undefined && scope.openstack_server_custom == undefined)
scope.openstack_server = 'https://identity.api.rackspacecloud.com/';
@@ -318,7 +318,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
}
if (scope.openstack_versions == null) {
- AppService.post('/webmodule/openstack-getconfig', {'openstack-config': 'Versions'}).then(function (data) {
+ AppService.postJson('/webmodule/openstack-getconfig', {'openstack-config': 'Versions'}).then(function (data) {
scope.openstack_versions = data.data.Result;
if (scope.openstack_version == undefined)
scope.openstack_version = 'v2.0';
@@ -333,7 +333,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
EditUriBackendConfig.loaders['gcs'] = function (scope) {
if (scope.gcs_locations == null) {
- AppService.post('/webmodule/gcs-getconfig', {'gcs-config': 'Locations'}).then(function (data) {
+ AppService.postJson('/webmodule/gcs-getconfig', {'gcs-config': 'Locations'}).then(function (data) {
scope.gcs_locations = data.data.Result;
for (var k in scope.gcs_locations)
if (scope.gcs_locations[k] === null)
@@ -346,7 +346,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
}
if (scope.gcs_storageclasses == null) {
- AppService.post('/webmodule/gcs-getconfig', {'gcs-config': 'StorageClasses'}).then(function (data) {
+ AppService.postJson('/webmodule/gcs-getconfig', {'gcs-config': 'StorageClasses'}).then(function (data) {
scope.gcs_storageclasses = data.data.Result;
for (var k in scope.gcs_storageclasses)
if (scope.gcs_storageclasses[k] === null)
diff --git a/Duplicati/Server/webroot/ngax/templates/import.html b/Duplicati/Server/webroot/ngax/templates/import.html
index dca98524a..4d40d2888 100644
--- a/Duplicati/Server/webroot/ngax/templates/import.html
+++ b/Duplicati/Server/webroot/ngax/templates/import.html
@@ -12,7 +12,7 @@
-
+
@@ -26,6 +26,7 @@
+