mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-01-28 02:41:58 +08:00
Decode base64 encoded attachments before storing to DB
This commit is contained in:
parent
9b54586e2a
commit
ee4a76e30e
6 changed files with 85 additions and 26 deletions
9
config/attachments.toml
Normal file
9
config/attachments.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Attachment storage options
|
||||
|
||||
# For now there's only a single option for attachment storage
|
||||
type="gridstore"
|
||||
bucket="attachments"
|
||||
|
||||
# If true then decodes base64 encoded attachments to binary before storing to DB.
|
||||
# Decoding base64 attachments expects consistent line length and default base64 alphabet
|
||||
decodeBase64=true
|
|
@ -37,9 +37,7 @@ bugsnagCode=""
|
|||
#secret="a secret cat"
|
||||
|
||||
[attachments]
|
||||
# For now there's only a single option for attachment storage
|
||||
type="gridstore"
|
||||
bucket="attachments"
|
||||
# @include "attachments.toml"
|
||||
|
||||
[log]
|
||||
level="silly"
|
||||
|
|
|
@ -193,18 +193,24 @@ class Indexer {
|
|||
attachmentId = mimeTree.attachmentMap[node.attachmentId];
|
||||
}
|
||||
|
||||
let attachmentStream = this.attachmentStorage.createReadStream(attachmentId);
|
||||
return this.attachmentStorage.get(attachmentId, (err, attachmentData) => {
|
||||
if (err) {
|
||||
return res.emit('error', err);
|
||||
}
|
||||
|
||||
attachmentStream.once('error', err => {
|
||||
res.errored = err;
|
||||
let attachmentStream = this.attachmentStorage.createReadStream(attachmentId, attachmentData);
|
||||
|
||||
attachmentStream.once('error', err => {
|
||||
// res.errored = err;
|
||||
res.emit('error', err);
|
||||
});
|
||||
|
||||
attachmentStream.once('end', () => finalize());
|
||||
|
||||
attachmentStream.pipe(res, {
|
||||
end: false
|
||||
});
|
||||
});
|
||||
|
||||
attachmentStream.once('end', () => finalize());
|
||||
|
||||
attachmentStream.pipe(res, {
|
||||
end: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let pos = 0;
|
||||
|
@ -399,6 +405,7 @@ class Indexer {
|
|||
magic: maildata.magic,
|
||||
contentType,
|
||||
transferEncoding,
|
||||
lineCount: node.lineCount,
|
||||
body: node.body
|
||||
});
|
||||
|
||||
|
|
|
@ -885,10 +885,21 @@ module.exports = (db, server, messageHandler) => {
|
|||
'Content-Type': attachmentData.contentType || 'application/octet-stream'
|
||||
});
|
||||
|
||||
let attachmentStream = messageHandler.attachmentStorage.createReadStream(attachmentId);
|
||||
let decode = true;
|
||||
|
||||
if (attachmentData.metadata.decoded) {
|
||||
attachmentData.metadata.decoded = false;
|
||||
decode = false;
|
||||
}
|
||||
|
||||
let attachmentStream = messageHandler.attachmentStorage.createReadStream(attachmentId, attachmentData);
|
||||
|
||||
attachmentStream.once('error', err => res.emit('error', err));
|
||||
|
||||
if (!decode) {
|
||||
return attachmentStream.pipe(res);
|
||||
}
|
||||
|
||||
if (attachmentData.transferEncoding === 'base64') {
|
||||
attachmentStream.pipe(new libbase64.Decoder()).pipe(res);
|
||||
} else if (attachmentData.transferEncoding === 'quoted-printable') {
|
||||
|
|
|
@ -36,8 +36,8 @@ class AttachmentStorage {
|
|||
});
|
||||
}
|
||||
|
||||
createReadStream(id) {
|
||||
return this.storage.createReadStream(id);
|
||||
createReadStream(id, attachmentData) {
|
||||
return this.storage.createReadStream(id, attachmentData);
|
||||
}
|
||||
|
||||
deleteMany(ids, magic, callback) {
|
||||
|
@ -68,7 +68,15 @@ class AttachmentStorage {
|
|||
let algo = 'sha256';
|
||||
|
||||
if (!cryptoAsync) {
|
||||
setImmediate(() => callback(null, crypto.createHash(algo).update(input).digest('hex')));
|
||||
setImmediate(() =>
|
||||
callback(
|
||||
null,
|
||||
crypto
|
||||
.createHash(algo)
|
||||
.update(input)
|
||||
.digest('hex')
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
const GridFSBucket = require('mongodb').GridFSBucket;
|
||||
const libbase64 = require('libbase64');
|
||||
|
||||
const FEATURE_DECODE_ATTACHMENTS = false;
|
||||
// Set to false to disable base64 decoding feature
|
||||
const FEATURE_DECODE_ATTACHMENTS = true;
|
||||
|
||||
class GridstoreStorage {
|
||||
constructor(options) {
|
||||
|
@ -87,8 +88,18 @@ class GridstoreStorage {
|
|||
}
|
||||
|
||||
if (lineLen && lineLen <= 998) {
|
||||
metadata.decoded = true;
|
||||
metadata.lineLen = lineLen;
|
||||
if (attachment.body.length === lineLen && lineLen < 76) {
|
||||
lineLen = 76;
|
||||
}
|
||||
|
||||
// check if expected line count matches with attachment line count
|
||||
let expectedLineCount = Math.ceil(attachment.body.length / (lineLen + 2));
|
||||
|
||||
// allow 1 line shift
|
||||
if (attachment.lineCount >= expectedLineCount - 1 && attachment.lineCount <= expectedLineCount + 1) {
|
||||
metadata.decoded = true;
|
||||
metadata.lineLen = lineLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,8 +173,23 @@ class GridstoreStorage {
|
|||
tryStore();
|
||||
}
|
||||
|
||||
createReadStream(id) {
|
||||
return this.gridstore.openDownloadStream(id);
|
||||
createReadStream(id, attachmentData) {
|
||||
let stream = this.gridstore.openDownloadStream(id);
|
||||
if (attachmentData.metadata.decoded) {
|
||||
let encoder = new libbase64.Encoder({
|
||||
lineLength: attachmentData.metadata.lineLen
|
||||
});
|
||||
|
||||
stream.once('error', err => {
|
||||
// pass error forward
|
||||
encoder.emit('error', err);
|
||||
});
|
||||
stream.pipe(encoder);
|
||||
|
||||
return encoder;
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
delete(id, magic, callback) {
|
||||
|
@ -254,7 +280,7 @@ class GridstoreStorage {
|
|||
}
|
||||
|
||||
// delete file entry first
|
||||
this.gridfs.collection('attachments.files').deleteOne({
|
||||
this.gridfs.collection(this.bucketName + '.files').deleteOne({
|
||||
_id: attachment._id,
|
||||
// make sure that we do not delete a message that is already re-used
|
||||
'metadata.c': 0,
|
||||
|
@ -265,7 +291,7 @@ class GridstoreStorage {
|
|||
}
|
||||
|
||||
// delete data chunks
|
||||
this.gridfs.collection('attachments.chunks').deleteMany({
|
||||
this.gridfs.collection(this.bucketName + '.chunks').deleteMany({
|
||||
files_id: attachment._id
|
||||
}, err => {
|
||||
if (err) {
|
||||
|
@ -283,7 +309,7 @@ class GridstoreStorage {
|
|||
}
|
||||
|
||||
cleanupGarbage(id, next) {
|
||||
this.gridfs.collection('attachments.files').findOne({
|
||||
this.gridfs.collection(this.bucketName + '.files').findOne({
|
||||
_id: id
|
||||
}, (err, file) => {
|
||||
if (err) {
|
||||
|
@ -295,7 +321,7 @@ class GridstoreStorage {
|
|||
}
|
||||
|
||||
// orphaned attachment, delete data chunks
|
||||
this.gridfs.collection('attachments.chunks').deleteMany({
|
||||
this.gridfs.collection(this.bucketName + '.chunks').deleteMany({
|
||||
files_id: id
|
||||
}, (err, info) => {
|
||||
if (err) {
|
||||
|
|
Loading…
Reference in a new issue