@mavogel/awscdk-rootmail 0.1.23 → 0.1.25

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.
Files changed (96) hide show
  1. package/.jsii +4 -4
  2. package/lib/rootmail.js +1 -1
  3. package/lib/ses-receive.js +1 -1
  4. package/node_modules/@zone-eu/mailsplit/README.md +1 -1
  5. package/node_modules/@zone-eu/mailsplit/lib/headers.js +10 -4
  6. package/node_modules/@zone-eu/mailsplit/lib/message-splitter.js +5 -4
  7. package/node_modules/@zone-eu/mailsplit/lib/mime-node.d.ts +4 -1
  8. package/node_modules/@zone-eu/mailsplit/lib/mime-node.js +23 -2
  9. package/node_modules/@zone-eu/mailsplit/lib/types.d.ts +3 -1
  10. package/node_modules/@zone-eu/mailsplit/package.json +4 -4
  11. package/node_modules/libbase64/CHANGELOG.md +20 -0
  12. package/node_modules/libbase64/README.md +40 -13
  13. package/node_modules/libbase64/dist/cjs/libbase64.d.ts +80 -0
  14. package/node_modules/libbase64/dist/cjs/libbase64.js +310 -0
  15. package/node_modules/libbase64/dist/cjs/package.json +3 -0
  16. package/node_modules/libbase64/dist/esm/libbase64.d.ts +80 -0
  17. package/node_modules/libbase64/{lib → dist/esm}/libbase64.js +74 -117
  18. package/node_modules/libbase64/dist/esm/package.json +3 -0
  19. package/node_modules/libbase64/package.json +59 -13
  20. package/node_modules/libmime/CHANGELOG.md +31 -0
  21. package/node_modules/libmime/README.md +55 -26
  22. package/node_modules/libmime/SECURITY.md +152 -0
  23. package/node_modules/libmime/dist/cjs/charset.d.ts +43 -0
  24. package/node_modules/libmime/dist/cjs/charset.js +135 -0
  25. package/node_modules/libmime/dist/cjs/charsets.d.ts +2 -0
  26. package/node_modules/libmime/dist/cjs/charsets.js +213 -0
  27. package/node_modules/libmime/dist/cjs/get-charset-name.d.ts +1 -0
  28. package/node_modules/libmime/dist/cjs/get-charset-name.js +224 -0
  29. package/node_modules/libmime/dist/cjs/libmime.d.ts +218 -0
  30. package/node_modules/libmime/dist/cjs/libmime.js +1013 -0
  31. package/node_modules/libmime/dist/cjs/mimetypes.d.ts +7 -0
  32. package/node_modules/libmime/dist/cjs/mimetypes.js +2072 -0
  33. package/node_modules/libmime/dist/cjs/package.json +3 -0
  34. package/node_modules/libmime/dist/cjs/types.d.ts +59 -0
  35. package/node_modules/libmime/dist/cjs/types.js +4 -0
  36. package/node_modules/libmime/dist/esm/charset.d.ts +43 -0
  37. package/node_modules/libmime/dist/esm/charset.js +125 -0
  38. package/node_modules/libmime/dist/esm/charsets.d.ts +2 -0
  39. package/node_modules/libmime/{lib → dist/esm}/charsets.js +3 -5
  40. package/node_modules/libmime/dist/esm/get-charset-name.d.ts +1 -0
  41. package/node_modules/libmime/{lib → dist/esm}/get-charset-name.js +1 -8
  42. package/node_modules/libmime/dist/esm/libmime.d.ts +219 -0
  43. package/node_modules/libmime/{lib → dist/esm}/libmime.js +256 -329
  44. package/node_modules/libmime/dist/esm/mimetypes.d.ts +7 -0
  45. package/node_modules/libmime/dist/esm/mimetypes.js +2068 -0
  46. package/node_modules/libmime/dist/esm/package.json +3 -0
  47. package/node_modules/libmime/dist/esm/types.d.ts +59 -0
  48. package/node_modules/libmime/dist/esm/types.js +3 -0
  49. package/node_modules/libmime/package.json +58 -15
  50. package/node_modules/libqp/CHANGELOG.md +18 -0
  51. package/node_modules/libqp/README.md +50 -19
  52. package/node_modules/libqp/dist/cjs/libqp.d.ts +74 -0
  53. package/node_modules/libqp/dist/cjs/libqp.js +378 -0
  54. package/node_modules/libqp/dist/cjs/package.json +3 -0
  55. package/node_modules/libqp/dist/esm/libqp.d.ts +74 -0
  56. package/node_modules/libqp/dist/esm/libqp.js +369 -0
  57. package/node_modules/libqp/dist/esm/package.json +3 -0
  58. package/node_modules/libqp/lib/libqp.js +4 -383
  59. package/node_modules/libqp/lib/package.json +3 -0
  60. package/node_modules/libqp/package.json +62 -6
  61. package/node_modules/mailparser/.release-please-manifest.json +1 -1
  62. package/node_modules/mailparser/CHANGELOG.md +42 -0
  63. package/node_modules/mailparser/package.json +5 -5
  64. package/node_modules/nodemailer/CHANGELOG.md +44 -0
  65. package/node_modules/nodemailer/dist/cjs/addressparser/index.js +695 -267
  66. package/node_modules/nodemailer/dist/cjs/mailer/mail-message.js +4 -1
  67. package/node_modules/nodemailer/dist/cjs/mime-funcs/index.js +8 -2
  68. package/node_modules/nodemailer/dist/cjs/mime-node/index.js +48 -16
  69. package/node_modules/nodemailer/dist/cjs/package-info.d.ts +1 -1
  70. package/node_modules/nodemailer/dist/cjs/package-info.js +1 -1
  71. package/node_modules/nodemailer/dist/cjs/shared/url.js +22 -5
  72. package/node_modules/nodemailer/dist/esm/addressparser/index.js +695 -267
  73. package/node_modules/nodemailer/dist/esm/mailer/mail-message.js +4 -1
  74. package/node_modules/nodemailer/dist/esm/mime-funcs/index.js +8 -2
  75. package/node_modules/nodemailer/dist/esm/mime-node/index.js +48 -16
  76. package/node_modules/nodemailer/dist/esm/package-info.d.ts +1 -1
  77. package/node_modules/nodemailer/dist/esm/package-info.js +1 -1
  78. package/node_modules/nodemailer/dist/esm/shared/url.js +22 -5
  79. package/node_modules/nodemailer/package.json +1 -1
  80. package/package.json +3 -3
  81. package/node_modules/libbase64/.eslintrc +0 -11
  82. package/node_modules/libbase64/.github/FUNDING.yml +0 -4
  83. package/node_modules/libbase64/.github/VULNERABILITY_REPORT.yml +0 -75
  84. package/node_modules/libbase64/.github/workflows/release.yaml +0 -39
  85. package/node_modules/libbase64/.github/workflows/test.yml +0 -21
  86. package/node_modules/libbase64/.ncurc.js +0 -13
  87. package/node_modules/libbase64/.prettierrc.js +0 -8
  88. package/node_modules/libmime/lib/charset.js +0 -131
  89. package/node_modules/libmime/lib/mimetypes.js +0 -2049
  90. package/node_modules/libqp/.eslintrc +0 -15
  91. package/node_modules/libqp/.github/FUNDING.yml +0 -4
  92. package/node_modules/libqp/.github/VULNERABILITY_REPORT.yml +0 -75
  93. package/node_modules/libqp/.github/workflows/release.yaml +0 -39
  94. package/node_modules/libqp/.github/workflows/test.yml +0 -21
  95. package/node_modules/libqp/.prettierrc.js +0 -8
  96. package/node_modules/libqp/test/libqp-test.js +0 -366
