Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions app/api/server/lib/getUploadFormData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Busboy from 'busboy';

export const getUploadFormData = async ({ request }) => new Promise((resolve, reject) => {
const busboy = new Busboy({ headers: request.headers });

const fields = {};

busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
const fileData = [];

file.on('data', (data) => fileData.push(data));

file.on('end', () => {
if (fields.hasOwnProperty(fieldname)) {
return reject('Just 1 file is allowed');
}

fields[fieldname] = { file, filename, encoding, mimetype, fileBuffer: Buffer.concat(fileData) };
});
});

busboy.on('field', (fieldname, value) => { fields[fieldname] = value; });

busboy.on('finish', () => resolve(fields));

request.pipe(busboy);
});
70 changes: 16 additions & 54 deletions app/api/server/v1/rooms.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Meteor } from 'meteor/meteor';
import Busboy from 'busboy';

import { FileUpload } from '../../../file-upload';
import { Rooms, Messages } from '../../../models';
Expand All @@ -9,6 +8,7 @@ import { sendFile, sendViaEmail } from '../../../../server/lib/channelExport';
import { canAccessRoom, hasPermission } from '../../../authorization/server';
import { Media } from '../../../../server/sdk';
import { settings } from '../../../settings/server/index';
import { getUploadFormData } from '../lib/getUploadFormData';

function findRoomByIdOrName({ params, checkedArchived = true }) {
if ((!params.roomId || !params.roomId.trim()) && (!params.roomName || !params.roomName.trim())) {
Expand Down Expand Up @@ -63,33 +63,6 @@ API.v1.addRoute('rooms.get', { authRequired: true }, {
},
});

const getFiles = Meteor.wrapAsync(({ request }, callback) => {
const busboy = new Busboy({ headers: request.headers });
const files = [];

const fields = {};


busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
if (fieldname !== 'file') {
return callback(new Meteor.Error('invalid-field'));
}

const fileDate = [];
file.on('data', (data) => fileDate.push(data));

file.on('end', () => {
files.push({ fieldname, file, filename, encoding, mimetype, fileBuffer: Buffer.concat(fileDate) });
});
});

busboy.on('field', (fieldname, value) => { fields[fieldname] = value; });

busboy.on('finish', Meteor.bindEnvironment(() => callback(null, { files, fields })));

request.pipe(busboy);
});

