/* * Copyright 2013 Jive Software * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Library of useful functions. * @module jiveutil */ /////////////////////////////////////////////////////////////////////////////////// // private var q = require('q'); var fs = require('fs-extra'); var uuid = require('uuid'); var mustache = require('mustache'); var jive = require('../../api'); var oauth = require('./oauth'); var iterator = require('./iterator'); var crypto = require('crypto'); var constants = require("./constants"); var jiveRequest = require("./request"); var hex_high_10 = { // set the highest bit and clear the next highest '0': '8', '1': '9', '2': 'a', '3': 'b', '4': '8', '5': '9', '6': 'a', '7': 'b', '8': '8', '9': '9', 'a': 'a', 'b': 'b', 'c': '8', 'd': '9', 'e': 'a', 'f': 'b' }; /** * Useful general utility functions. * @returns {String} guid */ exports.guid = function (src) { if (!src) { return uuid.v4(); } else { var sum = crypto.createHash('sha1'); // namespace in raw form. FIXME using ns:URL for now, what should it be? sum.update(new Buffer('a6e4EZ2tEdGAtADAT9QwyA==', 'base64')); // add HTTP path sum.update(src); // get sha1 hash in hex form var u = sum.digest('hex'); // format as UUID (add dashes, version bits and reserved bits) u = u.substr(0, 8) + '-' + // time_low u.substr(8, 4) + '-' + // time_mid '5' + // time_hi_and_version high 4 bits (version) u.substr(13, 3) + '-' + // time_hi_and_version low 4 bits (time high) hex_high_10[u.substr(16, 1)] + u.substr(17, 1) + // cloc_seq_hi_and_reserved u.substr(18, 2) + '-' + // clock_seq_low u.substr(20, 12); // node return u; } }; /** * By default this will build a request of type 'application/json'. Set a Content-Type header * explicitly if its supposed to be a different type. * @param {String} url * @param {String} method * @param {Object} postBody leave null unless PUT or POST * @param {Object} headers leave null or empty [] if no additional headers * @param {Object} requestOptions leave null or empty [] if no additional request optinos * @return {Promise} Promise */ exports.buildRequest = function (url, method, postBody, headers, requestOptions) { return jiveRequest.buildRequest(url, method, postBody, headers, requestOptions ); }; /** * Useful request related utilities * @type module:jiveRequest */ exports.request = jiveRequest; /** * Gets the file size in bytes. * @param filename - the path to the file. * @return {Promise} Promise */ exports.fsGetSize = function (filename) { var deferred = q.defer(); fs.stat(filename, function (err, stats) { if (err) { deferred.reject(err); } else { deferred.resolve(stats.size); } }); return deferred.promise; }; /** * @param path * @return {Promise} Promise */ exports.fsexists = function (path) { var deferred = q.defer(); var method = fs.exists ? fs.exists : require('path').exists; method(path, function (exists) { deferred.resolve(exists); }); return deferred.promise; }; /** * @param source * @param target * @return {Promise} Promise */ exports.fscopy = function (source, target) { jive.logger.debug('Copying', source, 'to', target); var deferred = q.defer(); fs.copy(source, target, function (err) { if (err) { deferred.reject(err); } else { deferred.resolve(); } }); return q.promise; }; var fsSimpleRename = function (source, target) { var deferred = q.defer(); fs.rename(source, target, function (err) { if (err) { deferred.reject(err); } else { deferred.resolve(); } }); return deferred.promise; }; /** * @param source * @param target * @param force * @return {Promise} Promise */ exports.fsrename = function (source, target, force) { jive.logger.debug('Renaming', source, 'to', target); if (!force) { return fsSimpleRename(source, target); } return exports.fsexists(target).then(function (exists) { if (exists) { // delete return exports.fsrmdir(target).then(function () { // do rename jive.logger.debug('Renaming', source, '->', target); return fsSimpleRename(source, target); }); } else { // do rename return fsSimpleRename(source, target); } }); }; /** * @param path * @return {Promise} Promise */ exports.fsmkdir = function (path) { jive.logger.debug('Creating directory', path); var deferred = q.defer(); fs.mkdirs(path, function (err) { if (err) { deferred.reject(err); } else { deferred.resolve(); } }); return deferred.promise; }; /** * @param path * @return {Promise} Promise */ exports.fsread = function (path) { var deferred = q.defer(); fs.readFile(path, function (err, data) { deferred.resolve(data); return data; }); return deferred.promise; }; /** * @param path * @return {Promise} Promise */ exports.fsreadJson = function (path) { return exports.fsread(path).then(function (data) { return JSON.parse(new Buffer(data).toString()); }); }; /** * @param path * @return {Promise} Promise */ exports.fsreaddir = function (path) { var deferred = q.defer(); fs.readdir(path, function (err, items) { deferred.resolve(items); return items; }); return deferred.promise; }; var removeRecursive = function (path, cb) { fs.stat(path, function (err, stats) { if (err) { cb(err, stats); return; } if (stats.isFile()) { fs.unlink(path, function (err) { if (err) { cb(err, null); } else { cb(null, true); } return; }); } else if (stats.isDirectory()) { // A folder may contain files // We need to delete the files first // When all are deleted we could delete the // dir itself fs.readdir(path, function (err, files) { if (err) { cb(err, null); return; } var f_length = files.length; var f_delete_index = 0; // Check and keep track of deleted files // Delete the folder itself when the files are deleted var checkStatus = function () { // We check the status // and count till we r done if (f_length === f_delete_index) { fs.rmdir(path, function (err) { if (err) { cb(err, null); } else { cb(null, true); } }); return true; } return false; }; if (!checkStatus()) { for (var i = 0; i < f_length; i++) { // Create a local scope for filePath // Not really needed, but just good practice // (as strings arn't passed by reference) (function () { var filePath = path + '/' + files[i]; // Add a named function as callback // just to enlighten debugging removeRecursive(filePath, function removeRecursiveCB(err, status) { if (!err) { f_delete_index++; checkStatus(); } else { cb(err, null); return; } }); })() } } }); } }); }; /** * @param path * @return {Promise} Promise */ exports.fsrmdir = function (path) { var deferred = q.defer(); removeRecursive(path, function (err, stats) { if (err) { deferred.reject(err); } else { deferred.resolve(); } }); return deferred.promise; }; /** * @param path * @return {Promise} Promise */ exports.fsisdir = function (path) { var deferred = q.defer(); fs.stat(path, function (err, stats) { if (err) { deferred.reject(err); } else { deferred.resolve(stats.isDirectory()); } }); return deferred.promise; }; /** * @param data * @param path * @return {Promise} Promise */ exports.fswrite = function (data, path) { var deferred = q.defer(); fs.writeFile(path, data, function (err) { if (err) { deferred.reject(err); } else { deferred.resolve(); } }); return deferred.promise; }; var supportedTemplatableExtensions = [ '.json', '.txt', '.text', '.js', '.sql', '.html', '.xml' ]; function getExtension(filename) { if (!filename) { return; } var i = filename.lastIndexOf('.'); return (i < 0) ? '' : filename.substr(i); } /** * @param source * @param target * @param substitutions * @return {Promise} Promise */ exports.fsTemplateCopy = function (source, target, substitutions) { var ext = getExtension(source); if (!ext || supportedTemplatableExtensions.indexOf(ext.toLowerCase()) < 0 || !substitutions) { jive.logger.debug(source + ' is not a supported templatable file type. Doing straight copying', source, '->', target); return exports.fscopy(source, target); } else { jive.logger.debug('Templatized Copying', source, '->', target); return exports.fsread(source).then(function (data) { var raw = data.toString(); var processed = mustache.render(raw, substitutions || {}); return exports.fswrite(processed, target); }); } }; /** * @param data * @param target * @param substitutions * @return {Promise} Promise */ exports.fsTemplateWrite = function (data, target, substitutions) { var ext = getExtension(target); if (!ext || supportedTemplatableExtensions.indexOf(ext.toLowerCase()) < 0) { jive.logger.debug(target + ' is not a supported templatable file type. Doing straight write ->', target); return exports.fswrite(data, target); } else { jive.logger.debug('Templatized write ->', target); var processed = mustache.render(data, substitutions || {}); return exports.fswrite(processed, target); } }; /** * @param source * @param substitutions * @return {Promise} Promise */ exports.fsTemplateRead = function (source, substitutions) { return exports.fsread(source).then(function (data) { var raw = data.toString(); return mustache.render(raw, substitutions || {}); }); }; /** * @param object * @returns {*|string} */ exports.base64Encode = function (object) { return new Buffer(JSON.stringify(object)).toString('base64'); }; /** * @param str * @returns {*|string} */ exports.base64Decode = function (str) { return new Buffer(str, 'base64').toString('ascii'); }; /** * @param auth * @param clientId * @param clientSecret * @param authRequired * @returns {boolean} */ exports.basicAuthorizationHeaderValid = function (auth, clientId, clientSecret, authRequired) { if (!auth && !authRequired) { return true; } if (auth.indexOf('Basic ') == 0) { var authParts = auth.split('Basic '); var p = new Buffer(authParts[1], 'base64').toString(); var pParts = p.split(':'); var authClientId = pParts[0]; var authSecret = pParts[1]; if (authClientId !== clientId || authSecret !== clientSecret) { return false; } } else { return !authRequired; } return true; }; /** * @param auth * @param clientId * @param clientSecret * @param authRequired * @returns {boolean} */ exports.jiveAuthorizationHeaderValid = function (auth, clientId, clientSecret, authRequired) { if (!auth && !authRequired) { return true; } if ( !clientSecret ) { return false; } var authVars = auth.split(' '); var authFlag = authVars[0]; if (authFlag == 'JiveEXTN') { var str = ''; var authParams = authVars[1].split('&'); var signature; authParams.forEach(function (p) { if (p.indexOf('signature') == 0) { signature = p.split("signature=")[1]; } else { if (str.length > 0) { str += '&'; } str += p; } }); //do signature verification var hmac_signature = crypto.createHmac('SHA256', new Buffer(clientSecret, 'base64')).update(str).digest('base64'); return hmac_signature == decodeURIComponent(signature); } else { return true; } }; /** * @param o * @returns {{}} */ exports.sortObject = function (o) { var sorted = {}, key, a = []; for (key in o) { if (o.hasOwnProperty(key)) { a.push(key); } } a.sort(); for (key = 0; key < a.length; key++) { sorted[a[key]] = o[a[key]]; } return sorted; }; /** * @param currentFsItem * @param root * @param targetRoot * @param force * @param processor * @return {Promise} Promise */ exports.recursiveDirectoryProcessor = function (currentFsItem, root, targetRoot, force, processor) { var recurseDirectory = function (directory) { return q.nfcall(fs.readdir, directory).then(function (subItems) { var promises = []; subItems.forEach(function (subItem) { promises.push(exports.recursiveDirectoryProcessor(directory + '/' + subItem, root, targetRoot, force, processor)); }); return q.all(promises); }); }; return q.nfcall(fs.stat, currentFsItem).then(function (stat) { var targetPath = targetRoot + '/' + currentFsItem.substr(root.length + 1, currentFsItem.length); if (stat.isDirectory()) { if (root !== currentFsItem) { return exports.fsexists(targetPath).then(function (exists) { if (root == currentFsItem || (exists && !force)) { return recurseDirectory(currentFsItem); } else { return processor('dir', currentFsItem, targetPath).then(function () { return recurseDirectory(currentFsItem) }); } }); } return recurseDirectory(currentFsItem); } // must be a file return exports.fsexists(targetPath).then(function (exists) { if (!exists || force) { return processor('file', currentFsItem, targetPath) } else { return q.fcall(function () { }); } }); }); }; var copyFileProcessor = function (type, currentFsItem, targetPath, substitutions) { return q.fcall(function () { if (type === 'dir') { return exports.fsmkdir(targetPath); } else { // must be file return exports.fsTemplateCopy(currentFsItem, targetPath, substitutions); } }); }; /** * @param root * @param target * @param force * @param substitutions * @param file * @return {Promise} Promise */ exports.recursiveCopy = function (root, target, force, substitutions, file) { return exports.fsisdir(root).then( function(isDir) { if( !isDir ) { return copyFileProcessor("file", root, target, substitutions); } var substitutionProcessor = function (type, currentFsItem, targetPath) { return copyFileProcessor(type, currentFsItem, targetPath, substitutions); }; return exports.recursiveDirectoryProcessor( root, root, target, force, substitutionProcessor ); }); }; /** * @param root * @param targetZip * @param flatten * @return {Promise} Promise */ exports.zipFolder = function (root, targetZip, flatten) { var fs = require('fs'); var archiver = require('archiver'); var output = fs.createWriteStream(targetZip); var archive = archiver('zip'); archive.on('error', function (err) { throw err; }); archive.pipe(output); return exports.recursiveDirectoryProcessor(root, root, '/tmp', true,function (type, currentFsItem, targetPath, substitutions) { return q.fcall(function () { if (type === 'file') { var target = currentFsItem.substring(currentFsItem.indexOf('/') + 1, currentFsItem.length); if (flatten) { target = require('path').basename(target); } jive.logger.debug('Zipping', currentFsItem, 'to', targetZip, ' : ', target); archive.append(fs.createReadStream(currentFsItem), { name: target }) } }) }).then(function () { archive.finalize(function (err, written) { if (err) { throw err; } jive.logger.info(written + ' total bytes written to extension archive ', targetZip); }); }); }; /** * @type {*} */ exports.oauth = oauth; /** * @type {*} */ exports.iterator = iterator;