diff --git a/packages/rocketchat-file-upload/package.js b/packages/rocketchat-file-upload/package.js index 1a7a050d427..9a9b53b11b0 100644 --- a/packages/rocketchat-file-upload/package.js +++ b/packages/rocketchat-file-upload/package.js @@ -13,6 +13,7 @@ Package.onUse(function(api) { api.use('rocketchat:lib'); api.use('random'); api.use('underscore'); + api.use('webapp'); api.addFiles('globalFileRestrictions.js'); @@ -24,6 +25,7 @@ Package.onUse(function(api) { api.addFiles('client/lib/fileUploadHandler.js', 'client'); api.addFiles('server/configS3.js', 'server'); + api.addFiles('server/requests.js', 'server'); api.addFiles('server/settings.js', 'server'); api.addFiles('server/methods/sendFileMessage.js', 'server'); diff --git a/packages/rocketchat-file-upload/server/configS3.js b/packages/rocketchat-file-upload/server/configS3.js index 6122013d2f6..414073db982 100644 --- a/packages/rocketchat-file-upload/server/configS3.js +++ b/packages/rocketchat-file-upload/server/configS3.js @@ -17,14 +17,8 @@ createS3Directive = _.debounce(() => { AWSSecretAccessKey: secretKey, key: function (file, metaContext) { var serverIdentifier = ''; - - // @TODO what should we do when a file already exists with the given name? var path = serverIdentifier + this.userId + '-' + metaContext.rid + "/"; - if (file.name) { - return path + file.name; - } else { - return path + Random.id(); - } + return path + Random.id(); } }; diff --git a/packages/rocketchat-file-upload/server/methods/sendFileMessage.js b/packages/rocketchat-file-upload/server/methods/sendFileMessage.js index aa552116422..1658702a151 100644 --- a/packages/rocketchat-file-upload/server/methods/sendFileMessage.js +++ b/packages/rocketchat-file-upload/server/methods/sendFileMessage.js @@ -18,23 +18,26 @@ Meteor.methods({ fileId = file._id; } + var fileUrl = '/file-upload/' + fileId + '/' + file.name; + var attachment = { title: `File Uploaded: ${file.name}`, - title_link: file.url + title_link: fileUrl }; if (/^image\/.+/.test(file.type)) { - attachment.image_url = file.url; + attachment.image_url = fileUrl; attachment.image_type = file.type; attachment.image_size = file.size; - // @TODO - // attachment.image_dimensions = file.identify?.size; + if (file.identify && file.identify.size) { + attachment.image_dimensions = file.identify.size; + } } else if (/^audio\/.+/.test(file.type)) { - attachment.audio_url = file.url; + attachment.audio_url = fileUrl; attachment.audio_type = file.type; attachment.audio_size = file.size; } else if (/^video\/.+/.test(file.type)) { - attachment.video_url = file.url; + attachment.video_url = fileUrl; attachment.video_type = file.type; attachment.video_size = file.size; }1 diff --git a/packages/rocketchat-file-upload/server/requests.js b/packages/rocketchat-file-upload/server/requests.js new file mode 100644 index 00000000000..3c6a3a773ce --- /dev/null +++ b/packages/rocketchat-file-upload/server/requests.js @@ -0,0 +1,135 @@ +var crypto = Npm.require("crypto"); +var stream = Npm.require('stream'); +var zlib = Npm.require('zlib'); + +var S3bucket, S3accessKey, S3secretKey, protectedFiles; + +RocketChat.settings.get('FileUpload_S3_Bucket', function(key, value) { + S3bucket = value; +}); +RocketChat.settings.get('FileUpload_S3_AWSAccessKeyId', function(key, value) { + S3accessKey = value; +}); +RocketChat.settings.get('FileUpload_S3_AWSSecretAccessKey', function(key, value) { + S3secretKey = value; +}); + +RocketChat.settings.get('FileUpload_ProtectFiles', function(key, value) { + protectedFiles = value; +}); + +var generateURL = function(fullUrl) { + var resourceURL = '/' + fullUrl.substr(fullUrl.indexOf(S3bucket)); + var expires = parseInt(new Date().getTime() / 1000) + 60; + var StringToSign = 'GET\n\n\n' + expires +'\n'+resourceURL; + + var signature = crypto.createHmac('sha1', S3secretKey).update(new Buffer(StringToSign, "utf-8")).digest('base64'); + return fullUrl + '?AWSAccessKeyId='+encodeURIComponent(S3accessKey)+'&Expires='+expires+'&Signature='+encodeURIComponent(signature); +}; + +// code from: https://github.com/jalik/jalik-ufs/blob/master/ufs-server.js#L91 +var readFromGridFS = function(storeName, fileId, file, req, res) { + var store = UploadFS.getStore(storeName); + var rs = store.getReadStream(fileId, file); + var ws = new stream.PassThrough(); + + rs.on('error', function (err) { + store.onReadError.call(store, err, fileId, file); + res.end(); + }); + ws.on('error', function (err) { + store.onReadError.call(store, err, fileId, file); + res.end(); + }); + ws.on('close', function () { + // Close output stream at the end + ws.emit('end'); + }); + + var accept = req.headers['accept-encoding'] || ''; + var headers = { + 'Content-Type': file.type, + 'Content-Length': file.size + }; + + // Transform stream + store.transformRead(rs, ws, fileId, file, req, headers); + + // Compress data using gzip + if (accept.match(/\bgzip\b/)) { + headers['Content-Encoding'] = 'gzip'; + delete headers['Content-Length']; + res.writeHead(200, headers); + ws.pipe(zlib.createGzip()).pipe(res); + } + // Compress data using deflate + else if (accept.match(/\bdeflate\b/)) { + headers['Content-Encoding'] = 'deflate'; + delete headers['Content-Length']; + res.writeHead(200, headers); + ws.pipe(zlib.createDeflate()).pipe(res); + } + // Send raw data + else { + res.writeHead(200, headers); + ws.pipe(res); + } +}; + +WebApp.connectHandlers.use('/file-upload/', function(req, res, next) { + var file; + + var match = /^\/([^\/]+)\/(.*)/.exec(req.url); + + if (match[1]) { + file = RocketChat.models.Uploads.findOneById(match[1]); + + if (file) { + if (protectedFiles) { + var cookie, rawCookies, ref, token, uid; + cookie = new Cookies(); + + if ((typeof req !== "undefined" && req !== null ? (ref = req.headers) != null ? ref.cookie : void 0 : void 0) != null) { + rawCookies = req.headers.cookie; + } + + if (rawCookies != null) { + uid = cookie.get('rc_uid', rawCookies); + } + + if (rawCookies != null) { + token = cookie.get('rc_token', rawCookies); + } + + if (uid == null) { + uid = req.query.rc_uid; + token = req.query.rc_token; + } + + if (!(uid && token && RocketChat.models.Users.findOneByIdAndLoginToken(uid, token))) { + res.writeHead(403); + res.end(); + return false; + } + } + res.setHeader('Content-Disposition', "attachment; filename=\"" + encodeURIComponent(file.name) + "\""); + res.setHeader('Last-Modified', file.uploadedAt.toUTCString()); + res.setHeader('Content-Type', file.type); + res.setHeader('Content-Length', file.size); + + if (file.store === 's3') { + var newUrl = generateURL(file.url); + res.setHeader('Location', newUrl); + res.writeHead(302); + res.end(); + return; + } else { + return readFromGridFS(file.store, file._id, file, req, res); + } + } + } + + res.writeHead(404); + res.end(); + return; +}); diff --git a/packages/rocketchat-lib/server/models/Uploads.coffee b/packages/rocketchat-lib/server/models/Uploads.coffee index 401fbea8fbd..5011627c125 100644 --- a/packages/rocketchat-lib/server/models/Uploads.coffee +++ b/packages/rocketchat-lib/server/models/Uploads.coffee @@ -5,6 +5,9 @@ RocketChat.models.Uploads = new class extends RocketChat.models._Base @tryEnsureIndex { 'rid': 1 } @tryEnsureIndex { 'uploadedAt': 1 } + findOneById: (fileId) -> + @findOne { _id: fileId } + findNotHiddenFilesOfRoom: (roomId, limit) -> fileQuery = rid: roomId