diff --git a/.changeset/dry-pumpkins-design.md b/.changeset/dry-pumpkins-design.md new file mode 100644 index 0000000000000..fe0c0d25c0efd --- /dev/null +++ b/.changeset/dry-pumpkins-design.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes image orientation issues which were related to `Message_Attachments_Strip_Exif` setting. diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 068bcdf266500..e191c75b8c882 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -1,4 +1,4 @@ -import { FederationMatrix, Media, MeteorError, Team } from '@rocket.chat/core-services'; +import { FederationMatrix, MeteorError, Team } from '@rocket.chat/core-services'; import type { IRoom, IUpload } from '@rocket.chat/core-typings'; import { isPrivateRoom, isPublicRoom } from '@rocket.chat/core-typings'; import { Messages, Rooms, Users, Uploads, Subscriptions } from '@rocket.chat/models'; @@ -208,7 +208,7 @@ API.v1.addRoute( throw new Meteor.Error('invalid-field'); } - let { fileBuffer } = file; + const { fileBuffer } = file; const expiresAt = new Date(); expiresAt.setHours(expiresAt.getHours() + 24); @@ -236,13 +236,6 @@ API.v1.addRoute( expiresAt, }; - const stripExif = settings.get('Message_Attachments_Strip_Exif'); - if (stripExif) { - // No need to check mime. Library will ignore any files without exif/xmp tags (like BMP, ico, PDF, etc) - fileBuffer = await Media.stripExifFromBuffer(fileBuffer); - details.size = fileBuffer.length; - } - const fileStore = FileUpload.getStore('Uploads'); const uploadedFile = await fileStore.insert(details, fileBuffer); diff --git a/apps/meteor/app/file-upload/server/lib/FileUpload.ts b/apps/meteor/app/file-upload/server/lib/FileUpload.ts index 73f67fb473744..7b4e92ed471d8 100644 --- a/apps/meteor/app/file-upload/server/lib/FileUpload.ts +++ b/apps/meteor/app/file-upload/server/lib/FileUpload.ts @@ -5,6 +5,7 @@ import { unlink, rename, writeFile } from 'fs/promises'; import type * as http from 'http'; import type * as https from 'https'; import stream from 'stream'; +import { pipeline } from 'stream/promises'; import URL from 'url'; import { hashLoginToken } from '@rocket.chat/account-utils'; @@ -13,6 +14,7 @@ import { AppsEngineException } from '@rocket.chat/apps-engine/definition/excepti import { isE2EEUpload, type IUpload } from '@rocket.chat/core-typings'; import { Users, Avatars, UserDataFiles, Uploads, Settings, Subscriptions, Messages, Rooms } from '@rocket.chat/models'; import type { NextFunction } from 'connect'; +import ExifTransformer from 'exif-be-gone'; import filesize from 'filesize'; import { Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -394,8 +396,9 @@ export const FileUpload = { return; } - await s.rotate().toFile(`${tmpFile}.tmp`); - + // By default, the metadata is not preserved when rotating the image. + await s.rotate().withMetadata().toFile(`${tmpFile}.tmp`); + console.log('rotating image', file._id); await unlink(tmpFile); await rename(`${tmpFile}.tmp`, tmpFile); @@ -404,6 +407,39 @@ export const FileUpload = { await reorientation(); + const stripExif = settings.get('Message_Attachments_Strip_Exif'); + + if (stripExif) { + const exifTmpPath = `${tmpFile}.exif-stripped`; + + try { + await new Promise((resolve, reject) => { + const readStream = fs.createReadStream(tmpFile); + const writeStream = fs.createWriteStream(exifTmpPath); + const exifTransformer = new ExifTransformer(); + + readStream.pipe(exifTransformer).pipe(writeStream); + writeStream.on('finish', () => resolve()); + readStream.on('error', reject); + writeStream.on('error', reject); + }); + // No need to check mime. Library will ignore any files without exif/xmp tags (like BMP, ico, PDF, etc) + // const exifTransformer = new ExifTransformer(); + // const readStream = fs.createReadStream(tmpFile); + // const writeStream = fs.createWriteStream(exifTmpPath); + + // readStream.pipe(exifTransformer).pipe(writeStream); + + // await pipeline(fs.createReadStream(tmpFile), exifTransformer, fs.createWriteStream(exifTmpPath)); + + await rename(exifTmpPath, tmpFile); + } catch (err) { + console.log('ERROR', err); + await unlink(exifTmpPath); + SystemLogger.error(`Error stripping exif from image: ${err}`); + } + } + const { size } = await fs.lstatSync(tmpFile); await this.getCollection().updateOne( { _id: file._id }, @@ -833,7 +869,7 @@ export class FileUploadClass { if (filter?.check) { await filter.check(fileData, streamOrBuffer); } - + console.trace('insert'); return this._doInsert(fileData, streamOrBuffer, { session: options?.session }); } }