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,41 @@
wekan-cfs-filesystem
=========================
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 local server filesystem storage for
[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS). When you
use this storage adapter, file data is stored in a directory of your choosing
on the same server on which your Meteor app is running.
## Installation
Install using Meteorite. When in a Meteor app directory, enter:
```
$ meteor add wekan-cfs-filesystem
```
## Important Note
Note that using this Storage Adapter on the free Meteor deployment servers on `*.meteor.com` will cause a reset of files at every code deploy. You may want to have a look at the [GridFS Storage Adapter](https://github.com/zcfs/Meteor-CollectionFS/tree/devel/packages/gridfs) for persistent file storage.
## Usage
```js
var imageStore = new FS.Store.FileSystem("images", {
path: "~/app-files/images", //optional, default is "/cfs/files" path within app container
transformWrite: myTransformWriteFunction, //optional
transformRead: myTransformReadFunction, //optional
maxTries: 1 //optional, default 5
});
Images = new FS.Collection("images", {
stores: [imageStore]
});
```
Refer to the [CollectionFS](https://github.com/zcfs/Meteor-CollectionFS)
package documentation for more information.

View file

@ -0,0 +1,10 @@
// On the client we have just a shell
FS.Store.FileSystem = function(name, options) {
var self = this;
if (!(self instanceof FS.Store.FileSystem))
throw new Error('FS.Store.FileSystem missing keyword "new"');
return new FS.StorageAdapter(name, options, {
typeName: 'storage.filesystem'
});
};

View file

@ -0,0 +1,157 @@
var fs = Npm.require('fs');
var path = Npm.require('path');
var mkdirp = Npm.require('mkdirp');
//var chokidar = Npm.require('chokidar');
FS.Store.FileSystem = function(name, options) {
var self = this;
if (!(self instanceof FS.Store.FileSystem))
throw new Error('FS.Store.FileSystem missing keyword "new"');
// We allow options to be string/path empty or options.path
options = (options !== ''+options) ? options || {} : { path: options };
// Provide a default FS directory one level up from the build/bundle directory
var pathname = options.path;
if (!pathname && __meteor_bootstrap__ && __meteor_bootstrap__.serverDir) {
pathname = path.join(__meteor_bootstrap__.serverDir, '../../../cfs/files/' + name);
}
if (!pathname)
throw new Error('FS.Store.FileSystem unable to determine path');
// Check if we have '~/foo/bar'
if (pathname.split(path.sep)[0] === '~') {
var homepath = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
if (homepath) {
pathname = pathname.replace('~', homepath);
} else {
throw new Error('FS.Store.FileSystem unable to resolve "~" in path');
}
}
// Set absolute path
var absolutePath = path.resolve(pathname);
// Ensure the path exists
mkdirp.sync(absolutePath);
FS.debug && console.log(name + ' FileSystem mounted on: ' + absolutePath);
return new FS.StorageAdapter(name, options, {
typeName: 'storage.filesystem',
fileKey: function(fileObj) {
// Lookup the copy
var store = fileObj && fileObj._getInfo(name);
// If the store and key is found return the key
if (store && store.key) return store.key;
var filename = fileObj.name();
var filenameInStore = fileObj.name({store: name});
// If no store key found we resolve / generate a key
return fileObj.collectionName + '-' + fileObj._id + '-' + (filenameInStore || filename);
},
createReadStream: function(fileKey, options) {
// this is the Storage adapter scope
var filepath = path.join(absolutePath, fileKey);
// return the read stream - Options allow { start, end }
return fs.createReadStream(filepath, options);
},
createWriteStream: function(fileKey, options) {
options = options || {};
// this is the Storage adapter scope
var filepath = path.join(absolutePath, fileKey);
// Return the stream handle
var writeStream = fs.createWriteStream(filepath, options);
// The filesystem does not emit the "end" event only close - so we
// manually send the end event
writeStream.on('close', function() {
if (FS.debug) console.log('SA FileSystem - DONE!! fileKey: "' + fileKey + '"');
// Get the exact size of the stored file, so that we can pass it to onEnd/onStored.
// Since stream transforms might have altered the size, this is the best way to
// ensure we update the fileObj.copies with the correct size.
try {
// Get the stats of the file
var stats = fs.statSync(filepath);
// Emit end and return the fileKey, size, and updated date
writeStream.emit('stored', {
fileKey: fileKey,
size: stats.size,
storedAt: stats.mtime
});
} catch(err) {
// On error we emit the error on
writeStream.emit('error', err);
}
});
return writeStream;
},
remove: function(fileKey, callback) {
// this is the Storage adapter scope
var filepath = path.join(absolutePath, fileKey);
// Call node unlink file
fs.unlink(filepath, function (error, result) {
if (error && error.errno === 34) {
console.warn("SA FileSystem: Could not delete " + filepath + " because the file was not found.");
callback && callback(null);
} else {
callback && callback(error, result);
}
});
},
stats: function(fileKey, callback) {
// this is the Storage adapter scope
var filepath = path.join(absolutePath, fileKey);
if (typeof callback === 'function') {
fs.stat(filepath, callback);
} else {
return fs.statSync(filepath);
}
}
// Add this back and add the chokidar dependency back when we make this work eventually
// watch: function(callback) {
// function fileKey(filePath) {
// return filePath.replace(absolutePath, "");
// }
// FS.debug && console.log('Watching ' + absolutePath);
// // chokidar seems to be most widely used and production ready watcher
// var watcher = chokidar.watch(absolutePath, {ignored: /\/\./, ignoreInitial: true});
// watcher.on('add', Meteor.bindEnvironment(function(filePath, stats) {
// callback("change", fileKey(filePath), {
// name: path.basename(filePath),
// type: null,
// size: stats.size,
// utime: stats.mtime
// });
// }, function(err) {
// throw err;
// }));
// watcher.on('change', Meteor.bindEnvironment(function(filePath, stats) {
// callback("change", fileKey(filePath), {
// name: path.basename(filePath),
// type: null,
// size: stats.size,
// utime: stats.mtime
// });
// }, function(err) {
// throw err;
// }));
// watcher.on('unlink', Meteor.bindEnvironment(function(filePath) {
// callback("remove", fileKey(filePath));
// }, function(err) {
// throw err;
// }));
// }
});
};

View file

@ -0,0 +1,24 @@
Package.describe({
git: 'https://github.com/zcfs/Meteor-cfs-filesystem.git',
name: 'wekan-cfs-filesystem',
version: '0.1.2',
summary: "Filesystem storage adapter for CollectionFS"
});
Npm.depends({
//chokidar: "0.8.2",
mkdirp: "0.3.5"
});
Package.onUse(function(api) {
api.versionsFrom('1.0');
api.use(['wekan-cfs-base-package@0.0.30', 'wekan-cfs-storage-adapter@0.2.1']);
api.addFiles('filesystem.server.js', 'server');
api.addFiles('filesystem.client.js', 'client');
});
Package.onTest(function(api) {
api.use(['wekan-cfs-filesystem', 'test-helpers', 'tinytest'], 'server');
api.addFiles('tests.js', 'server');
});

View file

@ -0,0 +1,9 @@
//TODO
/*
* FileSystem Tests (Server Only)
*
* Create FS SA and use for all tests (verify the "~" correctly uses the home directory)
* Test get, getBytes, put, remove, stats. Verify that the correct things happen
* in the correct places on the local filesystem.
*/

View file

@ -0,0 +1,44 @@
function equals(a, b) {
return !!(EJSON.stringify(a) === EJSON.stringify(b));
}
Tinytest.add('cfs-filesystem - 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-filesystem - 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)