API.v1.addRoute('rooms.upload/:rid', { authRequired: true }, {
post() {
const room = Meteor.call('canAccessRoom', this.urlParams.rid, this.userId);
Expand All @@ -98,21 +71,14 @@ API.v1.addRoute('rooms.upload/:rid', { authRequired: true }, {
return API.v1.unauthorized();
}


const { files, fields } = getFiles({
const { file, ...fields } = Promise.await(getUploadFormData({
request: this.request,
});

if (files.length === 0) {
return API.v1.failure('File required');
}
}));

if (files.length > 1) {
return API.v1.failure('Just 1 file is allowed');
if (!file) {
throw new Meteor.Error('invalid-field');
}

const file = files[0];

const details = {
name: file.filename,
size: file.fileBuffer.length,
Expand All @@ -121,25 +87,21 @@ API.v1.addRoute('rooms.upload/:rid', { authRequired: true }, {
userId: this.userId,
};

const fileData = Meteor.runAsUser(this.userId, () => {
const stripExif = settings.get('Message_Attachments_Strip_Exif');
const fileStore = FileUpload.getStore('Uploads');
if (stripExif) {
// No need to check mime. Library will ignore any files without exif/xmp tags (like BMP, ico, PDF, etc)
file.fileBuffer = Promise.await(Media.stripExifFromBuffer(file.fileBuffer));
}
const uploadedFile = fileStore.insertSync(details, file.fileBuffer);

uploadedFile.description = fields.description;
const stripExif = settings.get('Message_Attachments_Strip_Exif');
const fileStore = FileUpload.getStore('Uploads');
if (stripExif) {
// No need to check mime. Library will ignore any files without exif/xmp tags (like BMP, ico, PDF, etc)
file.fileBuffer = Promise.await(Media.stripExifFromBuffer(file.fileBuffer));
}
const uploadedFile = fileStore.insertSync(details, file.fileBuffer);

delete fields.description;
uploadedFile.description = fields.description;

Meteor.call('sendFileMessage', this.urlParams.rid, null, uploadedFile, fields);
delete fields.description;

return uploadedFile;
});
Meteor.call('sendFileMessage', this.urlParams.rid, null, uploadedFile, fields);

return API.v1.success({ message: Messages.getMessageByFileIdAndUsername(fileData._id, this.userId) });
return API.v1.success({ message: Messages.getMessageByFileIdAndUsername(uploadedFile._id, this.userId) });
},
});

Expand Down
33 changes: 9 additions & 24 deletions app/apps/server/communication/rest.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Meteor } from 'meteor/meteor';
import { HTTP } from 'meteor/http';
import Busboy from 'busboy';

import { API } from '../../../api/server';
import { getUploadFormData } from '../../../api/server/lib/getUploadFormData';
import { getWorkspaceAccessToken, getUserCloudAccessToken } from '../../../cloud/server';
import { settings } from '../../../settings';
import { Info } from '../../../utils';
Expand All @@ -24,24 +24,6 @@ export class AppsRestApi {
this.loadAPI();
}

_handleMultipartFormData(request) {
const busboy = new Busboy({ headers: request.headers });
return Meteor.wrapAsync((callback) => {
const formFields = {};
busboy.on('file', Meteor.bindEnvironment((fieldname, file) => {
const fileData = [];
file.on('data', Meteor.bindEnvironment((data) => {
fileData.push(data);
}));

file.on('end', Meteor.bindEnvironment(() => { formFields[fieldname] = Buffer.concat(fileData); }));
}));
busboy.on('field', (fieldname, val) => { formFields[fieldname] = val; });
busboy.on('finish', Meteor.bindEnvironment(() => callback(undefined, formFields)));
request.pipe(busboy);
})();
}

async loadAPI() {
this.api = new API.ApiClass({
version: 'apps',
Expand All @@ -56,7 +38,6 @@ export class AppsRestApi {
addManagementRoutes() {
const orchestrator = this._orch;
const manager = this._manager;
const multipartFormDataHandler = this._handleMultipartFormData;

const handleError = (message, e) => {
// when there is no `response` field in the error, it means the request
Expand Down Expand Up @@ -239,8 +220,10 @@ export class AppsRestApi {
return API.v1.failure({ error: 'Direct installation of an App is disabled.' });
}

const formData = multipartFormDataHandler(this.request);
buff = formData?.app;
const formData = Promise.await(getUploadFormData({
request: this.request,
}));
buff = formData?.app?.fileBuffer;
permissionsGranted = (() => {
try {
const permissions = JSON.parse(formData?.permissions || '');
Expand Down Expand Up @@ -460,8 +443,10 @@ export class AppsRestApi {
return API.v1.failure({ error: 'Direct updating of an App is disabled.' });
}

const formData = multipartFormDataHandler(this.request);
buff = formData?.app;
const formData = Promise.await(getUploadFormData({
request: this.request,
}));
buff = formData?.app?.fileBuffer;
permissionsGranted = (() => {
try {
const permissions = JSON.parse(formData?.permissions || '');
Expand Down
41 changes: 5 additions & 36 deletions app/livechat/imports/server/rest/upload.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Meteor } from 'meteor/meteor';
import Busboy from 'busboy';
import filesize from 'filesize';

import { settings } from '../../../../settings';
import { Settings, LivechatRooms, LivechatVisitors } from '../../../../models';
import { fileUploadIsValidContentType } from '../../../../utils';
import { FileUpload } from '../../../../file-upload';
import { API } from '../../../../api/server';
import { getUploadFormData } from '../../../../api/server/lib/getUploadFormData';

let maxFileSize;

Expand Down Expand Up @@ -36,40 +36,9 @@ API.v1.addRoute('livechat/upload/:rid', {
return API.v1.unauthorized();
}

const busboy = new Busboy({ headers: this.request.headers });
const files = [];
const fields = {};

Meteor.wrapAsync((callback) => {
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
if (fieldname !== 'file') {
return files.push(new Meteor.Error('invalid-field'));
}

const fileDate = [];
file.on('data', (data) => fileDate.push(data));

file.on('end', () => {
files.push({ fieldname, file, filename, encoding, mimetype, fileBuffer: Buffer.concat(fileDate) });
});
});

busboy.on('field', (fieldname, value) => { fields[fieldname] = value; });

busboy.on('finish', Meteor.bindEnvironment(() => callback()));

this.request.pipe(busboy);
})();

if (files.length === 0) {
return API.v1.failure('File required');
}

if (files.length > 1) {
return API.v1.failure('Just 1 file is allowed');
}

const file = files[0];
const { file, ...fields } = Promise.await(getUploadFormData({
request: this.request,
}));

if (!fileUploadIsValidContentType(file.mimetype)) {
return API.v1.failure({
Expand All @@ -95,7 +64,7 @@ API.v1.addRoute('livechat/upload/:rid', {
visitorToken,
};

const uploadedFile = Meteor.wrapAsync(fileStore.insert.bind(fileStore))(details, file.fileBuffer);
const uploadedFile = fileStore.insertSync(details, file.fileBuffer);
if (!uploadedFile) {
return API.v1.error('Invalid file');
}
Expand Down