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 [@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,48 @@
wekan-cfs-gridfs
=========================
NOTE: This package is under active development right now (2014-3-31). It has
bugs and the API may continue to change. Please help test it and fix bugs,
but don't use in production yet.
A Meteor package that adds [GridFS](http://docs.mongodb.org/manual/core/gridfs/) file storage for
[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS). When you
use this storage adapter, file data is stored in chunks in your MongoDB database.
## Installation
Install using Meteorite. When in a Meteor app directory, enter:
```
$ meteor add wekan-cfs-gridfs
```
## Usage
```js
var imageStore = new FS.Store.GridFS("images", {
mongoUrl: 'mongodb://127.0.0.1:27017/test/', // optional, defaults to Meteor's local MongoDB
mongoOptions: {...}, // optional, see note below
transformWrite: myTransformWriteFunction, //optional
transformRead: myTransformReadFunction, //optional
maxTries: 1, // optional, default 5
chunkSize: 1024*1024 // optional, default GridFS chunk size in bytes (can be overridden per file).
// Default: 2MB. Reasonable range: 512KB - 4MB
});
Images = new FS.Collection("images", {
stores: [imageStore]
});
```
More control over the MongoDB connection is available by specifying [MongoClient.connect options](http://mongodb.github.io/node-mongodb-native/driver-articles/mongoclient.html#mongoclient-connect-options) as a `mongoOptions` attribute in the options object on the constructor.
Refer to the [CollectionFS](https://github.com/zcfs/Meteor-CollectionFS)
package documentation for more information.
## API
[For Users](https://github.com/zcfs/Meteor-CollectionFS/blob/master/packages/gridfs/api.md)
[For Contributors](https://github.com/zcfs/Meteor-CollectionFS/blob/master/packages/gridfs/internal.api.md)

View file

@ -0,0 +1,69 @@
## cfs-gridfs Public API ##
GridFS storage adapter for CollectionFS
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
-
### <a name="FS.Store.GridFS"></a>new *fsStore*.GridFS(name, options)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __GridFS__ is defined in `FS.Store`*
__Arguments__
* __name__ *{String}*
The store name
* __options__ *{Object}*
* __beforeSave__ *{Function}* (Optional)
Function to run before saving a file from the server. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties.
* __maxTries__ *{Number}* (Optional, Default = 5)
Max times to attempt saving a file
__Returns__ *{FS.StorageAdapter}*
An instance of FS.StorageAdapter.
Creates a GridFS store instance on the server. Inherits from FS.StorageAdapter
type.
> ```FS.Store.GridFS = function(name, options) { ...``` [gridfs.server.js:16](gridfs.server.js#L16)
-
### <a name="FS.Store.GridFS"></a>new *fsStore*.GridFS(name, options)&nbsp;&nbsp;<sub><i>Client</i></sub> ###
*This method __GridFS__ is defined in `FS.Store`*
__Arguments__
* __name__ *{String}*
The store name
* __options__ *{Object}*
* __beforeSave__ *{Function}* (Optional)
Function to run before saving a file from the client. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties.
* __maxTries__ *{Number}* (Optional, Default = 5)
Max times to attempt saving a file
__Returns__ *{undefined}*
Creates a GridFS store instance on the client, which is just a shell object
storing some info.
> ```FS.Store.GridFS = function(name, options) { ...``` [gridfs.client.js:13](gridfs.client.js#L13)

View file

@ -0,0 +1,21 @@
/**
* @public
* @constructor
* @param {String} name - The store name
* @param {Object} options
* @param {Function} [options.beforeSave] - Function to run before saving a file from the client. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties.
* @param {Number} [options.maxTries=5] - Max times to attempt saving a file
* @returns {undefined}
*
* Creates a GridFS store instance on the client, which is just a shell object
* storing some info.
*/
FS.Store.GridFS = function(name, options) {
var self = this;
if (!(self instanceof FS.Store.GridFS))
throw new Error('FS.Store.GridFS missing keyword "new"');
return new FS.StorageAdapter(name, options, {
typeName: 'storage.gridfs'
});
};

View file

@ -0,0 +1,176 @@
var path = Npm.require('path');
var mongodb = Npm.require('mongodb');
var ObjectID = Npm.require('mongodb').ObjectID;
var Grid = Npm.require('gridfs-stream');
//var Grid = Npm.require('gridfs-locking-stream');
var chunkSize = 1024*1024*2; // 256k is default GridFS chunk size, but performs terribly for largish files
/**
* @public
* @constructor
* @param {String} name - The store name
* @param {Object} options
* @param {Function} [options.beforeSave] - Function to run before saving a file from the server. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties.
* @param {Number} [options.maxTries=5] - Max times to attempt saving a file
* @returns {FS.StorageAdapter} An instance of FS.StorageAdapter.
*
* Creates a GridFS store instance on the server. Inherits from FS.StorageAdapter
* type.
*/
FS.Store.GridFS = function(name, options) {
var self = this;
options = options || {};
var gridfsName = name;
var mongoOptions = options.mongoOptions || {};
if (!(self instanceof FS.Store.GridFS))
throw new Error('FS.Store.GridFS missing keyword "new"');
if (!options.mongoUrl) {
options.mongoUrl = process.env.MONGO_URL;
// When using a Meteor MongoDB instance, preface name with "cfs_gridfs."
gridfsName = "cfs_gridfs." + name;
}
if (!options.mongoOptions) {
options.mongoOptions = { db: { native_parser: true }, server: { auto_reconnect: true }};
}
if (options.chunkSize) {
chunkSize = options.chunkSize;
}
return new FS.StorageAdapter(name, options, {
typeName: 'storage.gridfs',
fileKey: function(fileObj) {
// We should not have to mount the file here - We assume its taken
// care of - Otherwise we create new files instead of overwriting
var key = {
_id: null,
filename: null
};
// If we're passed a fileObj, we retrieve the _id and filename from it.
if (fileObj) {
var info = fileObj._getInfo(name, {updateFileRecordFirst: false});
key._id = info.key || null;
key.filename = info.name || fileObj.name({updateFileRecordFirst: false}) || (fileObj.collectionName + '-' + fileObj._id);
}
// If key._id is null at this point, createWriteStream will let GridFS generate a new ID
return key;
},
createReadStream: function(fileKey, options) {
options = options || {};
// Init GridFS
var gfs = new Grid(self.db, mongodb);
// Set the default streamning settings
var settings = {
_id: new ObjectID(fileKey._id),
root: gridfsName
};
// Check if this should be a partial read
if (typeof options.start !== 'undefined' && typeof options.end !== 'undefined' ) {
// Add partial info
settings.range = {
startPos: options.start,
endPos: options.end
};
}
FS.debug && console.log('GRIDFS', settings);
return gfs.createReadStream(settings);
},
createWriteStream: function(fileKey, options) {
options = options || {};
// Init GridFS
var gfs = new Grid(self.db, mongodb);
var opts = {
filename: fileKey.filename,
mode: 'w',
root: gridfsName,
chunk_size: options.chunk_size || chunkSize,
// We allow aliases, metadata and contentType to be passed in via
// options
aliases: options.aliases || [],
metadata: options.metadata || null,
content_type: options.contentType || 'application/octet-stream'
};
if (fileKey._id) {
opts._id = new ObjectID(fileKey._id);
}
var writeStream = gfs.createWriteStream(opts);
writeStream.on('close', function(file) {
if (!file) {
// gridfs-stream will emit "close" without passing a file
// if there is an error. We can simply exit here because
// the "error" listener will also be called in this case.
return;
}
if (FS.debug) console.log('SA GridFS - DONE!');
// Emit end and return the fileKey, size, and updated date
writeStream.emit('stored', {
// Set the generated _id so that we know it for future reads and writes.
// We store the _id as a string and only convert to ObjectID right before
// reading, writing, or deleting. If we store the ObjectID itself,
// Meteor (EJSON?) seems to convert it to a LocalCollection.ObjectID,
// which GFS doesn't understand.
fileKey: file._id.toString(),
size: file.length,
storedAt: file.uploadDate || new Date()
});
});
writeStream.on('error', function(error) {
console.log('SA GridFS - ERROR!', error);
});
return writeStream;
},
remove: function(fileKey, callback) {
// Init GridFS
var gfs = new Grid(self.db, mongodb);
try {
gfs.remove({ _id: new ObjectID(fileKey._id), root: gridfsName }, callback);
} catch(err) {
callback(err);
}
},
// Not implemented
watch: function() {
throw new Error("GridFS storage adapter does not support the sync option");
},
init: function(callback) {
mongodb.MongoClient.connect(options.mongoUrl, mongoOptions, function (err, db) {
if (err) { return callback(err); }
self.db = db;
// ensure that indexes are added as otherwise CollectionFS fails for Mongo >= 3.0
var collection = new Mongo.Collection(gridfsName);
collection.rawCollection().ensureIndex({ "files_id": 1, "n": 1});
callback(null);
});
}
});
};

View file

@ -0,0 +1,75 @@
## Public and Private API ##
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
***
__File: ["gridfs.server.js"](gridfs.server.js) Where: {server}__
***
### <a name="FS.Store.GridFS"></a>new *fsStore*.GridFS(name, options)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __GridFS__ is defined in `FS.Store`*
__Arguments__
* __name__ *{String}*
The store name
* __options__ *{Object}*
* __beforeSave__ *{Function}* (Optional)
Function to run before saving a file from the server. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties.
* __maxTries__ *{Number}* (Optional, Default = 5)
Max times to attempt saving a file
__Returns__ *{FS.StorageAdapter}*
An instance of FS.StorageAdapter.
Creates a GridFS store instance on the server. Inherits from FS.StorageAdapter
type.
> ```FS.Store.GridFS = function(name, options) { ...``` [gridfs.server.js:16](gridfs.server.js#L16)
***
__File: ["gridfs.client.js"](gridfs.client.js) Where: {client}__
***
### <a name="FS.Store.GridFS"></a>new *fsStore*.GridFS(name, options)&nbsp;&nbsp;<sub><i>Client</i></sub> ###
*This method __GridFS__ is defined in `FS.Store`*
__Arguments__
* __name__ *{String}*
The store name
* __options__ *{Object}*
* __beforeSave__ *{Function}* (Optional)
Function to run before saving a file from the client. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties.
* __maxTries__ *{Number}* (Optional, Default = 5)
Max times to attempt saving a file
__Returns__ *{undefined}*
Creates a GridFS store instance on the client, which is just a shell object
storing some info.
> ```FS.Store.GridFS = function(name, options) { ...``` [gridfs.client.js:13](gridfs.client.js#L13)

View file

@ -0,0 +1,24 @@
Package.describe({
name: 'wekan-cfs-gridfs',
version: '0.0.34',
summary: 'GridFS storage adapter for CollectionFS',
git: 'https://github.com/zcfs/Meteor-cfs-gridfs.git'
});
Npm.depends({
mongodb: '2.2.9',
'gridfs-stream': '1.1.1'
//'gridfs-locking-stream': '0.0.3'
});
Package.onUse(function (api) {
api.use(['wekan-cfs-base-package@0.0.30', 'wekan-cfs-storage-adapter@0.2.3', 'ecmascript@0.1.0']);
api.addFiles('gridfs.server.js', 'server');
api.addFiles('gridfs.client.js', 'client');
});
Package.onTest(function (api) {
api.use(['wekan-cfs-gridfs', 'test-helpers', 'tinytest'], 'server');
api.addFiles('tests/server-tests.js', 'server');
api.addFiles('tests/client-tests.js', 'client');
});

View file

@ -0,0 +1,44 @@
function equals(a, b) {
return !!(EJSON.stringify(a) === EJSON.stringify(b));
}
Tinytest.add('cfs-gridfs - client - test environment', function(test) {
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
test.isTrue(typeof CFSErrorType !== 'undefined', 'test environment not initialized CFSErrorType');
});
/*
* FS.File Client Tests
*
* construct FS.File with no arguments
* construct FS.File passing in File
* construct FS.File passing in Blob
* load blob into FS.File and then call FS.File.toDataUrl
* call FS.File.setDataFromBinary, then FS.File.getBlob(); make sure correct data is returned
* load blob into FS.File and then call FS.File.getBinary() with and without start/end; make sure correct data is returned
* construct FS.File, set FS.File.collectionName to a CFS name, and then test FS.File.update/remove/get/put/del/url
* set FS.File.name to a filename and test that FS.File.getExtension() returns the extension
* load blob into FS.File and make sure FS.File.saveLocal initiates a download (possibly can't do automatically)
*
*/
//Test API:
//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)

View file

@ -0,0 +1,49 @@
function equals(a, b) {
return !!(EJSON.stringify(a) === EJSON.stringify(b));
}
Tinytest.add('cfs-gridfs - server - test environment', function(test) {
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
test.isTrue(typeof CFSErrorType !== 'undefined', 'test environment not initialized CFSErrorType');
});
/*
* FS.File Server Tests
*
* construct FS.File with no arguments
* load data with FS.File.setDataFromBuffer
* load data with FS.File.setDataFromBinary
* load data and then call FS.File.toDataUrl with and without callback
* load buffer into FS.File and then call FS.File.getBinary with and without start/end; make sure correct data is returned
* construct FS.File, set FS.File.collectionName to a CFS name, and then test FS.File.update/remove/get/put/del/url
* (call these with and without callback to test sync vs. async)
* set FS.File.name to a filename and test that FS.File.getExtension() returns the extension
*
*
* FS.Collection Server Tests
*
* Make sure options.filter is respected
*
*
*/
//Test API:
//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)