base-common.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. // Exported namespace
  2. FS = {};
  3. // namespace for adapters; XXX should this be added by cfs-storage-adapter pkg instead?
  4. FS.Store = {
  5. GridFS: function () {
  6. throw new Error('To use FS.Store.GridFS, you must add the "wekan-cfs-gridfs" package.');
  7. },
  8. FileSystem: function () {
  9. throw new Error('To use FS.Store.FileSystem, you must add the "wekan-cfs-filesystem" package.');
  10. },
  11. S3: function () {
  12. throw new Error('To use FS.Store.S3, you must add the "wekan-cfs-s3" package.');
  13. },
  14. WABS: function () {
  15. throw new Error('To use FS.Store.WABS, you must add the "wekan-cfs-wabs" package.');
  16. },
  17. Dropbox: function () {
  18. throw new Error('To use FS.Store.Dropbox, you must add the "wekan-cfs-dropbox" package.');
  19. }
  20. };
  21. // namespace for access points
  22. FS.AccessPoint = {};
  23. // namespace for utillities
  24. FS.Utility = {};
  25. // A general place for any package to store global config settings
  26. FS.config = {};
  27. // An internal collection reference
  28. FS._collections = {};
  29. // Test scope
  30. _Utility = {};
  31. // #############################################################################
  32. //
  33. // HELPERS
  34. //
  35. // #############################################################################
  36. /** @method _Utility.defaultZero
  37. * @private
  38. * @param {Any} val Returns number or 0 if value is a falsy
  39. */
  40. _Utility.defaultZero = function(val) {
  41. return +(val || 0);
  42. };
  43. /**
  44. * @method FS.Utility.cloneFileRecord
  45. * @public
  46. * @param {FS.File|FS.Collection filerecord} rec
  47. * @param {Object} [options]
  48. * @param {Boolean} [options.full=false] Set `true` to prevent certain properties from being omitted from the clone.
  49. * @returns {Object} Cloned filerecord
  50. *
  51. * Makes a shallow clone of `rec`, filtering out some properties that might be present if
  52. * it's an FS.File instance, but which we never want to be part of the stored
  53. * filerecord.
  54. *
  55. * This is a blacklist clone rather than a whitelist because we want the user to be able
  56. * to specify whatever additional properties they wish.
  57. *
  58. * In general, we expect the following whitelist properties used by the internal and
  59. * external APIs:
  60. *
  61. * _id, name, size, type, chunkCount, chunkSize, chunkSum, copies, createdAt, updatedAt, uploadedAt
  62. *
  63. * Those properties, and any additional properties added by the user, should be present
  64. * in the returned object, which is suitable for inserting into the backing collection or
  65. * extending an FS.File instance.
  66. *
  67. */
  68. FS.Utility.cloneFileRecord = function(rec, options) {
  69. options = options || {};
  70. var result = {};
  71. // We use this method for two purposes. If using it to clone one FS.File into another, then
  72. // we want a full clone. But if using it to get a filerecord object for inserting into the
  73. // internal collection, then there are certain properties we want to omit so that they aren't
  74. // stored in the collection.
  75. var omit = options.full ? [] : ['collectionName', 'collection', 'data', 'createdByTransform'];
  76. for (var prop in rec) {
  77. if (rec.hasOwnProperty(prop) && !_.contains(omit, prop)) {
  78. result[prop] = rec[prop];
  79. }
  80. }
  81. return result;
  82. };
  83. /**
  84. * @method FS.Utility.defaultCallback
  85. * @public
  86. * @param {Error} [err]
  87. * @returns {undefined}
  88. *
  89. * Can be used as a default callback for client methods that need a callback.
  90. * Simply throws the provided error if there is one.
  91. */
  92. FS.Utility.defaultCallback = function defaultCallback(err) {
  93. if (err) {
  94. // Show gentle error if Meteor error
  95. if (err instanceof Meteor.Error) {
  96. console.error(err.message);
  97. } else {
  98. // Normal error, just throw error
  99. throw err;
  100. }
  101. }
  102. };
  103. /**
  104. * @method FS.Utility.defaultCallback
  105. * @public
  106. * @param {Function} [f] A callback function, if you have one. Can be undefined or null.
  107. * @param {Meteor.Error | Error | String} [err] Error or error message (string)
  108. * @returns {Any} the callback result if any
  109. *
  110. * Handle Error, creates an Error instance with the given text. If callback is
  111. * a function, passes the error to that function. Otherwise throws it. Useful
  112. * for dealing with errors in methods that optionally accept a callback.
  113. */
  114. FS.Utility.handleError = function(f, err, result) {
  115. // Set callback
  116. var callback = (typeof f === 'function')? f : FS.Utility.defaultCallback;
  117. // Set the err
  118. var error = (err === ''+err)? new Error(err) : err;
  119. // callback
  120. return callback(error, result);
  121. }
  122. /**
  123. * @method FS.Utility.noop
  124. * @public
  125. * Use this to hand a no operation / empty function
  126. */
  127. FS.Utility.noop = function() {};
  128. /**
  129. * @method validateAction
  130. * @private
  131. * @param {Object} validators - The validators object to use, with `deny` and `allow` properties.
  132. * @param {FS.File} fileObj - Mounted or mountable file object to be passed to validators.
  133. * @param {String} userId - The ID of the user who is attempting the action.
  134. * @returns {undefined}
  135. *
  136. * Throws a "400-Bad Request" Meteor error if the file is not mounted or
  137. * a "400-Access denied" Meteor error if the action is not allowed.
  138. */
  139. FS.Utility.validateAction = function validateAction(validators, fileObj, userId) {
  140. var denyValidators = validators.deny;
  141. var allowValidators = validators.allow;
  142. // If insecure package is used and there are no validators defined,
  143. // allow the action.
  144. if (typeof Package === 'object'
  145. && Package.insecure
  146. && denyValidators.length + allowValidators.length === 0) {
  147. return;
  148. }
  149. // If already mounted, validators should receive a fileObj
  150. // that is fully populated
  151. if (fileObj.isMounted()) {
  152. fileObj.getFileRecord();
  153. }
  154. // Any deny returns true means denied.
  155. if (_.any(denyValidators, function(validator) {
  156. return validator(userId, fileObj);
  157. })) {
  158. throw new Meteor.Error(403, "Access denied");
  159. }
  160. // Any allow returns true means proceed. Throw error if they all fail.
  161. if (_.all(allowValidators, function(validator) {
  162. return !validator(userId, fileObj);
  163. })) {
  164. throw new Meteor.Error(403, "Access denied");
  165. }
  166. };
  167. /**
  168. * @method FS.Utility.getFileName
  169. * @private
  170. * @param {String} name - A filename, filepath, or URL
  171. * @returns {String} The filename without the URL, filepath, or query string
  172. */
  173. FS.Utility.getFileName = function utilGetFileName(name) {
  174. // in case it's a URL, strip off potential query string
  175. // should have no effect on filepath
  176. name = name.split('?')[0];
  177. // strip off beginning path or url
  178. var lastSlash = name.lastIndexOf('/');
  179. if (lastSlash !== -1) {
  180. name = name.slice(lastSlash + 1);
  181. }
  182. return name;
  183. };
  184. /**
  185. * @method FS.Utility.getFileExtension
  186. * @public
  187. * @param {String} name - A filename, filepath, or URL that may or may not have an extension.
  188. * @returns {String} The extension or an empty string if no extension found.
  189. */
  190. FS.Utility.getFileExtension = function utilGetFileExtension(name) {
  191. name = FS.Utility.getFileName(name);
  192. // Seekout the last '.' if found
  193. var found = name.lastIndexOf('.');
  194. // Return the extension if found else ''
  195. // If found is -1, we return '' because there is no extension
  196. // If found is 0, we return '' because it's a hidden file
  197. return (found > 0 ? name.slice(found + 1).toLowerCase() : '');
  198. };
  199. /**
  200. * @method FS.Utility.setFileExtension
  201. * @public
  202. * @param {String} name - A filename that may or may not already have an extension.
  203. * @param {String} ext - An extension without leading period, which you want to be the new extension on `name`.
  204. * @returns {String} The filename with changed extension.
  205. */
  206. FS.Utility.setFileExtension = function utilSetFileExtension(name, ext) {
  207. if (!name || !name.length) {
  208. return name;
  209. }
  210. var currentExt = FS.Utility.getFileExtension(name);
  211. if (currentExt.length) {
  212. name = name.slice(0, currentExt.length * -1) + ext;
  213. } else {
  214. name = name + '.' + ext;
  215. }
  216. return name;
  217. };
  218. /*
  219. * Borrowed these from http package
  220. */
  221. FS.Utility.encodeParams = function encodeParams(params) {
  222. var buf = [];
  223. _.each(params, function(value, key) {
  224. if (buf.length)
  225. buf.push('&');
  226. buf.push(FS.Utility.encodeString(key), '=', FS.Utility.encodeString(value));
  227. });
  228. return buf.join('').replace(/%20/g, '+');
  229. };
  230. FS.Utility.encodeString = function encodeString(str) {
  231. return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
  232. };
  233. /*
  234. * btoa and atob shims for client and server
  235. */
  236. FS.Utility._btoa = function _fsUtility_btoa(str) {
  237. var buffer;
  238. if (str instanceof Buffer) {
  239. buffer = str;
  240. } else {
  241. buffer = new Buffer(str.toString(), 'binary');
  242. }
  243. return buffer.toString('base64');
  244. };
  245. FS.Utility.btoa = function fsUtility_btoa(str) {
  246. if (typeof btoa === 'function') {
  247. // Client
  248. return btoa(str);
  249. } else if (typeof Buffer !== 'undefined') {
  250. // Server
  251. return FS.Utility._btoa(str);
  252. } else {
  253. throw new Error('FS.Utility.btoa: Cannot base64 encode on your system');
  254. }
  255. };
  256. FS.Utility._atob = function _fsUtility_atob(str) {
  257. return new Buffer(str, 'base64').toString('binary');
  258. };
  259. FS.Utility.atob = function fsUtility_atob(str) {
  260. if (typeof atob === 'function') {
  261. // Client
  262. return atob(str);
  263. } else if (typeof Buffer !== 'undefined') {
  264. // Server
  265. return FS.Utility._atob(str);
  266. } else {
  267. throw new Error('FS.Utility.atob: Cannot base64 encode on your system');
  268. }
  269. };
  270. // Api wrap for 3party libs like underscore
  271. FS.Utility.extend = _.extend;
  272. FS.Utility.each = _.each;
  273. FS.Utility.isEmpty = _.isEmpty;
  274. FS.Utility.indexOf = _.indexOf;
  275. FS.Utility.isArray = _.isArray;
  276. FS.Utility.map = _.map;
  277. FS.Utility.once = _.once;
  278. FS.Utility.include = _.include;
  279. FS.Utility.size = _.size;