@@ -0,0 +1,1013 @@
1
+ "use strict";
2
+ /* eslint no-div-regex: 0 */
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.Libmime = void 0;
41
+ const node_buffer_1 = require("node:buffer");
42
+ const libbase64_1 = __importDefault(require("libbase64"));
43
+ const libqp_1 = __importDefault(require("libqp"));
44
+ const libcharset = __importStar(require("./charset.js"));
45
+ const mimetypes = __importStar(require("./mimetypes.js"));
46
+ const STAGE_KEY = 0x1001;
47
+ const STAGE_VALUE = 0x1002;
48
+ // Sets an own property on a plain object. A regular assignment with a
49
+ // '__proto__' key would silently modify the object's prototype instead of
50
+ // creating an own property, so it needs special handling.
51
+ const setOwnProperty = (obj, key, value) => {
52
+ if (key === '__proto__') {
53
+ Object.defineProperty(obj, key, {
54
+ value,
55
+ writable: true,
56
+ enumerable: true,
57
+ configurable: true
58
+ });
59
+ }
60
+ else {
61
+ obj[key] = value;
62
+ }
63
+ };
64
+ // Whitespace that separates the tokens of a header value. Kept as a char comparison,
65
+ // a regex test would be run for every single char of every parsed header
66
+ const isWSP = (chr) => chr === ' ' || chr === '\t' || chr === '\r' || chr === '\n' || chr === '\f' || chr === '\v';
67
+ class Libmime {
68
+ constructor(config) {
69
+ this.config = config || {};
70
+ }
71
+ /**
72
+ * Checks if a value is plaintext string (uses only printable 7bit chars)
73
+ *
74
+ * When isParam is set the value is destined for a header parameter, so HT, CR and LF
75
+ * are not plaintext either: a header parameter has no way to carry them. HT is a valid
76
+ * fold point, so folding and unfolding a header would rewrite it as a space, and CR/LF
77
+ * cannot appear in a header value at all. DEL is neither a token character nor qtext,
78
+ * so it can not be carried bare or quoted. Such values have to go through the rfc2231
79
+ * parameter continuation encoding instead, the same way non-ASCII text does.
80
+ *
81
+ * @param {String} value String to be tested
82
+ * @param {Boolean} [isParam] Set to true if the value is a header parameter value
83
+ * @returns {Boolean} true if it is a plaintext string
84
+ */
85
+ isPlainText(value, isParam) {
86
+ let re = isParam ? /[\x00-\x1f\x7f\u0080-\uFFFF]/ : /[\x00-\x08\x0b\x0c\x0e-\x1f\u0080-\uFFFF]/;
87
+ return typeof value === 'string' && !re.test(value);
88
+ }
89
+ // A parameter value goes out as is only when it is plain text that does not look like an encoded
90
+ // word. Receivers decode encoded words found in parameter values (and libmime itself does so for
91
+ // unencoded continuations), so as plain text it would come back as something else
92
+ isPlainParamValue(value) {
93
+ return this.isPlainText(value, true) && !value.includes('=?');
94
+ }
95
+ /**
96
+ * Checks if a multi line string containes lines longer than the selected value.
97
+ *
98
+ * Useful when detecting if a mail message needs any processing at all:
99
+ * if only plaintext characters are used and lines are short, then there is
100
+ * no need to encode the values in any way. If the value is plaintext but has
101
+ * longer lines then allowed, then use format=flowed
102
+ *
103
+ * @param {String} str String to be tested
104
+ * @param {Number} lineLength Max line length to check for
105
+ * @returns {Boolean} Returns true if there is at least one line longer than lineLength chars
106
+ */
107
+ hasLongerLines(str, lineLength) {
108
+ return new RegExp('^.{' + (lineLength + 1) + ',}', 'm').test(str);
109
+ }
110
+ /**
111
+ * Decodes a string from a format=flowed soft wrapping.
112
+ *
113
+ * @param {String} str Plaintext string with format=flowed to decode
114
+ * @param {Boolean} [delSp] If true, delete leading spaces (delsp=yes)
115
+ * @return {String} Mime decoded string
116
+ */
117
+ decodeFlowed(input, delSp) {
118
+ let str = (input || '').toString();
119
+ let lines = str.split(/\r?\n/);
120
+ let result = [];
121
+ // The current paragraph is kept as a list of non-empty pieces and joined once when it
122
+ // ends. Appending to one string and testing it with a regex on every line re-scans (and
123
+ // flattens) the whole paragraph each time, which is quadratic on long soft-wrapped bodies.
124
+ let parts = [], partsLength = 0;
125
+ let pushPart = (part) => {
126
+ if (part) {
127
+ parts.push(part);
128
+ partsLength += part.length;
129
+ }
130
+ };
131
+ // remove soft linebreaks
132
+ // soft linebreaks are added after space symbols
133
+ for (let i = 0; i < lines.length; i++) {
134
+ let line = lines[i];
135
+ // the last piece is never empty, so it holds the last character of the paragraph
136
+ let last = parts.length ? parts[parts.length - 1] : '';
137
+ // Lines never contain a line break, so a signature separator can only be the whole
138
+ // paragraph being exactly "-- "
139
+ let isSoftBreak = last.endsWith(' ') && !(partsLength === 3 && parts.join('') === '-- ');
140
+ if (isSoftBreak) {
141
+ if (delSp) {
142
+ // delsp adds space to text to be able to fold it
143
+ // these spaces can be removed once the text is unfolded
144
+ parts.pop();
145
+ partsLength -= last.length;
146
+ pushPart(last.slice(0, -1));
147
+ pushPart(line);
148
+ }
149
+ else {
150
+ // remove space stuffing of the continuation line, the stuffing of a paragraph's first
151
+ // line is removed below. Without delsp the text is only wrapped after spaces, so a
152
+ // leading space can only be stuffing (http://tools.ietf.org/html/rfc3676#section-4.4)
153
+ pushPart(line.replace(/^ /, ''));
154
+ }
155
+ }
156
+ else {
157
+ if (i > 0) {
158
+ result.push(parts.join(''));
159
+ }
160
+ parts = [];
161
+ partsLength = 0;
162
+ pushPart(line);
163
+ }
164
+ }
165
+ if (partsLength) {
166
+ result.push(parts.join(''));
167
+ }
168
+ // remove whitespace stuffing
169
+ // http://tools.ietf.org/html/rfc3676#section-4.4
170
+ return result.join('\n').replace(/^ /gm, '');
171
+ }
172
+ /**
173
+ * Adds soft line breaks to content marked with format=flowed to
174
+ * ensure that no line in the message is never longer than lineLength
175
+ *
176
+ * @param {String} str Plaintext string that requires wrapping
177
+ * @param {Number} [lineLength=76] Maximum length of a line
178
+ * @return {String} String with forced line breaks
179
+ */
180
+ encodeFlowed(str, lineLength) {
181
+ lineLength = lineLength || 76;
182
+ // space stuffing http://tools.ietf.org/html/rfc3676#section-4.2
183
+ let stuff = (line) => line.replace(/^( |From|>)/i, ' $1');
184
+ let flowed = [];
185
+ str.split(/\r?\n/).forEach(line => {
186
+ // A space in front of a hard line break would turn it into a soft one and the decoder would
187
+ // join the next line to this one, so trailing spaces are trimmed (rfc3676 section 4.2). The
188
+ // signature separator "-- " is the one line that keeps its trailing space
189
+ if (line !== '-- ') {
190
+ line = line.replace(/ +$/, '');
191
+ }
192
+ let folded = this.foldLines(stuff(line), lineLength, true).split('\r\n');
193
+ // the lines that folding starts need stuffing as much as the first one does,
194
+ // a continuation line that starts with ">" would be read as a quote
195
+ for (let i = 1; i < folded.length; i++) {
196
+ folded[i] = stuff(folded[i]);
197
+ }
198
+ flowed.push(folded.join('\r\n'));
199
+ });
200
+ return flowed.join('\r\n');
201
+ }
202
+ /**
203
+ * Encodes a string or an Buffer to an UTF-8 MIME Word (rfc2047)
204
+ *
205
+ * @param {String|Buffer} data String to be encoded
206
+ * @param {String} mimeWordEncoding='Q' Encoding for the mime word, either Q or B
207
+ * @param {Number} [maxLength=0] If set, split mime words into several chunks if needed
208
+ * @return {String} Single or several mime words joined together
209
+ */
210
+ encodeWord(data, mimeWordEncoding, maxLength) {
211
+ mimeWordEncoding = (mimeWordEncoding || 'Q').toString().toUpperCase().trim().charAt(0);
212
+ maxLength = maxLength || 0;
213
+ let encodedStr = '';
214
+ let toCharset = 'UTF-8';
215
+ if (maxLength && maxLength > 7 + toCharset.length) {
216
+ maxLength -= 7 + toCharset.length;
217
+ }
218
+ if (mimeWordEncoding === 'Q') {
219
+ // https://tools.ietf.org/html/rfc2047#section-5 rule (3)
220
+ encodedStr = libqp_1.default.encode(data).replace(/[^a-z0-9!*+\-/=]/gi, chr => {
221
+ let ord = chr.charCodeAt(0).toString(16).toUpperCase();
222
+ if (chr === ' ') {
223
+ return '_';
224
+ }
225
+ else {
226
+ return '=' + (ord.length === 1 ? '0' + ord : ord);
227
+ }
228
+ });
229
+ }
230
+ else if (mimeWordEncoding === 'B') {
231
+ // the chunking loop below splits raw text and base64 encodes each part, so a
232
+ // Buffer goes in as its UTF-8 string: handing it the base64 of the whole input
233
+ // would encode the encoding itself and decode back to base64 text
234
+ encodedStr = typeof data === 'string' ? data : data.toString('utf-8');
235
+ maxLength = maxLength ? Math.max(3, ((maxLength - (maxLength % 4)) / 4) * 3) : 0;
236
+ }
237
+ if (maxLength && (mimeWordEncoding !== 'B' ? encodedStr : libbase64_1.default.encode(data)).length > maxLength) {
238
+ if (mimeWordEncoding === 'Q') {
239
+ encodedStr = this.splitMimeEncodedString(encodedStr, maxLength).join('?= =?' + toCharset + '?' + mimeWordEncoding + '?');
240
+ }
241
+ else {
242
+ // RFC2047 6.3 (2) states that encoded-word must include an integral number of characters, so no chopping unicode sequences
243
+ let parts = [];
244
+ let lpart = '';
245
+ // iterating a string yields whole code points, a surrogate pair stays together and an
246
+ // unpaired surrogate comes on its own without swallowing the unit that follows it
247
+ for (let chr of encodedStr) {
248
+ // check if we can add this character to the existing string
249
+ // without breaking byte length limit
250
+ if (node_buffer_1.Buffer.byteLength(lpart + chr) <= maxLength || !lpart) {
251
+ lpart += chr;
252
+ }
253
+ else {
254
+ // we hit the length limit, so push the existing string and start over
255
+ parts.push(libbase64_1.default.encode(lpart));
256
+ lpart = chr;
257
+ }
258
+ }
259
+ if (lpart) {
260
+ parts.push(libbase64_1.default.encode(lpart));
261
+ }
262
+ if (parts.length > 1) {
263
+ encodedStr = parts.join('?= =?' + toCharset + '?' + mimeWordEncoding + '?');
264
+ }
265
+ else {
266
+ encodedStr = parts.join('');
267
+ }
268
+ }
269
+ }
270
+ else if (mimeWordEncoding === 'B') {
271
+ encodedStr = libbase64_1.default.encode(data);
272
+ }
273
+ return '=?' + toCharset + '?' + mimeWordEncoding + '?' + encodedStr + (encodedStr.slice(-2) === '?=' ? '' : '?=');
274
+ }
275
+ /**
276
+ * Normalizes a charset name to its canonical form, resolving common aliases and
277
+ * invalid names. For example `iso-8859-8-i` and `iso-8859-8-e` become `ISO-8859-8`
278
+ * and `win-1257` becomes `windows-1257`. Unknown charsets are returned uppercased.
279
+ * Missing or non-string input defaults to `UTF-8`.
280
+ *
281
+ * @param {String} charset Charset name to normalize
282
+ * @return {String} Canonical charset name
283
+ */
284
+ normalizeCharset(charset) {
285
+ return libcharset.normalizeCharset(charset);
286
+ }
287
+ /**
288
+ * Decodes the inner payload of a single mime encoded-word into a unicode string.
289
+ * Expects the three components of `=?charset?encoding?text?=` already separated.
290
+ *
291
+ * @param {String} charset Charset name from the encoded-word (may include an RFC 2231 language tag, which is ignored)
292
+ * @param {String} encoding Encoding indicator, either 'Q' or 'B' (case insensitive)
293
+ * @param {String} str Encoded payload between the second `?` and the trailing `?=`
294
+ * @return {String} Decoded unicode string
295
+ */
296
+ decodeWord(charset, encoding, str) {
297
+ // RFC2231 added language tag to the encoding
298
+ // see: https://tools.ietf.org/html/rfc2231#section-5
299
+ // this implementation silently ignores this tag
300
+ let splitPos = charset.indexOf('*');
301
+ if (splitPos >= 0) {
302
+ charset = charset.substring(0, splitPos);
303
+ }
304
+ charset = libcharset.normalizeCharset(charset);
305
+ encoding = encoding.toUpperCase();
306
+ let decoded;
307
+ if (encoding === 'Q') {
308
+ str = str
309
+ // remove spaces between = and hex char, this might indicate invalidly applied line splitting
310
+ .replace(/=\s+([0-9a-fA-F])/g, '=$1')
311
+ // convert all underscores to spaces
312
+ .replace(/[_\s]/g, ' ');
313
+ let buf = node_buffer_1.Buffer.from(str);
314
+ let bytes = [];
315
+ for (let i = 0, len = buf.length; i < len; i++) {
316
+ let c = buf[i];
317
+ if (i <= len - 2 && c === 0x3d /* = */) {
318
+ let c1 = this.getHex(buf[i + 1]);
319
+ let c2 = this.getHex(buf[i + 2]);
320
+ if (c1 && c2) {
321
+ let c = parseInt(c1 + c2, 16);
322
+ bytes.push(c);
323
+ i += 2;
324
+ continue;
325
+ }
326
+ }
327
+ bytes.push(c);
328
+ }
329
+ decoded = node_buffer_1.Buffer.from(bytes);
330
+ }
331
+ else if (encoding === 'B') {
332
+ // libbase64 decodes input made of several padded segments segment by segment
333
+ decoded = libbase64_1.default.decode(str);
334
+ }
335
+ else {
336
+ // keep as is, convert Buffer to unicode string, assume utf8
337
+ decoded = node_buffer_1.Buffer.from(str);
338
+ }
339
+ return libcharset.decode(decoded, charset);
340
+ }
341
+ /**
342
+ * Finds word sequences with non ascii text and converts these to mime words
343
+ *
344
+ * @param {String|Buffer} data String to be encoded
345
+ * @param {String} mimeWordEncoding='Q' Encoding for the mime word, either Q or B
346
+ * @param {Number} [maxLength=0] If set, split mime words into several chunks if needed
347
+ * @param {String} [fromCharset='UTF-8'] Source character set
348
+ * @return {String} String with possible mime words
349
+ */
350
+ encodeWords(data, mimeWordEncoding, maxLength, fromCharset) {
351
+ if (!fromCharset && typeof maxLength === 'string' && !maxLength.match(/^[0-9]+$/)) {
352
+ fromCharset = maxLength;
353
+ maxLength = undefined;
354
+ }
355
+ let maxLineLength = Number(maxLength) || 0;
356
+ let decodedValue = libcharset.decode(libcharset.convert(data || '', fromCharset));
357
+ let encodedValue;
358
+ let firstMatch = decodedValue.match(/(?:^|\s)([^\s]*[\u0080-\uFFFF])/);
359
+ if (!firstMatch) {
360
+ return decodedValue;
361
+ }
362
+ let startIndex = firstMatch.index +
363
+ (firstMatch[0].match(/[^\s]/) || {
364
+ index: 0
365
+ }).index;
366
+ // The encoded part ends with the word that holds the last non ascii char. Found with a scan,
367
+ // the regex that used to do this rescanned the rest of the word from every non ascii char in
368
+ // it, which made a long non ascii word quadratic
369
+ let lastIndex = decodedValue.length - 1;
370
+ while (lastIndex >= 0 && decodedValue.charCodeAt(lastIndex) < 0x80) {
371
+ lastIndex--;
372
+ }
373
+ let wordEnd = decodedValue.slice(lastIndex + 1).search(/\s/);
374
+ let endIndex = wordEnd < 0 ? decodedValue.length : lastIndex + 1 + wordEnd;
375
+ encodedValue =
376
+ (startIndex ? decodedValue.substring(0, startIndex) : '') +
377
+ this.encodeWord(decodedValue.substring(startIndex, endIndex), mimeWordEncoding || 'Q', maxLineLength) +
378
+ (endIndex < decodedValue.length ? decodedValue.slice(endIndex) : '');
379
+ return encodedValue;
380
+ }
381
+ /**
382
+ * Decode a string that might include one or several mime words
383
+ *
384
+ * @param {String} str String including some mime words that will be decoded
385
+ * @return {String} Decoded unicode string
386
+ */
387
+ decodeWords(str) {
388
+ return ((str || '')
389
+ .toString()
390
+ // find base64 words that can be joined
391
+ .replace(/(=\?([^?]+)\?[Bb]\?[^?]*\?=)\s*(?==\?([^?]+)\?[Bb]\?[^?]*\?=)/g, (match, left, chLeft, chRight) => {
392
+ // only mark b64 chunks to be joined if charsets match
393
+ if (libcharset.normalizeCharset(chLeft || '') === libcharset.normalizeCharset(chRight || '')) {
394
+ // set a joiner marker
395
+ return left + '__\x00JOIN\x00__';
396
+ }
397
+ return match;
398
+ })
399
+ // find QP words that can be joined
400
+ .replace(/(=\?([^?]+)\?[Qq]\?[^?]*\?=)\s*(?==\?([^?]+)\?[Qq]\?[^?]*\?=)/g, (match, left, chLeft, chRight) => {
401
+ // only mark QP chunks to be joined if charsets match
402
+ if (libcharset.normalizeCharset(chLeft || '') === libcharset.normalizeCharset(chRight || '')) {
403
+ // set a joiner marker
404
+ return left + '__\x00JOIN\x00__';
405
+ }
406
+ return match;
407
+ })
408
+ // join base64 encoded words
409
+ .replace(/(\?=)?__\x00JOIN\x00__(=\?([^?]+)\?[QqBb]\?)?/g, '')
410
+ // remove spaces between mime encoded words
411
+ .replace(/(=\?[^?]+\?[QqBb]\?[^?]*\?=)\s+(?==\?[^?]+\?[QqBb]\?[^?]*\?=)/g, '$1')
412
+ // decode words
413
+ .replace(/=\?([\w_\-*]+)\?([QqBb])\?([^?]*)\?=/g, (_m, charset, encoding, text) => this.decodeWord(charset, encoding, text)));
414
+ }
415
+ getHex(c) {
416
+ if (c !== undefined &&
417
+ ((c >= 0x30 /* 0 */ && c <= 0x39) /* 9 */ || (c >= 0x61 /* a */ && c <= 0x66) /* f */ || (c >= 0x41 /* A */ && c <= 0x46)) /* F */) {
418
+ return String.fromCharCode(c);
419
+ }
420
+ return false;
421
+ }
422
+ /**
423
+ * Splits a string by :
424
+ * The result is not mime word decoded, you need to do your own decoding based
425
+ * on the rules for the specific header key
426
+ *
427
+ * @param {String} headerLine Single header line, might include linebreaks as well if folded
428
+ * @return {Object} An object of {key, value}
429
+ */
430
+ decodeHeader(headerLine) {
431
+ let line = (headerLine || '')
432
+ .toString()
433
+ .replace(/(?:\r?\n|\r)[ \t]*/g, ' ')
434
+ .trim(), match = line.match(/^\s*([^:]+):(.*)$/), key = ((match && match[1]) || '').trim().toLowerCase(), value = ((match && match[2]) || '').trim();
435
+ return {
436
+ key,
437
+ value
438
+ };
439
+ }
440
+ /**
441
+ * Parses a block of header lines. Does not decode mime words as every
442
+ * header might have its own rules (eg. formatted email addresses and such)
443
+ *
444
+ * @param {String} headers Headers string
445
+ * @return {Object} An object of headers, where header keys are object keys and every value is an Array of the values for that key
446
+ */
447
+ decodeHeaders(headers) {
448
+ let lines = headers.split(/\r?\n|\r/), headersObj = {}, header, i, len;
449
+ // Empty lines in front of the block are not part of any header, a caller might
450
+ // include the line break that separates a mime part boundary from its headers
451
+ let headersPos = 0;
452
+ while (headersPos < lines.length && lines[headersPos] === '') {
453
+ headersPos++;
454
+ }
455
+ // An empty line terminates the header block, anything after it is the message body.
456
+ // Without this a body line that starts with whitespace would be folded into the last header
457
+ let bodyPos = lines.indexOf('', headersPos);
458
+ lines = lines.slice(headersPos, bodyPos >= 0 ? bodyPos : lines.length);
459
+ // unfold folded lines, a continuation line always starts with a space or a tab
460
+ // Built in one forward pass, removing continuation lines with splice() is quadratic
461
+ let unfolded = [];
462
+ for (i = 0, len = lines.length; i < len; i++) {
463
+ if (unfolded.length && /^[ \t]/.test(lines[i])) {
464
+ unfolded[unfolded.length - 1] += '\r\n' + lines[i];
465
+ }
466
+ else {
467
+ unfolded.push(lines[i]);
468
+ }
469
+ }
470
+ lines = unfolded;
471
+ for (i = 0, len = lines.length; i < len; i++) {
472
+ header = this.decodeHeader(lines[i]);
473
+ // own properties only, an attacker controlled key such as '__proto__' or 'toString' would
474
+ // otherwise resolve to an inherited Object.prototype member
475
+ if (!Object.hasOwn(headersObj, header.key)) {
476
+ setOwnProperty(headersObj, header.key, [header.value]);
477
+ }
478
+ else {
479
+ headersObj[header.key].push(header.value);
480
+ }
481
+ }
482
+ return headersObj;
483
+ }
484
+ /**
485
+ * Joins parsed header value together as 'value; param1=value1; param2=value2'
486
+ * PS: We are following RFC 822 for the list of special characters that we need to keep in quotes.
487
+ * Refer: https://www.w3.org/Protocols/rfc1341/4_Content-Type.html
488
+ * @param {Object} structured Parsed header value
489
+ * @return {String} joined header value
490
+ */
491
+ buildHeaderValue(structured) {
492
+ let paramsArray = [];
493
+ let params = structured.params || {};
494
+ Object.keys(params).forEach(key => {
495
+ // filename might include unicode characters so it is a special case
496
+ let value = params[key];
497
+ // a parameter name is a token too and it is emitted without any quoting around it
498
+ let param = key.replace(/[\x00-\x1f\x7f]/g, '');
499
+ if (!this.isPlainParamValue(value) || value.length >= 75) {
500
+ this.buildHeaderParam(param, value, 50).forEach(encodedParam => {
501
+ if (!/[\s"\\;:/=(),<>@[\]?]|^[-']|'$/.test(encodedParam.value) || encodedParam.key.endsWith('*')) {
502
+ paramsArray.push(encodedParam.key + '=' + encodedParam.value);
503
+ }
504
+ else {
505
+ paramsArray.push(encodedParam.key + '=' + JSON.stringify(encodedParam.value));
506
+ }
507
+ });
508
+ }
509
+ else if (!value.length || /[\s'"\\;:/=(),<>@[\]?]|^-/.test(value)) {
510
+ // a parameter value is a token or a quoted-string and a token is never empty, so
511
+ // a valueless parameter such as the 'flag' of 'multipart/mixed; flag; boundary=b'
512
+ // goes out as 'flag=""' rather than as the 'flag=' that parses as neither
513
+ paramsArray.push(param + '=' + JSON.stringify(value));
514
+ }
515
+ else {
516
+ paramsArray.push(param + '=' + value);
517
+ }
518
+ });
519
+ // the value ahead of the parameters is a token, it has no way to carry a control
520
+ // char or DEL and there is no quoting construct around it to escape one into
521
+ let value = typeof structured.value === 'string' ? structured.value.replace(/[\x00-\x1f\x7f]/g, '') : structured.value;
522
+ return value + (paramsArray.length ? '; ' + paramsArray.join('; ') : '');
523
+ }
524
+ /**
525
+ * Parses a header value with key=value arguments into a structured
526
+ * object.
527
+ *
528
+ * parseHeaderValue('text/plain; CHARSET=UTF-8') ->
529
+ * {
530
+ * 'value': 'text/plain',
531
+ * 'params': {
532
+ * 'charset': 'UTF-8'
533
+ * }
534
+ * }
535
+ *
536
+ * The result also carries a non-enumerable `decodedParams` array that names the params
537
+ * assembled from rfc2231 segments (title*=, title*0*=, title*0=). Those values are already
538
+ * decoded, so a caller that runs decodeWords() over the params has to leave them out
539
+ *
540
+ * @param {String} str Header value
541
+ * @return {Object} Header value as a parsed structure
542
+ */
543
+ parseHeaderValue(str) {
544
+ let response = {
545
+ value: '',
546
+ params: {}
547
+ };
548
+ // names of the params assembled from rfc2231 segments, see the continuation handling below.
549
+ // Non-enumerable, so the {value, params} shape callers serialize and compare stays as it is
550
+ Object.defineProperty(response, 'decodedParams', {
551
+ value: [],
552
+ writable: true,
553
+ enumerable: false,
554
+ configurable: true
555
+ });
556
+ let key = false;
557
+ let value = '';
558
+ // Length of the significant part of `value`. Whitespace outside of quotes is
559
+ // not part of the value, while whitespace inside quotes is (rfc2045 section 5.1)
560
+ let valueEnd = 0;
561
+ let stage = STAGE_VALUE;
562
+ let quote = false;
563
+ let escaped = false;
564
+ let chr;
565
+ // Stores the collected value, either as the default value, as the value for the
566
+ // current key, or as a key that has no value at all. Runs once per parameter
567
+ let commit = () => {
568
+ let collected = value.substring(0, valueEnd);
569
+ value = '';
570
+ valueEnd = 0;
571
+ if (stage === STAGE_KEY) {
572
+ // key without a value, see emptykey:
573
+ // Header-Key: somevalue; key=value; emptykey
574
+ if (collected) {
575
+ setOwnProperty(response.params, collected.toLowerCase(), '');
576
+ }
577
+ }
578
+ else if (key === false) {
579
+ // default value
580
+ response.value = collected;
581
+ }
582
+ else {
583
+ // subkey value
584
+ setOwnProperty(response.params, key, collected);
585
+ }
586
+ };
587
+ for (let i = 0, len = str.length; i < len; i++) {
588
+ chr = str.charAt(i);
589
+ switch (stage) {
590
+ case STAGE_KEY:
591
+ if (chr === '=') {
592
+ key = value.substring(0, valueEnd).toLowerCase();
593
+ value = '';
594
+ valueEnd = 0;
595
+ stage = STAGE_VALUE;
596
+ }
597
+ else if (chr === ';') {
598
+ // key without a value, eg. the "inline" in "attachment; inline; filename=a.txt"
599
+ commit();
600
+ }
601
+ else if (isWSP(chr)) {
602
+ if (value.length) {
603
+ value += chr;
604
+ }
605
+ }
606
+ else {
607
+ value += chr;
608
+ valueEnd = value.length;
609
+ }
610
+ break;
611
+ case STAGE_VALUE:
612
+ if (escaped) {
613
+ value += chr;
614
+ valueEnd = value.length;
615
+ }
616
+ else if (chr === '\\') {
617
+ escaped = true;
618
+ continue;
619
+ }
620
+ else if (chr === '"') {
621
+ // every char between the quotes is significant, including whitespace
622
+ quote = !quote;
623
+ }
624
+ else if (!quote && chr === ';') {
625
+ commit();
626
+ stage = STAGE_KEY;
627
+ }
628
+ else if (!quote && isWSP(chr)) {
629
+ if (value.length) {
630
+ value += chr;
631
+ }
632
+ }
633
+ else {
634
+ value += chr;
635
+ valueEnd = value.length;
636
+ }
637
+ escaped = false;
638
+ break;
639
+ }
640
+ }
641
+ // finalize remainder
642
+ commit();
643
+ // handle parameter value continuations
644
+ // https://tools.ietf.org/html/rfc2231#section-3
645
+ // Collect the segments of every continuation parameter. These are kept out of
646
+ // response.params so that a half assembled value can not leak into the result
647
+ let continuations = new Map();
648
+ for (let key of Object.keys(response.params)) {
649
+ let match = key.match(/\*((\d+)\*?)?$/);
650
+ if (!match) {
651
+ // nothing to do here, does not seem like a continuation param
652
+ continue;
653
+ }
654
+ let actualKey = key.substring(0, match.index).toLowerCase();
655
+ let nr = Number(match[2]) || 0;
656
+ let value = response.params[key];
657
+ // remove the old reference
658
+ delete response.params[key];
659
+ let continuation = continuations.get(actualKey);
660
+ if (!continuation) {
661
+ continuation = {
662
+ charset: false,
663
+ values: []
664
+ };
665
+ continuations.set(actualKey, continuation);
666
+ }
667
+ if (nr === 0 && match[0].charAt(match[0].length - 1) === '*' && (match = value.match(/^([^']*)'[^']*'(.*)$/))) {
668
+ continuation.charset = match[1] || 'utf-8';
669
+ value = match[2];
670
+ }
671
+ continuation.values.push({ nr, value });
672
+ }
673
+ // concatenate the split rfc2231 strings and decode the encoded ones
674
+ for (let [key, continuation] of continuations) {
675
+ // NB! An assembled continuation always overrides a plain parameter with the same
676
+ // name, in either source order. The plain form is the legacy ascii fallback of the
677
+ // extended one, so rfc6266 section 4.3 requires picking the extended value. Letting
678
+ // the fallback win would make libmime disagree with the mail clients that follow it
679
+ let value = continuation.values
680
+ .sort((a, b) => a.nr - b.nr)
681
+ .map(val => val.value)
682
+ .join('');
683
+ // Both branches below store a decoded value, so a caller that runs decodeWords() over the
684
+ // params has to leave this one out. A value that percent-encodes an encoded word would
685
+ // otherwise be decoded twice: filename*=utf-8''invoice%3D%3Futf-8%3FQ%3F%3D2Eexe%3F%3D
686
+ // names the file "invoice=?utf-8?Q?=2Eexe?=", not "invoice.exe"
687
+ response.decodedParams.push(key);
688
+ if (!continuation.charset) {
689
+ setOwnProperty(response.params, key, this.decodeWords(value));
690
+ continue;
691
+ }
692
+ // convert "%AB" to the quoted printable "=AB" and decode it as the charset of the parameter.
693
+ // NB! The charset is passed in as an argument instead of building a "=?charset?Q?=AB?=" string,
694
+ // a charset that contains "?" would otherwise inject an encoded word of its own
695
+ let qpValue = value
696
+ // fix invalidly encoded chars
697
+ .replace(/[=_\s]/g, s => {
698
+ if (s === ' ') {
699
+ return '_';
700
+ }
701
+ let c = s.charCodeAt(0).toString(16);
702
+ return '%' + (c.length < 2 ? '0' : '') + c;
703
+ })
704
+ // change from urlencoding to percent encoding
705
+ .replace(/%/g, '=');
706
+ setOwnProperty(response.params, key, this.decodeWord(continuation.charset, 'Q', qpValue));
707
+ }
708
+ return response;
709
+ }
710
+ /**
711
+ * Encodes a string or an Buffer to an UTF-8 Parameter Value Continuation encoding (rfc2231)
712
+ * Useful for splitting long parameter values.
713
+ *
714
+ * For example
715
+ * title="unicode string"
716
+ * becomes
717
+ * title*0*=utf-8''unicode
718
+ * title*1*=%20string
719
+ *
720
+ * @param {String} key Parameter name (eg. 'filename')
721
+ * @param {String|Buffer} data String to be encoded
722
+ * @param {Number} [maxLength=50] Max length for generated chunks
723
+ * @param {String} [fromCharset='UTF-8'] Source character set
724
+ * @return {Array} A list of encoded keys and headers
725
+ */
726
+ buildHeaderParam(key, data, maxLength, fromCharset) {
727
+ let list = [];
728
+ if (typeof data !== 'string' && !node_buffer_1.Buffer.isBuffer(data)) {
729
+ // documented input is a string or a Buffer, normalize anything else
730
+ data = data === null || data === undefined ? '' : data.toString();
731
+ }
732
+ let encodedStr = typeof data === 'string' ? data : libcharset.decode(data, fromCharset);
733
+ let chr;
734
+ let line;
735
+ let startPos = 0;
736
+ let isEncoded = false;
737
+ let i, len;
738
+ maxLength = maxLength || 50;
739
+ // process ascii only text
740
+ if (this.isPlainParamValue(data)) {
741
+ // check if conversion is even needed
742
+ if (encodedStr.length <= maxLength) {
743
+ return [
744
+ {
745
+ key,
746
+ value: encodedStr
747
+ }
748
+ ];
749
+ }
750
+ encodedStr = encodedStr.replace(new RegExp('.{' + maxLength + '}', 'g'), str => {
751
+ list.push({
752
+ line: str
753
+ });
754
+ return '';
755
+ });
756
+ if (encodedStr) {
757
+ list.push({
758
+ line: encodedStr
759
+ });
760
+ }
761
+ }
762
+ else {
763
+ if (/[\uD800-\uDBFF]/.test(encodedStr)) {
764
+ // string contains surrogate pairs, so split it into code points. An unpaired
765
+ // surrogate comes on its own and does not tear apart a valid pair following it
766
+ encodedStr = Array.from(encodedStr);
767
+ }
768
+ // first line includes the charset and language info and needs to be encoded
769
+ // even if it does not contain any unicode characters
770
+ line = "utf-8''";
771
+ isEncoded = true;
772
+ startPos = 0;
773
+ // process text with unicode or special chars
774
+ for (i = 0, len = encodedStr.length; i < len; i++) {
775
+ chr = encodedStr[i];
776
+ if (isEncoded) {
777
+ chr = this.safeEncodeURIComponent(chr);
778
+ }
779
+ else {
780
+ // try to urlencode current char
781
+ chr = chr === ' ' ? chr : this.safeEncodeURIComponent(chr);
782
+ // By default it is not required to encode a line, the need
783
+ // only appears when the string contains unicode or special chars
784
+ // in this case we start processing the line over and encode all chars
785
+ if (chr !== encodedStr[i]) {
786
+ // Check if it is even possible to add the encoded char to the line
787
+ // If not, there is no reason to use this line, just push it to the list
788
+ // and start a new line with the char that needs encoding
789
+ if ((this.safeEncodeURIComponent(line) + chr).length >= maxLength) {
790
+ list.push({
791
+ line,
792
+ encoded: isEncoded
793
+ });
794
+ // the line we start here holds an encoded char, so it has to be
795
+ // flagged as one. Otherwise it gets an unstarred continuation key
796
+ // and a receiver reads the percent escapes as literal text
797
+ line = '';
798
+ isEncoded = true;
799
+ }
800
+ else {
801
+ isEncoded = true;
802
+ i = startPos;
803
+ line = '';
804
+ continue;
805
+ }
806
+ }
807
+ }
808
+ // if the line is already too long, push it to the list and start a new one
809
+ if ((line + chr).length >= maxLength) {
810
+ list.push({
811
+ line,
812
+ encoded: isEncoded
813
+ });
814
+ line = chr = encodedStr[i] === ' ' ? ' ' : this.safeEncodeURIComponent(encodedStr[i]);
815
+ if (chr === encodedStr[i]) {
816
+ isEncoded = false;
817
+ startPos = i - 1;
818
+ }
819
+ else {
820
+ isEncoded = true;
821
+ }
822
+ }
823
+ else {
824
+ line += chr;
825
+ }
826
+ }
827
+ if (line) {
828
+ list.push({
829
+ line,
830
+ encoded: isEncoded
831
+ });
832
+ }
833
+ }
834
+ return list.map((item, i) => ({
835
+ // encoded lines: {name}*{part}*
836
+ // unencoded lines: {name}*{part}
837
+ // if any line needs to be encoded then the first line (part==0) is always encoded
838
+ key: key + '*' + i + (item.encoded ? '*' : ''),
839
+ value: item.line
840
+ }));
841
+ }
842
+ /**
843
+ * Returns file extension for a content type string. If no suitable extensions
844
+ * are found, 'bin' is used as the default extension
845
+ *
846
+ * @param {String} mimeType Content type to be checked for
847
+ * @return {String} File extension
848
+ */
849
+ detectExtension(input) {
850
+ let mimeType = (input || '').toString().toLowerCase().replace(/\s/g, '');
851
+ if (!Object.hasOwn(mimetypes.list, mimeType)) {
852
+ return 'bin';
853
+ }
854
+ if (typeof mimetypes.list[mimeType] === 'string') {
855
+ return mimetypes.list[mimeType];
856
+ }
857
+ let mimeParts = mimeType.split('/');
858
+ // search for name match
859
+ for (let i = 0, len = mimetypes.list[mimeType].length; i < len; i++) {
860
+ if (mimeParts[1] === mimetypes.list[mimeType][i]) {
861
+ return mimetypes.list[mimeType][i];
862
+ }
863
+ }
864
+ // use the first one
865
+ return mimetypes.list[mimeType][0] !== '*' ? mimetypes.list[mimeType][0] : 'bin';
866
+ }
867
+ /**
868
+ * Returns content type for a file extension. If no suitable content types
869
+ * are found, 'application/octet-stream' is used as the default content type
870
+ *
871
+ * @param {String} extension Extension (or filename) to be checked for
872
+ * @return {String} Content type
873
+ */
874
+ detectMimeType(input) {
875
+ let extension = (input || '').toString().toLowerCase().replace(/\s/g, '').replace(/^\./g, '').split('.').pop() || '';
876
+ if (!Object.hasOwn(mimetypes.extensions, extension)) {
877
+ return 'application/octet-stream';
878
+ }
879
+ if (typeof mimetypes.extensions[extension] === 'string') {
880
+ return mimetypes.extensions[extension];
881
+ }
882
+ let mimeParts;
883
+ // search for name match
884
+ for (let i = 0, len = mimetypes.extensions[extension].length; i < len; i++) {
885
+ mimeParts = mimetypes.extensions[extension][i].split('/');
886
+ if (mimeParts[1] === extension) {
887
+ return mimetypes.extensions[extension][i];
888
+ }
889
+ }
890
+ // use the first one
891
+ return mimetypes.extensions[extension][0];
892
+ }
893
+ /**
894
+ * Folds long lines, useful for folding header lines (afterSpace=false) and
895
+ * flowed text (afterSpace=true)
896
+ *
897
+ * @param {String} str String to be folded
898
+ * @param {Number} [lineLength=76] Maximum length of a line
899
+ * @param {Boolean} afterSpace If true, leave a space in the end of a line
900
+ * @return {String} String with folded lines
901
+ */
902
+ foldLines(input, lineLength, afterSpace) {
903
+ let str = (input || '').toString();
904
+ lineLength = lineLength || 76;
905
+ let pos = 0, len = str.length, result = '', line, match;
906
+ while (pos < len) {
907
+ line = str.substring(pos, pos + Math.max(lineLength, 0));
908
+ if (line.length < lineLength) {
909
+ result += line;
910
+ break;
911
+ }
912
+ if ((match = line.match(/^[^\n\r]*(\r?\n|\r)/))) {
913
+ line = match[0];
914
+ result += line;
915
+ pos += line.length;
916
+ continue;
917
+ }
918
+ else if ((match = line.match(/(\s+)[^\s]*$/)) && match[0].length - (afterSpace ? (match[1] || '').length : 0) < line.length) {
919
+ line = line.substring(0, line.length - (match[0].length - (afterSpace ? (match[1] || '').length : 0)));
920
+ }
921
+ else if ((match = str.slice(pos + line.length).match(/^[^\s]+(\s*)/))) {
922
+ line = line + match[0].substring(0, match[0].length - (!afterSpace ? (match[1] || '').length : 0));
923
+ }
924
+ result += line;
925
+ pos += line.length;
926
+ if (pos < len) {
927
+ result += '\r\n';
928
+ }
929
+ }
930
+ return result;
931
+ }
932
+ /**
933
+ * Splits a mime encoded string. Needed for dividing mime words into smaller chunks
934
+ *
935
+ * @param {String} str Mime encoded string to be split up
936
+ * @param {Number} maxlen Maximum length of characters for one part (minimum 12)
937
+ * @return {Array} Split string
938
+ */
939
+ splitMimeEncodedString(str, maxlen) {
940
+ let curLine, fallbackLine, match, chr, done, lines = [];
941
+ // require at least 12 symbols to fit possible 4 octet UTF-8 sequences
942
+ maxlen = Math.max(maxlen || 0, 12);
943
+ while (str.length) {
944
+ curLine = str.substring(0, maxlen);
945
+ // move incomplete escaped char back to main
946
+ if ((match = curLine.match(/[=][0-9A-F]?$/i))) {
947
+ curLine = curLine.substring(0, match.index);
948
+ }
949
+ // Malformed input (a run of stray UTF-8 continuation bytes, or a dangling "=" at the
950
+ // end) has no split point that keeps a character sequence whole, so the loop below
951
+ // walks back to an empty line looking for one. An empty line never consumes anything
952
+ // and would loop forever, so keep the widest chunk that at least does not cut a "=XX"
953
+ // escape in half, or the rest of the string when there is not even that
954
+ fallbackLine = curLine.length ? curLine : str.substring(0, maxlen);
955
+ done = false;
956
+ while (!done && curLine.length) {
957
+ done = true;
958
+ // check if not middle of a unicode char sequence
959
+ if ((match = str.slice(curLine.length).match(/^[=]([0-9A-F]{2})/i))) {
960
+ chr = parseInt(match[1], 16);
961
+ // invalid sequence, move one escaped char back and recheck. Only over a whole
962
+ // "=XX" escape, a plain char in front of a stray continuation byte ends the walk
963
+ if (chr < 0xc2 && chr > 0x7f && /=[0-9A-F]{2}$/i.test(curLine)) {
964
+ curLine = curLine.substring(0, curLine.length - 3);
965
+ done = false;
966
+ }
967
+ }
968
+ }
969
+ if (!curLine.length) {
970
+ curLine = fallbackLine;
971
+ }
972
+ lines.push(curLine);
973
+ str = str.slice(curLine.length);
974
+ }
975
+ return lines;
976
+ }
977
+ encodeURICharComponent(chr) {
978
+ let res = '';
979
+ // percent encoding is defined over bytes, so encode the char as UTF-8 first
980
+ let buf = node_buffer_1.Buffer.from(chr, 'utf-8');
981
+ for (let i = 0, len = buf.length; i < len; i++) {
982
+ let ord = buf[i].toString(16).toUpperCase();
983
+ res += '%' + (ord.length < 2 ? '0' : '') + ord;
984
+ }
985
+ return res;
986
+ }
987
+ safeEncodeURIComponent(input) {
988
+ let str = (input || '').toString();
989
+ try {
990
+ // might throw if we try to encode invalid sequences, eg. partial emoji
991
+ str = encodeURIComponent(str);
992
+ }
993
+ catch (_err) {
994
+ // an unpaired surrogate has no utf-8 representation, so run the value through a
995
+ // utf-8 roundtrip to get the same U+FFFD every other encoder here produces and
996
+ // retry. The value must never come back unencoded, it goes into a header parameter
997
+ // where a bare quote or semicolon would break it out into a parameter of its own
998
+ str = encodeURIComponent(node_buffer_1.Buffer.from(str, 'utf-8').toString('utf-8'));
999
+ }
1000
+ // ensure chars that are not handled by encodeURICompent are converted as well
1001
+ return str.replace(/[\x00-\x1F *'()<>@,;:\\"[\]?=\u007F-\uFFFF]/g, chr => this.encodeURICharComponent(chr));
1002
+ }
1003
+ }
1004
+ exports.Libmime = Libmime;
1005
+ /**
1006
+ * The shared instance, the default export of the package. The class is attached to it as `Libmime`,
1007
+ * so `require('libmime').Libmime` keeps working
1008
+ */
1009
+ const libmime = new Libmime();
1010
+ libmime.Libmime = Libmime;
1011
+ exports.default = libmime;
1012
+ module.exports = exports.default;
1013
+ Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });