diff --git a/lib/api-client/gmail-client.js b/lib/api-client/gmail-client.js index bcf4f4968..a1199c685 100644 --- a/lib/api-client/gmail-client.js +++ b/lib/api-client/gmail-client.js @@ -8,6 +8,7 @@ const logger = require('../logger'); const util = require('util'); const addressparser = require('nodemailer/lib/addressparser'); const libmime = require('libmime'); +const he = require('he'); const fs = require('fs'); @@ -37,6 +38,31 @@ const SYSTEM_NAMES = { CATEGORY_PROMOTIONS: 'Promotions' }; +/* + +✅ listMessages +✅ getText +✅ getMessage +case 'updateMessage': +case 'updateMessages': +✅ listMailboxes +case 'moveMessage': +case 'moveMessages': +case 'deleteMessage': +case 'deleteMessages': +case 'getRawMessage': +case 'getQuota': +case 'createMailbox': +case 'renameMailbox': +case 'deleteMailbox': +✅ getAttachment +case 'submitMessage': +case 'queueMessage': +case 'uploadMessage': +case 'subconnections': + +*/ + class GmailClient { constructor(account, options) { this.account = account; @@ -75,7 +101,7 @@ class GmailClient { await this.getClient(); } - async listMailboxes(query) { + async listMailboxes(options) { await this.prepare(); const accessToken = await this.getToken(); @@ -85,7 +111,7 @@ class GmailClient { let labels = labelsResult.labels.filter(label => !SKIP_LABELS.includes(label.id)); let resultLabels; - if (query && query.counters) { + if (options && options.counters) { resultLabels = []; for (let label of labels) { let labelResult = await this.oAuth2Client.request(accessToken, `${GMAIL_API_BASE}/gmail/v1/users/me/labels/${label.id}`); @@ -212,9 +238,11 @@ class GmailClient { if (!/^multipart\//.test(node.mimeType)) { if (node.body.attachmentId) { - let attachment = { + const attachmentIdProps = [messageData.id, node.mimeType || null, disposition?.value || null, node.filename || null]; + + const attachment = { // append body part nr to message id - id: `${messageData.id}_${node.body.attachmentId}`, + id: `${msgpack.encode(attachmentIdProps).toString('base64url')}.${node.body.attachmentId}`, contentType: node.mimeType, encodedSize: node.body.size, @@ -474,11 +502,11 @@ class GmailClient { } async getAttachmentContent(attachmentId) { - let sepPos = attachmentId.indexOf('_'); + let sepPos = attachmentId.indexOf('.'); if (sepPos < 0) { return null; } - const messageId = attachmentId.substring(0, sepPos); + const [messageId, contentType, disposition, filename] = msgpack.decode(Buffer.from(attachmentId.substring(0, sepPos), 'base64url')); const id = attachmentId.substring(sepPos + 1); await this.prepare(); @@ -493,9 +521,45 @@ class GmailClient { requestQuery ); - console.log(result); + return { + content: result?.data ? Buffer.from(result?.data, 'base64url') : null, + contentType, + disposition, + filename + }; + } + + async getAttachment(attachmentId) { + let attachmentData = await this.getAttachmentContent(attachmentId); + + if (!attachmentData || !attachmentData.content) { + return false; + } - return result?.data ? Buffer.from(result?.data, 'base64url') : null; + let filenameParam = ''; + if (attachmentData.filename) { + let isCleartextFilename = attachmentData.filename && /^[a-z0-9 _\-()^[\]~=,+*$]$/i.test(attachmentData.filename); + if (isCleartextFilename) { + filenameParam = `; filename=${JSON.stringify(attachmentData.filename)}`; + } else { + filenameParam = `; filename=${JSON.stringify(he.encode(attachmentData.filename))}; filename*=utf-8''${encodeURIComponent( + attachmentData.filename + )}`; + } + } + + const content = { + headers: { + 'content-type': attachmentData.mimeType || 'application/octet-stream', + 'content-disposition': 'attachment' + filenameParam + }, + contentType: attachmentData.contentType, + filename: attachmentData.filename, + disposition: attachmentData.disposition, + data: attachmentData.content + }; + + return content; } async getMessage(messageId, options) { @@ -605,8 +669,21 @@ let main = async () => { let raw = await gmailClient.getRawMessage(msg.id); await fs.promises.writeFile(`/Users/andris/Desktop/${msg.id}.eml`, raw); for (let a of msg.attachments) { - let attachment = await gmailClient.getAttachmentContent(a.id); - await fs.promises.writeFile(`/Users/andris/Desktop/${a.filename}`, attachment); + let attachment = await gmailClient.getAttachment(a.id); + console.log(attachment); + let s = fs.createWriteStream(`/Users/andris/Desktop/${a.filename}`); + + console.log('PIPING TO STREAM'); + await new Promise((r, e) => { + s.once('finish', r); + s.once('error', e); + + s.write(attachment.data); + s.end(); + }); + console.log('DONE'); + + //await fs.promises.writeFile(`/Users/andris/Desktop/${a.filename}`, attachment); process.exit(); } } diff --git a/workers/imap.js b/workers/imap.js index 43e96174d..0954d32ed 100644 --- a/workers/imap.js +++ b/workers/imap.js @@ -538,7 +538,11 @@ class ConnectionHandler { } setImmediate(() => { - source.pipe(stream); + if (Buffer.isBuffer(source.data)) { + stream.end(source.data); + } else { + source.pipe(stream); + } }); return {