Files
binoc-central-mirror/mail/components/cloudfile/nsNextcloud.js
T
2020-05-10 13:52:36 -04:00

902 lines
27 KiB
JavaScript

/* global Components, Services */
/**
* @copyright Copyright (c) 2017, Olivier Paroz (github@oparoz.com)
* @copyright Copyright (c) 2017, Philipp Kewisch
* @copyright Copyright (c) 2017, Mark James
* @copyright Copyright (c) 2017, Guillaume Viguier-Just (@guillaumev)
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* This file implements the nsIMsgCloudFileProvider interface.
*
* This component handles the Nextcloud implementation of the nsIMsgCloudFileProvider interface.
*/
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/oauth.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource:///modules/gloda/log4moz.js");
Cu.import("resource:///modules/cloudFileAccounts.js");
const kRestBase = "/ocs/v1.php";
const kAuthPath = kRestBase + "/cloud/user";
const kShareApp = kRestBase + "/apps/files_sharing/api/v1/shares";
const kWebDavPath = "/remote.php/webdav";
function wwwFormUrlEncode (aStr) {
return encodeURIComponent(aStr)
.replace(/!/g, '%21')
.replace(/'/g, '%27')
.replace(/\(/g, '%28')
.replace(/\)/g, '%29')
.replace(/\*/g, '%2A')
.replace(/\@/g, '%40');
}
function validURL(value) {
return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(value);
}
/**
* Our Nextcloud Provider
*/
function Nextcloud () {
//this.log = Log4Moz.getConfiguredLogger("Nextcloud", Log4Moz.Level.Info, Log4Moz.Level.Debug,
// Log4Moz.Level.Debug);
this.log = Log4Moz.getConfiguredLogger("Nextcloud");
}
Nextcloud.prototype = {
/* nsISupports */
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMsgCloudFileProvider]),
classID: Components.ID("{ad8c3b77-7dc8-41d1-8985-5be88b254ff3}"),
get type () {
return "Nextcloud";
},
get displayName () {
return this._displayName;
},
get serviceURL () {
return this._serverUrl;
},
get iconClass () {
return "chrome://messenger/skin/icons/nextcloud.png";
},
get accountKey () {
return this._accountKey;
},
get lastError () {
return this._lastErrorText;
},
get settingsURL () {
return "chrome://messenger/content/cloudfile/NextCloud/settings.xhtml";
},
get managementURL () {
return "chrome://messenger/content/cloudfile/NextCloud/management.xhtml";
},
/**
* If the provider doesn't have an API for creating an account, perhaps
* there's a url we can load in a content tab that will allow the user
* to create an account.
*/
get createNewAccountUrl () {
return "";
},
_displayName: "Nextcloud",
_accountKey: false,
_serverUrl: "",
_serverPort: 443,
_fullUrl: "",
_storageFolder: "",
_userName: "",
_password: "",
_protectUploads: "",
_prefBranch: null,
_loggedIn: false,
_authToken: "",
_userInfo: null,
_file: null,
_requestDate: null,
_successCallback: null,
_connection: null,
_request: null,
_uploadingFile: null,
_uploader: null,
_lastErrorStatus: 0,
_lastErrorText: "",
_maxFileSize: -1,
_totalStorage: -1,
_fileSpaceUsed: -1,
_uploads: [],
_urlsForFiles: {},
_uploadInfo: {}, // upload info keyed on aFiles.
/**
* Initialize this instance of Nextcloud, setting the accountKey.
*
* @param aAccountKey The account key that this instance of the nsIMsgCloudFileProvider should
* be associated with.
*/
init: function nsNc_init (aAccountKey) {
this._accountKey = aAccountKey;
this._prefBranch = Services.prefs.getBranch("mail.cloud_files.accounts." +
aAccountKey + ".");
if (this._prefBranch.getCharPref("displayName") != "") {
this._displayName = this._prefBranch.getCharPref("displayName");
}
this._serverUrl = this._prefBranch.getCharPref("server");
this._serverPort = this._prefBranch.getIntPref("port");
this._userName = this._prefBranch.getCharPref("username");
let fullUrl = this._serverUrl + ":" + this._serverPort;
const thirdSlash = this._serverUrl.indexOf('/', 9);
if (thirdSlash !== -1) {
fullUrl = this._serverUrl.slice(0, thirdSlash) + ":" + this._serverPort + this._serverUrl.slice(thirdSlash);
}
this._fullUrl = fullUrl;
if (this._prefBranch.prefHasUserValue("storageFolder")) {
this._storageFolder = this._prefBranch.getCharPref("storageFolder");
} else {
this._storageFolder = "/Mail-attachments";
}
if (this._prefBranch.prefHasUserValue("protectUploads")) {
this._protectUploads = this._prefBranch.getCharPref("protectUploads");
}
},
/**
* Attempts to upload a file to Nextcloud.
*
* @param aFile the nsILocalFile to be uploaded
* @param aCallback an nsIRequestObserver for listening for the starting
* and ending states of the upload.
*/
uploadFile: function nsNc_uploadFile (aFile, aCallback) {
if (Services.io.offline) {
throw Ci.nsIMsgCloudFileProvider.offlineErr;
}
this.log.info("uploading " + aFile.leafName);
// Some ugliness here - we stash requestObserver here, because we might
// use it again in _getUserInfo.
this.requestObserver = aCallback;
// if we're uploading a file, queue this request.
if (this._uploadingFile && this._uploadingFile != aFile) {
let uploader = new NextcloudFileUploader(this, aFile, this._uploaderCallback
.bind(this), aCallback);
this._uploads.push(uploader);
return;
}
this._file = aFile;
this._uploadingFile = aFile;
let successCallback = this._finishUpload.bind(this, aFile, aCallback);
if (!this._loggedIn) {
return this._logonAndGetUserInfo(successCallback, null, true);
}
this.log.info("getting user info");
if (!this._userInfo) {
return this._getUserInfo(successCallback);
}
successCallback();
},
/**
* Attempts to cancel a file upload.
*
* @param aFile the nsILocalFile to cancel the upload for.
*/
cancelFileUpload: function nsNc_cancelFileUpload (aFile) {
if (this._uploadingFile.equals(aFile)) {
this._uploader.cancel();
} else {
for (let i = 0; i < this._uploads.length; i++)
if (this._uploads[i].file.equals(aFile)) {
this._uploads[i].requestObserver.onStopRequest(
null, null, Ci.nsIMsgCloudFileProvider.uploadCanceled
);
this._uploads.splice(i, 1);
return;
}
}
},
/**
* For some nsILocalFile, return the associated sharing URL.
*
* @param aFile the nsILocalFile to retrieve the URL for
*/
urlForFile: function nsNc_urlForFile (aFile) {
return this._urlsForFiles[aFile.path];
},
/**
* Updates the profile information for the account associated with the
* account key.
*
* @param aWithUI a boolean for whether or not we should display authorization
* UI if we don't have a valid token anymore, or just fail out.
* @param aCallback an nsIRequestObserver for observing the starting and
* ending states of the request.
*/
refreshUserInfo: function nsNc_refreshUserInfo (aWithUI, aCallback) {
if (Services.io.offline) {
throw Ci.nsIMsgCloudFileProvider.offlineErr;
}
this.requestObserver = aCallback;
aCallback.onStartRequest(null, null);
if (!this._loggedIn) {
return this._logonAndGetUserInfo(null, null, aWithUI);
}
if (!this._userInfo) {
return this._getUserInfo();
}
return this._userInfo;
},
/**
* Allows for the creation of a new user on the Nextcloud instance.
*
* This implementation does not implement the createNewAccount
* function defined in nsIMsgCloudFileProvider.idl.
*/
createNewAccount: function nsNc_createNewAccount (aEmailAddress, aPassword, aFirstName, aLastName) {
return Cr.NS_ERROR_NOT_IMPLEMENTED;
},
/**
* If the user already has an account on a Nextcloud instance, we can get the user to just login
*
* This function does not appear to be called from the BigFiles UI, and
* might be excisable.
*/
createExistingAccount: function nsNc_createExistingAccount (aRequestObserver) {
// XXX: replace this with a better function
let successCb = function () {
let callbackCreateFolder = function (created) {
if (created) {
aRequestObserver.onStopRequest(null, this, Cr.NS_OK);
}
else {
aRequestObserver.onStopRequest(null, this, Ci.nsIMsgCloudFileProvider.authErr);
}
}.bind(this);
let folderExists = function (exists) {
if (exists) {
aRequestObserver.onStopRequest(null, this, Cr.NS_OK);
}
else {
this._createFolder(callbackCreateFolder);
}
}.bind(this);
this._checkFolderExists(folderExists);
}.bind(this);
let failureCb = function () {
aRequestObserver.onStopRequest(null, this, Ci.nsIMsgCloudFileProvider.authErr);
}.bind(this);
this.logon(successCb, failureCb, true);
},
/**
* For a particular error, return a URL if Nextcloud has a page for handling
* that particular error.
*
* @param aError the error to get the URL for
*/
providerUrlForError: function nsNc_providerUrlForError (aError) {
return "";
},
/**
* If we don't know the limit, this will return -1.
*/
get fileUploadSizeLimit () {
return this._maxFileSize;
},
get remainingFileSpace () {
return this._totalStorage > 0 ? this._totalStorage - this._fileSpaceUsed : -1;
},
get fileSpaceUsed () {
return this._fileSpaceUsed;
},
/**
* Attempt to delete an upload file if we've uploaded it.
*
* @param aFile the file that was originall uploaded
* @param aCallback an nsIRequestObserver for monitoring the starting and
* ending states of the deletion request.
*/
deleteFile: function nsNc_deleteFile (aFile, aCallback) {
return Cr.NS_ERROR_NOT_IMPLEMENTED;
},
/**
* Returns the saved password for this account if one exists, or prompts
* the user for a password. Returns the empty string on failure.
*
* @param aUsername the username associated with the account / password.
* @param aNoPrompt a boolean for whether or not we should suppress
* the password prompt if no password exists. If so,
* returns the empty string if no password exists.
*/
getPassword: function (aUsername, aNoPrompt) {
this.log.info("Getting password for user: " + aUsername);
if (aNoPrompt) {
this.log.info("Suppressing password prompt");
}
let passwordURI = this._fullUrl;
let logins = Services.logins.findLogins({}, passwordURI, null, passwordURI);
for (let loginInfo of logins) {
if (loginInfo.username == aUsername) {
return loginInfo.password;
}
}
if (aNoPrompt) {
return "";
}
// OK, let's prompt for it.
let win = Services.wm.getMostRecentWindow(null);
let authPrompter = Services.ww.getNewAuthPrompter(win);
let password = {value: ""};
// Use the service name in the prompt text
let userPos = this._fullUrl.indexOf("//") + 2;
let userNamePart = encodeURIComponent(this._userName) + '@';
let serverUrl =
this._fullUrl.substr(0, userPos) + userNamePart + this._fullUrl.substr(userPos);
let messengerBundle = Services.strings.createBundle(
"chrome://messenger/locale/messenger.properties");
let promptString = messengerBundle.formatStringFromName("passwordPrompt",
[
this._userName,
this.displayName
],
2);
if (validURL(serverUrl) && authPrompter.promptPassword(
this.displayName,
promptString,
serverUrl,
authPrompter.SAVE_PASSWORD_PERMANENTLY,
password)) {
return password.value;
}
return "";
},
/**
* This function is used by our testing framework to override the default
* URL's that Nextcloud connects to.
*/
overrideUrls: function nsNc_overrideUrls (aNumUrls, aUrls) {
this._serverUrl = aUrls[0];
},
/**
* logon to the Nextcloud account.
*
* @param successCallback - called if logon is successful
* @param failureCallback - called back on error.
* @param aWithUI if false, logon fails if it would have needed to put up UI.
* This is used for things like displaying account settings,
* where we don't want to pop up the oauth ui.
*/
logon: function nsNc_logon (successCallback, failureCallback, aWithUI) {
this.log.info("Logging in, aWithUI = " + aWithUI);
if (this._password == undefined || !this._password) {
this._password = this.getPassword(this._userName, !aWithUI);
}
if (this._password == "") {
this.log.error('Could not retrieve password');
this._loggedIn = false;
failureCallback();
}
this.log.info("Sending login information...");
let args = "?format=json";
let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
req.open("GET", this._fullUrl + kAuthPath + args, true);
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
req.setRequestHeader("OCS-APIREQUEST", "true");
req.setRequestHeader("Authorization",
"Basic " + btoa(this._userName + ':' + this._password));
req.onerror = function () {
this.log.info("logon failure");
failureCallback();
}.bind(this);
req.onload = function () {
if (req.status >= 200 && req.status < 400) {
try {
this.log.info("auth token response = " + req.responseText);
let docResponse = JSON.parse(req.responseText);
//this.log.info("login response parsed = " + docResponse);
let statuscode = docResponse.ocs.meta.statuscode;
this.log.info("statuscode = " + statuscode);
if (statuscode == 100) {
this._loggedIn = true;
successCallback();
}
else {
this._loggedIn = false;
this._lastErrorText = docResponse.ocs.meta.message;
this._lastErrorStatus = docResponse.ocs.meta.statuscode;
failureCallback();
}
} catch (e) {
this.log.error(e);
this._loggedIn = false;
failureCallback();
}
}
else {
failureCallback();
}
}.bind(this);
req.send();
this.log.info("Login information sent!");
},
/**
* The callback passed to an NextcloudFileUploader, which is fired when
* NextcloudFileUploader exits.
*
* @param aRequestObserver the request observer originally passed to
* uploadFile for the file associated with the
* NextcloudFileUploader
* @param aStatus the result of the upload
* @private
*/
_uploaderCallback: function nsNc__uploaderCallback (aRequestObserver, aStatus) {
aRequestObserver.onStopRequest(null, null, aStatus);
this._uploadingFile = null;
this._uploads.shift();
if (this._uploads.length > 0) {
let nextUpload = this._uploads[0];
this.log.info("chaining upload, file = " + nextUpload.file.leafName);
this._uploadingFile = nextUpload.file;
this._uploader = nextUpload;
try {
this.uploadFile(nextUpload.file, nextUpload.callback);
}
catch (ex) {
nextUpload.callback(nextUpload.requestObserver, Cr.NS_ERROR_FAILURE);
}
}
else
this._uploader = null;
},
/**
* Ensures that we can actually upload the file (we haven't exceeded file size or quota
* limitations), and then attempts to kick-off the upload.
*
* @param aFile the nsILocalFile to upload
* @param aCallback an nsIRequestObserver for monitoring the starting and
* ending states of the upload.
* @private
*/
_finishUpload: function nsNc_finishUpload (aFile, aCallback) {
let exceedsQuota = Ci.nsIMsgCloudFileProvider.uploadWouldExceedQuota;
if ((this._totalStorage > 0) && (aFile.fileSize > this.remainingFileSpace)) {
return aCallback.onStopRequest(null, null, exceedsQuota);
}
delete this._userInfo; // forces us to update userInfo on every upload.
if (!this._uploader) {
this._uploader = new NextcloudFileUploader(this, aFile, this._uploaderCallback
.bind(this), aCallback);
this._uploads.unshift(this._uploader);
}
this._uploadingFile = aFile;
this._uploader.uploadFile();
},
/**
* A private function used to retrieve the profile information for the
* user account associated with the accountKey.
*
* @param successCallback the function called if information retrieval
* is successful
* @param failureCallback the function called if information retrieval fails
* @private
*/
_getUserInfo: function nsNc_getUserInfo (successCallback, failureCallback) {
if (!successCallback) {
successCallback = function () {
this.requestObserver
.onStopRequest(null, null,
this._loggedIn ? Cr.NS_OK : Ci.nsIMsgCloudFileProvider.authErr);
}.bind(this);
}
if (!failureCallback) {
failureCallback = function () {
this.requestObserver
.onStopRequest(null, null, Ci.nsIMsgCloudFileProvider.authErr);
}.bind(this);
}
let body = '<propfind xmlns="DAV:">' +
'<prop>' +
'<quota-available-bytes/>' +
'<quota-used-bytes/>' +
'</prop>' +
'</propfind>';
let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(
Ci.nsIXMLHttpRequest);
req.open("PROPFIND", this._fullUrl + kWebDavPath, true,
this._userName, this._password);
req.onerror = function () {
this.log.info("logon failure");
failureCallback();
}.bind(this);
req.onload = function () {
if (req.status >= 200 && req.status < 400) {
this._fileSpaceUsed = this._getQuota(req.responseXML, "quota-used-bytes");
if (this._fileSpaceUsed < 0) {
this._fileSpaceUsed = -1;
}
let spaceAvailable = this._getQuota(req.responseXML, "quota-available-bytes");
if (spaceAvailable && spaceAvailable > -1) { // positive numbers
this._totalStorage = spaceAvailable + this._fileSpaceUsed;
} else if (!spaceAvailable && spaceAvailable !== 0) { // 0 and unequal 0
this._totalStorage = -1;
} else if (!spaceAvailable || spaceAvailable < 0) { // 0 or negative
this._totalStorage = 0;
}
successCallback();
} else {
failureCallback();
}
}.bind(this);
req.send(body);
},
/**
* Retrieves quota information from a WebDAV response
*
* @param req
* @param davVariable
* @returns {NodeList|number}
* @private
*/
_getQuota: function nsNc_getUserQuota (req, davVariable) {
let quota = req.documentElement.getElementsByTagNameNS("DAV:", davVariable);
return quota && quota.length && Number(quota[0].textContent) || -1;
},
/**
* A private function which makes sure that the folder entered in the
* settings screen exists on the server.
*
* @param callback callback function called with true or false as an argument
* @private
*/
_checkFolderExists: function nsNc_checkFolderExists (callback) {
if (this._storageFolder !== '/') {
let body = '<propfind xmlns="DAV:">' +
'<prop>' +
'<resourcetype />' +
'</prop>' +
'</propfind>';
let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
req.open("PROPFIND", this._fullUrl + kWebDavPath +
("/" + this._storageFolder + "/").replace(/\/+/g, '/'), true, this._userName,
this._password);
req.onerror = function () {
this.log.info("Failed to check if folder exists");
callback(false);
}.bind(this);
req.onload = function () {
if (req.status === 207) {
return callback(true);
}
else {
return callback(false);
}
}.bind(this);
req.send(body);
}
else {
return callback(true);
}
},
/**
* A private function which creates a folder entered
* in the settings screen on the server.
*
* @param callback callback function called with true or false as an argument
* @private
*/
_createFolder: function createFolder(callback) {
if (this._storageFolder !== '/') {
let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
req.open("MKCOL", this._fullUrl + kWebDavPath +
("/" + this._storageFolder + "/").replace(/\/+/g, '/'), true, this._userName,
this._password);
req.onload = function () {
if (req.status === 201) {
return callback(true);
}
else {
return callback(false);
}
}.bind(this);
req.send();
}
else {
return callback(false);
}
},
/**
* A private function that first ensures that the user is logged in, and then
* retrieves the user's profile information.
*
* @param aSuccessCallback the function called on successful information
* retrieval
* @param aFailureCallback the function called on failed information retrieval
* @param aWithUI a boolean for whether or not we should display authorization
* UI if we don't have a valid token anymore, or just fail out.
* @private
*/
_logonAndGetUserInfo: function nsNc_logonAndGetUserInfo (aSuccessCallback,
aFailureCallback,
aWithUI) {
if (!aFailureCallback) {
aFailureCallback = function () {
this.requestObserver.onStopRequest(null, null, Ci.nsIMsgCloudFileProvider.authErr);
}.bind(this);
}
return this.logon(function () {
this._getUserInfo(aSuccessCallback, aFailureCallback);
}.bind(this), aFailureCallback, aWithUI);
},
};
/**
* Uploads a file to the Nextcloud server
*
* @type {{nextcloud: null, file: null, callback: null, request: null, _fileUploadTS: {},
* uploadFile: NextcloudFileUploader.nsNCFU_uploadFile, cancel:
* NextcloudFileUploader.nsNCFU_cancel, _getShareUrl:
* NextcloudFileUploader.nsNCFU__getShareUrl}}
*/
function NextcloudFileUploader (aNextcloud, aFile, aCallback, aRequestObserver) {
this.nextcloud = aNextcloud;
this.log = this.nextcloud.log;
this.log.info("new NextcloudFileUploader file = " + aFile.leafName);
this.file = aFile;
this.callback = aCallback;
this.requestObserver = aRequestObserver;
}
NextcloudFileUploader.prototype = {
nextcloud: null,
file: null,
callback: null,
request: null,
_fileUploadTS: {}, // timestamps to prepend, avoiding filename conflict
/**
* Kicks off the upload request for the file associated with this Uploader.
*/
uploadFile: function nsNCFU_uploadFile () {
this.requestObserver.onStartRequest(null, null);
this._fileUploadTS[this.file.path] = new Date().getTime();
this.log.info(
"Ready to upload file " + wwwFormUrlEncode(this.file.leafName) +
" to folder " + this.nextcloud._storageFolder
);
let folder = ("/" + this.nextcloud._storageFolder + "/").replace(/\/+/g, '/');
let url = this.nextcloud._fullUrl +
kWebDavPath +
folder +
this._fileUploadTS[this.file.path] +
"_" +
this.file.leafName;
let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
Ci.nsIFileInputStream);
fstream.init(this.file, -1, 0, 0);
let bufStream = Cc["@mozilla.org/network/buffered-input-stream;1"].createInstance(
Ci.nsIBufferedInputStream);
bufStream.init(fstream, this.file.fileSize);
bufStream = bufStream.QueryInterface(Ci.nsIInputStream);
let contentLength = fstream.available();
let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(
Ci.nsIXMLHttpRequest);
req.open("PUT", url, true, this._userName, this._password);
req.onerror = function () {
this.log.info("Could not upload file");
if (this.callback) {
this.callback(this.requestObserver,
Ci.nsIMsgCloudFileProvider.uploadErr);
}
}.bind(this);
req.onload = function () {
if (req.status >= 200 && req.status < 400) {
this._getShareUrl(this.file, this.callback);
}
else {
if (this.callback)
this.callback(this.requestObserver,
Ci.nsIMsgCloudFileProvider.uploadErr);
}
}.bind(this);
req.setRequestHeader("Content-Length", contentLength);
req.send(bufStream);
},
/**
* Cancels the upload request for the file associated with this Uploader.
*/
cancel: function nsNCFU_cancel () {
this.callback(this.requestObserver, Ci.nsIMsgCloudFileProvider.uploadCanceled);
if (this.request) {
let req = this.request;
if (req.channel) {
this.log.info("Cancelling channel upload");
delete this.callback;
req.channel.cancel(Cr.NS_BINDING_ABORTED);
}
this.request = null;
}
},
/**
* Private function which attempts to retrieve the sharing URL for the file which was uploaded
* via this Uploader.
*
* @param aFile file which was just uploaded
* @param aCallback an nsIRequestObserver which monitors the start and end states of the URL
* retrieval request.
* @private
*/
_getShareUrl: function nsNCFU_getShareUrl (aFile, aCallback) {
this.file = aFile;
let shareType = 3;
let args = "?format=json";
let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
let path = wwwFormUrlEncode(
("/" + this.nextcloud._storageFolder + "/").replace(/\/+/g, '/') +
this._fileUploadTS[this.file.path] + "_" + this.file.leafName);
let formData = "shareType=" + shareType + "&path=" + path;
// Request a password for the link if it has been defined during setup time
if (this.nextcloud._protectUploads.length) {
formData += "&password=" + wwwFormUrlEncode(this.nextcloud._protectUploads);
}
req.open("POST",
this.nextcloud._fullUrl + kShareApp + args,
true,
this.nextcloud._userName,
this.nextcloud._password
);
req.withCredentials = true;
req.setRequestHeader('Content-Type', "application/x-www-form-urlencoded");
req.setRequestHeader("Content-Length", String(formData.length));
req.setRequestHeader("OCS-APIREQUEST", "true");
req.setRequestHeader("Authorization",
"Basic " + btoa(this.nextcloud._userName + ':' + this.nextcloud._password));
req.onload = function () {
this.log.debug("Raw response: " + req.responseText);
if (req.status >= 200 && req.status < 400) {
try {
let response = JSON.parse(req.responseText);
if (typeof response.ocs.data.url !== "undefined") {
this.nextcloud._urlsForFiles[this.file.path] = response.ocs.data.url;
if (!this.nextcloud._protectUploads.length) {
let arg_separator = this.nextcloud._urlsForFiles[this.file.path].lastIndexOf(
"?") > 0 ? "&" : "/";
this.nextcloud._urlsForFiles[this.file.path] +=
arg_separator + "download";
}
aCallback(this.requestObserver, Cr.NS_OK);
} else {
aCallback(this.requestObserver, Ci.nsIMsgCloudFileProvider.uploadErr);
}
} catch (e) {
this.log.error(e);
aCallback(this.requestObserver, Ci.nsIMsgCloudFileProvider.uploadErr);
}
} else {
this.log.info("Could not retrieve share URL");
aCallback(this.requestObserver, Cr.NS_ERROR_FAILURE);
}
}.bind(this);
req.onerror = function (e) {
this.log.debug("Other error: " + e);
aCallback(this.requestObserver, Cr.NS_ERROR_FAILURE);
}.bind(this);
this.log.debug("Raw formData: " + formData);
req.send(formData);
}
};
const NSGetFactory = XPCOMUtils.generateNSGetFactory([Nextcloud]);