@nocobase/plugin-workflow-mailer 2.2.20 → 2.2.21
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/dist/externalVersion.js +6 -6
- package/dist/node_modules/joi/package.json +1 -1
- package/dist/node_modules/nodemailer/dist/cjs/addressparser/index.d.ts +59 -0
- package/dist/node_modules/nodemailer/dist/cjs/addressparser/index.js +790 -0
- package/dist/node_modules/nodemailer/dist/cjs/base64/index.d.ts +36 -0
- package/dist/node_modules/nodemailer/dist/cjs/base64/index.js +119 -0
- package/dist/node_modules/nodemailer/dist/cjs/dkim/index.d.ts +52 -0
- package/dist/node_modules/nodemailer/dist/cjs/dkim/index.js +228 -0
- package/dist/node_modules/nodemailer/dist/cjs/dkim/message-parser.d.ts +48 -0
- package/dist/node_modules/nodemailer/dist/cjs/dkim/message-parser.js +180 -0
- package/dist/node_modules/nodemailer/dist/cjs/dkim/relaxed-body.d.ts +36 -0
- package/dist/node_modules/nodemailer/dist/cjs/dkim/relaxed-body.js +148 -0
- package/dist/node_modules/nodemailer/dist/cjs/dkim/sign.d.ts +52 -0
- package/dist/node_modules/nodemailer/dist/cjs/dkim/sign.js +165 -0
- package/dist/node_modules/nodemailer/dist/cjs/errors.d.ts +96 -0
- package/dist/node_modules/nodemailer/dist/cjs/errors.js +70 -0
- package/dist/node_modules/nodemailer/dist/cjs/fetch/cookies.d.ts +107 -0
- package/dist/node_modules/nodemailer/dist/cjs/fetch/cookies.js +282 -0
- package/dist/node_modules/nodemailer/dist/cjs/fetch/index.d.ts +65 -0
- package/dist/node_modules/nodemailer/dist/cjs/fetch/index.js +384 -0
- package/dist/node_modules/nodemailer/dist/cjs/json-transport/index.d.ts +64 -0
- package/dist/node_modules/nodemailer/dist/cjs/json-transport/index.js +99 -0
- package/dist/node_modules/nodemailer/dist/cjs/mail-composer/index.d.ts +174 -0
- package/dist/node_modules/nodemailer/dist/cjs/mail-composer/index.js +613 -0
- package/dist/node_modules/nodemailer/dist/cjs/mailer/index.d.ts +237 -0
- package/dist/node_modules/nodemailer/dist/cjs/mailer/index.js +415 -0
- package/dist/node_modules/nodemailer/dist/cjs/mailer/mail-message.d.ts +97 -0
- package/dist/node_modules/nodemailer/dist/cjs/mailer/mail-message.js +342 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-funcs/index.d.ts +161 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-funcs/index.js +787 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-funcs/mime-types.d.ts +2 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-funcs/mime-types.js +2103 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-node/index.d.ts +333 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-node/index.js +1493 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-node/last-newline.d.ts +5 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-node/last-newline.js +32 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-node/le-unix.d.ts +9 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-node/le-unix.js +40 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-node/le-windows.d.ts +10 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-node/le-windows.js +47 -0
- package/dist/node_modules/nodemailer/dist/cjs/nodemailer.d.ts +124 -0
- package/dist/node_modules/nodemailer/dist/cjs/nodemailer.js +1 -0
- package/dist/node_modules/nodemailer/dist/cjs/package-info.d.ts +3 -0
- package/dist/node_modules/nodemailer/dist/cjs/package-info.js +7 -0
- package/dist/node_modules/nodemailer/dist/cjs/package.json +3 -0
- package/dist/node_modules/nodemailer/dist/cjs/punycode/index.d.ts +73 -0
- package/dist/node_modules/nodemailer/{lib → dist/cjs}/punycode/index.js +62 -111
- package/dist/node_modules/nodemailer/dist/cjs/qp/index.d.ts +32 -0
- package/dist/node_modules/nodemailer/dist/cjs/qp/index.js +207 -0
- package/dist/node_modules/nodemailer/dist/cjs/sendmail-transport/index.d.ts +69 -0
- package/dist/node_modules/nodemailer/dist/cjs/sendmail-transport/index.js +210 -0
- package/dist/node_modules/nodemailer/dist/cjs/ses-transport/index.d.ts +112 -0
- package/dist/node_modules/nodemailer/dist/cjs/ses-transport/index.js +252 -0
- package/dist/node_modules/nodemailer/dist/cjs/shared/index.d.ts +260 -0
- package/dist/node_modules/nodemailer/dist/cjs/shared/index.js +680 -0
- package/dist/node_modules/nodemailer/dist/cjs/shared/objects.d.ts +23 -0
- package/dist/node_modules/nodemailer/dist/cjs/shared/objects.js +43 -0
- package/dist/node_modules/nodemailer/dist/cjs/shared/url.d.ts +21 -0
- package/dist/node_modules/nodemailer/dist/cjs/shared/url.js +255 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/data-stream.d.ts +15 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/data-stream.js +108 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/http-proxy-client.d.ts +47 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/http-proxy-client.js +189 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/index.d.ts +416 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/index.js +1837 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-pool/index.d.ts +133 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-pool/index.js +569 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-pool/pool-resource.d.ts +62 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-pool/pool-resource.js +257 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-transport/index.d.ts +156 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-transport/index.js +371 -0
- package/dist/node_modules/nodemailer/dist/cjs/stream-transport/index.d.ts +65 -0
- package/dist/node_modules/nodemailer/dist/cjs/stream-transport/index.js +146 -0
- package/dist/node_modules/nodemailer/dist/cjs/well-known/index.d.ts +35 -0
- package/dist/node_modules/nodemailer/dist/cjs/well-known/index.js +41 -0
- package/dist/node_modules/nodemailer/dist/cjs/well-known/services.d.ts +2 -0
- package/dist/node_modules/nodemailer/dist/cjs/well-known/services.js +674 -0
- package/dist/node_modules/nodemailer/dist/cjs/xoauth2/index.d.ts +201 -0
- package/dist/node_modules/nodemailer/dist/cjs/xoauth2/index.js +426 -0
- package/dist/node_modules/nodemailer/dist/esm/addressparser/index.d.ts +59 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/addressparser/index.js +361 -131
- package/dist/node_modules/nodemailer/dist/esm/base64/index.d.ts +36 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/base64/index.js +31 -56
- package/dist/node_modules/nodemailer/dist/esm/dkim/index.d.ts +52 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/dkim/index.js +49 -78
- package/dist/node_modules/nodemailer/dist/esm/dkim/message-parser.d.ts +48 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/dkim/message-parser.js +64 -42
- package/dist/node_modules/nodemailer/dist/esm/dkim/relaxed-body.d.ts +36 -0
- package/dist/node_modules/nodemailer/dist/esm/dkim/relaxed-body.js +141 -0
- package/dist/node_modules/nodemailer/dist/esm/dkim/sign.d.ts +52 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/dkim/sign.js +50 -46
- package/dist/node_modules/nodemailer/dist/esm/errors.d.ts +96 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/errors.js +26 -18
- package/dist/node_modules/nodemailer/dist/esm/fetch/cookies.d.ts +107 -0
- package/dist/node_modules/nodemailer/dist/esm/fetch/cookies.js +242 -0
- package/dist/node_modules/nodemailer/dist/esm/fetch/index.d.ts +65 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/fetch/index.js +80 -147
- package/dist/node_modules/nodemailer/dist/esm/json-transport/index.d.ts +64 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/json-transport/index.js +19 -39
- package/dist/node_modules/nodemailer/dist/esm/mail-composer/index.d.ts +174 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/mail-composer/index.js +199 -265
- package/dist/node_modules/nodemailer/dist/esm/mailer/index.d.ts +237 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/mailer/index.js +128 -234
- package/dist/node_modules/nodemailer/dist/esm/mailer/mail-message.d.ts +97 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/mailer/mail-message.js +62 -114
- package/dist/node_modules/nodemailer/dist/esm/mime-funcs/index.d.ts +161 -0
- package/dist/node_modules/nodemailer/dist/esm/mime-funcs/index.js +738 -0
- package/dist/node_modules/nodemailer/dist/esm/mime-funcs/mime-types.d.ts +2 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-funcs/mime-types.js +29 -42
- package/dist/node_modules/nodemailer/dist/esm/mime-node/index.d.ts +333 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-node/index.js +259 -372
- package/dist/node_modules/nodemailer/dist/esm/mime-node/last-newline.d.ts +5 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-node/last-newline.js +4 -10
- package/dist/node_modules/nodemailer/dist/esm/mime-node/le-unix.d.ts +9 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-node/le-unix.js +6 -11
- package/dist/node_modules/nodemailer/dist/esm/mime-node/le-windows.d.ts +10 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-node/le-windows.js +6 -13
- package/dist/node_modules/nodemailer/dist/esm/nodemailer.d.ts +124 -0
- package/dist/node_modules/nodemailer/dist/esm/nodemailer.js +166 -0
- package/dist/node_modules/nodemailer/dist/esm/package-info.d.ts +3 -0
- package/dist/node_modules/nodemailer/dist/esm/package-info.js +4 -0
- package/dist/node_modules/nodemailer/dist/esm/package.json +3 -0
- package/dist/node_modules/nodemailer/dist/esm/punycode/index.d.ts +73 -0
- package/dist/node_modules/nodemailer/dist/esm/punycode/index.js +405 -0
- package/dist/node_modules/nodemailer/dist/esm/qp/index.d.ts +32 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/qp/index.js +38 -67
- package/dist/node_modules/nodemailer/dist/esm/sendmail-transport/index.d.ts +69 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/sendmail-transport/index.js +51 -101
- package/dist/node_modules/nodemailer/dist/esm/ses-transport/index.d.ts +112 -0
- package/dist/node_modules/nodemailer/dist/esm/ses-transport/index.js +212 -0
- package/dist/node_modules/nodemailer/dist/esm/shared/index.d.ts +260 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/shared/index.js +156 -272
- package/dist/node_modules/nodemailer/dist/esm/shared/objects.d.ts +23 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/shared/objects.js +13 -17
- package/dist/node_modules/nodemailer/dist/esm/shared/url.d.ts +21 -0
- package/dist/node_modules/nodemailer/dist/esm/shared/url.js +214 -0
- package/dist/node_modules/nodemailer/dist/esm/smtp-connection/data-stream.d.ts +15 -0
- package/dist/node_modules/nodemailer/dist/esm/smtp-connection/data-stream.js +103 -0
- package/dist/node_modules/nodemailer/dist/esm/smtp-connection/http-proxy-client.d.ts +47 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-connection/http-proxy-client.js +49 -74
- package/dist/node_modules/nodemailer/dist/esm/smtp-connection/index.d.ts +416 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-connection/index.js +574 -794
- package/dist/node_modules/nodemailer/dist/esm/smtp-pool/index.d.ts +133 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-pool/index.js +172 -284
- package/dist/node_modules/nodemailer/dist/esm/smtp-pool/pool-resource.d.ts +62 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-pool/pool-resource.js +57 -97
- package/dist/node_modules/nodemailer/dist/esm/smtp-transport/index.d.ts +156 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-transport/index.js +81 -179
- package/dist/node_modules/nodemailer/dist/esm/stream-transport/index.d.ts +65 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/stream-transport/index.js +36 -74
- package/dist/node_modules/nodemailer/dist/esm/well-known/index.d.ts +35 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/well-known/index.js +5 -16
- package/dist/node_modules/nodemailer/dist/esm/well-known/services.d.ts +2 -0
- package/dist/node_modules/nodemailer/dist/esm/well-known/services.js +671 -0
- package/dist/node_modules/nodemailer/dist/esm/xoauth2/index.d.ts +201 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/xoauth2/index.js +125 -187
- package/dist/node_modules/nodemailer/{lib → dist}/well-known/services.json +9 -2
- package/dist/node_modules/nodemailer/package.json +1 -1
- package/package.json +3 -3
- package/dist/node_modules/nodemailer/.gitattributes +0 -6
- package/dist/node_modules/nodemailer/.ncurc.js +0 -9
- package/dist/node_modules/nodemailer/.prettierignore +0 -8
- package/dist/node_modules/nodemailer/.prettierrc +0 -12
- package/dist/node_modules/nodemailer/.prettierrc.js +0 -10
- package/dist/node_modules/nodemailer/.release-please-config.json +0 -9
- package/dist/node_modules/nodemailer/eslint.config.js +0 -88
- package/dist/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
- package/dist/node_modules/nodemailer/lib/fetch/cookies.js +0 -276
- package/dist/node_modules/nodemailer/lib/mime-funcs/index.js +0 -671
- package/dist/node_modules/nodemailer/lib/nodemailer.js +0 -1
- package/dist/node_modules/nodemailer/lib/ses-transport/index.js +0 -266
- package/dist/node_modules/nodemailer/lib/shared/url.js +0 -151
- package/dist/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -105
|
@@ -1,41 +1,31 @@
|
|
|
1
1
|
/* eslint no-undefined: 0, prefer-spread: 0, no-control-regex: 0 */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const errors = require('../errors');
|
|
18
|
-
const LastNewline = require('./last-newline');
|
|
19
|
-
|
|
20
|
-
const LeWindows = require('./le-windows');
|
|
21
|
-
const LeUnix = require('./le-unix');
|
|
22
|
-
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import * as punycode from '../punycode/index.js';
|
|
5
|
+
import { PassThrough } from 'node:stream';
|
|
6
|
+
import * as shared from '../shared/index.js';
|
|
7
|
+
import urlModule from 'node:url';
|
|
8
|
+
import * as mimeFuncs from '../mime-funcs/index.js';
|
|
9
|
+
import * as qp from '../qp/index.js';
|
|
10
|
+
import * as base64 from '../base64/index.js';
|
|
11
|
+
import addressparser from '../addressparser/index.js';
|
|
12
|
+
import nmfetch from '../fetch/index.js';
|
|
13
|
+
import * as errors from '../errors.js';
|
|
14
|
+
import LastNewline from './last-newline.js';
|
|
15
|
+
import LeWindows from './le-windows.js';
|
|
16
|
+
import LeUnix from './le-unix.js';
|
|
23
17
|
const FORMATTED_HEADERS = ['From', 'Sender', 'To', 'Cc', 'Bcc', 'Reply-To', 'Date', 'References'];
|
|
24
|
-
|
|
25
18
|
// RFC 5321 atext, plus the non-ascii bytes that SMTPUTF8 (RFC 6531) adds to it. A local part
|
|
26
19
|
// built from these, with '.' as a separator, is a dot-atom and can be emitted bare
|
|
27
20
|
const ATEXT = "[A-Za-z0-9!#$%&'*+\\-/=?^_`{|}~\\x80-\\uFFFF]";
|
|
28
21
|
const DOT_ATOM = new RegExp('^' + ATEXT + '+(?:\\.' + ATEXT + '+)*$');
|
|
29
|
-
|
|
30
22
|
// A complete quoted-string: everything between the outer quotes is either a plain char or
|
|
31
23
|
// a quoted-pair. Anchored, so a value that only starts and ends with a quote does not pass
|
|
32
24
|
const QUOTED_STRING = /^"(?:[^"\\]|\\[\s\S])*"$/;
|
|
33
|
-
|
|
34
25
|
// An address that carries no special anywhere can be emitted bare in a header, everything
|
|
35
26
|
// else goes into angle brackets so that the header can not be read as more addresses than
|
|
36
27
|
// the envelope carries
|
|
37
28
|
const PLAIN_ADDRESS = /^[^\s"(),:;<>@[\\\]]+@[^\s"(),:;<>@[\\\]]+$/;
|
|
38
|
-
|
|
39
29
|
// domainToASCII and domainToUnicode are WHATWG host parsers rather than plain IDNA
|
|
40
30
|
// mappers, so they do more than map: they cut the host at '/', '\\', '?' and '#', drop C0
|
|
41
31
|
// controls, and percent-decode. Handing them 'evil.example/mail.corp.example' returns the
|
|
@@ -43,7 +33,6 @@ const PLAIN_ADDRESS = /^[^\s"(),:;<>@[\\\]]+@[^\s"(),:;<>@[\\\]]+$/;
|
|
|
43
33
|
// unroutable garbage into mail for a domain the sender never named. None of these
|
|
44
34
|
// characters are legal in a domain, so keep them away from the mapper.
|
|
45
35
|
const URL_PARSER_UNSAFE = /[/\\?#%\x00-\x20\x7F]/;
|
|
46
|
-
|
|
47
36
|
/**
|
|
48
37
|
* Encodes a domain the way browsers, the WHATWG URL Standard and DNS facing resolvers do,
|
|
49
38
|
* which is with UTS-46 mapping applied before the Punycode step.
|
|
@@ -57,71 +46,78 @@ const URL_PARSER_UNSAFE = /[/\\?#%\x00-\x20\x7F]/;
|
|
|
57
46
|
* '[127.0.0.1]' included, comes back empty and falls through to the bundled codec, which
|
|
58
47
|
* leaves those as they were supplied.
|
|
59
48
|
*
|
|
60
|
-
* @param
|
|
61
|
-
* @param
|
|
62
|
-
* @return
|
|
49
|
+
* @param domain Domain to encode, already lowercased by the caller
|
|
50
|
+
* @param toUnicode Return the U-label form instead of the A-label form
|
|
51
|
+
* @return Encoded domain
|
|
63
52
|
*/
|
|
64
53
|
function normalizeDomain(domain, toUnicode) {
|
|
65
54
|
// domainToASCII and domainToUnicode landed in Node 7, the bundled codec covers Node 6
|
|
66
55
|
const mapper = toUnicode ? urlModule.domainToUnicode : urlModule.domainToASCII;
|
|
67
|
-
|
|
68
56
|
if (typeof mapper === 'function' && !URL_PARSER_UNSAFE.test(domain)) {
|
|
69
57
|
const mapped = mapper(domain);
|
|
70
58
|
if (mapped) {
|
|
71
59
|
return mapped;
|
|
72
60
|
}
|
|
73
61
|
}
|
|
74
|
-
|
|
75
62
|
return toUnicode ? punycode.toUnicode(domain) : punycode.toASCII(domain);
|
|
76
63
|
}
|
|
77
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Removes the characters that must never reach a multipart delimiter line.
|
|
66
|
+
*
|
|
67
|
+
* A line break splits the delimiter, so the boundary declared in the header can never
|
|
68
|
+
* match it again and the parts go out as body lines instead. The other C0 controls and
|
|
69
|
+
* DEL do not split anything but must not be written either: RFC 5321 does not allow a
|
|
70
|
+
* NUL in DATA at all, and an MTA or a scanner that stops at one reads a different
|
|
71
|
+
* message than a client that does not, which is the same parser disagreement a split
|
|
72
|
+
* delimiter creates. Both sides are cleaned together, since the declared value and the
|
|
73
|
+
* delimiters come from this one result.
|
|
74
|
+
*
|
|
75
|
+
* @param value Value to clean
|
|
76
|
+
* @return Value with every control character removed
|
|
77
|
+
*/
|
|
78
|
+
function _stripBoundaryControls(value) {
|
|
79
|
+
return value.replace(/[\x00-\x1f\x7f]+/g, '');
|
|
80
|
+
}
|
|
78
81
|
/**
|
|
79
82
|
* Creates a new mime tree node. Assumes 'multipart/*' as the content type
|
|
80
83
|
* if it is a branch, anything else counts as leaf. If rootNode is missing from
|
|
81
84
|
* the options, assumes this is the root.
|
|
82
85
|
*
|
|
83
|
-
* @param
|
|
84
|
-
* @param
|
|
85
|
-
* @param
|
|
86
|
-
* @param
|
|
87
|
-
* @param
|
|
88
|
-
* @param
|
|
89
|
-
* @param
|
|
90
|
-
* @param
|
|
91
|
-
* @param
|
|
86
|
+
* @param contentType Define the content type for the node. Can be left blank for attachments (derived from filename)
|
|
87
|
+
* @param [options] optional options
|
|
88
|
+
* @param [options.rootNode] root node for this tree
|
|
89
|
+
* @param [options.parentNode] immediate parent for this node
|
|
90
|
+
* @param [options.filename] filename for an attachment node
|
|
91
|
+
* @param [options.baseBoundary] shared part of the unique multipart boundary
|
|
92
|
+
* @param [options.keepBcc] If true, do not exclude Bcc from the generated headers
|
|
93
|
+
* @param [options.normalizeHeaderKey] method to normalize header keys for custom caseing
|
|
94
|
+
* @param [options.textEncoding] either 'Q' (the default) or 'B'
|
|
92
95
|
*/
|
|
93
96
|
class MimeNode {
|
|
94
97
|
constructor(contentType, options) {
|
|
95
98
|
this.nodeCounter = 0;
|
|
96
|
-
|
|
97
99
|
options = options || {};
|
|
98
|
-
|
|
99
100
|
/**
|
|
100
|
-
* shared part of the unique multipart boundary
|
|
101
|
+
* shared part of the unique multipart boundary. Control characters are dropped
|
|
102
|
+
* here rather than at the delimiter, see _stripBoundaryControls
|
|
101
103
|
*/
|
|
102
|
-
this.baseBoundary = options.baseBoundary || crypto.randomBytes(8).toString('hex');
|
|
103
|
-
this.boundaryPrefix = options.boundaryPrefix || '--_NmP';
|
|
104
|
-
|
|
104
|
+
this.baseBoundary = _stripBoundaryControls(options.baseBoundary || crypto.randomBytes(8).toString('hex'));
|
|
105
|
+
this.boundaryPrefix = _stripBoundaryControls(options.boundaryPrefix || '--_NmP');
|
|
105
106
|
this.disableFileAccess = !!options.disableFileAccess;
|
|
106
107
|
this.disableUrlAccess = !!options.disableUrlAccess;
|
|
107
|
-
|
|
108
108
|
this.normalizeHeaderKey = options.normalizeHeaderKey;
|
|
109
|
-
|
|
110
109
|
/**
|
|
111
110
|
* If date headers is missing and current node is the root, this value is used instead
|
|
112
111
|
*/
|
|
113
112
|
this.date = options.parentNode ? null : new Date();
|
|
114
|
-
|
|
115
113
|
/**
|
|
116
114
|
* Root node for current mime tree
|
|
117
115
|
*/
|
|
118
116
|
this.rootNode = options.rootNode || this;
|
|
119
|
-
|
|
120
117
|
/**
|
|
121
118
|
* If true include Bcc in generated headers (if available)
|
|
122
119
|
*/
|
|
123
120
|
this.keepBcc = !!options.keepBcc;
|
|
124
|
-
|
|
125
121
|
/**
|
|
126
122
|
* If filename is specified but contentType is not (probably an attachment)
|
|
127
123
|
* detect the content type from filename extension
|
|
@@ -135,80 +131,66 @@ class MimeNode {
|
|
|
135
131
|
contentType = mimeFuncs.detectMimeType(this.filename.split('.').pop());
|
|
136
132
|
}
|
|
137
133
|
}
|
|
138
|
-
|
|
139
134
|
/**
|
|
140
135
|
* Indicates which encoding should be used for header strings: "Q" or "B"
|
|
141
136
|
*/
|
|
142
137
|
this.textEncoding = (options.textEncoding || '').toString().trim().charAt(0).toUpperCase();
|
|
143
|
-
|
|
144
138
|
/**
|
|
145
139
|
* Immediate parent for this node (or undefined if not set)
|
|
146
140
|
*/
|
|
147
141
|
this.parentNode = options.parentNode;
|
|
148
|
-
|
|
149
142
|
/**
|
|
150
143
|
* Hostname for default message-id values
|
|
151
144
|
*/
|
|
152
145
|
this.hostname = options.hostname;
|
|
153
|
-
|
|
154
146
|
/**
|
|
155
147
|
* If set to 'win' then uses \r\n, if 'linux' then \n. If not set (or `raw` is used) then newlines are kept as is.
|
|
156
148
|
*/
|
|
157
149
|
this.newline = options.newline;
|
|
158
|
-
|
|
159
150
|
/**
|
|
160
151
|
* An array for possible child nodes
|
|
161
152
|
*/
|
|
162
153
|
this.childNodes = [];
|
|
163
|
-
|
|
164
154
|
/**
|
|
165
155
|
* Used for generating unique boundaries (prepended to the shared base)
|
|
166
156
|
*/
|
|
167
157
|
this._nodeId = ++this.rootNode.nodeCounter;
|
|
168
|
-
|
|
169
158
|
/**
|
|
170
159
|
* A list of header values for this node in the form of [{key:'', value:''}]
|
|
171
160
|
*/
|
|
172
161
|
this._headers = [];
|
|
173
|
-
|
|
174
162
|
/**
|
|
175
163
|
* True if the content only uses ASCII printable characters
|
|
176
164
|
* @type {Boolean}
|
|
177
165
|
*/
|
|
178
166
|
this._isPlainText = false;
|
|
179
|
-
|
|
180
167
|
/**
|
|
181
168
|
* True if the content is plain text but has longer lines than allowed
|
|
182
169
|
* @type {Boolean}
|
|
183
170
|
*/
|
|
184
171
|
this._hasLongLines = false;
|
|
185
|
-
|
|
186
172
|
/**
|
|
187
173
|
* If set, use instead this value for envelopes instead of generating one
|
|
188
174
|
* @type {Boolean}
|
|
189
175
|
*/
|
|
190
176
|
this._envelope = false;
|
|
191
|
-
|
|
192
177
|
/**
|
|
193
178
|
* If set then use this value as the stream content instead of building it
|
|
194
179
|
* @type {String|Buffer|Stream}
|
|
195
180
|
*/
|
|
196
181
|
this._raw = false;
|
|
197
|
-
|
|
198
182
|
/**
|
|
199
183
|
* Additional transform streams that the message will be piped before
|
|
200
184
|
* exposing by createReadStream
|
|
201
185
|
* @type {Array}
|
|
202
186
|
*/
|
|
203
187
|
this._transforms = [];
|
|
204
|
-
|
|
205
188
|
/**
|
|
206
189
|
* Additional process functions that the message will be piped through before
|
|
207
190
|
* exposing by createReadStream. These functions are run after transforms
|
|
208
191
|
* @type {Array}
|
|
209
192
|
*/
|
|
210
193
|
this._processFuncs = [];
|
|
211
|
-
|
|
212
194
|
/**
|
|
213
195
|
* If content type is set (or derived from the filename) add it to headers
|
|
214
196
|
*/
|
|
@@ -216,15 +198,13 @@ class MimeNode {
|
|
|
216
198
|
this.setHeader('Content-Type', contentType);
|
|
217
199
|
}
|
|
218
200
|
}
|
|
219
|
-
|
|
220
201
|
/////// PUBLIC METHODS
|
|
221
|
-
|
|
222
202
|
/**
|
|
223
203
|
* Creates and appends a child node.Arguments provided are passed to MimeNode constructor
|
|
224
204
|
*
|
|
225
|
-
* @param
|
|
226
|
-
* @param
|
|
227
|
-
* @return
|
|
205
|
+
* @param [contentType] Optional content type
|
|
206
|
+
* @param [options] Optional options object
|
|
207
|
+
* @return Created node object
|
|
228
208
|
*/
|
|
229
209
|
createChild(contentType, options) {
|
|
230
210
|
if (!options && typeof contentType === 'object') {
|
|
@@ -235,13 +215,12 @@ class MimeNode {
|
|
|
235
215
|
this.appendChild(node);
|
|
236
216
|
return node;
|
|
237
217
|
}
|
|
238
|
-
|
|
239
218
|
/**
|
|
240
219
|
* Appends an existing node to the mime tree. Removes the node from an existing
|
|
241
220
|
* tree if needed
|
|
242
221
|
*
|
|
243
|
-
* @param
|
|
244
|
-
* @return
|
|
222
|
+
* @param childNode node to be appended
|
|
223
|
+
* @return Appended node object
|
|
245
224
|
*/
|
|
246
225
|
appendChild(childNode) {
|
|
247
226
|
// Take the node out of the tree it is in first. Leaving it there keeps it in that
|
|
@@ -251,55 +230,45 @@ class MimeNode {
|
|
|
251
230
|
if (childNode.parentNode && childNode.parentNode !== this) {
|
|
252
231
|
childNode.remove();
|
|
253
232
|
}
|
|
254
|
-
|
|
255
233
|
if (childNode.rootNode !== this.rootNode) {
|
|
256
234
|
childNode.rootNode = this.rootNode;
|
|
257
235
|
childNode._nodeId = ++this.rootNode.nodeCounter;
|
|
258
236
|
}
|
|
259
|
-
|
|
260
237
|
childNode.parentNode = this;
|
|
261
|
-
|
|
262
238
|
this.childNodes.push(childNode);
|
|
263
239
|
return childNode;
|
|
264
240
|
}
|
|
265
|
-
|
|
266
241
|
/**
|
|
267
242
|
* Replaces current node with another node
|
|
268
243
|
*
|
|
269
|
-
* @param
|
|
270
|
-
* @return
|
|
244
|
+
* @param node Replacement node
|
|
245
|
+
* @return Replacement node
|
|
271
246
|
*/
|
|
272
247
|
replace(node) {
|
|
273
248
|
if (node === this) {
|
|
274
249
|
return this;
|
|
275
250
|
}
|
|
276
|
-
|
|
277
251
|
this.parentNode.childNodes.forEach((childNode, i) => {
|
|
278
252
|
if (childNode === this) {
|
|
279
253
|
node.rootNode = this.rootNode;
|
|
280
254
|
node.parentNode = this.parentNode;
|
|
281
255
|
node._nodeId = this._nodeId;
|
|
282
|
-
|
|
283
256
|
this.rootNode = this;
|
|
284
257
|
this.parentNode = undefined;
|
|
285
|
-
|
|
286
258
|
node.parentNode.childNodes[i] = node;
|
|
287
259
|
}
|
|
288
260
|
});
|
|
289
|
-
|
|
290
261
|
return node;
|
|
291
262
|
}
|
|
292
|
-
|
|
293
263
|
/**
|
|
294
264
|
* Removes current node from the mime tree
|
|
295
265
|
*
|
|
296
|
-
* @return
|
|
266
|
+
* @return removed node
|
|
297
267
|
*/
|
|
298
268
|
remove() {
|
|
299
269
|
if (!this.parentNode) {
|
|
300
270
|
return this;
|
|
301
271
|
}
|
|
302
|
-
|
|
303
272
|
for (let i = this.parentNode.childNodes.length - 1; i >= 0; i--) {
|
|
304
273
|
if (this.parentNode.childNodes[i] === this) {
|
|
305
274
|
this.parentNode.childNodes.splice(i, 1);
|
|
@@ -309,30 +278,30 @@ class MimeNode {
|
|
|
309
278
|
}
|
|
310
279
|
}
|
|
311
280
|
}
|
|
312
|
-
|
|
313
281
|
/**
|
|
314
282
|
* Sets a header value. If the value for selected key exists, it is overwritten.
|
|
315
283
|
* You can set multiple values as well by using [{key:'', value:''}] or
|
|
316
284
|
* {key: 'value'} as the first argument.
|
|
317
285
|
*
|
|
318
|
-
* @param
|
|
319
|
-
* @param
|
|
320
|
-
* @return
|
|
286
|
+
* @param key Header key or a list of key value pairs
|
|
287
|
+
* @param value Header value
|
|
288
|
+
* @return current node
|
|
321
289
|
*/
|
|
322
290
|
setHeader(key, value) {
|
|
323
291
|
let added = false;
|
|
324
|
-
|
|
325
292
|
// Allow setting multiple headers at once
|
|
326
293
|
if (!value && key && typeof key === 'object') {
|
|
327
294
|
// allow {key:'content-type', value: 'text/plain'}
|
|
328
295
|
if (key.key && 'value' in key) {
|
|
329
296
|
this.setHeader(key.key, key.value);
|
|
330
|
-
}
|
|
297
|
+
}
|
|
298
|
+
else if (Array.isArray(key)) {
|
|
331
299
|
// allow [{key:'content-type', value: 'text/plain'}]
|
|
332
300
|
key.forEach(i => {
|
|
333
301
|
this.setHeader(i.key, i.value);
|
|
334
302
|
});
|
|
335
|
-
}
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
336
305
|
// allow {'content-type': 'text/plain'}
|
|
337
306
|
Object.keys(key).forEach(i => {
|
|
338
307
|
this.setHeader(i, key[i]);
|
|
@@ -340,14 +309,11 @@ class MimeNode {
|
|
|
340
309
|
}
|
|
341
310
|
return this;
|
|
342
311
|
}
|
|
343
|
-
|
|
344
312
|
key = this._normalizeHeaderKey(key);
|
|
345
|
-
|
|
346
313
|
const headerValue = {
|
|
347
314
|
key,
|
|
348
315
|
value
|
|
349
316
|
};
|
|
350
|
-
|
|
351
317
|
// Check if the value exists and overwrite
|
|
352
318
|
for (let i = 0, len = this._headers.length; i < len; i++) {
|
|
353
319
|
if (this._headers[i].key === key) {
|
|
@@ -355,7 +321,8 @@ class MimeNode {
|
|
|
355
321
|
// replace the first match
|
|
356
322
|
this._headers[i] = headerValue;
|
|
357
323
|
added = true;
|
|
358
|
-
}
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
359
326
|
// remove following matches
|
|
360
327
|
this._headers.splice(i, 1);
|
|
361
328
|
i--;
|
|
@@ -363,24 +330,21 @@ class MimeNode {
|
|
|
363
330
|
}
|
|
364
331
|
}
|
|
365
332
|
}
|
|
366
|
-
|
|
367
333
|
// match not found, append the value
|
|
368
334
|
if (!added) {
|
|
369
335
|
this._headers.push(headerValue);
|
|
370
336
|
}
|
|
371
|
-
|
|
372
337
|
return this;
|
|
373
338
|
}
|
|
374
|
-
|
|
375
339
|
/**
|
|
376
340
|
* Adds a header value. If the value for selected key exists, the value is appended
|
|
377
341
|
* as a new field and old one is not touched.
|
|
378
342
|
* You can set multiple values as well by using [{key:'', value:''}] or
|
|
379
343
|
* {key: 'value'} as the first argument.
|
|
380
344
|
*
|
|
381
|
-
* @param
|
|
382
|
-
* @param
|
|
383
|
-
* @return
|
|
345
|
+
* @param key Header key or a list of key value pairs
|
|
346
|
+
* @param value Header value
|
|
347
|
+
* @return current node
|
|
384
348
|
*/
|
|
385
349
|
addHeader(key, value) {
|
|
386
350
|
// Allow setting multiple headers at once
|
|
@@ -388,38 +352,38 @@ class MimeNode {
|
|
|
388
352
|
// allow {key:'content-type', value: 'text/plain'}
|
|
389
353
|
if (key.key && key.value) {
|
|
390
354
|
this.addHeader(key.key, key.value);
|
|
391
|
-
}
|
|
355
|
+
}
|
|
356
|
+
else if (Array.isArray(key)) {
|
|
392
357
|
// allow [{key:'content-type', value: 'text/plain'}]
|
|
393
358
|
key.forEach(i => {
|
|
394
359
|
this.addHeader(i.key, i.value);
|
|
395
360
|
});
|
|
396
|
-
}
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
397
363
|
// allow {'content-type': 'text/plain'}
|
|
398
364
|
Object.keys(key).forEach(i => {
|
|
399
365
|
this.addHeader(i, key[i]);
|
|
400
366
|
});
|
|
401
367
|
}
|
|
402
368
|
return this;
|
|
403
|
-
}
|
|
369
|
+
}
|
|
370
|
+
else if (Array.isArray(value)) {
|
|
404
371
|
value.forEach(val => {
|
|
405
372
|
this.addHeader(key, val);
|
|
406
373
|
});
|
|
407
374
|
return this;
|
|
408
375
|
}
|
|
409
|
-
|
|
410
376
|
this._headers.push({
|
|
411
377
|
key: this._normalizeHeaderKey(key),
|
|
412
378
|
value
|
|
413
379
|
});
|
|
414
|
-
|
|
415
380
|
return this;
|
|
416
381
|
}
|
|
417
|
-
|
|
418
382
|
/**
|
|
419
383
|
* Retrieves the first mathcing value of a selected key
|
|
420
384
|
*
|
|
421
|
-
* @param
|
|
422
|
-
* @retun
|
|
385
|
+
* @param key Key to search for
|
|
386
|
+
* @retun Value for the key
|
|
423
387
|
*/
|
|
424
388
|
getHeader(key) {
|
|
425
389
|
key = this._normalizeHeaderKey(key);
|
|
@@ -429,14 +393,13 @@ class MimeNode {
|
|
|
429
393
|
}
|
|
430
394
|
}
|
|
431
395
|
}
|
|
432
|
-
|
|
433
396
|
/**
|
|
434
397
|
* Sets body content for current node. If the value is a string, charset is added automatically
|
|
435
398
|
* to Content-Type (if it is text/*). If the value is a Buffer, you need to specify
|
|
436
399
|
* the charset yourself
|
|
437
400
|
*
|
|
438
|
-
* @param
|
|
439
|
-
* @return
|
|
401
|
+
* @param content Body content
|
|
402
|
+
* @return current node
|
|
440
403
|
*/
|
|
441
404
|
setContent(content) {
|
|
442
405
|
this.content = content;
|
|
@@ -448,7 +411,8 @@ class MimeNode {
|
|
|
448
411
|
this.content = err;
|
|
449
412
|
};
|
|
450
413
|
this.content.once('error', this._contentErrorHandler);
|
|
451
|
-
}
|
|
414
|
+
}
|
|
415
|
+
else if (typeof this.content === 'string') {
|
|
452
416
|
this._isPlainText = mimeFuncs.isPlainText(this.content);
|
|
453
417
|
if (this._isPlainText && mimeFuncs.hasLongerLines(this.content, 76)) {
|
|
454
418
|
// If there are lines longer than 76 symbols/bytes do not use 7bit
|
|
@@ -457,59 +421,48 @@ class MimeNode {
|
|
|
457
421
|
}
|
|
458
422
|
return this;
|
|
459
423
|
}
|
|
460
|
-
|
|
461
424
|
build(callback) {
|
|
462
425
|
let promise;
|
|
463
|
-
|
|
464
426
|
if (!callback) {
|
|
465
427
|
promise = new Promise((resolve, reject) => {
|
|
466
428
|
callback = shared.callbackPromise(resolve, reject);
|
|
467
429
|
});
|
|
468
430
|
}
|
|
469
|
-
|
|
431
|
+
const done = callback;
|
|
470
432
|
const stream = this.createReadStream();
|
|
471
433
|
const buf = [];
|
|
472
434
|
let buflen = 0;
|
|
473
435
|
let returned = false;
|
|
474
|
-
|
|
475
436
|
stream.on('readable', () => {
|
|
476
437
|
let chunk;
|
|
477
|
-
|
|
478
438
|
while ((chunk = stream.read()) !== null) {
|
|
479
439
|
buf.push(chunk);
|
|
480
440
|
buflen += chunk.length;
|
|
481
441
|
}
|
|
482
442
|
});
|
|
483
|
-
|
|
484
443
|
stream.once('error', err => {
|
|
485
444
|
if (returned) {
|
|
486
445
|
return;
|
|
487
446
|
}
|
|
488
447
|
returned = true;
|
|
489
|
-
|
|
490
|
-
return callback(err);
|
|
448
|
+
return done(err);
|
|
491
449
|
});
|
|
492
|
-
|
|
493
|
-
stream.once('end', chunk => {
|
|
450
|
+
stream.once('end', (chunk) => {
|
|
494
451
|
if (returned) {
|
|
495
452
|
return;
|
|
496
453
|
}
|
|
497
454
|
returned = true;
|
|
498
|
-
|
|
499
455
|
if (chunk && chunk.length) {
|
|
500
456
|
buf.push(chunk);
|
|
501
457
|
buflen += chunk.length;
|
|
502
458
|
}
|
|
503
|
-
return
|
|
459
|
+
return done(null, Buffer.concat(buf, buflen));
|
|
504
460
|
});
|
|
505
|
-
|
|
506
461
|
return promise;
|
|
507
462
|
}
|
|
508
|
-
|
|
509
463
|
getTransferEncoding() {
|
|
510
464
|
let transferEncoding = false;
|
|
511
465
|
const contentType = (this.getHeader('Content-Type') || '').toString().toLowerCase().trim();
|
|
512
|
-
|
|
513
466
|
if (this.content) {
|
|
514
467
|
transferEncoding = (this.getHeader('Content-Transfer-Encoding') || '').toString().toLowerCase().trim();
|
|
515
468
|
if (!transferEncoding || !['base64', 'quoted-printable'].includes(transferEncoding)) {
|
|
@@ -517,51 +470,47 @@ class MimeNode {
|
|
|
517
470
|
// If there are no special symbols, no need to modify the text
|
|
518
471
|
if (this._isPlainText && !this._hasLongLines) {
|
|
519
472
|
transferEncoding = '7bit';
|
|
520
|
-
}
|
|
473
|
+
}
|
|
474
|
+
else if (typeof this.content === 'string' || this.content instanceof Buffer) {
|
|
521
475
|
// detect preferred encoding for string value
|
|
522
476
|
transferEncoding = this._getTextEncoding(this.content) === 'Q' ? 'quoted-printable' : 'base64';
|
|
523
|
-
}
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
524
479
|
// we can not check content for a stream, so either use preferred encoding or fallback to QP
|
|
525
480
|
transferEncoding = this.textEncoding === 'B' ? 'base64' : 'quoted-printable';
|
|
526
481
|
}
|
|
527
|
-
}
|
|
482
|
+
}
|
|
483
|
+
else if (!/^(multipart|message)\//i.test(contentType)) {
|
|
528
484
|
transferEncoding = transferEncoding || 'base64';
|
|
529
485
|
}
|
|
530
486
|
}
|
|
531
487
|
}
|
|
532
488
|
return transferEncoding;
|
|
533
489
|
}
|
|
534
|
-
|
|
535
490
|
/**
|
|
536
491
|
* Builds the header block for the mime node. Append \r\n\r\n before writing the content
|
|
537
492
|
*
|
|
538
|
-
* @returns
|
|
493
|
+
* @returns Headers
|
|
539
494
|
*/
|
|
540
495
|
buildHeaders() {
|
|
541
496
|
const transferEncoding = this.getTransferEncoding();
|
|
542
497
|
const headers = [];
|
|
543
|
-
|
|
544
498
|
if (transferEncoding) {
|
|
545
499
|
this.setHeader('Content-Transfer-Encoding', transferEncoding);
|
|
546
500
|
}
|
|
547
|
-
|
|
548
501
|
if (this.filename && !this.getHeader('Content-Disposition')) {
|
|
549
502
|
this.setHeader('Content-Disposition', 'attachment');
|
|
550
503
|
}
|
|
551
|
-
|
|
552
504
|
// Ensure mandatory header fields
|
|
553
505
|
if (this.rootNode === this) {
|
|
554
506
|
if (!this.getHeader('Date')) {
|
|
555
507
|
this.setHeader('Date', this.date.toUTCString().replace(/GMT/, '+0000'));
|
|
556
508
|
}
|
|
557
|
-
|
|
558
509
|
// ensure that Message-Id is present
|
|
559
510
|
this.messageId();
|
|
560
|
-
|
|
561
511
|
if (!this.getHeader('MIME-Version')) {
|
|
562
512
|
this.setHeader('MIME-Version', '1.0');
|
|
563
513
|
}
|
|
564
|
-
|
|
565
514
|
// Ensure that Content-Type is the last header for the root node
|
|
566
515
|
for (let i = this._headers.length - 2; i >= 0; i--) {
|
|
567
516
|
const header = this._headers[i];
|
|
@@ -571,7 +520,6 @@ class MimeNode {
|
|
|
571
520
|
}
|
|
572
521
|
}
|
|
573
522
|
}
|
|
574
|
-
|
|
575
523
|
this._headers.forEach(header => {
|
|
576
524
|
let key = header.key;
|
|
577
525
|
let value = header.value;
|
|
@@ -579,7 +527,6 @@ class MimeNode {
|
|
|
579
527
|
let param;
|
|
580
528
|
const options = {};
|
|
581
529
|
const formattedHeaders = FORMATTED_HEADERS;
|
|
582
|
-
|
|
583
530
|
if (value && typeof value === 'object' && !formattedHeaders.includes(key)) {
|
|
584
531
|
// the keys come from a caller supplied header object and `options.prepared`
|
|
585
532
|
// below decides whether the value is emitted raw, so an own "__proto__" key
|
|
@@ -590,17 +537,16 @@ class MimeNode {
|
|
|
590
537
|
return;
|
|
591
538
|
}
|
|
592
539
|
}
|
|
593
|
-
|
|
594
540
|
if (options.prepared) {
|
|
595
541
|
// header value is
|
|
596
542
|
if (options.foldLines) {
|
|
597
543
|
headers.push(mimeFuncs.foldLines(key + ': ' + value));
|
|
598
|
-
}
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
599
546
|
headers.push(key + ': ' + value);
|
|
600
547
|
}
|
|
601
548
|
return;
|
|
602
549
|
}
|
|
603
|
-
|
|
604
550
|
switch (header.key) {
|
|
605
551
|
case 'Content-Disposition':
|
|
606
552
|
structured = mimeFuncs.parseHeaderValue(value);
|
|
@@ -609,27 +555,19 @@ class MimeNode {
|
|
|
609
555
|
}
|
|
610
556
|
value = mimeFuncs.buildHeaderValue(structured);
|
|
611
557
|
break;
|
|
612
|
-
|
|
613
558
|
case 'Content-Type':
|
|
614
559
|
structured = mimeFuncs.parseHeaderValue(value);
|
|
615
|
-
|
|
616
560
|
// the type token decides multipart and charset below, so clean it before
|
|
617
561
|
// those run and not just on the way out, otherwise a control char makes
|
|
618
562
|
// the checks miss and the header ends up claiming a type it is not set up for
|
|
619
563
|
structured.value = (structured.value || '').toString().replace(/[\x00-\x1f\x7f]/g, '');
|
|
620
|
-
|
|
621
564
|
this._handleContentType(structured);
|
|
622
|
-
|
|
623
|
-
if (
|
|
624
|
-
structured.value.match(/^text\/plain\b/) &&
|
|
565
|
+
if (structured.value.match(/^text\/plain\b/) &&
|
|
625
566
|
typeof this.content === 'string' &&
|
|
626
|
-
/[\u0080-\uFFFF]/.test(this.content)
|
|
627
|
-
) {
|
|
567
|
+
/[\u0080-\uFFFF]/.test(this.content)) {
|
|
628
568
|
structured.params.charset = 'utf-8';
|
|
629
569
|
}
|
|
630
|
-
|
|
631
570
|
value = mimeFuncs.buildHeaderValue(structured);
|
|
632
|
-
|
|
633
571
|
if (this.filename) {
|
|
634
572
|
// add support for non-compliant clients like QQ webmail
|
|
635
573
|
// we can't build the value with buildHeaderValue as the value is non standard and
|
|
@@ -640,7 +578,6 @@ class MimeNode {
|
|
|
640
578
|
param = /[\x00-\x1f\x7f]/.test(this.filename)
|
|
641
579
|
? mimeFuncs.encodeWord(this.filename, this._getTextEncoding(this.filename), 52)
|
|
642
580
|
: this._encodeWords(this.filename);
|
|
643
|
-
|
|
644
581
|
if (param !== this.filename || /[\s'"\\;:/=(),<>@[\]?]|^-/.test(param)) {
|
|
645
582
|
// include value in quotes if needed, escaping backslashes and quotes as
|
|
646
583
|
// quoted-pairs exactly like buildHeaderValue does for filename=, otherwise
|
|
@@ -650,7 +587,6 @@ class MimeNode {
|
|
|
650
587
|
value += '; name=' + param;
|
|
651
588
|
}
|
|
652
589
|
break;
|
|
653
|
-
|
|
654
590
|
case 'Bcc':
|
|
655
591
|
if (!this.keepBcc) {
|
|
656
592
|
// skip BCC values
|
|
@@ -658,14 +594,11 @@ class MimeNode {
|
|
|
658
594
|
}
|
|
659
595
|
break;
|
|
660
596
|
}
|
|
661
|
-
|
|
662
597
|
value = this._encodeHeaderValue(key, value);
|
|
663
|
-
|
|
664
598
|
// skip empty lines
|
|
665
599
|
if (!(value || '').toString().trim()) {
|
|
666
600
|
return;
|
|
667
601
|
}
|
|
668
|
-
|
|
669
602
|
if (typeof this.normalizeHeaderKey === 'function') {
|
|
670
603
|
const normalized = this.normalizeHeaderKey(key, value);
|
|
671
604
|
// the result replaces the key on the way into the header, so it gets the same
|
|
@@ -676,26 +609,21 @@ class MimeNode {
|
|
|
676
609
|
key = cleaned;
|
|
677
610
|
}
|
|
678
611
|
}
|
|
679
|
-
|
|
680
612
|
headers.push(mimeFuncs.foldLines(key + ': ' + value, 76));
|
|
681
613
|
});
|
|
682
|
-
|
|
683
614
|
return headers.join('\r\n');
|
|
684
615
|
}
|
|
685
|
-
|
|
686
616
|
/**
|
|
687
617
|
* Streams the rfc2822 message from the current node. If this is a root node,
|
|
688
618
|
* mandatory header fields are set if missing (Date, Message-Id, MIME-Version)
|
|
689
619
|
*
|
|
690
|
-
* @return
|
|
620
|
+
* @return Compiled message
|
|
691
621
|
*/
|
|
692
622
|
createReadStream(options) {
|
|
693
623
|
options = options || {};
|
|
694
|
-
|
|
695
624
|
const stream = new PassThrough(options);
|
|
696
625
|
let outputStream = stream;
|
|
697
626
|
let transform;
|
|
698
|
-
|
|
699
627
|
this.stream(stream, options, err => {
|
|
700
628
|
if (err) {
|
|
701
629
|
outputStream.emit('error', err);
|
|
@@ -703,77 +631,67 @@ class MimeNode {
|
|
|
703
631
|
}
|
|
704
632
|
stream.end();
|
|
705
633
|
});
|
|
706
|
-
|
|
707
634
|
for (let i = 0, len = this._transforms.length; i < len; i++) {
|
|
708
|
-
transform =
|
|
635
|
+
transform =
|
|
636
|
+
typeof this._transforms[i] === 'function' ? this._transforms[i]() : this._transforms[i];
|
|
709
637
|
outputStream.once('error', err => {
|
|
710
638
|
transform.emit('error', err);
|
|
711
639
|
});
|
|
712
640
|
outputStream = outputStream.pipe(transform);
|
|
713
641
|
}
|
|
714
|
-
|
|
715
642
|
// ensure terminating newline after possible user transforms
|
|
716
643
|
transform = new LastNewline();
|
|
717
644
|
outputStream.once('error', err => {
|
|
718
645
|
transform.emit('error', err);
|
|
719
646
|
});
|
|
720
647
|
outputStream = outputStream.pipe(transform);
|
|
721
|
-
|
|
722
648
|
// dkim and stuff
|
|
723
649
|
for (let i = 0, len = this._processFuncs.length; i < len; i++) {
|
|
724
650
|
transform = this._processFuncs[i];
|
|
725
651
|
outputStream = transform(outputStream);
|
|
726
652
|
}
|
|
727
|
-
|
|
728
653
|
if (this.newline) {
|
|
729
654
|
const winbreak = ['win', 'windows', 'dos', '\r\n'].includes(this.newline.toString().toLowerCase());
|
|
730
655
|
const newlineTransform = winbreak ? new LeWindows() : new LeUnix();
|
|
731
|
-
|
|
732
656
|
const stream = outputStream.pipe(newlineTransform);
|
|
733
657
|
outputStream.on('error', err => stream.emit('error', err));
|
|
734
658
|
return stream;
|
|
735
659
|
}
|
|
736
|
-
|
|
737
660
|
return outputStream;
|
|
738
661
|
}
|
|
739
|
-
|
|
740
662
|
/**
|
|
741
663
|
* Appends a transform stream object to the transforms list. Final output
|
|
742
664
|
* is passed through this stream before exposing
|
|
743
665
|
*
|
|
744
|
-
* @param
|
|
666
|
+
* @param transform Read-Write stream
|
|
745
667
|
*/
|
|
746
668
|
transform(transform) {
|
|
747
669
|
this._transforms.push(transform);
|
|
748
670
|
}
|
|
749
|
-
|
|
750
671
|
/**
|
|
751
672
|
* Appends a post process function. The functon is run after transforms and
|
|
752
673
|
* uses the following syntax
|
|
753
674
|
*
|
|
754
675
|
* processFunc(input) -> outputStream
|
|
755
676
|
*
|
|
756
|
-
* @param
|
|
677
|
+
* @param processFunc Read-Write stream
|
|
757
678
|
*/
|
|
758
679
|
processFunc(processFunc) {
|
|
759
680
|
this._processFuncs.push(processFunc);
|
|
760
681
|
}
|
|
761
|
-
|
|
762
682
|
stream(outputStream, options, done) {
|
|
763
683
|
const transferEncoding = this.getTransferEncoding();
|
|
764
684
|
let contentStream;
|
|
765
685
|
let localStream;
|
|
766
|
-
|
|
767
686
|
// protect actual callback against multiple triggering
|
|
768
687
|
let returned = false;
|
|
769
|
-
const callback = err => {
|
|
688
|
+
const callback = (err) => {
|
|
770
689
|
if (returned) {
|
|
771
690
|
return;
|
|
772
691
|
}
|
|
773
692
|
returned = true;
|
|
774
693
|
done(err);
|
|
775
694
|
};
|
|
776
|
-
|
|
777
695
|
// for multipart nodes, push child nodes
|
|
778
696
|
// for content nodes end the stream
|
|
779
697
|
const finalize = () => {
|
|
@@ -792,14 +710,13 @@ class MimeNode {
|
|
|
792
710
|
setImmediate(processChildNode);
|
|
793
711
|
});
|
|
794
712
|
};
|
|
795
|
-
|
|
796
713
|
if (this.multipart) {
|
|
797
714
|
setImmediate(processChildNode);
|
|
798
|
-
}
|
|
715
|
+
}
|
|
716
|
+
else {
|
|
799
717
|
return callback();
|
|
800
718
|
}
|
|
801
719
|
};
|
|
802
|
-
|
|
803
720
|
// pushes node content
|
|
804
721
|
const sendContent = () => {
|
|
805
722
|
if (this.content) {
|
|
@@ -807,26 +724,23 @@ class MimeNode {
|
|
|
807
724
|
// content is already errored
|
|
808
725
|
return callback(this.content);
|
|
809
726
|
}
|
|
810
|
-
|
|
811
727
|
if (typeof this.content.pipe === 'function') {
|
|
812
728
|
this.content.removeListener('error', this._contentErrorHandler);
|
|
813
729
|
this._contentErrorHandler = err => callback(err);
|
|
814
730
|
this.content.once('error', this._contentErrorHandler);
|
|
815
731
|
}
|
|
816
|
-
|
|
817
732
|
const createStream = () => {
|
|
818
733
|
if (['quoted-printable', 'base64'].includes(transferEncoding)) {
|
|
819
734
|
contentStream = new (transferEncoding === 'base64' ? base64 : qp).Encoder(options);
|
|
820
|
-
|
|
821
735
|
contentStream.pipe(outputStream, {
|
|
822
736
|
end: false
|
|
823
737
|
});
|
|
824
738
|
contentStream.once('end', finalize);
|
|
825
739
|
contentStream.once('error', err => callback(err));
|
|
826
|
-
|
|
827
740
|
localStream = this._getStream(this.content);
|
|
828
741
|
localStream.pipe(contentStream);
|
|
829
|
-
}
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
830
744
|
// anything that is not QP or Base54 passes as-is
|
|
831
745
|
localStream = this._getStream(this.content);
|
|
832
746
|
localStream.pipe(outputStream, {
|
|
@@ -834,10 +748,8 @@ class MimeNode {
|
|
|
834
748
|
});
|
|
835
749
|
localStream.once('end', finalize);
|
|
836
750
|
}
|
|
837
|
-
|
|
838
751
|
localStream.once('error', err => callback(err));
|
|
839
752
|
};
|
|
840
|
-
|
|
841
753
|
if (this.content._resolve) {
|
|
842
754
|
const chunks = [];
|
|
843
755
|
let chunklen = 0;
|
|
@@ -866,26 +778,24 @@ class MimeNode {
|
|
|
866
778
|
this.content._resolvedValue = Buffer.concat(chunks, chunklen);
|
|
867
779
|
setImmediate(createStream);
|
|
868
780
|
});
|
|
869
|
-
}
|
|
781
|
+
}
|
|
782
|
+
else {
|
|
870
783
|
setImmediate(createStream);
|
|
871
784
|
}
|
|
872
785
|
return;
|
|
873
786
|
}
|
|
874
787
|
return setImmediate(finalize);
|
|
875
788
|
};
|
|
876
|
-
|
|
877
789
|
if (this._raw) {
|
|
878
790
|
setImmediate(() => {
|
|
879
791
|
if (Object.prototype.toString.call(this._raw) === '[object Error]') {
|
|
880
792
|
// content is already errored
|
|
881
793
|
return callback(this._raw);
|
|
882
794
|
}
|
|
883
|
-
|
|
884
795
|
// remove default error handler (if set)
|
|
885
796
|
if (typeof this._raw.pipe === 'function') {
|
|
886
797
|
this._raw.removeListener('error', this._contentErrorHandler);
|
|
887
798
|
}
|
|
888
|
-
|
|
889
799
|
const raw = this._getStream(this._raw);
|
|
890
800
|
raw.pipe(outputStream, {
|
|
891
801
|
end: false
|
|
@@ -893,25 +803,23 @@ class MimeNode {
|
|
|
893
803
|
raw.on('error', err => outputStream.emit('error', err));
|
|
894
804
|
raw.on('end', finalize);
|
|
895
805
|
});
|
|
896
|
-
}
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
897
808
|
outputStream.write(this.buildHeaders() + '\r\n\r\n');
|
|
898
809
|
setImmediate(sendContent);
|
|
899
810
|
}
|
|
900
811
|
}
|
|
901
|
-
|
|
902
812
|
/**
|
|
903
813
|
* Sets envelope to be used instead of the generated one
|
|
904
814
|
*
|
|
905
|
-
* @return
|
|
815
|
+
* @return SMTP envelope in the form of {from: 'from@example.com', to: ['to@example.com']}
|
|
906
816
|
*/
|
|
907
817
|
setEnvelope(envelope) {
|
|
908
818
|
let list;
|
|
909
|
-
|
|
910
819
|
this._envelope = {
|
|
911
820
|
from: false,
|
|
912
821
|
to: []
|
|
913
822
|
};
|
|
914
|
-
|
|
915
823
|
if (envelope.from) {
|
|
916
824
|
list = [];
|
|
917
825
|
this._convertAddresses(this._parseEnvelopeAddresses(envelope.from), list);
|
|
@@ -921,29 +829,25 @@ class MimeNode {
|
|
|
921
829
|
}
|
|
922
830
|
}
|
|
923
831
|
const seenRecipients = new Set();
|
|
832
|
+
const recipients = [];
|
|
924
833
|
['to', 'cc', 'bcc'].forEach(key => {
|
|
925
834
|
if (envelope[key]) {
|
|
926
|
-
this._convertAddresses(this._parseEnvelopeAddresses(envelope[key]),
|
|
835
|
+
this._convertAddresses(this._parseEnvelopeAddresses(envelope[key]), recipients, seenRecipients);
|
|
927
836
|
}
|
|
928
837
|
});
|
|
929
|
-
|
|
930
|
-
this._envelope.to = this._envelope.to.map(to => to.address).filter(address => address);
|
|
931
|
-
|
|
838
|
+
this._envelope.to = recipients.map(to => to.address).filter(address => address);
|
|
932
839
|
const standardFields = ['to', 'cc', 'bcc', 'from'];
|
|
933
840
|
shared.copyOwnKeys(this._envelope, envelope, key => standardFields.includes(key));
|
|
934
|
-
|
|
935
841
|
return this;
|
|
936
842
|
}
|
|
937
|
-
|
|
938
843
|
/**
|
|
939
844
|
* Generates and returns an object with parsed address fields
|
|
940
845
|
*
|
|
941
|
-
* @return
|
|
846
|
+
* @return Address object
|
|
942
847
|
*/
|
|
943
848
|
getAddresses() {
|
|
944
849
|
const addresses = {};
|
|
945
850
|
const seenByKey = new Map();
|
|
946
|
-
|
|
947
851
|
this._headers.forEach(header => {
|
|
948
852
|
const key = header.key.toLowerCase();
|
|
949
853
|
if (['from', 'sender', 'reply-to', 'to', 'cc', 'bcc'].includes(key)) {
|
|
@@ -951,34 +855,29 @@ class MimeNode {
|
|
|
951
855
|
addresses[key] = [];
|
|
952
856
|
seenByKey.set(key, new Set());
|
|
953
857
|
}
|
|
954
|
-
|
|
955
858
|
this._convertAddresses(this._parseAddresses(header.value), addresses[key], seenByKey.get(key));
|
|
956
859
|
}
|
|
957
860
|
});
|
|
958
|
-
|
|
959
861
|
return addresses;
|
|
960
862
|
}
|
|
961
|
-
|
|
962
863
|
/**
|
|
963
864
|
* Generates and returns SMTP envelope with the sender address and a list of recipients addresses
|
|
964
865
|
*
|
|
965
|
-
* @return
|
|
866
|
+
* @return SMTP envelope in the form of {from: 'from@example.com', to: ['to@example.com']}
|
|
966
867
|
*/
|
|
967
868
|
getEnvelope() {
|
|
968
869
|
if (this._envelope) {
|
|
969
870
|
return this._envelope;
|
|
970
871
|
}
|
|
971
|
-
|
|
972
872
|
const envelope = {
|
|
973
873
|
from: false,
|
|
974
874
|
to: []
|
|
975
875
|
};
|
|
976
|
-
|
|
977
876
|
// Built once and carried across the headers. Letting _convertAddresses seed it per
|
|
978
877
|
// call would cost O(headers x recipients), and a message can carry many address
|
|
979
878
|
// headers: `headers: { to: [...] }` emits one To per entry.
|
|
980
879
|
const seenRecipients = new Set();
|
|
981
|
-
|
|
880
|
+
const recipients = [];
|
|
982
881
|
this._headers.forEach(header => {
|
|
983
882
|
const list = [];
|
|
984
883
|
if (header.key === 'From' || (!envelope.from && ['Reply-To', 'Sender'].includes(header.key))) {
|
|
@@ -986,20 +885,18 @@ class MimeNode {
|
|
|
986
885
|
if (list.length && list[0]) {
|
|
987
886
|
envelope.from = list[0].address;
|
|
988
887
|
}
|
|
989
|
-
}
|
|
990
|
-
|
|
888
|
+
}
|
|
889
|
+
else if (['To', 'Cc', 'Bcc'].includes(header.key)) {
|
|
890
|
+
this._convertAddresses(this._parseAddresses(header.value), recipients, seenRecipients);
|
|
991
891
|
}
|
|
992
892
|
});
|
|
993
|
-
|
|
994
|
-
envelope.to = envelope.to.map(to => to.address);
|
|
995
|
-
|
|
893
|
+
envelope.to = recipients.map(to => to.address);
|
|
996
894
|
return envelope;
|
|
997
895
|
}
|
|
998
|
-
|
|
999
896
|
/**
|
|
1000
897
|
* Returns Message-Id value. If it does not exist, then creates one
|
|
1001
898
|
*
|
|
1002
|
-
* @return
|
|
899
|
+
* @return Message-Id value
|
|
1003
900
|
*/
|
|
1004
901
|
messageId() {
|
|
1005
902
|
let messageId = this.getHeader('Message-ID');
|
|
@@ -1010,15 +907,13 @@ class MimeNode {
|
|
|
1010
907
|
}
|
|
1011
908
|
return messageId;
|
|
1012
909
|
}
|
|
1013
|
-
|
|
1014
910
|
/**
|
|
1015
911
|
* Sets pregenerated content that will be used as the output of this node
|
|
1016
912
|
*
|
|
1017
|
-
* @param
|
|
913
|
+
* @param raw Raw MIME contents
|
|
1018
914
|
*/
|
|
1019
915
|
setRaw(raw) {
|
|
1020
916
|
this._raw = raw;
|
|
1021
|
-
|
|
1022
917
|
if (this._raw && typeof this._raw.pipe === 'function') {
|
|
1023
918
|
// pre-stream handler. might be triggered if a stream is set as content
|
|
1024
919
|
// and 'error' fires before anything is done with this stream
|
|
@@ -1028,22 +923,21 @@ class MimeNode {
|
|
|
1028
923
|
};
|
|
1029
924
|
this._raw.once('error', this._contentErrorHandler);
|
|
1030
925
|
}
|
|
1031
|
-
|
|
1032
926
|
return this;
|
|
1033
927
|
}
|
|
1034
|
-
|
|
1035
928
|
/////// PRIVATE METHODS
|
|
1036
|
-
|
|
1037
929
|
/**
|
|
1038
930
|
* Checks an access policy flag for this node and every node above it. The flags are set
|
|
1039
931
|
* from the options the node was built with, and createChild only ever sees the options
|
|
1040
932
|
* the caller passed, so a child of a closed tree starts out open. Reading the answer off
|
|
1041
933
|
* the parent chain keeps it right whatever order the tree was assembled in.
|
|
1042
934
|
*
|
|
1043
|
-
* @param
|
|
1044
|
-
* @return
|
|
935
|
+
* @param flag Either 'disableFileAccess' or 'disableUrlAccess'
|
|
936
|
+
* @return true if this node or an ancestor closed that access
|
|
937
|
+
* @internal
|
|
1045
938
|
*/
|
|
1046
939
|
_accessDisabled(flag) {
|
|
940
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
1047
941
|
let node = this;
|
|
1048
942
|
while (node) {
|
|
1049
943
|
if (node[flag]) {
|
|
@@ -1053,36 +947,32 @@ class MimeNode {
|
|
|
1053
947
|
}
|
|
1054
948
|
return false;
|
|
1055
949
|
}
|
|
1056
|
-
|
|
1057
950
|
/**
|
|
1058
951
|
* Detects and returns handle to a stream related with the content.
|
|
1059
952
|
*
|
|
1060
|
-
* @param
|
|
1061
|
-
* @returns
|
|
953
|
+
* @param content Node content
|
|
954
|
+
* @returns Stream object
|
|
955
|
+
* @internal
|
|
1062
956
|
*/
|
|
1063
957
|
_getStream(content) {
|
|
1064
958
|
let contentStream;
|
|
1065
|
-
|
|
1066
959
|
if (content._resolvedValue) {
|
|
1067
960
|
// pass string or buffer content as a stream
|
|
1068
961
|
contentStream = new PassThrough();
|
|
1069
|
-
|
|
1070
962
|
setImmediate(() => {
|
|
1071
963
|
try {
|
|
1072
964
|
contentStream.end(content._resolvedValue);
|
|
1073
|
-
}
|
|
965
|
+
}
|
|
966
|
+
catch (_err) {
|
|
1074
967
|
contentStream.emit('error', _err);
|
|
1075
968
|
}
|
|
1076
969
|
});
|
|
1077
|
-
|
|
1078
970
|
return contentStream;
|
|
1079
971
|
}
|
|
1080
|
-
|
|
1081
972
|
if (typeof content.pipe === 'function') {
|
|
1082
973
|
// assume as stream
|
|
1083
974
|
return content;
|
|
1084
975
|
}
|
|
1085
|
-
|
|
1086
976
|
if (content && typeof content.path === 'string' && !content.href) {
|
|
1087
977
|
if (this._accessDisabled('disableFileAccess')) {
|
|
1088
978
|
contentStream = new PassThrough();
|
|
@@ -1096,7 +986,6 @@ class MimeNode {
|
|
|
1096
986
|
// read file
|
|
1097
987
|
return fs.createReadStream(content.path);
|
|
1098
988
|
}
|
|
1099
|
-
|
|
1100
989
|
if (content && typeof content.href === 'string') {
|
|
1101
990
|
if (this._accessDisabled('disableUrlAccess')) {
|
|
1102
991
|
contentStream = new PassThrough();
|
|
@@ -1112,42 +1001,60 @@ class MimeNode {
|
|
|
1112
1001
|
// forms the parser accepts, such as a leading space or a slash-less authority
|
|
1113
1002
|
return nmfetch(content.href, { headers: content.httpHeaders, tls: content.tls });
|
|
1114
1003
|
}
|
|
1115
|
-
|
|
1116
1004
|
// pass string or buffer content as a stream
|
|
1117
1005
|
contentStream = new PassThrough();
|
|
1118
|
-
|
|
1119
1006
|
setImmediate(() => {
|
|
1120
1007
|
try {
|
|
1121
1008
|
contentStream.end(content || '');
|
|
1122
|
-
}
|
|
1009
|
+
}
|
|
1010
|
+
catch (_err) {
|
|
1123
1011
|
contentStream.emit('error', _err);
|
|
1124
1012
|
}
|
|
1125
1013
|
});
|
|
1126
1014
|
return contentStream;
|
|
1127
1015
|
}
|
|
1128
|
-
|
|
1129
1016
|
/**
|
|
1130
1017
|
* Parses addresses. Takes in a single address or an array or an
|
|
1131
1018
|
* array of address arrays (eg. To: [[first group], [second group],...])
|
|
1132
1019
|
*
|
|
1133
|
-
* @param
|
|
1134
|
-
* @return
|
|
1020
|
+
* @param addresses Addresses to be parsed
|
|
1021
|
+
* @return An array of address objects
|
|
1022
|
+
* @internal
|
|
1135
1023
|
*/
|
|
1136
1024
|
_parseAddresses(addresses) {
|
|
1137
|
-
// Collected into one list as we go. concat.apply spreads the entries into arguments
|
|
1138
|
-
// and throws a RangeError once a recipient array is long enough to pass the
|
|
1139
|
-
// argument limit, which a large Bcc list reaches on its own.
|
|
1140
1025
|
const flattened = [];
|
|
1141
|
-
|
|
1142
|
-
|
|
1026
|
+
// Nested arrays are flattened iteratively. concat.apply throws once a recipient list
|
|
1027
|
+
// is long enough to pass the argument limit, and an array handed to addressparser is
|
|
1028
|
+
// stringified, which recurses once per nesting level and exhausts the call stack on
|
|
1029
|
+
// a deep enough value. Every array is walked once, which also ends a self-referential
|
|
1030
|
+
// input
|
|
1031
|
+
const seen = new WeakSet();
|
|
1032
|
+
const stack = [];
|
|
1033
|
+
const enter = (list) => {
|
|
1034
|
+
if (!seen.has(list)) {
|
|
1035
|
+
seen.add(list);
|
|
1036
|
+
stack.push({ list, pos: 0 });
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
enter(Array.isArray(addresses) ? addresses : [addresses]);
|
|
1040
|
+
while (stack.length) {
|
|
1041
|
+
const frame = stack[stack.length - 1];
|
|
1042
|
+
if (frame.pos >= frame.list.length) {
|
|
1043
|
+
stack.pop();
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
const address = frame.list[frame.pos++];
|
|
1047
|
+
if (Array.isArray(address)) {
|
|
1048
|
+
enter(address);
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1143
1051
|
if (address && address.address) {
|
|
1144
1052
|
const normalized = this._normalizeAddress(address.address);
|
|
1145
1053
|
if (normalized === address.address && typeof address.name === 'string') {
|
|
1146
1054
|
// there is nothing to rewrite, so there is nothing to keep off the original
|
|
1147
1055
|
flattened.push(address);
|
|
1148
|
-
|
|
1056
|
+
continue;
|
|
1149
1057
|
}
|
|
1150
|
-
|
|
1151
1058
|
// rewriting would land on the object the caller passed in and might
|
|
1152
1059
|
// still hold a reference to, so rewrite a copy of it instead. An own
|
|
1153
1060
|
// "__proto__" key would make the copy inherit from caller data, and
|
|
@@ -1156,18 +1063,15 @@ class MimeNode {
|
|
|
1156
1063
|
copy.address = normalized;
|
|
1157
1064
|
copy.name = address.name || '';
|
|
1158
1065
|
flattened.push(copy);
|
|
1159
|
-
|
|
1066
|
+
continue;
|
|
1160
1067
|
}
|
|
1161
|
-
|
|
1162
1068
|
const parsed = this._normalizeParsedAddresses(addressparser(address));
|
|
1163
1069
|
for (let i = 0; i < parsed.length; i++) {
|
|
1164
1070
|
flattened.push(parsed[i]);
|
|
1165
1071
|
}
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1072
|
+
}
|
|
1168
1073
|
return flattened;
|
|
1169
1074
|
}
|
|
1170
|
-
|
|
1171
1075
|
/**
|
|
1172
1076
|
* Normalizes the addresses of a freshly parsed address list, groups included.
|
|
1173
1077
|
*
|
|
@@ -1176,22 +1080,22 @@ class MimeNode {
|
|
|
1176
1080
|
* a consumer reading the parsed form back is handed the ambiguous
|
|
1177
1081
|
* 'user@evil.com@good.com' that the header and the envelope no longer carry.
|
|
1178
1082
|
*
|
|
1179
|
-
* @param
|
|
1180
|
-
* @return
|
|
1083
|
+
* @param parsed An array of address objects, as returned by addressparser
|
|
1084
|
+
* @return The same array, with every address normalized
|
|
1085
|
+
* @internal
|
|
1181
1086
|
*/
|
|
1182
1087
|
_normalizeParsedAddresses(parsed) {
|
|
1183
1088
|
// addressparser builds these objects, so no caller holds a reference to rewrite around
|
|
1184
1089
|
parsed.forEach(entry => {
|
|
1185
1090
|
if (entry.address) {
|
|
1186
1091
|
entry.address = this._normalizeAddress(entry.address);
|
|
1187
|
-
}
|
|
1092
|
+
}
|
|
1093
|
+
else if (entry.group) {
|
|
1188
1094
|
this._normalizeParsedAddresses(entry.group);
|
|
1189
1095
|
}
|
|
1190
1096
|
});
|
|
1191
|
-
|
|
1192
1097
|
return parsed;
|
|
1193
1098
|
}
|
|
1194
|
-
|
|
1195
1099
|
/**
|
|
1196
1100
|
* Parses the addresses of an explicitly set envelope.
|
|
1197
1101
|
*
|
|
@@ -1199,8 +1103,9 @@ class MimeNode {
|
|
|
1199
1103
|
* such as 'root' is the address here. Header parsing has to read the same value as a
|
|
1200
1104
|
* display name, as a value with no '@' in it can not be an addr-spec in a header.
|
|
1201
1105
|
*
|
|
1202
|
-
* @param
|
|
1203
|
-
* @return
|
|
1106
|
+
* @param addresses Addresses to be parsed
|
|
1107
|
+
* @return An array of address objects
|
|
1108
|
+
* @internal
|
|
1204
1109
|
*/
|
|
1205
1110
|
_parseEnvelopeAddresses(addresses) {
|
|
1206
1111
|
return this._parseAddresses(addresses).map(entry => {
|
|
@@ -1210,12 +1115,12 @@ class MimeNode {
|
|
|
1210
1115
|
return { address: this._normalizeAddress(entry.name), name: '' };
|
|
1211
1116
|
});
|
|
1212
1117
|
}
|
|
1213
|
-
|
|
1214
1118
|
/**
|
|
1215
1119
|
* Normalizes a header key, uses Camel-Case form, except for uppercase MIME-
|
|
1216
1120
|
*
|
|
1217
|
-
* @param
|
|
1218
|
-
* @return
|
|
1121
|
+
* @param key Key to be normalized
|
|
1122
|
+
* @return key in Camel-Case form
|
|
1123
|
+
* @internal
|
|
1219
1124
|
*/
|
|
1220
1125
|
_normalizeHeaderKey(key) {
|
|
1221
1126
|
key = (key || '')
|
|
@@ -1231,46 +1136,58 @@ class MimeNode {
|
|
|
1231
1136
|
.replace(/^X-SMTPAPI$|^(MIME|DKIM|ARC|BIMI)\b|^[a-z]|-(SPF|FBL|ID|MD5)$|-[a-z]/gi, c => c.toUpperCase())
|
|
1232
1137
|
// special case
|
|
1233
1138
|
.replace(/^Content-Features$/i, 'Content-features');
|
|
1234
|
-
|
|
1235
1139
|
return key;
|
|
1236
1140
|
}
|
|
1237
|
-
|
|
1238
1141
|
/**
|
|
1239
1142
|
* Checks if the content type is multipart and defines boundary if needed.
|
|
1240
1143
|
* Doesn't return anything, modifies object argument instead.
|
|
1241
1144
|
*
|
|
1242
|
-
* @param
|
|
1145
|
+
* @param structured Parsed header value for 'Content-Type' key
|
|
1146
|
+
* @internal
|
|
1243
1147
|
*/
|
|
1244
1148
|
_handleContentType(structured) {
|
|
1245
1149
|
this.contentType = structured.value.trim().toLowerCase();
|
|
1246
|
-
|
|
1247
1150
|
this.multipart = /^multipart\//i.test(this.contentType) ? this.contentType.substr(this.contentType.indexOf('/') + 1) : false;
|
|
1248
|
-
|
|
1249
1151
|
if (this.multipart) {
|
|
1250
|
-
|
|
1251
|
-
|
|
1152
|
+
// The declared value and the delimiters are assigned from the same expression
|
|
1153
|
+
// here, so whatever the header emitter then does with it, the two sides cannot
|
|
1154
|
+
// disagree about which characters the boundary is made of.
|
|
1155
|
+
//
|
|
1156
|
+
// Stripping runs before the fallback rather than over the whole chain: a
|
|
1157
|
+
// boundary made only of control characters would otherwise strip to '' and
|
|
1158
|
+
// leave the node declaring no boundary and streaming bare '--' delimiters.
|
|
1159
|
+
// _generateBoundary cleans what it builds, but this is the one place the boundary
|
|
1160
|
+
// is written, so it cleans the value it is about to write rather than trusting
|
|
1161
|
+
// where it came from. MimeNode is exported and subclassable, so the generator is
|
|
1162
|
+
// not necessarily the one below. Stripping twice costs nothing, it is idempotent.
|
|
1163
|
+
const declared = _stripBoundaryControls(structured.params.boundary || this.boundary || '');
|
|
1164
|
+
this.boundary = structured.params.boundary =
|
|
1165
|
+
declared || _stripBoundaryControls(this._generateBoundary());
|
|
1166
|
+
}
|
|
1167
|
+
else {
|
|
1252
1168
|
this.boundary = false;
|
|
1253
1169
|
}
|
|
1254
1170
|
}
|
|
1255
|
-
|
|
1256
1171
|
/**
|
|
1257
1172
|
* Generates a multipart boundary value
|
|
1258
1173
|
*
|
|
1259
|
-
* @return
|
|
1174
|
+
* @return boundary value
|
|
1175
|
+
* @internal
|
|
1260
1176
|
*/
|
|
1261
1177
|
_generateBoundary() {
|
|
1262
|
-
|
|
1178
|
+
// baseBoundary and boundaryPrefix are public, so they may hold something the
|
|
1179
|
+
// constructor never cleaned, and the -Part_ suffix keeps the result non empty
|
|
1180
|
+
return _stripBoundaryControls(this.rootNode.boundaryPrefix + '-' + this.rootNode.baseBoundary) + '-Part_' + this._nodeId;
|
|
1263
1181
|
}
|
|
1264
|
-
|
|
1265
1182
|
/**
|
|
1266
1183
|
* Encodes a header value for use in the generated rfc2822 email.
|
|
1267
1184
|
*
|
|
1268
|
-
* @param
|
|
1269
|
-
* @param
|
|
1185
|
+
* @param key Header key
|
|
1186
|
+
* @param value Header value
|
|
1187
|
+
* @internal
|
|
1270
1188
|
*/
|
|
1271
1189
|
_encodeHeaderValue(key, value) {
|
|
1272
1190
|
key = this._normalizeHeaderKey(key);
|
|
1273
|
-
|
|
1274
1191
|
switch (key) {
|
|
1275
1192
|
// Structured headers
|
|
1276
1193
|
case 'From':
|
|
@@ -1280,7 +1197,6 @@ class MimeNode {
|
|
|
1280
1197
|
case 'Bcc':
|
|
1281
1198
|
case 'Reply-To':
|
|
1282
1199
|
return this._convertAddresses(this._parseAddresses(value));
|
|
1283
|
-
|
|
1284
1200
|
// values enclosed in <>
|
|
1285
1201
|
case 'Message-ID':
|
|
1286
1202
|
case 'In-Reply-To':
|
|
@@ -1292,73 +1208,61 @@ class MimeNode {
|
|
|
1292
1208
|
.toString()
|
|
1293
1209
|
.replace(/\r?\n|\r/g, ' ')
|
|
1294
1210
|
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
|
|
1295
|
-
|
|
1296
1211
|
if (value.charAt(0) !== '<') {
|
|
1297
1212
|
value = '<' + value;
|
|
1298
1213
|
}
|
|
1299
|
-
|
|
1300
1214
|
if (value.charAt(value.length - 1) !== '>') {
|
|
1301
1215
|
value = value + '>';
|
|
1302
1216
|
}
|
|
1303
1217
|
return value;
|
|
1304
|
-
|
|
1305
1218
|
// space separated list of values enclosed in <>
|
|
1306
1219
|
case 'References':
|
|
1307
1220
|
value = [].concat
|
|
1308
|
-
.apply(
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
return elm.replace(/<[^>]*>/g, str => str.replace(/\s/g, '')).split(/\s+/);
|
|
1317
|
-
})
|
|
1318
|
-
)
|
|
1221
|
+
.apply([], [].concat(value || '').map(elm => {
|
|
1222
|
+
elm = (elm || '')
|
|
1223
|
+
.toString()
|
|
1224
|
+
.replace(/\r?\n|\r/g, ' ')
|
|
1225
|
+
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '')
|
|
1226
|
+
.trim();
|
|
1227
|
+
return elm.replace(/<[^>]*>/g, str => str.replace(/\s/g, '')).split(/\s+/);
|
|
1228
|
+
}))
|
|
1319
1229
|
.map(elm => {
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1230
|
+
if (elm.charAt(0) !== '<') {
|
|
1231
|
+
elm = '<' + elm;
|
|
1232
|
+
}
|
|
1233
|
+
if (elm.charAt(elm.length - 1) !== '>') {
|
|
1234
|
+
elm = elm + '>';
|
|
1235
|
+
}
|
|
1236
|
+
return elm;
|
|
1237
|
+
});
|
|
1329
1238
|
return value.join(' ').trim();
|
|
1330
|
-
|
|
1331
1239
|
case 'Date':
|
|
1332
1240
|
if (Object.prototype.toString.call(value) === '[object Date]') {
|
|
1333
1241
|
return value.toUTCString().replace(/GMT/, '+0000');
|
|
1334
1242
|
}
|
|
1335
|
-
|
|
1336
1243
|
value = (value || '').toString().replace(/\r?\n|\r/g, ' ');
|
|
1337
1244
|
return this._encodeHeaderText(value);
|
|
1338
|
-
|
|
1339
1245
|
case 'Content-Type':
|
|
1340
1246
|
case 'Content-Disposition':
|
|
1341
1247
|
// if it includes a filename then it is already encoded
|
|
1342
1248
|
return (value || '').toString().replace(/\r?\n|\r/g, ' ');
|
|
1343
|
-
|
|
1344
1249
|
default:
|
|
1345
1250
|
value = (value || '').toString().replace(/\r?\n|\r/g, ' ');
|
|
1346
1251
|
return this._encodeHeaderText(value);
|
|
1347
1252
|
}
|
|
1348
1253
|
}
|
|
1349
|
-
|
|
1350
1254
|
/**
|
|
1351
1255
|
* Rebuilds address object using punycode and other adjustments
|
|
1352
1256
|
*
|
|
1353
|
-
* @param
|
|
1354
|
-
* @param
|
|
1355
|
-
* @
|
|
1257
|
+
* @param addresses An array of address objects
|
|
1258
|
+
* @param [uniqueList] An array to be populated with addresses
|
|
1259
|
+
* @param [seenAddresses] Addresses already added to uniqueList, shared with recursive calls to keep deduplication linear
|
|
1260
|
+
* @return address string
|
|
1261
|
+
* @internal
|
|
1356
1262
|
*/
|
|
1357
1263
|
_convertAddresses(addresses, uniqueList, seenAddresses) {
|
|
1358
1264
|
const values = [];
|
|
1359
|
-
|
|
1360
1265
|
uniqueList = uniqueList || [];
|
|
1361
|
-
|
|
1362
1266
|
// Membership is checked once per address, so scanning uniqueList itself would make
|
|
1363
1267
|
// a recipient list cost O(n^2). Groups recurse with the same set so that a nested
|
|
1364
1268
|
// group still dedupes against the addresses collected around it, and a caller that
|
|
@@ -1369,84 +1273,72 @@ class MimeNode {
|
|
|
1369
1273
|
seenAddresses.add(uniqueList[i].address);
|
|
1370
1274
|
}
|
|
1371
1275
|
}
|
|
1372
|
-
|
|
1373
1276
|
[].concat(addresses || []).forEach(address => {
|
|
1374
1277
|
if (address.address) {
|
|
1375
1278
|
address.address = this._normalizeAddress(address.address);
|
|
1376
|
-
|
|
1377
1279
|
if (!address.name) {
|
|
1378
1280
|
// an address that carries a special, be it a quoted local part or a domain
|
|
1379
1281
|
// that could not be normalized, is only unambiguous inside angle brackets.
|
|
1380
1282
|
// Without them a ',' or a ';' anywhere in it reads as a recipient separator
|
|
1381
1283
|
// and the header would list more recipients than the envelope carries
|
|
1382
1284
|
values.push(PLAIN_ADDRESS.test(address.address) ? address.address : `<${address.address}>`);
|
|
1383
|
-
}
|
|
1285
|
+
}
|
|
1286
|
+
else {
|
|
1384
1287
|
values.push(`${this._encodeAddressName(address.name)} <${address.address}>`);
|
|
1385
1288
|
}
|
|
1386
|
-
|
|
1387
1289
|
if (!seenAddresses.has(address.address)) {
|
|
1388
1290
|
seenAddresses.add(address.address);
|
|
1389
1291
|
uniqueList.push(address);
|
|
1390
1292
|
}
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
).trim();
|
|
1293
|
+
}
|
|
1294
|
+
else if (address.group) {
|
|
1295
|
+
const groupListAddresses = (address.group.length ? this._convertAddresses(address.group, uniqueList, seenAddresses) : '').trim();
|
|
1395
1296
|
values.push(`${this._encodeAddressName(address.name)}:${groupListAddresses};`);
|
|
1396
1297
|
}
|
|
1397
1298
|
});
|
|
1398
|
-
|
|
1399
1299
|
return values.join(', ');
|
|
1400
1300
|
}
|
|
1401
|
-
|
|
1402
1301
|
/**
|
|
1403
1302
|
* Normalizes an email address
|
|
1404
1303
|
*
|
|
1405
|
-
* @param
|
|
1406
|
-
* @return
|
|
1304
|
+
* @param address An array of address objects
|
|
1305
|
+
* @return address string
|
|
1306
|
+
* @internal
|
|
1407
1307
|
*/
|
|
1408
1308
|
_normalizeAddress(address) {
|
|
1409
1309
|
address = (address || '')
|
|
1410
1310
|
.toString()
|
|
1411
1311
|
.replace(/[\x00-\x1F\x7F<>]+/g, ' ') // remove unallowed characters
|
|
1412
1312
|
.trim();
|
|
1413
|
-
|
|
1414
1313
|
if (!address) {
|
|
1415
1314
|
// callers use an empty value to detect a missing address
|
|
1416
1315
|
return address;
|
|
1417
1316
|
}
|
|
1418
|
-
|
|
1419
1317
|
const lastAt = address.lastIndexOf('@');
|
|
1420
1318
|
if (lastAt < 0) {
|
|
1421
1319
|
// Bare username, there is no domain to split off
|
|
1422
1320
|
return this._normalizeLocalPart(address);
|
|
1423
1321
|
}
|
|
1424
|
-
|
|
1425
1322
|
const user = address.substr(0, lastAt);
|
|
1426
1323
|
const domain = address.substr(lastAt + 1);
|
|
1427
|
-
|
|
1428
1324
|
// Unicode in the local part is kept as is, see _normalizeLocalPart for the rest of it.
|
|
1429
1325
|
// A domain has no quoting construct to fall back on, so whatever is not a valid domain
|
|
1430
1326
|
// is kept as supplied and it is _convertAddresses that keeps such an address unambiguous.
|
|
1431
1327
|
// Domains are punycoded when the local part is ASCII ('safe@jõgeva.ee' -> 'safe@xn--jgeva-dua.ee').
|
|
1432
1328
|
// When the local part contains non-ASCII bytes the address already requires SMTPUTF8,
|
|
1433
1329
|
// so the domain is kept (or decoded back) as UTF-8 for symmetry on both sides of '@'.
|
|
1434
|
-
|
|
1435
1330
|
let encodedDomain = domain;
|
|
1436
|
-
|
|
1437
1331
|
// A non-ASCII local part already requires SMTPUTF8, so the domain stays UTF-8 for
|
|
1438
1332
|
// symmetry on both sides of the '@' rather than being encoded to an A-label
|
|
1439
1333
|
const smtputf8 = /[\x80-\uFFFF]/.test(user);
|
|
1440
|
-
|
|
1441
1334
|
try {
|
|
1442
1335
|
encodedDomain = normalizeDomain(domain.toLowerCase(), smtputf8);
|
|
1443
|
-
}
|
|
1336
|
+
}
|
|
1337
|
+
catch (_err) {
|
|
1444
1338
|
// keep domain as supplied
|
|
1445
1339
|
}
|
|
1446
|
-
|
|
1447
1340
|
return `${this._normalizeLocalPart(user)}@${encodedDomain}`;
|
|
1448
1341
|
}
|
|
1449
|
-
|
|
1450
1342
|
/**
|
|
1451
1343
|
* Normalizes the local part of an address into a form that can be emitted as is.
|
|
1452
1344
|
*
|
|
@@ -1456,54 +1348,55 @@ class MimeNode {
|
|
|
1456
1348
|
* while the split here is always at the last one. So whatever is not already one of
|
|
1457
1349
|
* the two valid forms goes back out as a quoted-string.
|
|
1458
1350
|
*
|
|
1459
|
-
* @param
|
|
1460
|
-
* @return
|
|
1351
|
+
* @param user Local part of an address
|
|
1352
|
+
* @return Local part as a dot-atom or as a quoted-string
|
|
1353
|
+
* @internal
|
|
1461
1354
|
*/
|
|
1462
1355
|
_normalizeLocalPart(user) {
|
|
1463
1356
|
if (DOT_ATOM.test(user) || QUOTED_STRING.test(user)) {
|
|
1464
1357
|
return user;
|
|
1465
1358
|
}
|
|
1466
|
-
|
|
1467
1359
|
return mimeFuncs.quoteString(user);
|
|
1468
1360
|
}
|
|
1469
|
-
|
|
1470
1361
|
/**
|
|
1471
1362
|
* If needed, mime encodes the name part
|
|
1472
1363
|
*
|
|
1473
|
-
* @param
|
|
1474
|
-
* @returns
|
|
1364
|
+
* @param name Name part of an address
|
|
1365
|
+
* @returns Mime word encoded string if needed
|
|
1366
|
+
* @internal
|
|
1475
1367
|
*/
|
|
1476
1368
|
_encodeAddressName(name) {
|
|
1477
1369
|
if (!/^[\w ]*$/.test(name)) {
|
|
1478
1370
|
if (/^[\x20-\x7e]*$/.test(name)) {
|
|
1479
1371
|
return mimeFuncs.quoteString(name);
|
|
1480
|
-
}
|
|
1372
|
+
}
|
|
1373
|
+
else {
|
|
1481
1374
|
return mimeFuncs.encodeWord(name, this._getTextEncoding(name), 52);
|
|
1482
1375
|
}
|
|
1483
1376
|
}
|
|
1484
1377
|
return name;
|
|
1485
1378
|
}
|
|
1486
|
-
|
|
1487
1379
|
/**
|
|
1488
1380
|
* Encodes an unstructured header value. Such a value can only carry VCHAR and WSP, so a
|
|
1489
1381
|
* control char or DEL has to be forced into the mime encoded word that a non-ascii value
|
|
1490
1382
|
* would get anyway. HT stays as it is, it is valid folding whitespace here.
|
|
1491
1383
|
*
|
|
1492
|
-
* @param
|
|
1493
|
-
* @returns
|
|
1384
|
+
* @param value Header value to encode
|
|
1385
|
+
* @returns Mime word encoded string if needed
|
|
1386
|
+
* @internal
|
|
1494
1387
|
*/
|
|
1495
1388
|
_encodeHeaderText(value) {
|
|
1496
1389
|
return /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(value)
|
|
1497
1390
|
? mimeFuncs.encodeWord(value, this._getTextEncoding(value), 52)
|
|
1498
1391
|
: // encodeWords only encodes if needed, otherwise the original string is returned
|
|
1499
|
-
|
|
1392
|
+
this._encodeWords(value);
|
|
1500
1393
|
}
|
|
1501
|
-
|
|
1502
1394
|
/**
|
|
1503
1395
|
* If needed, mime encodes the name part
|
|
1504
1396
|
*
|
|
1505
|
-
* @param
|
|
1506
|
-
* @returns
|
|
1397
|
+
* @param value Name part of an address
|
|
1398
|
+
* @returns Mime word encoded string if needed
|
|
1399
|
+
* @internal
|
|
1507
1400
|
*/
|
|
1508
1401
|
_encodeWords(value) {
|
|
1509
1402
|
// set encodeAll parameter to true even though it is against the recommendation of RFC2047,
|
|
@@ -1511,20 +1404,18 @@ class MimeNode {
|
|
|
1511
1404
|
// but some clients (eg. Zimbra) do not handle it properly and remove surrounding whitespace
|
|
1512
1405
|
return mimeFuncs.encodeWords(value, this._getTextEncoding(value), 52, true);
|
|
1513
1406
|
}
|
|
1514
|
-
|
|
1515
1407
|
/**
|
|
1516
1408
|
* Detects best mime encoding for a text value
|
|
1517
1409
|
*
|
|
1518
|
-
* @param
|
|
1519
|
-
* @return
|
|
1410
|
+
* @param value Value to check for
|
|
1411
|
+
* @return either 'Q' or 'B'
|
|
1412
|
+
* @internal
|
|
1520
1413
|
*/
|
|
1521
1414
|
_getTextEncoding(value) {
|
|
1522
1415
|
value = (value || '').toString();
|
|
1523
|
-
|
|
1524
1416
|
if (this.textEncoding) {
|
|
1525
1417
|
return this.textEncoding;
|
|
1526
1418
|
}
|
|
1527
|
-
|
|
1528
1419
|
// count latin alphabet symbols and 8-bit range symbols + control symbols
|
|
1529
1420
|
// if there are more latin characters, then use quoted-printable
|
|
1530
1421
|
// encoding, otherwise use base64
|
|
@@ -1534,33 +1425,29 @@ class MimeNode {
|
|
|
1534
1425
|
const code = value.charCodeAt(i);
|
|
1535
1426
|
if ((code >= 0x00 && code <= 0x08) || code === 0x0b || code === 0x0c || (code >= 0x0e && code <= 0x1f) || code >= 0x80) {
|
|
1536
1427
|
nonLatinLen++;
|
|
1537
|
-
}
|
|
1428
|
+
}
|
|
1429
|
+
else if ((code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a)) {
|
|
1538
1430
|
latinLen++;
|
|
1539
1431
|
}
|
|
1540
1432
|
}
|
|
1541
1433
|
// if there are more latin symbols than binary/unicode, then prefer Q, otherwise B
|
|
1542
1434
|
return nonLatinLen < latinLen ? 'Q' : 'B';
|
|
1543
1435
|
}
|
|
1544
|
-
|
|
1545
1436
|
/**
|
|
1546
1437
|
* Generates a message id
|
|
1547
1438
|
*
|
|
1548
|
-
* @return
|
|
1439
|
+
* @return Random Message-ID value
|
|
1440
|
+
* @internal
|
|
1549
1441
|
*/
|
|
1550
1442
|
_generateMessageId() {
|
|
1551
|
-
return (
|
|
1552
|
-
'<' +
|
|
1443
|
+
return ('<' +
|
|
1553
1444
|
[2, 2, 2, 6].reduce(
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
crypto.randomBytes(4).toString('hex')
|
|
1557
|
-
) +
|
|
1445
|
+
// crux to generate UUID-like random strings
|
|
1446
|
+
(prev, len) => prev + '-' + crypto.randomBytes(len).toString('hex'), crypto.randomBytes(4).toString('hex')) +
|
|
1558
1447
|
'@' +
|
|
1559
1448
|
// try to use the domain of the FROM address or fallback to server hostname
|
|
1560
1449
|
(this.getEnvelope().from || this.hostname || 'localhost').split('@').pop() +
|
|
1561
|
-
'>'
|
|
1562
|
-
);
|
|
1450
|
+
'>');
|
|
1563
1451
|
}
|
|
1564
1452
|
}
|
|
1565
|
-
|
|
1566
|
-
module.exports = MimeNode;
|
|
1453
|
+
export default MimeNode;
|