aiplang 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/aiplang.js +7 -7
- package/package.json +7 -5
- package/server/node_modules/.package-lock.json +9 -0
- package/server/node_modules/nodemailer/.gitattributes +6 -0
- package/server/node_modules/nodemailer/.ncurc.js +9 -0
- package/server/node_modules/nodemailer/.prettierignore +8 -0
- package/server/node_modules/nodemailer/.prettierrc +12 -0
- package/server/node_modules/nodemailer/.prettierrc.js +10 -0
- package/server/node_modules/nodemailer/.release-please-config.json +9 -0
- package/server/node_modules/nodemailer/CHANGELOG.md +976 -0
- package/server/node_modules/nodemailer/CODE_OF_CONDUCT.md +76 -0
- package/server/node_modules/nodemailer/LICENSE +16 -0
- package/server/node_modules/nodemailer/README.md +86 -0
- package/server/node_modules/nodemailer/SECURITY.txt +22 -0
- package/server/node_modules/nodemailer/eslint.config.js +88 -0
- package/server/node_modules/nodemailer/lib/addressparser/index.js +382 -0
- package/server/node_modules/nodemailer/lib/base64/index.js +140 -0
- package/server/node_modules/nodemailer/lib/dkim/index.js +245 -0
- package/server/node_modules/nodemailer/lib/dkim/message-parser.js +154 -0
- package/server/node_modules/nodemailer/lib/dkim/relaxed-body.js +154 -0
- package/server/node_modules/nodemailer/lib/dkim/sign.js +116 -0
- package/server/node_modules/nodemailer/lib/errors.js +58 -0
- package/server/node_modules/nodemailer/lib/fetch/cookies.js +276 -0
- package/server/node_modules/nodemailer/lib/fetch/index.js +278 -0
- package/server/node_modules/nodemailer/lib/json-transport/index.js +82 -0
- package/server/node_modules/nodemailer/lib/mail-composer/index.js +599 -0
- package/server/node_modules/nodemailer/lib/mailer/index.js +446 -0
- package/server/node_modules/nodemailer/lib/mailer/mail-message.js +312 -0
- package/server/node_modules/nodemailer/lib/mime-funcs/index.js +610 -0
- package/server/node_modules/nodemailer/lib/mime-funcs/mime-types.js +2109 -0
- package/server/node_modules/nodemailer/lib/mime-node/index.js +1334 -0
- package/server/node_modules/nodemailer/lib/mime-node/last-newline.js +33 -0
- package/server/node_modules/nodemailer/lib/mime-node/le-unix.js +40 -0
- package/server/node_modules/nodemailer/lib/mime-node/le-windows.js +49 -0
- package/server/node_modules/nodemailer/lib/nodemailer.js +151 -0
- package/server/node_modules/nodemailer/lib/punycode/index.js +460 -0
- package/server/node_modules/nodemailer/lib/qp/index.js +230 -0
- package/server/node_modules/nodemailer/lib/sendmail-transport/index.js +205 -0
- package/server/node_modules/nodemailer/lib/ses-transport/index.js +223 -0
- package/server/node_modules/nodemailer/lib/shared/index.js +698 -0
- package/server/node_modules/nodemailer/lib/smtp-connection/data-stream.js +105 -0
- package/server/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +144 -0
- package/server/node_modules/nodemailer/lib/smtp-connection/index.js +1903 -0
- package/server/node_modules/nodemailer/lib/smtp-pool/index.js +641 -0
- package/server/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +256 -0
- package/server/node_modules/nodemailer/lib/smtp-transport/index.js +402 -0
- package/server/node_modules/nodemailer/lib/stream-transport/index.js +135 -0
- package/server/node_modules/nodemailer/lib/well-known/index.js +47 -0
- package/server/node_modules/nodemailer/lib/well-known/services.json +619 -0
- package/server/node_modules/nodemailer/lib/xoauth2/index.js +436 -0
- package/server/node_modules/nodemailer/package.json +48 -0
- package/server/server.js +686 -865
- /package/{FLUX-PROJECT-KNOWLEDGE.md → aiplang-knowledge.md} +0 -0
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
/* eslint no-undefined: 0 */
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const MimeNode = require('../mime-node');
|
|
6
|
+
const mimeFuncs = require('../mime-funcs');
|
|
7
|
+
const { parseDataURI } = require('../shared');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates the object for composing a MimeNode instance out from the mail options
|
|
11
|
+
*
|
|
12
|
+
* @constructor
|
|
13
|
+
* @param {Object} mail Mail options
|
|
14
|
+
*/
|
|
15
|
+
class MailComposer {
|
|
16
|
+
constructor(mail) {
|
|
17
|
+
this.mail = mail || {};
|
|
18
|
+
this.message = false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Builds MimeNode instance
|
|
23
|
+
*/
|
|
24
|
+
compile() {
|
|
25
|
+
this._alternatives = this.getAlternatives();
|
|
26
|
+
this._htmlNode = this._alternatives.filter(alternative => /^text\/html\b/i.test(alternative.contentType)).pop();
|
|
27
|
+
this._attachments = this.getAttachments(!!this._htmlNode);
|
|
28
|
+
|
|
29
|
+
this._useRelated = !!(this._htmlNode && this._attachments.related.length);
|
|
30
|
+
this._useAlternative = this._alternatives.length > 1;
|
|
31
|
+
this._useMixed = this._attachments.attached.length > 1 || (this._alternatives.length && this._attachments.attached.length === 1);
|
|
32
|
+
|
|
33
|
+
// Compose MIME tree
|
|
34
|
+
if (this.mail.raw) {
|
|
35
|
+
this.message = new MimeNode('message/rfc822', { newline: this.mail.newline }).setRaw(this.mail.raw);
|
|
36
|
+
} else if (this._useMixed) {
|
|
37
|
+
this.message = this._createMixed();
|
|
38
|
+
} else if (this._useAlternative) {
|
|
39
|
+
this.message = this._createAlternative();
|
|
40
|
+
} else if (this._useRelated) {
|
|
41
|
+
this.message = this._createRelated();
|
|
42
|
+
} else {
|
|
43
|
+
this.message = this._createContentNode(
|
|
44
|
+
false,
|
|
45
|
+
[]
|
|
46
|
+
.concat(this._alternatives || [])
|
|
47
|
+
.concat(this._attachments.attached || [])
|
|
48
|
+
.shift() || {
|
|
49
|
+
contentType: 'text/plain',
|
|
50
|
+
content: ''
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Add custom headers
|
|
56
|
+
if (this.mail.headers) {
|
|
57
|
+
this.message.addHeader(this.mail.headers);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Add headers to the root node, always overrides custom headers
|
|
61
|
+
['from', 'sender', 'to', 'cc', 'bcc', 'reply-to', 'in-reply-to', 'references', 'subject', 'message-id', 'date'].forEach(header => {
|
|
62
|
+
const key = header.replace(/-(\w)/g, (o, c) => c.toUpperCase());
|
|
63
|
+
if (this.mail[key]) {
|
|
64
|
+
this.message.setHeader(header, this.mail[key]);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Sets custom envelope
|
|
69
|
+
if (this.mail.envelope) {
|
|
70
|
+
this.message.setEnvelope(this.mail.envelope);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ensure Message-Id value
|
|
74
|
+
this.message.messageId();
|
|
75
|
+
|
|
76
|
+
return this.message;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* List all attachments. Resulting attachment objects can be used as input for MimeNode nodes
|
|
81
|
+
*
|
|
82
|
+
* @param {Boolean} findRelated If true separate related attachments from attached ones
|
|
83
|
+
* @returns {Object} An object of arrays (`related` and `attached`)
|
|
84
|
+
*/
|
|
85
|
+
getAttachments(findRelated) {
|
|
86
|
+
let icalEvent, eventObject;
|
|
87
|
+
const attachments = [].concat(this.mail.attachments || []).map((attachment, i) => {
|
|
88
|
+
if (/^data:/i.test(attachment.path || attachment.href)) {
|
|
89
|
+
attachment = this._processDataUrl(attachment);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const contentType =
|
|
93
|
+
attachment.contentType || mimeFuncs.detectMimeType(attachment.filename || attachment.path || attachment.href || 'bin');
|
|
94
|
+
|
|
95
|
+
const isImage = /^image\//i.test(contentType);
|
|
96
|
+
const isMessageNode = /^message\//i.test(contentType);
|
|
97
|
+
|
|
98
|
+
const contentDisposition =
|
|
99
|
+
attachment.contentDisposition || (isMessageNode || (isImage && attachment.cid) ? 'inline' : 'attachment');
|
|
100
|
+
|
|
101
|
+
let contentTransferEncoding;
|
|
102
|
+
if ('contentTransferEncoding' in attachment) {
|
|
103
|
+
// also contains `false`, to set
|
|
104
|
+
contentTransferEncoding = attachment.contentTransferEncoding;
|
|
105
|
+
} else if (isMessageNode) {
|
|
106
|
+
// the content might include non-ASCII bytes but at this point we do not know it yet
|
|
107
|
+
contentTransferEncoding = '8bit';
|
|
108
|
+
} else {
|
|
109
|
+
contentTransferEncoding = 'base64'; // the default
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const data = {
|
|
113
|
+
contentType,
|
|
114
|
+
contentDisposition,
|
|
115
|
+
contentTransferEncoding
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
if (attachment.filename) {
|
|
119
|
+
data.filename = attachment.filename;
|
|
120
|
+
} else if (!isMessageNode && attachment.filename !== false) {
|
|
121
|
+
data.filename = (attachment.path || attachment.href || '').split('/').pop().split('?').shift() || 'attachment-' + (i + 1);
|
|
122
|
+
if (data.filename.indexOf('.') < 0) {
|
|
123
|
+
data.filename += '.' + mimeFuncs.detectExtension(data.contentType);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (/^https?:\/\//i.test(attachment.path)) {
|
|
128
|
+
attachment.href = attachment.path;
|
|
129
|
+
attachment.path = undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (attachment.cid) {
|
|
133
|
+
data.cid = attachment.cid;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (attachment.raw) {
|
|
137
|
+
data.raw = attachment.raw;
|
|
138
|
+
} else if (attachment.path) {
|
|
139
|
+
data.content = {
|
|
140
|
+
path: attachment.path
|
|
141
|
+
};
|
|
142
|
+
} else if (attachment.href) {
|
|
143
|
+
data.content = {
|
|
144
|
+
href: attachment.href,
|
|
145
|
+
httpHeaders: attachment.httpHeaders
|
|
146
|
+
};
|
|
147
|
+
} else {
|
|
148
|
+
data.content = attachment.content || '';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (attachment.encoding) {
|
|
152
|
+
data.encoding = attachment.encoding;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (attachment.headers) {
|
|
156
|
+
data.headers = attachment.headers;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return data;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (this.mail.icalEvent) {
|
|
163
|
+
if (
|
|
164
|
+
typeof this.mail.icalEvent === 'object' &&
|
|
165
|
+
(this.mail.icalEvent.content || this.mail.icalEvent.path || this.mail.icalEvent.href || this.mail.icalEvent.raw)
|
|
166
|
+
) {
|
|
167
|
+
icalEvent = this.mail.icalEvent;
|
|
168
|
+
} else {
|
|
169
|
+
icalEvent = {
|
|
170
|
+
content: this.mail.icalEvent
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
eventObject = Object.assign({}, icalEvent);
|
|
175
|
+
|
|
176
|
+
eventObject.contentType = 'application/ics';
|
|
177
|
+
if (!eventObject.headers) {
|
|
178
|
+
eventObject.headers = {};
|
|
179
|
+
}
|
|
180
|
+
eventObject.filename = eventObject.filename || 'invite.ics';
|
|
181
|
+
eventObject.headers['Content-Disposition'] = 'attachment';
|
|
182
|
+
eventObject.headers['Content-Transfer-Encoding'] = 'base64';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!findRelated) {
|
|
186
|
+
return {
|
|
187
|
+
attached: attachments.concat(eventObject || []),
|
|
188
|
+
related: []
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
attached: attachments.filter(attachment => !attachment.cid).concat(eventObject || []),
|
|
194
|
+
related: attachments.filter(attachment => !!attachment.cid)
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* List alternatives. Resulting objects can be used as input for MimeNode nodes
|
|
200
|
+
*
|
|
201
|
+
* @returns {Array} An array of alternative elements. Includes the `text` and `html` values as well
|
|
202
|
+
*/
|
|
203
|
+
getAlternatives() {
|
|
204
|
+
const alternatives = [];
|
|
205
|
+
let text, html, watchHtml, amp, icalEvent, eventObject;
|
|
206
|
+
|
|
207
|
+
if (this.mail.text) {
|
|
208
|
+
if (
|
|
209
|
+
typeof this.mail.text === 'object' &&
|
|
210
|
+
(this.mail.text.content || this.mail.text.path || this.mail.text.href || this.mail.text.raw)
|
|
211
|
+
) {
|
|
212
|
+
text = this.mail.text;
|
|
213
|
+
} else {
|
|
214
|
+
text = {
|
|
215
|
+
content: this.mail.text
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
text.contentType = 'text/plain; charset=utf-8';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (this.mail.watchHtml) {
|
|
222
|
+
if (
|
|
223
|
+
typeof this.mail.watchHtml === 'object' &&
|
|
224
|
+
(this.mail.watchHtml.content || this.mail.watchHtml.path || this.mail.watchHtml.href || this.mail.watchHtml.raw)
|
|
225
|
+
) {
|
|
226
|
+
watchHtml = this.mail.watchHtml;
|
|
227
|
+
} else {
|
|
228
|
+
watchHtml = {
|
|
229
|
+
content: this.mail.watchHtml
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
watchHtml.contentType = 'text/watch-html; charset=utf-8';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (this.mail.amp) {
|
|
236
|
+
if (
|
|
237
|
+
typeof this.mail.amp === 'object' &&
|
|
238
|
+
(this.mail.amp.content || this.mail.amp.path || this.mail.amp.href || this.mail.amp.raw)
|
|
239
|
+
) {
|
|
240
|
+
amp = this.mail.amp;
|
|
241
|
+
} else {
|
|
242
|
+
amp = {
|
|
243
|
+
content: this.mail.amp
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
amp.contentType = 'text/x-amp-html; charset=utf-8';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// NB! when including attachments with a calendar alternative you might end up in a blank screen on some clients
|
|
250
|
+
if (this.mail.icalEvent) {
|
|
251
|
+
if (
|
|
252
|
+
typeof this.mail.icalEvent === 'object' &&
|
|
253
|
+
(this.mail.icalEvent.content || this.mail.icalEvent.path || this.mail.icalEvent.href || this.mail.icalEvent.raw)
|
|
254
|
+
) {
|
|
255
|
+
icalEvent = this.mail.icalEvent;
|
|
256
|
+
} else {
|
|
257
|
+
icalEvent = {
|
|
258
|
+
content: this.mail.icalEvent
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
eventObject = Object.assign({}, icalEvent);
|
|
263
|
+
|
|
264
|
+
if (eventObject.content && typeof eventObject.content === 'object') {
|
|
265
|
+
// we are going to have the same attachment twice, so mark this to be
|
|
266
|
+
// resolved just once
|
|
267
|
+
eventObject.content._resolve = true;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
eventObject.filename = false;
|
|
271
|
+
eventObject.contentType =
|
|
272
|
+
'text/calendar; charset=utf-8; method=' + (eventObject.method || 'PUBLISH').toString().trim().toUpperCase();
|
|
273
|
+
if (!eventObject.headers) {
|
|
274
|
+
eventObject.headers = {};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (this.mail.html) {
|
|
279
|
+
if (
|
|
280
|
+
typeof this.mail.html === 'object' &&
|
|
281
|
+
(this.mail.html.content || this.mail.html.path || this.mail.html.href || this.mail.html.raw)
|
|
282
|
+
) {
|
|
283
|
+
html = this.mail.html;
|
|
284
|
+
} else {
|
|
285
|
+
html = {
|
|
286
|
+
content: this.mail.html
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
html.contentType = 'text/html; charset=utf-8';
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
[]
|
|
293
|
+
.concat(text || [])
|
|
294
|
+
.concat(watchHtml || [])
|
|
295
|
+
.concat(amp || [])
|
|
296
|
+
.concat(html || [])
|
|
297
|
+
.concat(eventObject || [])
|
|
298
|
+
.concat(this.mail.alternatives || [])
|
|
299
|
+
.forEach(alternative => {
|
|
300
|
+
if (/^data:/i.test(alternative.path || alternative.href)) {
|
|
301
|
+
alternative = this._processDataUrl(alternative);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const data = {
|
|
305
|
+
contentType:
|
|
306
|
+
alternative.contentType ||
|
|
307
|
+
mimeFuncs.detectMimeType(alternative.filename || alternative.path || alternative.href || 'txt'),
|
|
308
|
+
contentTransferEncoding: alternative.contentTransferEncoding
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
if (alternative.filename) {
|
|
312
|
+
data.filename = alternative.filename;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (/^https?:\/\//i.test(alternative.path)) {
|
|
316
|
+
alternative.href = alternative.path;
|
|
317
|
+
alternative.path = undefined;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (alternative.raw) {
|
|
321
|
+
data.raw = alternative.raw;
|
|
322
|
+
} else if (alternative.path) {
|
|
323
|
+
data.content = {
|
|
324
|
+
path: alternative.path
|
|
325
|
+
};
|
|
326
|
+
} else if (alternative.href) {
|
|
327
|
+
data.content = {
|
|
328
|
+
href: alternative.href
|
|
329
|
+
};
|
|
330
|
+
} else {
|
|
331
|
+
data.content = alternative.content || '';
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (alternative.encoding) {
|
|
335
|
+
data.encoding = alternative.encoding;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (alternative.headers) {
|
|
339
|
+
data.headers = alternative.headers;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
alternatives.push(data);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
return alternatives;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Builds multipart/mixed node. It should always contain different type of elements on the same level
|
|
350
|
+
* eg. text + attachments
|
|
351
|
+
*
|
|
352
|
+
* @param {Object} parentNode Parent for this note. If it does not exist, a root node is created
|
|
353
|
+
* @returns {Object} MimeNode node element
|
|
354
|
+
*/
|
|
355
|
+
_createMixed(parentNode) {
|
|
356
|
+
const node = parentNode
|
|
357
|
+
? parentNode.createChild('multipart/mixed', {
|
|
358
|
+
disableUrlAccess: this.mail.disableUrlAccess,
|
|
359
|
+
disableFileAccess: this.mail.disableFileAccess,
|
|
360
|
+
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
|
361
|
+
newline: this.mail.newline
|
|
362
|
+
})
|
|
363
|
+
: new MimeNode('multipart/mixed', {
|
|
364
|
+
baseBoundary: this.mail.baseBoundary,
|
|
365
|
+
textEncoding: this.mail.textEncoding,
|
|
366
|
+
boundaryPrefix: this.mail.boundaryPrefix,
|
|
367
|
+
disableUrlAccess: this.mail.disableUrlAccess,
|
|
368
|
+
disableFileAccess: this.mail.disableFileAccess,
|
|
369
|
+
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
|
370
|
+
newline: this.mail.newline
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
if (this._useAlternative) {
|
|
374
|
+
this._createAlternative(node);
|
|
375
|
+
} else if (this._useRelated) {
|
|
376
|
+
this._createRelated(node);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
[]
|
|
380
|
+
.concat((!this._useAlternative && this._alternatives) || [])
|
|
381
|
+
.concat(this._attachments.attached || [])
|
|
382
|
+
.forEach(element => {
|
|
383
|
+
// if the element is a html node from related subpart then ignore it
|
|
384
|
+
if (!this._useRelated || element !== this._htmlNode) {
|
|
385
|
+
this._createContentNode(node, element);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
return node;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Builds multipart/alternative node. It should always contain same type of elements on the same level
|
|
394
|
+
* eg. text + html view of the same data
|
|
395
|
+
*
|
|
396
|
+
* @param {Object} parentNode Parent for this note. If it does not exist, a root node is created
|
|
397
|
+
* @returns {Object} MimeNode node element
|
|
398
|
+
*/
|
|
399
|
+
_createAlternative(parentNode) {
|
|
400
|
+
const node = parentNode
|
|
401
|
+
? parentNode.createChild('multipart/alternative', {
|
|
402
|
+
disableUrlAccess: this.mail.disableUrlAccess,
|
|
403
|
+
disableFileAccess: this.mail.disableFileAccess,
|
|
404
|
+
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
|
405
|
+
newline: this.mail.newline
|
|
406
|
+
})
|
|
407
|
+
: new MimeNode('multipart/alternative', {
|
|
408
|
+
baseBoundary: this.mail.baseBoundary,
|
|
409
|
+
textEncoding: this.mail.textEncoding,
|
|
410
|
+
boundaryPrefix: this.mail.boundaryPrefix,
|
|
411
|
+
disableUrlAccess: this.mail.disableUrlAccess,
|
|
412
|
+
disableFileAccess: this.mail.disableFileAccess,
|
|
413
|
+
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
|
414
|
+
newline: this.mail.newline
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
this._alternatives.forEach(alternative => {
|
|
418
|
+
if (this._useRelated && this._htmlNode === alternative) {
|
|
419
|
+
this._createRelated(node);
|
|
420
|
+
} else {
|
|
421
|
+
this._createContentNode(node, alternative);
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
return node;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Builds multipart/related node. It should always contain html node with related attachments
|
|
430
|
+
*
|
|
431
|
+
* @param {Object} parentNode Parent for this note. If it does not exist, a root node is created
|
|
432
|
+
* @returns {Object} MimeNode node element
|
|
433
|
+
*/
|
|
434
|
+
_createRelated(parentNode) {
|
|
435
|
+
const node = parentNode
|
|
436
|
+
? parentNode.createChild('multipart/related; type="text/html"', {
|
|
437
|
+
disableUrlAccess: this.mail.disableUrlAccess,
|
|
438
|
+
disableFileAccess: this.mail.disableFileAccess,
|
|
439
|
+
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
|
440
|
+
newline: this.mail.newline
|
|
441
|
+
})
|
|
442
|
+
: new MimeNode('multipart/related; type="text/html"', {
|
|
443
|
+
baseBoundary: this.mail.baseBoundary,
|
|
444
|
+
textEncoding: this.mail.textEncoding,
|
|
445
|
+
boundaryPrefix: this.mail.boundaryPrefix,
|
|
446
|
+
disableUrlAccess: this.mail.disableUrlAccess,
|
|
447
|
+
disableFileAccess: this.mail.disableFileAccess,
|
|
448
|
+
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
|
449
|
+
newline: this.mail.newline
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
this._createContentNode(node, this._htmlNode);
|
|
453
|
+
|
|
454
|
+
this._attachments.related.forEach(alternative => this._createContentNode(node, alternative));
|
|
455
|
+
|
|
456
|
+
return node;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Creates a regular node with contents
|
|
461
|
+
*
|
|
462
|
+
* @param {Object} parentNode Parent for this note. If it does not exist, a root node is created
|
|
463
|
+
* @param {Object} element Node data
|
|
464
|
+
* @returns {Object} MimeNode node element
|
|
465
|
+
*/
|
|
466
|
+
_createContentNode(parentNode, element) {
|
|
467
|
+
element = element || {};
|
|
468
|
+
element.content = element.content || '';
|
|
469
|
+
|
|
470
|
+
const encoding = (element.encoding || 'utf8')
|
|
471
|
+
.toString()
|
|
472
|
+
.toLowerCase()
|
|
473
|
+
.replace(/[-_\s]/g, '');
|
|
474
|
+
|
|
475
|
+
const node = parentNode
|
|
476
|
+
? parentNode.createChild(element.contentType, {
|
|
477
|
+
filename: element.filename,
|
|
478
|
+
textEncoding: this.mail.textEncoding,
|
|
479
|
+
disableUrlAccess: this.mail.disableUrlAccess,
|
|
480
|
+
disableFileAccess: this.mail.disableFileAccess,
|
|
481
|
+
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
|
482
|
+
newline: this.mail.newline
|
|
483
|
+
})
|
|
484
|
+
: new MimeNode(element.contentType, {
|
|
485
|
+
filename: element.filename,
|
|
486
|
+
baseBoundary: this.mail.baseBoundary,
|
|
487
|
+
textEncoding: this.mail.textEncoding,
|
|
488
|
+
boundaryPrefix: this.mail.boundaryPrefix,
|
|
489
|
+
disableUrlAccess: this.mail.disableUrlAccess,
|
|
490
|
+
disableFileAccess: this.mail.disableFileAccess,
|
|
491
|
+
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
|
492
|
+
newline: this.mail.newline
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// add custom headers
|
|
496
|
+
if (element.headers) {
|
|
497
|
+
node.addHeader(element.headers);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (element.cid) {
|
|
501
|
+
node.setHeader('Content-Id', '<' + element.cid.replace(/[<>]/g, '') + '>');
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (element.contentTransferEncoding) {
|
|
505
|
+
node.setHeader('Content-Transfer-Encoding', element.contentTransferEncoding);
|
|
506
|
+
} else if (this.mail.encoding && /^text\//i.test(element.contentType)) {
|
|
507
|
+
node.setHeader('Content-Transfer-Encoding', this.mail.encoding);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (!/^text\//i.test(element.contentType) || element.contentDisposition) {
|
|
511
|
+
node.setHeader(
|
|
512
|
+
'Content-Disposition',
|
|
513
|
+
element.contentDisposition || (element.cid && /^image\//i.test(element.contentType) ? 'inline' : 'attachment')
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (typeof element.content === 'string' && !['utf8', 'usascii', 'ascii'].includes(encoding)) {
|
|
518
|
+
element.content = Buffer.from(element.content, encoding);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// prefer pregenerated raw content
|
|
522
|
+
if (element.raw) {
|
|
523
|
+
node.setRaw(element.raw);
|
|
524
|
+
} else {
|
|
525
|
+
node.setContent(element.content);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return node;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Parses data uri and converts it to a Buffer
|
|
533
|
+
*
|
|
534
|
+
* @param {Object} element Content element
|
|
535
|
+
* @return {Object} Parsed element
|
|
536
|
+
*/
|
|
537
|
+
_processDataUrl(element) {
|
|
538
|
+
const dataUrl = element.path || element.href;
|
|
539
|
+
|
|
540
|
+
// Early validation to prevent ReDoS
|
|
541
|
+
if (!dataUrl || typeof dataUrl !== 'string') {
|
|
542
|
+
return element;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (!dataUrl.startsWith('data:')) {
|
|
546
|
+
return element;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (dataUrl.length > 52428800) {
|
|
550
|
+
// 52428800 chars = 50MB limit for data URL string (~37.5MB decoded image)
|
|
551
|
+
// Extract content type before rejecting to preserve MIME type
|
|
552
|
+
let detectedType = 'application/octet-stream';
|
|
553
|
+
const commaPos = dataUrl.indexOf(',');
|
|
554
|
+
|
|
555
|
+
if (commaPos > 0 && commaPos < 200) {
|
|
556
|
+
// Parse header safely with size limit
|
|
557
|
+
const header = dataUrl.substring(5, commaPos); // skip 'data:'
|
|
558
|
+
const parts = header.split(';');
|
|
559
|
+
if (parts[0] && parts[0].includes('/')) {
|
|
560
|
+
detectedType = parts[0].trim();
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Return empty content for excessively long data URLs
|
|
565
|
+
return Object.assign({}, element, {
|
|
566
|
+
path: false,
|
|
567
|
+
href: false,
|
|
568
|
+
content: Buffer.alloc(0),
|
|
569
|
+
contentType: element.contentType || detectedType
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
let parsedDataUri;
|
|
574
|
+
try {
|
|
575
|
+
parsedDataUri = parseDataURI(dataUrl);
|
|
576
|
+
} catch (_err) {
|
|
577
|
+
return element;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (!parsedDataUri) {
|
|
581
|
+
return element;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
element.content = parsedDataUri.data;
|
|
585
|
+
element.contentType = element.contentType || parsedDataUri.contentType;
|
|
586
|
+
|
|
587
|
+
if ('path' in element) {
|
|
588
|
+
element.path = false;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if ('href' in element) {
|
|
592
|
+
element.href = false;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return element;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
module.exports = MailComposer;
|