js-owncloud-client
Version:
ownCloud client library for JavaScript
845 lines (724 loc) • 23.7 kB
JavaScript
/////////////////////////////
/////// HELPERS ///////
/////////////////////////////
var Promise = require('promise');
var request = require('request');
var parser = require('./xmlParser.js');
var parser2 = require('xml-js');
var fs = require('fs');
var utf8 = require('utf8');
var fileInfo = require('./fileInfo.js');
/**
* @class helpers
* @classdesc
* <b><i>This is a class for helper functions, dont mess with this until sure!</i></b><br><br>
*
* @author Noveen Sachdeva
* @version 1.0.0
*/
function helpers() {
this.OCS_BASEPATH = 'ocs/v1.php/';
this.OCS_SERVICE_SHARE = 'apps/files_sharing/api/v1';
this.OCS_SERVICE_PRIVATEDATA = 'privatedata';
this.OCS_SERVICE_CLOUD = 'cloud';
// constants from lib/public/constants.php
this.OCS_PERMISSION_READ = 1;
this.OCS_PERMISSION_UPDATE = 2;
this.OCS_PERMISSION_CREATE = 4;
this.OCS_PERMISSION_DELETE = 8;
this.OCS_PERMISSION_SHARE = 16;
this.OCS_PERMISSION_ALL = 31;
// constants from lib/public/share.php
this.OCS_SHARE_TYPE_USER = 0;
this.OCS_SHARE_TYPE_GROUP = 1;
this.OCS_SHARE_TYPE_LINK = 3;
this.OCS_SHARE_TYPE_REMOTE = 6;
this.instance = null;
this._username = null;
this._password = null;
this._version = null;
this._capabilities = null;
}
/**
* sets the OC instance
* @param {string} instance instance to be used for communication
*/
helpers.prototype.setInstance = function(instance) {
this.instance = instance;
};
/**
* sets the username
* @param {string} username username to be used for logging in
*/
helpers.prototype.setUsername = function(username) {
this._username = username;
var instancePath = '/' + this.instance.split('/').slice(3).join('/');
this._davPath = instancePath + 'remote.php/dav/files/' +
encodeURIComponent(this._encodeString(this._username));
this._webdavUrl = this.instance + 'remote.php/webdav';
};
/**
* sets the password
* @param {string} password password to be used for logging in
*/
helpers.prototype.setPassword = function(password) {
this._password = password;
};
/**
* gets the OC version
* @returns {string} OC version
*/
helpers.prototype.getVersion = function() {
return this._version;
};
/**
* Gets all capabilities of the logged in user
* @returns {object} all capabilities
*/
helpers.prototype.getCapabilities = function() {
return this._capabilities;
};
/**
* Updates the capabilities of user logging in.
* @returns {Promise.<capabilities>} object: all capabilities
* @returns {Promise.<error>} string: error message, if any.
*/
helpers.prototype._updateCapabilities = function() {
var self = this;
return new Promise((resolve, reject) => {
self._makeOCSrequest('GET', self.OCS_SERVICE_CLOUD, "capabilities")
.then(data => {
var body = parser.xml2js(data.body).ocs.data;
self._capabilities = body.capabilities;
self._version = body.version.string + '-' + body.version.edition;
resolve(self._capabilities);
}).catch(error => {
reject(error);
});
});
};
/**
* Makes an OCS API request.
* @param {string} method method of request (GET, POST etc.)
* @param {string} service service (cloud, privatedata etc.)
* @param {string} action action (apps?filter=enabled, capabilities etc.)
* @param {string} [data] formData for POST and PUT requests
* @returns {Promise.<data>} object: {response: response, body: request body}
* @returns {Promise.<error>} string: error message, if any.
*/
helpers.prototype._makeOCSrequest = function(method, service, action, data) {
var self = this;
var err = null;
if (!this.instance) {
err = "Please specify a server URL first";
}
if (!this._username || !this._password) {
err = "Please specify a username AND password first.";
}
// Set the headers
var headers = {
authorization: "Basic " + new Buffer(this._username + ":" + this._password).toString('base64'),
'OCS-APIREQUEST': true
};
var slash = '';
if (service) {
slash = '/';
}
var path = this.OCS_BASEPATH + service + slash + action;
//Configure the request
var options = {
url: this.instance + path,
method: method,
headers: headers,
};
if (method === 'PUT' || method === 'DELETE') {
options.headers['content-type'] = 'application/x-www-form-urlencoded';
options.form = data;
} else {
options.headers['content-type'] = 'multipart/form-data';
options.formData = data;
}
return new Promise((resolve, reject) => {
// Start the request
request(options, function(error, response, body) {
if (err) {
reject(err);
}
var validXml = self._isValidXML(body);
var validJson = self._isValidJSON(body);
if (error) {
error = "Please provide a valid owncloud instance";
}
if (validJson) {
body = JSON.parse(body);
if ("message" in body) {
error = body.message;
} else {
error = "Please provide a valid owncloud instance";
}
}
if (!error && !validXml) {
error = "Please provide a valid owncloud instance";
body = null;
}
if (!error) {
var tree = parser.xml2js(body);
error = self._checkOCSstatus(tree);
}
if (error) {
reject(error);
} else {
resolve({
response: response,
body: body
});
}
});
});
};
/**
* Makes a DAV request.
* @param {string} method method of request (PROPFIND, MKCOL etc.)
* @param {string} path path of file/folder
* @param {object} [headerData] headerData to be set before the request
* @param {object} [body] body of request
* @returns {Promise.<body>} string: parsed response
* @returns {Promise.<error>} string: error message, if any.
*/
helpers.prototype._makeDAVrequest = function(method, path, headerData, body) {
var self = this;
var err = null;
if (!this.instance) {
err = "Please specify a server URL first";
}
if (!this._username || !this._password) {
err = "Please specify a username AND password first.";
}
path = self._normalizePath(path);
path = encodeURIComponent(path);
path = path.split('%2F').join('/'); // '/' => %2F
var url = self._webdavUrl + self._encodeString(path);
// Set the headers
var headers = {
authorization: "Basic " + new Buffer(this._username + ":" + this._password).toString('base64')
};
//Configure the request
var options = {
url: url,
method: method,
headers: headers
};
for (var key in headerData) {
options.headers[key] = headerData[key];
}
options.body = body;
return new Promise((resolve, reject) => {
if (err) {
reject(err);
}
// Start the request
request(options, function(error, response, body) {
if (error) {
reject(error);
}
if ([200, 207].indexOf(response.statusCode) > -1) {
self._parseDAVresponse(resolve, reject, body);
} else if ([201, 204].indexOf(response.statusCode) > -1) {
resolve(true);
} else {
var err = self._parseDAVerror(body);
reject(err);
}
});
});
};
/**
* Parses a DAV response.
*/
helpers.prototype._parseDAVresponse = function(resolve, reject, body) {
var XMLns = this._getXMLns(body);
var tree = parser.xml2js(body, XMLns)['{DAV:}multistatus']['{DAV:}response'];
var items = [];
if (tree.constructor !== Array) {
tree = [tree];
}
for (var item = 0; item < tree.length; item++) {
items.push(this._parseDAVelement(tree[item]));
}
resolve(items);
};
/**
* Parses a DAV response element.
*/
helpers.prototype._parseDAVelement = function(item) {
var name = item['{DAV:}href'];
var attrs = item['{DAV:}propstat']['{DAV:}prop'];
var fileType = name.substr(-1) === '/' ? 'dir' : 'file';
var start = 0;
name = name.split('/');
for (var i = 0; i < name.length; i++) {
if (name[i] === 'webdav') {
start = i;
break;
}
}
name.splice(0, start + 1);
name = '/' + name.join('/');
name = decodeURIComponent(name);
name = utf8.encode(name);
name = utf8.decode(name);
var file = new fileInfo(name, fileType, attrs);
return file;
};
/**
* performs a simple GET request
* @param {string} url url to perform GET on
* @returns {Promise.<data>} object: {response: response, body: request body}
* @returns {Promise.<error>} string: error message, if any.
*/
helpers.prototype._get = function(url) {
var err = null;
if (!this.instance) {
err = "Please specify a server URL first";
}
if (!this._username || !this._password) {
err = "Please specify a username AND password first.";
}
var headers = {
authorization: "Basic " + new Buffer(this._username + ":" + this._password).toString('base64'),
'Content-Type': 'application/x-www-form-urlencoded'
};
//Configure the request
var options = {
url: url,
method: 'GET',
headers: headers
};
return new Promise((resolve, reject) => {
if (err) {
reject(err);
return;
}
// Start the request
request(options, function(error, response, body) {
if (error) {
reject(error);
} else {
resolve({
response: response,
body: body
});
}
});
});
};
/**
* performs a GET request and writes the output into a file
* @param {string} url url to perform GET on
* @param {string} fileName name of the file to write the response into
* @returns {Promise.<data>} object: {response: response, body: request body}
* @returns {Promise.<error>} string: error message, if any.
*/
helpers.prototype._writeData = function(url, fileName) {
var self = this;
var err = null;
if (!this.instance) {
err = "Please specify a server URL first";
}
if (!this._username || !this._password) {
err = "Please specify a username AND password first.";
}
var headers = {
authorization: "Basic " + new Buffer(this._username + ":" + this._password).toString('base64'),
'Content-Type': 'application/octet-stream'
};
//Configure the request
var options = {
url: url,
method: 'GET',
headers: headers
};
return new Promise((resolve, reject) => {
if (err) {
reject(err);
return;
}
var isPossible = 1;
try {
fs.closeSync(fs.openSync(fileName, 'w'));
} catch (error) {
isPossible = 0;
reject(error.message);
return;
}
// Start the request
/* jshint unused : false */
request(options, function(err2, response, body) {
if (err2) {
reject(err2);
}
if (response.statusCode === 200 && isPossible === 1 && body.split('\n')[0] !== "<!DOCTYPE html>") {
resolve(true);
} else {
try {
var err = self._parseDAVerror(body);
reject(err);
} catch (error) {
if (body.search("<li class=\"error\">") > -1) {
reject('specified file/folder could not be located');
} else {
reject("Current user is not logged in");
}
}
}
})
.on('error', function(error) {
reject(error);
return;
})
.pipe(fs.createWriteStream(fileName));
/* jshint unused : true */
});
};
/**
* performs a PUT request from a file
* @param {string} path path where to put at OC instance
* @param {string} localPath path of the file to read the data from
* @param {object} headers extra headers to add for the PUT request
* @returns {Promise.<data>} object: {response: response, body: request body}
* @returns {Promise.<error>} string: error message, if any.
*/
helpers.prototype._readFile = function(path, localPath, headers) {
var self = this;
return new Promise((resolve, reject) => {
try {
path = self._normalizePath(path);
path = encodeURIComponent(path);
path = path.split('%2F').join('/'); // '/' => %2F
var url = self._webdavUrl + self._encodeString(path);
/* jshint unused : false */
fs.createReadStream(localPath)
.pipe(request.put({
url: url,
headers: headers
}, function(error, response, body) {
if (response.statusCode >= 400) {
var parsedError = self._parseDAVerror(body);
parsedError = parsedError || 'not allowed';
reject(parsedError);
} else {
resolve(true);
}
}));
/* jshint unused : true */
} catch (err) {
reject(err);
}
});
};
/**
* checks whether a path's extension is ".ZIP"
* @param {string} path path to check
* @return {boolean} true if extension is ".ZIP"
*/
helpers.prototype._checkExtensionZip = function(path) {
var extension = path.slice(-4);
if (extension !== '.zip') {
path += '.zip';
}
return path;
};
/**
* Parses a DAV response error.
*/
helpers.prototype._parseDAVerror = function(body) {
var tree = parser.xml2js(body);
if (tree['d:error']['s:message']) {
return tree['d:error']['s:message'];
}
return tree;
};
/**
* Checks whether a response body is valid XML
* @param {string} body the response to be checked
* @return {Boolean} true if valid XML, else false
*/
helpers.prototype._isValidXML = function(body) {
try {
parser.xml2js(body);
} catch (e) {
return false;
}
return true;
};
/**
* Checks whether a response body is valid JSON
* @param {string} body the response to be checked
* @return {Boolean} true if valid JSON, else false
*/
helpers.prototype._isValidJSON = function(body) {
try {
JSON.parse(body);
} catch (e) {
return false;
}
return true;
};
/**
* Makes sure path starts with a '/'
* @param {string} path to the remote file share
* @returns {string} normalized path
*/
helpers.prototype._normalizePath = function(path) {
if (!path) {
path = '';
}
if (path.length === 0) {
return '/';
}
if (path[0] !== '/') {
path = '/' + path;
}
return path;
};
/**
* Checks the status code of an OCS request
* @param {object} json parsed response
* @param {array} [acceptedCodes = [100] ] array containing accepted codes
* @returns {string} error message or NULL
*/
helpers.prototype._checkOCSstatus = function(json, acceptedCodes) {
if (!acceptedCodes) {
acceptedCodes = [100];
}
var meta;
if (json.ocs) {
meta = json.ocs.meta;
}
var ret;
if (meta && acceptedCodes.indexOf(parseInt(meta.statuscode)) === -1) {
ret = meta.message;
if (Object.keys(meta.message).length === 0) {
// no error message returned, return the whole message
ret = json;
}
}
return ret;
};
/**
* Returns the status code of the xml response
* @param {object} json parsed response
* @return {integer} status-code
*/
helpers.prototype._checkOCSstatusCode = function(json) {
if (json.ocs) {
var meta = json.ocs.meta;
return parseInt(meta.statuscode);
}
return null;
};
/**
* Encodes the string according to UTF-8 standards
* @param {string} path path to be encoded
* @returns {string} encoded path
*/
helpers.prototype._encodeString = function(path) {
return utf8.encode(path);
};
/**
* converts all of object's "true" or "false" entries to booleans
* @param {object} object object to be typcasted
* @return {object} typecasted object
*/
helpers.prototype._convertObjectToBool = function(object) {
if (typeof(object) !== "object") {
return object;
}
for (var key in object) {
if (object[key] === "true") {
object[key] = true;
}
if (object[key] === "false") {
object[key] = false;
}
}
return object;
};
/**
* Handles Provisionging API boolean response
*/
helpers.prototype._OCSuserResponseHandler = function(data, resolve, reject) {
var tree = parser.xml2js(data.body);
var statuscode = parseInt(this._checkOCSstatusCode(tree));
if (statuscode === 999) {
reject("Provisioning API has been disabled at your instance");
}
resolve(true);
};
/**
* Recursive listing of all files and sub-folders
* @param {string} path local path to be recursively listed
* @param {string} pathToStore path to be stored at the OC instance
* @returns {array} array of objects : {
* path: path of the folder to be stored
* at the OC instance,
* localPath: localPath of the folder,
* files: contents of the folder
* }
*/
helpers.prototype._getAllFileInfo = function(path, pathToStore) {
function getAllFileInfo(path, pathToStore, localPath) {
var fl = 0;
var baseAddr = pathToStore;
for (var j = 0; j < filesToPut.length; j++) {
if (filesToPut[j].path === baseAddr) {
fl = 1;
break;
}
}
if (fl === 0) {
var count = filesToPut.length;
filesToPut[count] = {};
filesToPut[count].path = baseAddr;
filesToPut[count].localPath = localPath; ////////
filesToPut[count].files = [];
count++;
}
if (path.slice(-1) !== '/') {
path += '/';
}
var files = fs.readdirSync(path);
for (var i = 0; i < files.length; i++) {
var file = files[i];
var stat = fs.statSync(path + file);
if (stat.isDirectory()) {
getAllFileInfo(path + file + '/', pathToStore + file + '/', localPath + file + '/');
} else {
baseAddr = pathToStore;
fl = 0;
for (j = 0; j < filesToPut.length; j++) {
if (filesToPut[j].path === baseAddr) {
filesToPut[j].files.push(file);
fl = 1;
break;
}
}
if (fl === 0) {
var count2 = filesToPut.length;
filesToPut[count2] = {};
filesToPut[count2].path = baseAddr;
filesToPut[count2].localPath = localPath; ////////
filesToPut[count2].files = [file];
count2++;
}
}
}
}
var filesToPut = [];
var targetPath = pathToStore;
var localPath = path;
if (!targetPath || targetPath === '') {
targetPath = '/';
}
targetPath = this._normalizePath(targetPath);
var slash = '';
if (targetPath.slice(-1) !== '/') {
targetPath += '/';
}
if (localPath.slice(-1) !== '/') {
localPath += '/';
}
if (targetPath.slice(0, 1) !== '/') {
slash = '/';
}
var pathToAdd = localPath.split('/');
pathToAdd = pathToAdd.filter(function(n) {
return n !== '';
});
var slash2 = '/';
if (pathToAdd[pathToAdd.length - 1] === '.') {
pathToAdd[pathToAdd.length - 1] = '';
slash = '';
slash2 = '';
}
pathToAdd = targetPath + slash + pathToAdd[pathToAdd.length - 1] + slash2;
getAllFileInfo(path, pathToAdd, localPath);
return filesToPut;
};
/**
* gets the MTime of a file/folder
* @param {string} path path of the file/folder
* @returns {Date} MTime
*/
helpers.prototype._getMTime = function(path) {
var info = fs.statSync(path);
return info.mtime;
};
/**
* gets the size of a file/folder
* @param {string} path path of the file/folder
* @returns {integer} size of folder
*/
helpers.prototype._getFileSize = function(path) {
var info = fs.statSync(path);
return parseInt(info.size);
};
/**
* performs a PUT request from a file
* @param {string} source source path of the file to move/copy
* @param {string} target target path of the file to move/copy
* @param {object} headers extra headers to add for the PUT request
* @returns {Promise.<status>} boolean: whether the operation was successful
* @returns {Promise.<error>} string: error message, if any.
*/
helpers.prototype._webdavMoveCopy = function(source, target, method) {
var self = this;
return new Promise((resolve, reject) => {
if (method !== "MOVE" && method !== "COPY") {
reject('Please specify a valid method');
return;
}
source = self._normalizePath(source);
target = self._normalizePath(target);
target = encodeURIComponent(target);
target = target.split('%2F').join('/');
var headers = {
'Destination': self._webdavUrl + target
};
self._makeDAVrequest(method, source, headers).then(data => {
resolve(data);
}).catch(error => {
reject(error);
});
});
};
/**
* gets the fileName from a path
* @param {string} path path to get fileName from
* @return {string} fileName
*/
helpers.prototype._getFileName = function(path) {
var pathSplit = path.split('/');
pathSplit = pathSplit.filter(function(n) {
return n !== '';
});
return pathSplit[pathSplit.length - 1];
};
/**
* returns all xml namespaces in an object
* @param {string} xml xml which has namespace
* @return {object} object with namespace
*/
helpers.prototype._getXMLns = function (xml) {
var tree = parser2.xml2js(xml, {
compact: true
});
var xmlns = tree['d:multistatus']._attributes;
var replacedXMLns = {};
for (var ns in xmlns) {
var changedKey = ns.split(':')[1];
replacedXMLns[changedKey] = xmlns[ns];
}
return replacedXMLns;
};
module.exports = helpers;