mirror of
https://github.com/wekan/wekan.git
synced 2025-12-21 09:50:13 +01:00
Fixed Non-ASCII attachment filename will crash when downloading.
Thanks to xet7 ! Fixes #2759
This commit is contained in:
parent
843ff8eaaa
commit
c2da477735
277 changed files with 30568 additions and 52 deletions
176
packages/wekan-cfs-data-man/server/data-man-api.js
Normal file
176
packages/wekan-cfs-data-man/server/data-man-api.js
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
/* global DataMan:true, Buffer */
|
||||
|
||||
var fs = Npm.require("fs");
|
||||
var Readable = Npm.require('stream').Readable;
|
||||
|
||||
/**
|
||||
* @method DataMan
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {Buffer|ArrayBuffer|Uint8Array|String} data The data that you want to manipulate.
|
||||
* @param {String} [type] The data content (MIME) type, if known. Required if the first argument is a Buffer, ArrayBuffer, Uint8Array, or URL
|
||||
* @param {Object} [options] Currently used only to pass options for the GET request when `data` is a URL.
|
||||
*/
|
||||
DataMan = function DataMan(data, type, options) {
|
||||
var self = this, buffer;
|
||||
|
||||
if (!data) {
|
||||
throw new Error("DataMan constructor requires a data argument");
|
||||
}
|
||||
|
||||
// The end result of all this is that we will have this.source set to a correct
|
||||
// data type handler. We are simply detecting what the data arg is.
|
||||
//
|
||||
// Unless we already have in-memory data, we don't load anything into memory
|
||||
// and instead rely on obtaining a read stream when the time comes.
|
||||
if (typeof Buffer !== "undefined" && data instanceof Buffer) {
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed a Buffer");
|
||||
}
|
||||
self.source = new DataMan.Buffer(data, type);
|
||||
} else if (typeof ArrayBuffer !== "undefined" && data instanceof ArrayBuffer) {
|
||||
if (typeof Buffer === "undefined") {
|
||||
throw new Error("Buffer support required to handle an ArrayBuffer");
|
||||
}
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed an ArrayBuffer");
|
||||
}
|
||||
buffer = new Buffer(new Uint8Array(data));
|
||||
self.source = new DataMan.Buffer(buffer, type);
|
||||
} else if (EJSON.isBinary(data)) {
|
||||
if (typeof Buffer === "undefined") {
|
||||
throw new Error("Buffer support required to handle an ArrayBuffer");
|
||||
}
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed a Uint8Array");
|
||||
}
|
||||
buffer = new Buffer(data);
|
||||
self.source = new DataMan.Buffer(buffer, type);
|
||||
} else if (typeof Readable !== "undefined" && data instanceof Readable) {
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed a stream.Readable");
|
||||
}
|
||||
self.source = new DataMan.ReadStream(data, type);
|
||||
} else if (typeof data === "string") {
|
||||
if (data.slice(0, 5) === "data:") {
|
||||
self.source = new DataMan.DataURI(data);
|
||||
} else if (data.slice(0, 5) === "http:" || data.slice(0, 6) === "https:") {
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed a URL");
|
||||
}
|
||||
self.source = new DataMan.URL(data, type, options);
|
||||
} else {
|
||||
// assume it's a filepath
|
||||
self.source = new DataMan.FilePath(data, type);
|
||||
}
|
||||
} else {
|
||||
throw new Error("DataMan constructor received data that it doesn't support");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.getBuffer
|
||||
* @public
|
||||
* @param {function} [callback] callback(err, buffer)
|
||||
* @returns {Buffer|undefined}
|
||||
*
|
||||
* Returns a Buffer representing this data, or passes the Buffer to a callback.
|
||||
*/
|
||||
DataMan.prototype.getBuffer = function dataManGetBuffer(callback) {
|
||||
var self = this;
|
||||
return callback ? self.source.getBuffer(callback) : Meteor.wrapAsync(bind(self.source.getBuffer, self.source))();
|
||||
};
|
||||
|
||||
function _saveToFile(readStream, filePath, callback) {
|
||||
var writeStream = fs.createWriteStream(filePath);
|
||||
writeStream.on('close', Meteor.bindEnvironment(function () {
|
||||
callback();
|
||||
}, function (error) { callback(error); }));
|
||||
writeStream.on('error', Meteor.bindEnvironment(function (error) {
|
||||
callback(error);
|
||||
}, function (error) { callback(error); }));
|
||||
readStream.pipe(writeStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.saveToFile
|
||||
* @public
|
||||
* @param {String} filePath
|
||||
* @param {Function} callback
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Saves this data to a filepath on the local filesystem.
|
||||
*/
|
||||
DataMan.prototype.saveToFile = function dataManSaveToFile(filePath, callback) {
|
||||
var readStream = this.createReadStream();
|
||||
return callback ? _saveToFile(readStream, filePath, callback) : Meteor.wrapAsync(_saveToFile)(readStream, filePath);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.getDataUri
|
||||
* @public
|
||||
* @param {function} [callback] callback(err, dataUri)
|
||||
*
|
||||
* If no callback, returns the data URI.
|
||||
*/
|
||||
DataMan.prototype.getDataUri = function dataManGetDataUri(callback) {
|
||||
var self = this;
|
||||
return callback ? self.source.getDataUri(callback) : Meteor.wrapAsync(bind(self.source.getDataUri, self.source))();
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.createReadStream
|
||||
* @public
|
||||
*
|
||||
* Returns a read stream for the data.
|
||||
*/
|
||||
DataMan.prototype.createReadStream = function dataManCreateReadStream() {
|
||||
return this.source.createReadStream();
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.size
|
||||
* @public
|
||||
* @param {function} [callback] callback(err, size)
|
||||
*
|
||||
* If no callback, returns the size in bytes of the data.
|
||||
*/
|
||||
DataMan.prototype.size = function dataManSize(callback) {
|
||||
var self = this;
|
||||
return callback ? self.source.size(callback) : Meteor.wrapAsync(bind(self.source.size, self.source))();
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.type
|
||||
* @public
|
||||
*
|
||||
* Returns the type of the data.
|
||||
*/
|
||||
DataMan.prototype.type = function dataManType() {
|
||||
return this.source.type();
|
||||
};
|
||||
|
||||
/*
|
||||
* "bind" shim; from underscorejs, but we avoid a dependency
|
||||
*/
|
||||
var slice = Array.prototype.slice;
|
||||
var nativeBind = Function.prototype.bind;
|
||||
var ctor = function(){};
|
||||
function isFunction(obj) {
|
||||
return Object.prototype.toString.call(obj) == '[object Function]';
|
||||
}
|
||||
function bind(func, context) {
|
||||
var args, bound;
|
||||
if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
|
||||
if (!isFunction(func)) throw new TypeError;
|
||||
args = slice.call(arguments, 2);
|
||||
return bound = function() {
|
||||
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
|
||||
ctor.prototype = func.prototype;
|
||||
var self = new ctor;
|
||||
ctor.prototype = null;
|
||||
var result = func.apply(self, args.concat(slice.call(arguments)));
|
||||
if (Object(result) === result) return result;
|
||||
return self;
|
||||
};
|
||||
}
|
||||
82
packages/wekan-cfs-data-man/server/data-man-buffer.js
Normal file
82
packages/wekan-cfs-data-man/server/data-man-buffer.js
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
var bufferStreamReader = Npm.require('buffer-stream-reader');
|
||||
|
||||
/**
|
||||
* @method DataMan.Buffer
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {Buffer} buffer
|
||||
* @param {String} type The data content (MIME) type.
|
||||
*/
|
||||
DataMan.Buffer = function DataManBuffer(buffer, type) {
|
||||
var self = this;
|
||||
self.buffer = buffer;
|
||||
self._type = type;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.Buffer.prototype.getBuffer
|
||||
* @private
|
||||
* @param {function} callback callback(err, buffer)
|
||||
* @returns {Buffer|undefined}
|
||||
*
|
||||
* Passes a Buffer representing the data to a callback.
|
||||
*/
|
||||
DataMan.Buffer.prototype.getBuffer = function dataManBufferGetBuffer(callback) {
|
||||
callback(null, this.buffer);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.Buffer.prototype.getDataUri
|
||||
* @private
|
||||
* @param {function} callback callback(err, dataUri)
|
||||
*
|
||||
* Passes a data URI representing the data in the buffer to a callback.
|
||||
*/
|
||||
DataMan.Buffer.prototype.getDataUri = function dataManBufferGetDataUri(callback) {
|
||||
var self = this;
|
||||
if (!self._type) {
|
||||
callback(new Error("DataMan.getDataUri couldn't get a contentType"));
|
||||
} else {
|
||||
var dataUri = "data:" + self._type + ";base64," + self.buffer.toString("base64");
|
||||
callback(null, dataUri);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.Buffer.prototype.createReadStream
|
||||
* @private
|
||||
*
|
||||
* Returns a read stream for the data.
|
||||
*/
|
||||
DataMan.Buffer.prototype.createReadStream = function dataManBufferCreateReadStream() {
|
||||
return new bufferStreamReader(this.buffer);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.Buffer.prototype.size
|
||||
* @param {function} callback callback(err, size)
|
||||
* @private
|
||||
*
|
||||
* Passes the size in bytes of the data in the buffer to a callback.
|
||||
*/
|
||||
DataMan.Buffer.prototype.size = function dataManBufferSize(callback) {
|
||||
var self = this;
|
||||
|
||||
if (typeof self._size === "number") {
|
||||
callback(null, self._size);
|
||||
return;
|
||||
}
|
||||
|
||||
self._size = self.buffer.length;
|
||||
callback(null, self._size);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.Buffer.prototype.type
|
||||
* @private
|
||||
*
|
||||
* Returns the type of the data.
|
||||
*/
|
||||
DataMan.Buffer.prototype.type = function dataManBufferType() {
|
||||
return this._type;
|
||||
};
|
||||
14
packages/wekan-cfs-data-man/server/data-man-datauri.js
Normal file
14
packages/wekan-cfs-data-man/server/data-man-datauri.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* @method DataMan.DataURI
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {String} dataUri
|
||||
*/
|
||||
DataMan.DataURI = function DataManDataURI(dataUri) {
|
||||
var self = this;
|
||||
var pieces = dataUri.match(/^data:(.*);base64,(.*)$/);
|
||||
var buffer = new Buffer(pieces[2], 'base64');
|
||||
return new DataMan.Buffer(buffer, pieces[1]);
|
||||
};
|
||||
|
||||
DataMan.DataURI.prototype = DataMan.Buffer.prototype;
|
||||
108
packages/wekan-cfs-data-man/server/data-man-filepath.js
Normal file
108
packages/wekan-cfs-data-man/server/data-man-filepath.js
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
var mime = Npm.require('mime');
|
||||
var fs = Npm.require("fs");
|
||||
|
||||
/**
|
||||
* @method DataMan.FilePath
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {String} filepath
|
||||
* @param {String} [type] The data content (MIME) type. Will lookup from file if not passed.
|
||||
*/
|
||||
DataMan.FilePath = function DataManFilePath(filepath, type) {
|
||||
var self = this;
|
||||
self.filepath = filepath;
|
||||
self._type = type || mime.lookup(filepath);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.FilePath.prototype.getBuffer
|
||||
* @private
|
||||
* @param {function} callback callback(err, buffer)
|
||||
* @returns {Buffer|undefined}
|
||||
*
|
||||
* Passes a Buffer representing the data to a callback.
|
||||
*/
|
||||
DataMan.FilePath.prototype.getBuffer = function dataManFilePathGetBuffer(callback) {
|
||||
var self = this;
|
||||
|
||||
// Call node readFile
|
||||
fs.readFile(self.filepath, Meteor.bindEnvironment(function(err, buffer) {
|
||||
callback(err, buffer);
|
||||
}, function(err) {
|
||||
callback(err);
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.FilePath.prototype.getDataUri
|
||||
* @private
|
||||
* @param {function} callback callback(err, dataUri)
|
||||
*
|
||||
* Passes a data URI representing the data to a callback.
|
||||
*/
|
||||
DataMan.FilePath.prototype.getDataUri = function dataManFilePathGetDataUri(callback) {
|
||||
var self = this;
|
||||
|
||||
self.getBuffer(function (error, buffer) {
|
||||
if (error) {
|
||||
callback(error);
|
||||
} else {
|
||||
if (!self._type) {
|
||||
callback(new Error("DataMan.getDataUri couldn't get a contentType"));
|
||||
} else {
|
||||
var dataUri = "data:" + self._type + ";base64," + buffer.toString("base64");
|
||||
buffer = null;
|
||||
callback(null, dataUri);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.FilePath.prototype.createReadStream
|
||||
* @private
|
||||
*
|
||||
* Returns a read stream for the data.
|
||||
*/
|
||||
DataMan.FilePath.prototype.createReadStream = function dataManFilePathCreateReadStream() {
|
||||
// Stream from filesystem
|
||||
return fs.createReadStream(this.filepath);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.FilePath.prototype.size
|
||||
* @param {function} callback callback(err, size)
|
||||
* @private
|
||||
*
|
||||
* Passes the size in bytes of the data to a callback.
|
||||
*/
|
||||
DataMan.FilePath.prototype.size = function dataManFilePathSize(callback) {
|
||||
var self = this;
|
||||
|
||||
if (typeof self._size === "number") {
|
||||
callback(null, self._size);
|
||||
return;
|
||||
}
|
||||
|
||||
// We can get the size without buffering
|
||||
fs.stat(self.filepath, Meteor.bindEnvironment(function (error, stats) {
|
||||
if (stats && typeof stats.size === "number") {
|
||||
self._size = stats.size;
|
||||
callback(null, self._size);
|
||||
} else {
|
||||
callback(error);
|
||||
}
|
||||
}, function (error) {
|
||||
callback(error);
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.FilePath.prototype.type
|
||||
* @private
|
||||
*
|
||||
* Returns the type of the data.
|
||||
*/
|
||||
DataMan.FilePath.prototype.type = function dataManFilePathType() {
|
||||
return this._type;
|
||||
};
|
||||
80
packages/wekan-cfs-data-man/server/data-man-readstream.js
Normal file
80
packages/wekan-cfs-data-man/server/data-man-readstream.js
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/* global DataMan */
|
||||
|
||||
var PassThrough = Npm.require('stream').PassThrough;
|
||||
|
||||
/**
|
||||
* @method DataMan.ReadStream
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {ReadStream} stream
|
||||
* @param {String} type The data content (MIME) type.
|
||||
*/
|
||||
DataMan.ReadStream = function DataManBuffer(stream, type) {
|
||||
var self = this;
|
||||
|
||||
// Create a bufferable / paused new stream...
|
||||
var pt = new PassThrough();
|
||||
|
||||
// Pipe provided read stream into pass-through stream
|
||||
stream.pipe(pt);
|
||||
|
||||
// Set pass-through stream reference
|
||||
self.stream = pt;
|
||||
|
||||
// Set type as provided
|
||||
self._type = type;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.ReadStream.prototype.getBuffer
|
||||
* @private
|
||||
* @param {function} callback callback(err, buffer)
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Passes a Buffer representing the data to a callback.
|
||||
*/
|
||||
DataMan.ReadStream.prototype.getBuffer = function dataManReadStreamGetBuffer(/*callback*/) {
|
||||
// TODO implement as passthrough stream?
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.ReadStream.prototype.getDataUri
|
||||
* @private
|
||||
* @param {function} callback callback(err, dataUri)
|
||||
*
|
||||
* Passes a data URI representing the data in the stream to a callback.
|
||||
*/
|
||||
DataMan.ReadStream.prototype.getDataUri = function dataManReadStreamGetDataUri(/*callback*/) {
|
||||
// TODO implement as passthrough stream?
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.ReadStream.prototype.createReadStream
|
||||
* @private
|
||||
*
|
||||
* Returns a read stream for the data.
|
||||
*/
|
||||
DataMan.ReadStream.prototype.createReadStream = function dataManReadStreamCreateReadStream() {
|
||||
return this.stream;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.ReadStream.prototype.size
|
||||
* @param {function} callback callback(err, size)
|
||||
* @private
|
||||
*
|
||||
* Passes the size in bytes of the data in the stream to a callback.
|
||||
*/
|
||||
DataMan.ReadStream.prototype.size = function dataManReadStreamSize(callback) {
|
||||
callback(0); // will determine from stream later
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.ReadStream.prototype.type
|
||||
* @private
|
||||
*
|
||||
* Returns the type of the data.
|
||||
*/
|
||||
DataMan.ReadStream.prototype.type = function dataManReadStreamType() {
|
||||
return this._type;
|
||||
};
|
||||
133
packages/wekan-cfs-data-man/server/data-man-url.js
Normal file
133
packages/wekan-cfs-data-man/server/data-man-url.js
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
var request = Npm.require("request");
|
||||
|
||||
/**
|
||||
* @method DataMan.URL
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {String} url
|
||||
* @param {String} type The data content (MIME) type.
|
||||
*/
|
||||
DataMan.URL = function DataManURL(url, type, options) {
|
||||
var self = this;
|
||||
options = options || {};
|
||||
|
||||
self.url = url;
|
||||
self._type = type;
|
||||
|
||||
// This is some code borrowed from the http package. Hopefully
|
||||
// we can eventually use HTTP pkg directly instead of 'request'
|
||||
// once it supports streams and buffers and such. (`request` takes
|
||||
// and `auth` option, too, but not of the same form as `HTTP`.)
|
||||
if (options.auth) {
|
||||
if (options.auth.indexOf(':') < 0)
|
||||
throw new Error('auth option should be of the form "username:password"');
|
||||
options.headers = options.headers || {};
|
||||
options.headers['Authorization'] = "Basic "+
|
||||
(new Buffer(options.auth, "ascii")).toString("base64");
|
||||
delete options.auth;
|
||||
}
|
||||
|
||||
self.urlOpts = options;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.URL.prototype.getBuffer
|
||||
* @private
|
||||
* @param {function} callback callback(err, buffer)
|
||||
* @returns {Buffer|undefined}
|
||||
*
|
||||
* Passes a Buffer representing the data at the URL to a callback.
|
||||
*/
|
||||
DataMan.URL.prototype.getBuffer = function dataManUrlGetBuffer(callback) {
|
||||
var self = this;
|
||||
|
||||
request(_.extend({
|
||||
url: self.url,
|
||||
method: "GET",
|
||||
encoding: null,
|
||||
jar: false
|
||||
}, self.urlOpts), Meteor.bindEnvironment(function(err, res, body) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
self._type = res.headers['content-type'];
|
||||
callback(null, body);
|
||||
}
|
||||
}, function(err) {
|
||||
callback(err);
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.URL.prototype.getDataUri
|
||||
* @private
|
||||
* @param {function} callback callback(err, dataUri)
|
||||
*
|
||||
* Passes a data URI representing the data at the URL to a callback.
|
||||
*/
|
||||
DataMan.URL.prototype.getDataUri = function dataManUrlGetDataUri(callback) {
|
||||
var self = this;
|
||||
|
||||
self.getBuffer(function (error, buffer) {
|
||||
if (error) {
|
||||
callback(error);
|
||||
} else {
|
||||
if (!self._type) {
|
||||
callback(new Error("DataMan.getDataUri couldn't get a contentType"));
|
||||
} else {
|
||||
var dataUri = "data:" + self._type + ";base64," + buffer.toString("base64");
|
||||
callback(null, dataUri);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.URL.prototype.createReadStream
|
||||
* @private
|
||||
*
|
||||
* Returns a read stream for the data.
|
||||
*/
|
||||
DataMan.URL.prototype.createReadStream = function dataManUrlCreateReadStream() {
|
||||
var self = this;
|
||||
// Stream from URL
|
||||
return request(_.extend({
|
||||
url: self.url,
|
||||
method: "GET"
|
||||
}, self.urlOpts));
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.URL.prototype.size
|
||||
* @param {function} callback callback(err, size)
|
||||
* @private
|
||||
*
|
||||
* Returns the size in bytes of the data at the URL.
|
||||
*/
|
||||
DataMan.URL.prototype.size = function dataManUrlSize(callback) {
|
||||
var self = this;
|
||||
|
||||
if (typeof self._size === "number") {
|
||||
callback(null, self._size);
|
||||
return;
|
||||
}
|
||||
|
||||
self.getBuffer(function (error, buffer) {
|
||||
if (error) {
|
||||
callback(error);
|
||||
} else {
|
||||
self._size = buffer.length;
|
||||
callback(null, self._size);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.URL.prototype.type
|
||||
* @private
|
||||
*
|
||||
* Returns the type of the data.
|
||||
*/
|
||||
DataMan.URL.prototype.type = function dataManUrlType() {
|
||||
return this._type;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue