Fixed Non-ASCII attachment filename will crash when downloading.

Thanks to xet7 !

Fixes #2759
This commit is contained in:
Lauri Ojansivu 2021-04-29 13:26:49 +03:00
parent 843ff8eaaa
commit c2da477735
277 changed files with 30568 additions and 52 deletions

View file

@ -0,0 +1,5 @@
language: node_js
node_js:
- "0.10"
before_install:
- "curl -L http://git.io/s0Zu-w | /bin/sh"

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013-2015 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,11 @@
wekan-cfs-base-package
=========================
This is a Meteor package used by
[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS).
You don't need to manually add this package to your app. It is added when you
add the `wekan-cfs-standard-packages` package.
This package provides the `FS` namespace and helper methods used by many
CollectionFS packages.

View file

@ -0,0 +1,213 @@
## cfs-base-package Public API ##
CollectionFS, Base package
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
#############################################################################
HELPERS
#############################################################################
-
### <a name="FS.Utility.cloneFileRecord"></a>*fsUtility*.cloneFileRecord(rec, [options])&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __cloneFileRecord__ is defined in `FS.Utility`*
__Arguments__
* __rec__ *{[FS.File](#FS.File)|[FS.Collection filerecord](#FS.Collection filerecord)}*
* __options__ *{Object}* (Optional)
* __full__ *{Boolean}* (Optional, Default = false)
Set `true` to prevent certain properties from being omitted from the clone.
__Returns__ *{Object}*
Cloned filerecord
Makes a shallow clone of `rec`, filtering out some properties that might be present if
it's an FS.File instance, but which we never want to be part of the stored
filerecord.
This is a blacklist clone rather than a whitelist because we want the user to be able
to specify whatever additional properties they wish.
In general, we expect the following whitelist properties used by the internal and
external APIs:
_id, name, size, type, chunkCount, chunkSize, chunkSum, copies, createdAt, updatedAt, uploadedAt
Those properties, and any additional properties added by the user, should be present
in the returned object, which is suitable for inserting into the backing collection or
extending an FS.File instance.
> ```FS.Utility.cloneFileRecord = function(rec, options) { ...``` [base-common.js:71](base-common.js#L71)
-
### <a name="FS.Utility.defaultCallback"></a>*fsUtility*.defaultCallback([err])&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __defaultCallback__ is defined in `FS.Utility`*
__Arguments__
* __err__ *{[Error](#Error)}* (Optional)
__Returns__ *{undefined}*
Can be used as a default callback for client methods that need a callback.
Simply throws the provided error if there is one.
> ```FS.Utility.defaultCallback = function defaultCallback(err) { ...``` [base-common.js:96](base-common.js#L96)
-
### <a name="FS.Utility.defaultCallback"></a>*fsUtility*.defaultCallback([f], [err])&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __defaultCallback__ is defined in `FS.Utility`*
__Arguments__
* __f__ *{Function}* (Optional)
A callback function, if you have one. Can be undefined or null.
* __err__ *{[Meteor.Error ](#Meteor.Error )|[ Error ](# Error )|[ String](# String)}* (Optional)
Error or error message (string)
__Returns__ *{Any}*
the callback result if any
Handle Error, creates an Error instance with the given text. If callback is
a function, passes the error to that function. Otherwise throws it. Useful
for dealing with errors in methods that optionally accept a callback.
> ```FS.Utility.handleError = function(f, err, result) { ...``` [base-common.js:120](base-common.js#L120)
-
### <a name="FS.Utility.noop"></a>*fsUtility*.noop()&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __noop__ is defined in `FS.Utility`*
Use this to hand a no operation / empty function
> ```FS.Utility.noop = function() { ...``` [base-common.js:134](base-common.js#L134)
-
### <a name="FS.Utility.getFileExtension"></a>*fsUtility*.getFileExtension(name)&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __getFileExtension__ is defined in `FS.Utility`*
__Arguments__
* __name__ *{String}*
A filename, filepath, or URL that may or may not have an extension.
__Returns__ *{String}*
The extension or an empty string if no extension found.
> ```FS.Utility.getFileExtension = function utilGetFileExtension(name) { ...``` [base-common.js:205](base-common.js#L205)
-
### <a name="FS.Utility.setFileExtension"></a>*fsUtility*.setFileExtension(name, ext)&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __setFileExtension__ is defined in `FS.Utility`*
__Arguments__
* __name__ *{String}*
A filename that may or may not already have an extension.
* __ext__ *{String}*
An extension without leading period, which you want to be the new extension on `name`.
__Returns__ *{String}*
The filename with changed extension.
> ```FS.Utility.setFileExtension = function utilSetFileExtension(name, ext) { ...``` [base-common.js:222](base-common.js#L222)
-
### <a name="FS.Utility.binaryToBuffer"></a>*fsUtility*.binaryToBuffer(data)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __binaryToBuffer__ is defined in `FS.Utility`*
__Arguments__
* __data__ *{Uint8Array}*
__Returns__ *{Buffer}*
Converts a Uint8Array instance to a Node Buffer instance
> ```FS.Utility.binaryToBuffer = function(data) { ...``` [base-server.js:9](base-server.js#L9)
-
### <a name="FS.Utility.bufferToBinary"></a>*fsUtility*.bufferToBinary(data)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __bufferToBinary__ is defined in `FS.Utility`*
__Arguments__
* __data__ *{Buffer}*
__Returns__ *{Uint8Array}*
Converts a Node Buffer instance to a Uint8Array instance
> ```FS.Utility.bufferToBinary = function(data) { ...``` [base-server.js:26](base-server.js#L26)
-
### <a name="FS.Utility.eachFile"></a>*fsUtility*.eachFile(e, f)&nbsp;&nbsp;<sub><i>Client</i></sub> ###
*This method __eachFile__ is defined in `FS.Utility`*
__Arguments__
* __e__ *{[Event](#Event)}*
Browser event
* __f__ *{Function}*
Function to run for each file found in the event.
__Returns__ *{undefined}*
Utility for iteration over files in event
> ```FS.Utility.eachFile = function(e, f) { ...``` [base-client.js:37](base-client.js#L37)

View file

@ -0,0 +1,51 @@
//XXX not sure this is still working properly?
FS.Utility.connectionLogin = function(connection) {
// We check if the accounts package is installed, since we depend on
// `Meteor.userId()`
if (typeof Accounts !== 'undefined') {
// Monitor logout from main connection
Meteor.startup(function() {
Tracker.autorun(function() {
var userId = Meteor.userId();
if (userId) {
connection.onReconnect = function() {
var token = Accounts._storedLoginToken();
connection.apply('login', [{resume: token}], function(err, result) {
if (!err && result) {
connection.setUserId(result.id);
}
});
};
} else {
connection.onReconnect = null;
connection.setUserId(null);
}
});
});
}
};
/**
* @method FS.Utility.eachFile
* @public
* @param {Event} e - Browser event
* @param {Function} f - Function to run for each file found in the event.
* @returns {undefined}
*
* Utility for iteration over files in event
*/
FS.Utility.eachFile = function(e, f) {
var evt = (e.originalEvent || e);
var files = evt.target.files;
if (!files || files.length === 0) {
files = evt.dataTransfer ? evt.dataTransfer.files : [];
}
for (var i = 0; i < files.length; i++) {
f(files[i], i);
}
};

View file

@ -0,0 +1,317 @@
// Exported namespace
FS = {};
// namespace for adapters; XXX should this be added by cfs-storage-adapter pkg instead?
FS.Store = {
GridFS: function () {
throw new Error('To use FS.Store.GridFS, you must add the "wekan-cfs-gridfs" package.');
},
FileSystem: function () {
throw new Error('To use FS.Store.FileSystem, you must add the "wekan-cfs-filesystem" package.');
},
S3: function () {
throw new Error('To use FS.Store.S3, you must add the "wekan-cfs-s3" package.');
},
WABS: function () {
throw new Error('To use FS.Store.WABS, you must add the "wekan-cfs-wabs" package.');
},
Dropbox: function () {
throw new Error('To use FS.Store.Dropbox, you must add the "wekan-cfs-dropbox" package.');
}
};
// namespace for access points
FS.AccessPoint = {};
// namespace for utillities
FS.Utility = {};
// A general place for any package to store global config settings
FS.config = {};
// An internal collection reference
FS._collections = {};
// Test scope
_Utility = {};
// #############################################################################
//
// HELPERS
//
// #############################################################################
/** @method _Utility.defaultZero
* @private
* @param {Any} val Returns number or 0 if value is a falsy
*/
_Utility.defaultZero = function(val) {
return +(val || 0);
};
/**
* @method FS.Utility.cloneFileRecord
* @public
* @param {FS.File|FS.Collection filerecord} rec
* @param {Object} [options]
* @param {Boolean} [options.full=false] Set `true` to prevent certain properties from being omitted from the clone.
* @returns {Object} Cloned filerecord
*
* Makes a shallow clone of `rec`, filtering out some properties that might be present if
* it's an FS.File instance, but which we never want to be part of the stored
* filerecord.
*
* This is a blacklist clone rather than a whitelist because we want the user to be able
* to specify whatever additional properties they wish.
*
* In general, we expect the following whitelist properties used by the internal and
* external APIs:
*
* _id, name, size, type, chunkCount, chunkSize, chunkSum, copies, createdAt, updatedAt, uploadedAt
*
* Those properties, and any additional properties added by the user, should be present
* in the returned object, which is suitable for inserting into the backing collection or
* extending an FS.File instance.
*
*/
FS.Utility.cloneFileRecord = function(rec, options) {
options = options || {};
var result = {};
// We use this method for two purposes. If using it to clone one FS.File into another, then
// we want a full clone. But if using it to get a filerecord object for inserting into the
// internal collection, then there are certain properties we want to omit so that they aren't
// stored in the collection.
var omit = options.full ? [] : ['collectionName', 'collection', 'data', 'createdByTransform'];
for (var prop in rec) {
if (rec.hasOwnProperty(prop) && !_.contains(omit, prop)) {
result[prop] = rec[prop];
}
}
return result;
};
/**
* @method FS.Utility.defaultCallback
* @public
* @param {Error} [err]
* @returns {undefined}
*
* Can be used as a default callback for client methods that need a callback.
* Simply throws the provided error if there is one.
*/
FS.Utility.defaultCallback = function defaultCallback(err) {
if (err) {
// Show gentle error if Meteor error
if (err instanceof Meteor.Error) {
console.error(err.message);
} else {
// Normal error, just throw error
throw err;
}
}
};
/**
* @method FS.Utility.defaultCallback
* @public
* @param {Function} [f] A callback function, if you have one. Can be undefined or null.
* @param {Meteor.Error | Error | String} [err] Error or error message (string)
* @returns {Any} the callback result if any
*
* Handle Error, creates an Error instance with the given text. If callback is
* a function, passes the error to that function. Otherwise throws it. Useful
* for dealing with errors in methods that optionally accept a callback.
*/
FS.Utility.handleError = function(f, err, result) {
// Set callback
var callback = (typeof f === 'function')? f : FS.Utility.defaultCallback;
// Set the err
var error = (err === ''+err)? new Error(err) : err;
// callback
return callback(error, result);
}
/**
* @method FS.Utility.noop
* @public
* Use this to hand a no operation / empty function
*/
FS.Utility.noop = function() {};
/**
* @method validateAction
* @private
* @param {Object} validators - The validators object to use, with `deny` and `allow` properties.
* @param {FS.File} fileObj - Mounted or mountable file object to be passed to validators.
* @param {String} userId - The ID of the user who is attempting the action.
* @returns {undefined}
*
* Throws a "400-Bad Request" Meteor error if the file is not mounted or
* a "400-Access denied" Meteor error if the action is not allowed.
*/
FS.Utility.validateAction = function validateAction(validators, fileObj, userId) {
var denyValidators = validators.deny;
var allowValidators = validators.allow;
// If insecure package is used and there are no validators defined,
// allow the action.
if (typeof Package === 'object'
&& Package.insecure
&& denyValidators.length + allowValidators.length === 0) {
return;
}
// If already mounted, validators should receive a fileObj
// that is fully populated
if (fileObj.isMounted()) {
fileObj.getFileRecord();
}
// Any deny returns true means denied.
if (_.any(denyValidators, function(validator) {
return validator(userId, fileObj);
})) {
throw new Meteor.Error(403, "Access denied");
}
// Any allow returns true means proceed. Throw error if they all fail.
if (_.all(allowValidators, function(validator) {
return !validator(userId, fileObj);
})) {
throw new Meteor.Error(403, "Access denied");
}
};
/**
* @method FS.Utility.getFileName
* @private
* @param {String} name - A filename, filepath, or URL
* @returns {String} The filename without the URL, filepath, or query string
*/
FS.Utility.getFileName = function utilGetFileName(name) {
// in case it's a URL, strip off potential query string
// should have no effect on filepath
name = name.split('?')[0];
// strip off beginning path or url
var lastSlash = name.lastIndexOf('/');
if (lastSlash !== -1) {
name = name.slice(lastSlash + 1);
}
return name;
};
/**
* @method FS.Utility.getFileExtension
* @public
* @param {String} name - A filename, filepath, or URL that may or may not have an extension.
* @returns {String} The extension or an empty string if no extension found.
*/
FS.Utility.getFileExtension = function utilGetFileExtension(name) {
name = FS.Utility.getFileName(name);
// Seekout the last '.' if found
var found = name.lastIndexOf('.');
// Return the extension if found else ''
// If found is -1, we return '' because there is no extension
// If found is 0, we return '' because it's a hidden file
return (found > 0 ? name.slice(found + 1).toLowerCase() : '');
};
/**
* @method FS.Utility.setFileExtension
* @public
* @param {String} name - A filename that may or may not already have an extension.
* @param {String} ext - An extension without leading period, which you want to be the new extension on `name`.
* @returns {String} The filename with changed extension.
*/
FS.Utility.setFileExtension = function utilSetFileExtension(name, ext) {
if (!name || !name.length) {
return name;
}
var currentExt = FS.Utility.getFileExtension(name);
if (currentExt.length) {
name = name.slice(0, currentExt.length * -1) + ext;
} else {
name = name + '.' + ext;
}
return name;
};
/*
* Borrowed these from http package
*/
FS.Utility.encodeParams = function encodeParams(params) {
var buf = [];
_.each(params, function(value, key) {
if (buf.length)
buf.push('&');
buf.push(FS.Utility.encodeString(key), '=', FS.Utility.encodeString(value));
});
return buf.join('').replace(/%20/g, '+');
};
FS.Utility.encodeString = function encodeString(str) {
return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
};
/*
* btoa and atob shims for client and server
*/
FS.Utility._btoa = function _fsUtility_btoa(str) {
var buffer;
if (str instanceof Buffer) {
buffer = str;
} else {
buffer = new Buffer(str.toString(), 'binary');
}
return buffer.toString('base64');
};
FS.Utility.btoa = function fsUtility_btoa(str) {
if (typeof btoa === 'function') {
// Client
return btoa(str);
} else if (typeof Buffer !== 'undefined') {
// Server
return FS.Utility._btoa(str);
} else {
throw new Error('FS.Utility.btoa: Cannot base64 encode on your system');
}
};
FS.Utility._atob = function _fsUtility_atob(str) {
return new Buffer(str, 'base64').toString('binary');
};
FS.Utility.atob = function fsUtility_atob(str) {
if (typeof atob === 'function') {
// Client
return atob(str);
} else if (typeof Buffer !== 'undefined') {
// Server
return FS.Utility._atob(str);
} else {
throw new Error('FS.Utility.atob: Cannot base64 encode on your system');
}
};
// Api wrap for 3party libs like underscore
FS.Utility.extend = _.extend;
FS.Utility.each = _.each;
FS.Utility.isEmpty = _.isEmpty;
FS.Utility.indexOf = _.indexOf;
FS.Utility.isArray = _.isArray;
FS.Utility.map = _.map;
FS.Utility.once = _.once;
FS.Utility.include = _.include;
FS.Utility.size = _.size;

View file

@ -0,0 +1,95 @@
/**
* @method FS.Utility.binaryToBuffer
* @public
* @param {Uint8Array} data
* @returns {Buffer}
*
* Converts a Uint8Array instance to a Node Buffer instance
*/
FS.Utility.binaryToBuffer = function(data) {
var len = data.length;
var buffer = new Buffer(len);
for (var i = 0; i < len; i++) {
buffer[i] = data[i];
}
return buffer;
};
/**
* @method FS.Utility.bufferToBinary
* @public
* @param {Buffer} data
* @returns {Uint8Array}
*
* Converts a Node Buffer instance to a Uint8Array instance
*/
FS.Utility.bufferToBinary = function(data) {
var len = data.length;
var binary = EJSON.newBinary(len);
for (var i = 0; i < len; i++) {
binary[i] = data[i];
}
return binary;
};
/**
* @method FS.Utility.safeCallback
* @public
* @param {Function} callback
* @returns {Function}
*
* Makes a callback safe for Meteor code
*/
FS.Utility.safeCallback = function (callback) {
return Meteor.bindEnvironment(callback, function(err) { throw err; });
};
/**
* @method FS.Utility.safeStream
* @public
* @param {Stream} nodestream
* @returns {Stream}
*
* Adds `safeOn` and `safeOnce` methods to a NodeJS Stream
* object. These are the same as `on` and `once`, except
* that the callback is wrapped for use in Meteor.
*/
FS.Utility.safeStream = function(nodestream) {
if (!nodestream || typeof nodestream.on !== 'function')
throw new Error('FS.Utility.safeStream requires a NodeJS Stream');
// Create Meteor safe events
nodestream.safeOn = function(name, callback) {
return nodestream.on(name, FS.Utility.safeCallback(callback));
};
// Create Meteor safe events
nodestream.safeOnce = function(name, callback) {
return nodestream.once(name, FS.Utility.safeCallback(callback));
};
// Return the modified stream - modified anyway
return nodestream;
};
/**
* @method FS.Utility.eachFileFromPath
* @public
* @param {String} p - Server path
* @param {Function} f - Function to run for each file found in the path.
* @returns {undefined}
*
* Utility for iteration over files from path on server
*/
FS.Utility.eachFileFromPath = function(p, f) {
var fs = Npm.require('fs');
var path = Npm.require('path');
var files = fs.readdirSync(p);
files.map(function (file) {
return path.join(p, file);
}).filter(function (filePath) {
return fs.statSync(filePath).isFile() && path.basename(filePath)[0] !== '.';
}).forEach(function (filePath) {
f(filePath);
});
};

View file

@ -0,0 +1,293 @@
## Public and Private API ##
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
***
__File: ["base-common.js"](base-common.js) Where: {server|client}__
***
#############################################################################
HELPERS
#############################################################################
-
### <a name="_Utility.defaultZero"></a>*_utility*.defaultZero(val)&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method is private*
*This method __defaultZero__ is defined in `_Utility`*
__Arguments__
* __val__ *{Any}*
Returns number or 0 if value is a falsy
> ```_Utility.defaultZero = function(val) { ...``` [base-common.js:42](base-common.js#L42)
-
### <a name="FS.Utility.cloneFileRecord"></a>*fsUtility*.cloneFileRecord(rec, [options])&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __cloneFileRecord__ is defined in `FS.Utility`*
__Arguments__
* __rec__ *{[FS.File](#FS.File)|[FS.Collection filerecord](#FS.Collection filerecord)}*
* __options__ *{Object}* (Optional)
* __full__ *{Boolean}* (Optional, Default = false)
Set `true` to prevent certain properties from being omitted from the clone.
__Returns__ *{Object}*
Cloned filerecord
Makes a shallow clone of `rec`, filtering out some properties that might be present if
it's an FS.File instance, but which we never want to be part of the stored
filerecord.
This is a blacklist clone rather than a whitelist because we want the user to be able
to specify whatever additional properties they wish.
In general, we expect the following whitelist properties used by the internal and
external APIs:
_id, name, size, type, chunkCount, chunkSize, chunkSum, copies, createdAt, updatedAt, uploadedAt
Those properties, and any additional properties added by the user, should be present
in the returned object, which is suitable for inserting into the backing collection or
extending an FS.File instance.
> ```FS.Utility.cloneFileRecord = function(rec, options) { ...``` [base-common.js:71](base-common.js#L71)
-
### <a name="FS.Utility.defaultCallback"></a>*fsUtility*.defaultCallback([err])&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __defaultCallback__ is defined in `FS.Utility`*
__Arguments__
* __err__ *{[Error](#Error)}* (Optional)
__Returns__ *{undefined}*
Can be used as a default callback for client methods that need a callback.
Simply throws the provided error if there is one.
> ```FS.Utility.defaultCallback = function defaultCallback(err) { ...``` [base-common.js:96](base-common.js#L96)
-
### <a name="FS.Utility.defaultCallback"></a>*fsUtility*.defaultCallback([f], [err])&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __defaultCallback__ is defined in `FS.Utility`*
__Arguments__
* __f__ *{Function}* (Optional)
A callback function, if you have one. Can be undefined or null.
* __err__ *{[Meteor.Error ](#Meteor.Error )|[ Error ](# Error )|[ String](# String)}* (Optional)
Error or error message (string)
__Returns__ *{Any}*
the callback result if any
Handle Error, creates an Error instance with the given text. If callback is
a function, passes the error to that function. Otherwise throws it. Useful
for dealing with errors in methods that optionally accept a callback.
> ```FS.Utility.handleError = function(f, err, result) { ...``` [base-common.js:120](base-common.js#L120)
-
### <a name="FS.Utility.noop"></a>*fsUtility*.noop()&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __noop__ is defined in `FS.Utility`*
Use this to hand a no operation / empty function
> ```FS.Utility.noop = function() { ...``` [base-common.js:134](base-common.js#L134)
-
### <a name="validateAction"></a>validateAction(validators, fileObj, userId)&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method is private*
__Arguments__
* __validators__ *{Object}*
The validators object to use, with `deny` and `allow` properties.
* __fileObj__ *{[FS.File](#FS.File)}*
Mounted or mountable file object to be passed to validators.
* __userId__ *{String}*
The ID of the user who is attempting the action.
__Returns__ *{undefined}*
Throws a "400-Bad Request" Meteor error if the file is not mounted or
a "400-Access denied" Meteor error if the action is not allowed.
> ```FS.Utility.validateAction = function validateAction(validators, fileObj, userId) { ...``` [base-common.js:147](base-common.js#L147)
-
### <a name="FS.Utility.getFileName"></a>*fsUtility*.getFileName(name)&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method is private*
*This method __getFileName__ is defined in `FS.Utility`*
__Arguments__
* __name__ *{String}*
A filename, filepath, or URL
__Returns__ *{String}*
The filename without the URL, filepath, or query string
> ```FS.Utility.getFileName = function utilGetFileName(name) { ...``` [base-common.js:187](base-common.js#L187)
-
### <a name="FS.Utility.getFileExtension"></a>*fsUtility*.getFileExtension(name)&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __getFileExtension__ is defined in `FS.Utility`*
__Arguments__
* __name__ *{String}*
A filename, filepath, or URL that may or may not have an extension.
__Returns__ *{String}*
The extension or an empty string if no extension found.
> ```FS.Utility.getFileExtension = function utilGetFileExtension(name) { ...``` [base-common.js:205](base-common.js#L205)
-
### <a name="FS.Utility.setFileExtension"></a>*fsUtility*.setFileExtension(name, ext)&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __setFileExtension__ is defined in `FS.Utility`*
__Arguments__
* __name__ *{String}*
A filename that may or may not already have an extension.
* __ext__ *{String}*
An extension without leading period, which you want to be the new extension on `name`.
__Returns__ *{String}*
The filename with changed extension.
> ```FS.Utility.setFileExtension = function utilSetFileExtension(name, ext) { ...``` [base-common.js:222](base-common.js#L222)
***
__File: ["base-server.js"](base-server.js) Where: {server}__
***
### <a name="FS.Utility.binaryToBuffer"></a>*fsUtility*.binaryToBuffer(data)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __binaryToBuffer__ is defined in `FS.Utility`*
__Arguments__
* __data__ *{Uint8Array}*
__Returns__ *{Buffer}*
Converts a Uint8Array instance to a Node Buffer instance
> ```FS.Utility.binaryToBuffer = function(data) { ...``` [base-server.js:9](base-server.js#L9)
-
### <a name="FS.Utility.bufferToBinary"></a>*fsUtility*.bufferToBinary(data)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __bufferToBinary__ is defined in `FS.Utility`*
__Arguments__
* __data__ *{Buffer}*
__Returns__ *{Uint8Array}*
Converts a Node Buffer instance to a Uint8Array instance
> ```FS.Utility.bufferToBinary = function(data) { ...``` [base-server.js:26](base-server.js#L26)
***
__File: ["base-client.js"](base-client.js) Where: {client}__
***
### <a name="FS.Utility.eachFile"></a>*fsUtility*.eachFile(e, f)&nbsp;&nbsp;<sub><i>Client</i></sub> ###
*This method __eachFile__ is defined in `FS.Utility`*
__Arguments__
* __e__ *{[Event](#Event)}*
Browser event
* __f__ *{Function}*
Function to run for each file found in the event.
__Returns__ *{undefined}*
Utility for iteration over files in event
> ```FS.Utility.eachFile = function(e, f) { ...``` [base-client.js:37](base-client.js#L37)

View file

@ -0,0 +1,37 @@
Package.describe({
version: '0.0.30',
name: 'wekan-cfs-base-package',
summary: 'CollectionFS, Base package',
git: 'https://github.com/zcfs/Meteor-cfs-base-package.git'
});
Package.onUse(function(api) {
api.versionsFrom('1.0');
api.use(['deps', 'underscore', 'ejson']);
if (api.export) {
api.export('FS');
api.export('_Utility', { testOnly: true });
}
api.addFiles([
'base-common.js',
'base-server.js'
], 'server');
api.addFiles([
'polyfill.base64.js',
'base-common.js',
'base-client.js'
], 'client');
});
// Package.on_test(function (api) {
// api.use(['wekan-cfs-base-package', 'cfs-file']);
// api.use('test-helpers', 'server');
// api.use(['tinytest', 'underscore', 'ejson', 'ordered-dict',
// 'random', 'deps']);
// api.add_files('tests/common-tests.js', ['client', 'server']);
// });

View file

@ -0,0 +1,179 @@
/*
* Copyright (c) 2010 Nick Galbreath
* http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/* base64 encode/decode compatible with window.btoa/atob
*
* window.atob/btoa is a Firefox extension to convert binary data (the "b")
* to base64 (ascii, the "a").
*
* It is also found in Safari and Chrome. It is not available in IE.
*
* if (!window.btoa) window.btoa = base64.encode
* if (!window.atob) window.atob = base64.decode
*
* The original spec's for atob/btoa are a bit lacking
* https://developer.mozilla.org/en/DOM/window.atob
* https://developer.mozilla.org/en/DOM/window.btoa
*
* window.btoa and base64.encode takes a string where charCodeAt is [0,255]
* If any character is not [0,255], then an DOMException(5) is thrown.
*
* window.atob and base64.decode take a base64-encoded string
* If the input length is not a multiple of 4, or contains invalid characters
* then an DOMException(5) is thrown.
*/
var base64 = {};
base64.PADCHAR = '=';
base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
base64.makeDOMException = function() {
// sadly in FF,Safari,Chrome you can't make a DOMException
var e, tmp;
try {
return new DOMException(DOMException.INVALID_CHARACTER_ERR);
} catch (tmp) {
// not available, just passback a duck-typed equiv
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error/prototype
var ex = new Error("DOM Exception 5");
// ex.number and ex.description is IE-specific.
ex.code = ex.number = 5;
ex.name = ex.description = "INVALID_CHARACTER_ERR";
// Safari/Chrome output format
ex.toString = function() { return 'Error: ' + ex.name + ': ' + ex.message; };
return ex;
}
}
base64.getbyte64 = function(s,i) {
// This is oddly fast, except on Chrome/V8.
// Minimal or no improvement in performance by using a
// object with properties mapping chars to value (eg. 'A': 0)
var idx = base64.ALPHA.indexOf(s.charAt(i));
if (idx === -1) {
throw base64.makeDOMException();
}
return idx;
}
base64.decode = function(s) {
// convert to string
s = '' + s;
var getbyte64 = base64.getbyte64;
var pads, i, b10;
var imax = s.length
if (imax === 0) {
return s;
}
if (imax % 4 !== 0) {
throw base64.makeDOMException();
}
pads = 0
if (s.charAt(imax - 1) === base64.PADCHAR) {
pads = 1;
if (s.charAt(imax - 2) === base64.PADCHAR) {
pads = 2;
}
// either way, we want to ignore this last block
imax -= 4;
}
var x = [];
for (i = 0; i < imax; i += 4) {
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) |
(getbyte64(s,i+2) << 6) | getbyte64(s,i+3);
x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff, b10 & 0xff));
}
switch (pads) {
case 1:
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6);
x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff));
break;
case 2:
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12);
x.push(String.fromCharCode(b10 >> 16));
break;
}
return x.join('');
}
base64.getbyte = function(s,i) {
var x = s.charCodeAt(i);
if (x > 255) {
throw base64.makeDOMException();
}
return x;
}
base64.encode = function(s) {
if (arguments.length !== 1) {
throw new SyntaxError("Not enough arguments");
}
var padchar = base64.PADCHAR;
var alpha = base64.ALPHA;
var getbyte = base64.getbyte;
var i, b10;
var x = [];
// convert to string
s = '' + s;
var imax = s.length - s.length % 3;
if (s.length === 0) {
return s;
}
for (i = 0; i < imax; i += 3) {
b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2);
x.push(alpha.charAt(b10 >> 18));
x.push(alpha.charAt((b10 >> 12) & 0x3F));
x.push(alpha.charAt((b10 >> 6) & 0x3f));
x.push(alpha.charAt(b10 & 0x3f));
}
switch (s.length - imax) {
case 1:
b10 = getbyte(s,i) << 16;
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
padchar + padchar);
break;
case 2:
b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8);
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
alpha.charAt((b10 >> 6) & 0x3f) + padchar);
break;
}
return x.join('');
}
if (!window.btoa) window.btoa = base64.encode
if (!window.atob) window.atob = base64.decode

View file

@ -0,0 +1,161 @@
function equals(a, b) {
return EJSON.stringify(a) === EJSON.stringify(b);
}
Tinytest.add('cfs-base-package - test environment', function(test) {
test.isTrue(typeof FS !== 'undefined',
'FS scope not declared');
test.isTrue(typeof FS.Store !== 'undefined',
'FS scope "FS.Store" not declared');
test.isTrue(typeof FS.AccessPoint !== 'undefined',
'FS scope "FS.AccessPoint" not declared');
test.isTrue(typeof FS.Utility !== 'undefined',
'FS scope "FS.Utility" not declared');
test.isTrue(typeof FS._collections !== 'undefined',
'FS scope "FS._collections" not declared');
test.isTrue(typeof _Utility !== 'undefined',
'_Utility test scope not declared');
});
Tinytest.add('cfs-base-package - _Utility.defaultZero', function(test) {
test.equal(_Utility.defaultZero(), 0, 'Failes to return 0 when (undefined)');
test.equal(_Utility.defaultZero(undefined), 0, 'Failes to return 0 when undefined');
test.equal(_Utility.defaultZero(null), 0, 'Failes to return 0 when null');
test.equal(_Utility.defaultZero(false), 0, 'Failes to return 0 when false');
test.equal(_Utility.defaultZero(0), 0, 'Failes to return 0 when 0');
test.equal(_Utility.defaultZero(-1), -1, 'Failes to return -1');
test.equal(_Utility.defaultZero(1), 1, 'Failes to return 1');
test.equal(_Utility.defaultZero(-0.1), -0.1, 'Failes to return -0.1');
test.equal(_Utility.defaultZero(0.1), 0.1, 'Failes to return 0.1');
test.equal(_Utility.defaultZero(''), 0, 'Failes to return ""');
test.equal(_Utility.defaultZero({}), NaN, 'Failes to return NaN when object');
test.equal(_Utility.defaultZero("dfdsfs"), NaN, 'Failes to return NaN when string');
test.equal(_Utility.defaultZero("1"), 1, 'Failes to return 1 when string "1"');
});
Tinytest.add('cfs-base-package - FS.Utility.cloneFileRecord', function(test) {
// Given an object with any props, should filter out 'collectionName',
// 'collection', 'data', and 'createdByTransform'
var result = FS.Utility.cloneFileRecord({a: 1, b: {c: 1}, d: [1, 2], collectionName: 'test', collection: {}, data: {}, createdByTransform: false});
test.equal(result, {a: 1, b: {c: 1}, d: [1, 2]});
// Given an FS.File instance, should filter out 'collectionName',
// 'collection', 'data', and 'createdByTransform' and return a plain Object
var fileObj = new FS.File({a: 1, b: {c: 1}, d: [1, 2], name: 'name.png', type: 'image/png', size: 100, collectionName: 'test', collection: {}, data: {}, createdByTransform: false});
test.isTrue(fileObj instanceof FS.File);
var result = FS.Utility.cloneFileRecord(fileObj);
test.isFalse(result instanceof FS.File);
test.isTrue(equals(result, {a: 1, b: {c: 1}, d: [1, 2], name: 'name.png', type: 'image/png', size: 100}));
});
Tinytest.add('cfs-base-package - FS.Utility.defaultCallback', function(test) {
// should throw an error passed in, but not a Meteor.Error
test.throws(function () {
var cb = FS.Utility.defaultCallback;
cb(new Error('test'));
});
var cb2 = FS.Utility.defaultCallback;
test.isUndefined(cb2(new Meteor.Error('test')));
});
Tinytest.add('cfs-base-package - FS.Utility.handleError', function(test) {
test.isTrue(true);
// TODO
});
Tinytest.add('cfs-base-package - FS.Utility.binaryToBuffer', function(test) {
test.isTrue(true);
// TODO
});
Tinytest.add('cfs-base-package - FS.Utility.bufferToBinary', function(test) {
test.isTrue(true);
// TODO
});
Tinytest.add('cfs-base-package - FS.Utility.connectionLogin', function(test) {
test.isTrue(true);
// TODO
});
Tinytest.add('cfs-base-package - FS.Utility.getFileName', function(test) {
function t(input, expected) {
var ext = FS.Utility.getFileName(input);
test.equal(ext, expected, 'Got incorrect filename');
}
t('bar.png', 'bar.png');
t('foo/bar.png', 'bar.png');
t('/foo/foo/bar.png', 'bar.png');
t('http://foobar.com/file.png', 'file.png');
t('http://foobar.com/file', 'file');
t('http://foobar.com/file.png?a=b', 'file.png');
t('http://foobar.com/.file?a=b', '.file');
t('file', 'file');
t('.file', '.file');
t('foo/.file', '.file');
t('/foo/foo/.file', '.file');
});
Tinytest.add('cfs-base-package - FS.Utility.getFileExtension', function(test) {
function t(input, expected) {
var ext = FS.Utility.getFileExtension(input);
test.equal(ext, expected, 'Got incorrect extension');
}
t('bar.png', 'png');
t('foo/bar.png', 'png');
t('/foo/foo/bar.png', 'png');
t('http://foobar.com/file.png', 'png');
t('http://foobar.com/file', '');
t('http://foobar.com/file.png?a=b', 'png');
t('http://foobar.com/file?a=b', '');
t('file', '');
t('.file', '');
t('foo/.file', '');
t('/foo/foo/.file', '');
});
Tinytest.add('cfs-base-package - FS.Utility.setFileExtension', function(test) {
function t(name, ext, expected) {
var newName = FS.Utility.setFileExtension(name, ext);
test.equal(newName, expected, 'Extension was not set correctly');
}
t('bar.png', 'jpeg', 'bar.jpeg');
t('bar', 'jpeg', 'bar.jpeg');
t('.bar', 'jpeg', '.bar.jpeg');
t('', 'jpeg', '');
t(null, 'jpeg', null);
});
//Test API:
//Tinytest.add('', function(test) {});
//Tinytest.addAsync('', function(test, onComplete) {});
//test.isFalse(v, msg)
//test.isTrue(v, msg)
//test.equalactual, expected, message, not
//test.length(obj, len)
//test.include(s, v)
//test.isNaN(v, msg)
//test.isUndefined(v, msg)
//test.isNotNull
//test.isNull
//test.throws(func)
//test.instanceOf(obj, klass)
//test.notEqual(actual, expected, message)
//test.runId()
//test.exception(exception)
//test.expect_fail()
//test.ok(doc)
//test.fail(doc)
//test.equal(a, b, msg)