@mavogel/awscdk-rootmail 0.0.219 → 0.0.220

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 (99) 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/cdk-nag/.jsii +2 -2
  5. package/node_modules/cdk-nag/lib/ignore-suppression-conditions.js +5 -5
  6. package/node_modules/cdk-nag/lib/nag-logger.js +2 -2
  7. package/node_modules/cdk-nag/lib/nag-pack.js +1 -1
  8. package/node_modules/cdk-nag/lib/nag-rules.js +1 -1
  9. package/node_modules/cdk-nag/lib/nag-suppressions.js +1 -1
  10. package/node_modules/cdk-nag/lib/packs/aws-solutions.js +1 -1
  11. package/node_modules/cdk-nag/lib/packs/hipaa-security.js +1 -1
  12. package/node_modules/cdk-nag/lib/packs/nist-800-53-r4.js +1 -1
  13. package/node_modules/cdk-nag/lib/packs/nist-800-53-r5.js +1 -1
  14. package/node_modules/cdk-nag/lib/packs/pci-dss-321.js +1 -1
  15. package/node_modules/cdk-nag/package.json +1 -1
  16. package/node_modules/encoding-japanese/README.md +65 -13
  17. package/node_modules/encoding-japanese/encoding.js +7 -2
  18. package/node_modules/encoding-japanese/encoding.min.js +2 -2
  19. package/node_modules/encoding-japanese/package.json +2 -2
  20. package/node_modules/encoding-japanese/src/encoding-convert.js +5 -0
  21. package/node_modules/libbase64/.prettierrc.js +4 -1
  22. package/node_modules/libbase64/lib/libbase64.js +2 -1
  23. package/node_modules/libbase64/package.json +9 -8
  24. package/node_modules/libmime/CHANGELOG.md +7 -0
  25. package/node_modules/libmime/package.json +5 -5
  26. package/node_modules/libqp/CHANGELOG.md +15 -0
  27. package/node_modules/libqp/lib/libqp.js +13 -21
  28. package/node_modules/libqp/package.json +7 -6
  29. package/node_modules/libqp/test/libqp-test.js +161 -0
  30. package/node_modules/mailparser/CHANGELOG.md +7 -0
  31. package/node_modules/mailparser/package.json +8 -8
  32. package/node_modules/mailsplit/LICENSE.MIT +6 -1
  33. package/node_modules/mailsplit/README.md +2 -0
  34. package/node_modules/mailsplit/lib/headers.js +5 -1
  35. package/node_modules/mailsplit/package.json +10 -9
  36. package/node_modules/nodemailer/.ncurc.js +4 -1
  37. package/node_modules/nodemailer/CHANGELOG.md +25 -0
  38. package/node_modules/nodemailer/lib/addressparser/index.js +22 -8
  39. package/node_modules/nodemailer/lib/mime-funcs/mime-types.js +2 -0
  40. package/node_modules/nodemailer/lib/nodemailer.js +13 -6
  41. package/node_modules/nodemailer/lib/smtp-connection/index.js +9 -0
  42. package/node_modules/nodemailer/lib/well-known/services.json +14 -3
  43. package/node_modules/nodemailer/package.json +5 -5
  44. package/node_modules/tlds/index.json +0 -3
  45. package/node_modules/tlds/package.json +1 -1
  46. package/package.json +4 -4
  47. package/node_modules/libmime/node_modules/libbase64/.eslintrc +0 -11
  48. package/node_modules/libmime/node_modules/libbase64/.prettierrc.js +0 -8
  49. package/node_modules/libmime/node_modules/libbase64/LICENSE +0 -19
  50. package/node_modules/libmime/node_modules/libbase64/README.md +0 -113
  51. package/node_modules/libmime/node_modules/libbase64/lib/libbase64.js +0 -253
  52. package/node_modules/libmime/node_modules/libbase64/package.json +0 -35
  53. package/node_modules/libmime/node_modules/libqp/.eslintrc +0 -15
  54. package/node_modules/libmime/node_modules/libqp/.prettierrc.js +0 -8
  55. package/node_modules/libmime/node_modules/libqp/CHANGELOG.md +0 -8
  56. package/node_modules/libmime/node_modules/libqp/LICENSE +0 -19
  57. package/node_modules/libmime/node_modules/libqp/README.md +0 -119
  58. package/node_modules/libmime/node_modules/libqp/lib/libqp.js +0 -320
  59. package/node_modules/libmime/node_modules/libqp/package.json +0 -28
  60. package/node_modules/libmime/node_modules/libqp/test/libqp-test.js +0 -41
  61. package/node_modules/mailsplit/node_modules/encoding-japanese/LICENSE +0 -21
  62. package/node_modules/mailsplit/node_modules/encoding-japanese/README.md +0 -542
  63. package/node_modules/mailsplit/node_modules/encoding-japanese/encoding.js +0 -6077
  64. package/node_modules/mailsplit/node_modules/encoding-japanese/encoding.min.js +0 -8
  65. package/node_modules/mailsplit/node_modules/encoding-japanese/encoding.min.js.map +0 -1
  66. package/node_modules/mailsplit/node_modules/encoding-japanese/package.json +0 -70
  67. package/node_modules/mailsplit/node_modules/encoding-japanese/src/banner.js +0 -6
  68. package/node_modules/mailsplit/node_modules/encoding-japanese/src/config.js +0 -139
  69. package/node_modules/mailsplit/node_modules/encoding-japanese/src/encoding-convert.js +0 -1676
  70. package/node_modules/mailsplit/node_modules/encoding-japanese/src/encoding-detect.js +0 -502
  71. package/node_modules/mailsplit/node_modules/encoding-japanese/src/encoding-table.js +0 -4
  72. package/node_modules/mailsplit/node_modules/encoding-japanese/src/index.js +0 -593
  73. package/node_modules/mailsplit/node_modules/encoding-japanese/src/jis-to-utf8-table.js +0 -5
  74. package/node_modules/mailsplit/node_modules/encoding-japanese/src/jisx0212-to-utf8-table.js +0 -5
  75. package/node_modules/mailsplit/node_modules/encoding-japanese/src/kana-case-table.js +0 -41
  76. package/node_modules/mailsplit/node_modules/encoding-japanese/src/utf8-to-jis-table.js +0 -1493
  77. package/node_modules/mailsplit/node_modules/encoding-japanese/src/utf8-to-jisx0212-table.js +0 -1224
  78. package/node_modules/mailsplit/node_modules/encoding-japanese/src/util.js +0 -361
  79. package/node_modules/mailsplit/node_modules/libmime/.eslintrc +0 -7
  80. package/node_modules/mailsplit/node_modules/libmime/.gitattributes +0 -1
  81. package/node_modules/mailsplit/node_modules/libmime/.github/FUNDING.yml +0 -4
  82. package/node_modules/mailsplit/node_modules/libmime/.github/workflows/test.yml +0 -21
  83. package/node_modules/mailsplit/node_modules/libmime/.prettierrc.js +0 -8
  84. package/node_modules/mailsplit/node_modules/libmime/CHANGELOG.md +0 -110
  85. package/node_modules/mailsplit/node_modules/libmime/LICENSE +0 -19
  86. package/node_modules/mailsplit/node_modules/libmime/README.md +0 -207
  87. package/node_modules/mailsplit/node_modules/libmime/lib/charset.js +0 -117
  88. package/node_modules/mailsplit/node_modules/libmime/lib/charsets.js +0 -212
  89. package/node_modules/mailsplit/node_modules/libmime/lib/libmime.js +0 -903
  90. package/node_modules/mailsplit/node_modules/libmime/lib/mimetypes.js +0 -2049
  91. package/node_modules/mailsplit/node_modules/libmime/package.json +0 -37
  92. /package/node_modules/{libmime/node_modules/libbase64 → libbase64}/.github/FUNDING.yml +0 -0
  93. /package/node_modules/{libmime/node_modules/libbase64 → libbase64}/.github/workflows/release.yaml +0 -0
  94. /package/node_modules/{libmime/node_modules/libbase64 → libbase64}/.github/workflows/test.yml +0 -0
  95. /package/node_modules/{libmime/node_modules/libbase64 → libbase64}/.ncurc.js +0 -0
  96. /package/node_modules/{libmime/node_modules/libbase64 → libbase64}/CHANGELOG.md +0 -0
  97. /package/node_modules/{libmime/node_modules/libqp → libqp}/.github/FUNDING.yml +0 -0
  98. /package/node_modules/{libmime/node_modules/libqp → libqp}/.github/workflows/release.yaml +0 -0
  99. /package/node_modules/{libmime/node_modules/libqp → libqp}/.github/workflows/test.yml +0 -0
@@ -1,903 +0,0 @@
1
- /* eslint no-control-regex: 0, no-div-regex: 0, quotes: 0 */
2
- 'use strict';
3
-
4
- const libcharset = require('./charset');
5
- const libbase64 = require('libbase64');
6
- const libqp = require('libqp');
7
- const mimetypes = require('./mimetypes');
8
-
9
- const STAGE_KEY = 0x1001;
10
- const STAGE_VALUE = 0x1002;
11
-
12
- class Libmime {
13
- constructor(config) {
14
- this.config = config || {};
15
- }
16
-
17
- /**
18
- * Checks if a value is plaintext string (uses only printable 7bit chars)
19
- *
20
- * @param {String} value String to be tested
21
- * @returns {Boolean} true if it is a plaintext string
22
- */
23
- isPlainText(value) {
24
- if (typeof value !== 'string' || /[\x00-\x08\x0b\x0c\x0e-\x1f\u0080-\uFFFF]/.test(value)) {
25
- return false;
26
- } else {
27
- return true;
28
- }
29
- }
30
-
31
- /**
32
- * Checks if a multi line string containes lines longer than the selected value.
33
- *
34
- * Useful when detecting if a mail message needs any processing at all –
35
- * if only plaintext characters are used and lines are short, then there is
36
- * no need to encode the values in any way. If the value is plaintext but has
37
- * longer lines then allowed, then use format=flowed
38
- *
39
- * @param {Number} lineLength Max line length to check for
40
- * @returns {Boolean} Returns true if there is at least one line longer than lineLength chars
41
- */
42
- hasLongerLines(str, lineLength) {
43
- return new RegExp('^.{' + (lineLength + 1) + ',}', 'm').test(str);
44
- }
45
-
46
- /**
47
- * Decodes a string from a format=flowed soft wrapping.
48
- *
49
- * @param {String} str Plaintext string with format=flowed to decode
50
- * @param {Boolean} [delSp] If true, delete leading spaces (delsp=yes)
51
- * @return {String} Mime decoded string
52
- */
53
- decodeFlowed(str, delSp) {
54
- str = (str || '').toString();
55
-
56
- return (
57
- str
58
- .split(/\r?\n/)
59
- // remove soft linebreaks
60
- // soft linebreaks are added after space symbols
61
- .reduce((previousValue, currentValue) => {
62
- if (/ $/.test(previousValue) && !/(^|\n)-- $/.test(previousValue)) {
63
- if (delSp) {
64
- // delsp adds space to text to be able to fold it
65
- // these spaces can be removed once the text is unfolded
66
- return previousValue.slice(0, -1) + currentValue;
67
- } else {
68
- return previousValue + currentValue;
69
- }
70
- } else {
71
- return previousValue + '\n' + currentValue;
72
- }
73
- })
74
- // remove whitespace stuffing
75
- // http://tools.ietf.org/html/rfc3676#section-4.4
76
- .replace(/^ /gm, '')
77
- );
78
- }
79
-
80
- /**
81
- * Adds soft line breaks to content marked with format=flowed to
82
- * ensure that no line in the message is never longer than lineLength
83
- *
84
- * @param {String} str Plaintext string that requires wrapping
85
- * @param {Number} [lineLength=76] Maximum length of a line
86
- * @return {String} String with forced line breaks
87
- */
88
- encodeFlowed(str, lineLength) {
89
- lineLength = lineLength || 76;
90
-
91
- let flowed = [];
92
- str.split(/\r?\n/).forEach(line => {
93
- flowed.push(
94
- this.foldLines(
95
- line
96
- // space stuffing http://tools.ietf.org/html/rfc3676#section-4.2
97
- .replace(/^( |From|>)/gim, ' $1'),
98
- lineLength,
99
- true
100
- )
101
- );
102
- });
103
- return flowed.join('\r\n');
104
- }
105
-
106
- /**
107
- * Encodes a string or an Buffer to an UTF-8 MIME Word (rfc2047)
108
- *
109
- * @param {String|Buffer} data String to be encoded
110
- * @param {String} mimeWordEncoding='Q' Encoding for the mime word, either Q or B
111
- * @param {Number} [maxLength=0] If set, split mime words into several chunks if needed
112
- * @return {String} Single or several mime words joined together
113
- */
114
- encodeWord(data, mimeWordEncoding, maxLength) {
115
- mimeWordEncoding = (mimeWordEncoding || 'Q').toString().toUpperCase().trim().charAt(0);
116
- maxLength = maxLength || 0;
117
-
118
- let encodedStr;
119
- let toCharset = 'UTF-8';
120
-
121
- if (maxLength && maxLength > 7 + toCharset.length) {
122
- maxLength -= 7 + toCharset.length;
123
- }
124
-
125
- if (mimeWordEncoding === 'Q') {
126
- // https://tools.ietf.org/html/rfc2047#section-5 rule (3)
127
- encodedStr = libqp.encode(data).replace(/[^a-z0-9!*+\-/=]/gi, chr => {
128
- let ord = chr.charCodeAt(0).toString(16).toUpperCase();
129
- if (chr === ' ') {
130
- return '_';
131
- } else {
132
- return '=' + (ord.length === 1 ? '0' + ord : ord);
133
- }
134
- });
135
- } else if (mimeWordEncoding === 'B') {
136
- encodedStr = typeof data === 'string' ? data : libbase64.encode(data);
137
- maxLength = maxLength ? Math.max(3, ((maxLength - (maxLength % 4)) / 4) * 3) : 0;
138
- }
139
-
140
- if (maxLength && (mimeWordEncoding !== 'B' ? encodedStr : libbase64.encode(data)).length > maxLength) {
141
- if (mimeWordEncoding === 'Q') {
142
- encodedStr = this.splitMimeEncodedString(encodedStr, maxLength).join('?= =?' + toCharset + '?' + mimeWordEncoding + '?');
143
- } else {
144
- // RFC2047 6.3 (2) states that encoded-word must include an integral number of characters, so no chopping unicode sequences
145
- let parts = [];
146
- let lpart = '';
147
- for (let i = 0, len = encodedStr.length; i < len; i++) {
148
- let chr = encodedStr.charAt(i);
149
- // check if we can add this character to the existing string
150
- // without breaking byte length limit
151
- if (Buffer.byteLength(lpart + chr) <= maxLength || i === 0) {
152
- lpart += chr;
153
- } else {
154
- // we hit the length limit, so push the existing string and start over
155
- parts.push(libbase64.encode(lpart));
156
- lpart = chr;
157
- }
158
- }
159
- if (lpart) {
160
- parts.push(libbase64.encode(lpart));
161
- }
162
-
163
- if (parts.length > 1) {
164
- encodedStr = parts.join('?= =?' + toCharset + '?' + mimeWordEncoding + '?');
165
- } else {
166
- encodedStr = parts.join('');
167
- }
168
- }
169
- } else if (mimeWordEncoding === 'B') {
170
- encodedStr = libbase64.encode(data);
171
- }
172
-
173
- return '=?' + toCharset + '?' + mimeWordEncoding + '?' + encodedStr + (encodedStr.substr(-2) === '?=' ? '' : '?=');
174
- }
175
-
176
- /**
177
- * Decode a complete mime word encoded string
178
- *
179
- * @param {String} str Mime word encoded string
180
- * @return {String} Decoded unicode string
181
- */
182
- decodeWord(charset, encoding, str) {
183
- // RFC2231 added language tag to the encoding
184
- // see: https://tools.ietf.org/html/rfc2231#section-5
185
- // this implementation silently ignores this tag
186
- let splitPos = charset.indexOf('*');
187
- if (splitPos >= 0) {
188
- charset = charset.substr(0, splitPos);
189
- }
190
- charset = libcharset.normalizeCharset(charset);
191
-
192
- encoding = encoding.toUpperCase();
193
-
194
- if (encoding === 'Q') {
195
- str = str
196
- // remove spaces between = and hex char, this might indicate invalidly applied line splitting
197
- .replace(/=\s+([0-9a-fA-F])/g, '=$1')
198
- // convert all underscores to spaces
199
- .replace(/[_\s]/g, ' ');
200
-
201
- let buf = Buffer.from(str);
202
- let bytes = [];
203
- for (let i = 0, len = buf.length; i < len; i++) {
204
- let c = buf[i];
205
- if (i <= len - 2 && c === 0x3d /* = */) {
206
- let c1 = this.getHex(buf[i + 1]);
207
- let c2 = this.getHex(buf[i + 2]);
208
- if (c1 && c2) {
209
- let c = parseInt(c1 + c2, 16);
210
- bytes.push(c);
211
- i += 2;
212
- continue;
213
- }
214
- }
215
- bytes.push(c);
216
- }
217
- str = Buffer.from(bytes);
218
- } else if (encoding === 'B') {
219
- str = Buffer.concat(
220
- str
221
- .split('=')
222
- .filter(s => s !== '') // filter empty string
223
- .map(str => Buffer.from(str, 'base64'))
224
- );
225
- } else {
226
- // keep as is, convert Buffer to unicode string, assume utf8
227
- str = Buffer.from(str);
228
- }
229
-
230
- return libcharset.decode(str, charset);
231
- }
232
-
233
- /**
234
- * Finds word sequences with non ascii text and converts these to mime words
235
- *
236
- * @param {String|Buffer} data String to be encoded
237
- * @param {String} mimeWordEncoding='Q' Encoding for the mime word, either Q or B
238
- * @param {Number} [maxLength=0] If set, split mime words into several chunks if needed
239
- * @param {String} [fromCharset='UTF-8'] Source sharacter set
240
- * @return {String} String with possible mime words
241
- */
242
- encodeWords(data, mimeWordEncoding, maxLength, fromCharset) {
243
- if (!fromCharset && typeof maxLength === 'string' && !maxLength.match(/^[0-9]+$/)) {
244
- fromCharset = maxLength;
245
- maxLength = undefined;
246
- }
247
-
248
- maxLength = maxLength || 0;
249
-
250
- let decodedValue = libcharset.decode(libcharset.convert(data || '', fromCharset));
251
- let encodedValue;
252
-
253
- let firstMatch = decodedValue.match(/(?:^|\s)([^\s]*[\u0080-\uFFFF])/);
254
- if (!firstMatch) {
255
- return decodedValue;
256
- }
257
- let lastMatch = decodedValue.match(/([\u0080-\uFFFF][^\s]*)[^\u0080-\uFFFF]*$/);
258
- if (!lastMatch) {
259
- // should not happen
260
- return decodedValue;
261
- }
262
- let startIndex =
263
- firstMatch.index +
264
- (
265
- firstMatch[0].match(/[^\s]/) || {
266
- index: 0
267
- }
268
- ).index;
269
- let endIndex = lastMatch.index + (lastMatch[1] || '').length;
270
-
271
- encodedValue =
272
- (startIndex ? decodedValue.substr(0, startIndex) : '') +
273
- this.encodeWord(decodedValue.substring(startIndex, endIndex), mimeWordEncoding || 'Q', maxLength) +
274
- (endIndex < decodedValue.length ? decodedValue.substr(endIndex) : '');
275
-
276
- return encodedValue;
277
- }
278
-
279
- /**
280
- * Decode a string that might include one or several mime words
281
- *
282
- * @param {String} str String including some mime words that will be encoded
283
- * @return {String} Decoded unicode string
284
- */
285
- decodeWords(str) {
286
- return (
287
- (str || '')
288
- .toString()
289
- // find base64 words that can be joined
290
- .replace(/(=\?([^?]+)\?[Bb]\?[^?]*\?=)\s*(?==\?([^?]+)\?[Bb]\?[^?]*\?=)/g, (match, left, chLeft, chRight) => {
291
- // only mark b64 chunks to be joined if charsets match
292
- if (libcharset.normalizeCharset(chLeft || '') === libcharset.normalizeCharset(chRight || '')) {
293
- // set a joiner marker
294
- return left + '__\x00JOIN\x00__';
295
- }
296
- return match;
297
- })
298
- // find QP words that can be joined
299
- .replace(/(=\?([^?]+)\?[Qq]\?[^?]*\?=)\s*(?==\?([^?]+)\?[Qq]\?[^?]*\?=)/g, (match, left, chLeft, chRight) => {
300
- // only mark QP chunks to be joined if charsets match
301
- if (libcharset.normalizeCharset(chLeft || '') === libcharset.normalizeCharset(chRight || '')) {
302
- // set a joiner marker
303
- return left + '__\x00JOIN\x00__';
304
- }
305
- return match;
306
- })
307
- // join base64 encoded words
308
- .replace(/(\?=)?__\x00JOIN\x00__(=\?([^?]+)\?[QqBb]\?)?/g, '')
309
- // remove spaces between mime encoded words
310
- .replace(/(=\?[^?]+\?[QqBb]\?[^?]*\?=)\s+(?==\?[^?]+\?[QqBb]\?[^?]*\?=)/g, '$1')
311
- // decode words
312
- .replace(/=\?([\w_\-*]+)\?([QqBb])\?([^?]*)\?=/g, (m, charset, encoding, text) => this.decodeWord(charset, encoding, text))
313
- );
314
- }
315
-
316
- getHex(c) {
317
- if ((c >= 0x30 /* 0 */ && c <= 0x39) /* 9 */ || (c >= 0x61 /* a */ && c <= 0x66) /* f */ || (c >= 0x41 /* A */ && c <= 0x46) /* F */) {
318
- return String.fromCharCode(c);
319
- }
320
- return false;
321
- }
322
-
323
- /**
324
- * Splits a string by :
325
- * The result is not mime word decoded, you need to do your own decoding based
326
- * on the rules for the specific header key
327
- *
328
- * @param {String} headerLine Single header line, might include linebreaks as well if folded
329
- * @return {Object} And object of {key, value}
330
- */
331
- decodeHeader(headerLine) {
332
- let line = (headerLine || '')
333
- .toString()
334
- .replace(/(?:\r?\n|\r)[ \t]*/g, ' ')
335
- .trim(),
336
- match = line.match(/^\s*([^:]+):(.*)$/),
337
- key = ((match && match[1]) || '').trim().toLowerCase(),
338
- value = ((match && match[2]) || '').trim();
339
-
340
- return {
341
- key,
342
- value
343
- };
344
- }
345
-
346
- /**
347
- * Parses a block of header lines. Does not decode mime words as every
348
- * header might have its own rules (eg. formatted email addresses and such)
349
- *
350
- * @param {String} headers Headers string
351
- * @return {Object} An object of headers, where header keys are object keys. NB! Several values with the same key make up an Array
352
- */
353
- decodeHeaders(headers) {
354
- let lines = headers.split(/\r?\n|\r/),
355
- headersObj = {},
356
- header,
357
- i,
358
- len;
359
-
360
- for (i = lines.length - 1; i >= 0; i--) {
361
- if (i && lines[i].match(/^\s/)) {
362
- lines[i - 1] += '\r\n' + lines[i];
363
- lines.splice(i, 1);
364
- }
365
- }
366
-
367
- for (i = 0, len = lines.length; i < len; i++) {
368
- header = this.decodeHeader(lines[i]);
369
- if (!headersObj[header.key]) {
370
- headersObj[header.key] = [header.value];
371
- } else {
372
- headersObj[header.key].push(header.value);
373
- }
374
- }
375
-
376
- return headersObj;
377
- }
378
-
379
- /**
380
- * Joins parsed header value together as 'value; param1=value1; param2=value2'
381
- * PS: We are following RFC 822 for the list of special characters that we need to keep in quotes.
382
- * Refer: https://www.w3.org/Protocols/rfc1341/4_Content-Type.html
383
- * @param {Object} structured Parsed header value
384
- * @return {String} joined header value
385
- */
386
- buildHeaderValue(structured) {
387
- let paramsArray = [];
388
-
389
- Object.keys(structured.params || {}).forEach(param => {
390
- // filename might include unicode characters so it is a special case
391
- let value = structured.params[param];
392
- if (!this.isPlainText(value) || value.length >= 75) {
393
- this.buildHeaderParam(param, value, 50).forEach(encodedParam => {
394
- if (!/[\s"\\;:/=(),<>@[\]?]|^[-']|'$/.test(encodedParam.value) || encodedParam.key.substr(-1) === '*') {
395
- paramsArray.push(encodedParam.key + '=' + encodedParam.value);
396
- } else {
397
- paramsArray.push(encodedParam.key + '=' + JSON.stringify(encodedParam.value));
398
- }
399
- });
400
- } else if (/[\s'"\\;:/=(),<>@[\]?]|^-/.test(value)) {
401
- paramsArray.push(param + '=' + JSON.stringify(value));
402
- } else {
403
- paramsArray.push(param + '=' + value);
404
- }
405
- });
406
-
407
- return structured.value + (paramsArray.length ? '; ' + paramsArray.join('; ') : '');
408
- }
409
-
410
- /**
411
- * Parses a header value with key=value arguments into a structured
412
- * object.
413
- *
414
- * parseHeaderValue('content-type: text/plain; CHARSET='UTF-8'') ->
415
- * {
416
- * 'value': 'text/plain',
417
- * 'params': {
418
- * 'charset': 'UTF-8'
419
- * }
420
- * }
421
- *
422
- * @param {String} str Header value
423
- * @return {Object} Header value as a parsed structure
424
- */
425
- parseHeaderValue(str) {
426
- let response = {
427
- value: false,
428
- params: {}
429
- };
430
- let key = false;
431
- let value = '';
432
- let stage = STAGE_VALUE;
433
-
434
- let quote = false;
435
- let escaped = false;
436
- let chr;
437
-
438
- for (let i = 0, len = str.length; i < len; i++) {
439
- chr = str.charAt(i);
440
- switch (stage) {
441
- case STAGE_KEY:
442
- if (chr === '=') {
443
- key = value.trim().toLowerCase();
444
- stage = STAGE_VALUE;
445
- value = '';
446
- break;
447
- }
448
- value += chr;
449
- break;
450
- case STAGE_VALUE:
451
- if (escaped) {
452
- value += chr;
453
- } else if (chr === '\\') {
454
- escaped = true;
455
- continue;
456
- } else if (quote && chr === quote) {
457
- quote = false;
458
- } else if (!quote && chr === '"') {
459
- quote = chr;
460
- } else if (!quote && chr === ';') {
461
- if (key === false) {
462
- response.value = value.trim();
463
- } else {
464
- response.params[key] = value.trim();
465
- }
466
- stage = STAGE_KEY;
467
- value = '';
468
- } else {
469
- value += chr;
470
- }
471
- escaped = false;
472
- break;
473
- }
474
- }
475
-
476
- // finalize remainder
477
- value = value.trim();
478
- if (stage === STAGE_VALUE) {
479
- if (key === false) {
480
- // default value
481
- response.value = value;
482
- } else {
483
- // subkey value
484
- response.params[key] = value;
485
- }
486
- } else if (value) {
487
- // treat as key without value, see emptykey:
488
- // Header-Key: somevalue; key=value; emptykey
489
- response.params[value.toLowerCase()] = '';
490
- }
491
-
492
- // handle parameter value continuations
493
- // https://tools.ietf.org/html/rfc2231#section-3
494
-
495
- // preprocess values
496
- Object.keys(response.params).forEach(key => {
497
- let actualKey;
498
- let nr;
499
- let value;
500
-
501
- let match = key.match(/\*((\d+)\*?)?$/);
502
-
503
- if (!match) {
504
- // nothing to do here, does not seem like a continuation param
505
- return;
506
- }
507
-
508
- actualKey = key.substr(0, match.index).toLowerCase();
509
- nr = Number(match[2]) || 0;
510
-
511
- if (!response.params[actualKey] || typeof response.params[actualKey] !== 'object') {
512
- response.params[actualKey] = {
513
- charset: false,
514
- values: []
515
- };
516
- }
517
-
518
- value = response.params[key];
519
-
520
- if (nr === 0 && match[0].charAt(match[0].length - 1) === '*' && (match = value.match(/^([^']*)'[^']*'(.*)$/))) {
521
- response.params[actualKey].charset = match[1] || 'utf-8';
522
- value = match[2];
523
- }
524
-
525
- response.params[actualKey].values.push({ nr, value });
526
-
527
- // remove the old reference
528
- delete response.params[key];
529
- });
530
-
531
- // concatenate split rfc2231 strings and convert encoded strings to mime encoded words
532
- Object.keys(response.params).forEach(key => {
533
- let value;
534
- if (response.params[key] && Array.isArray(response.params[key].values)) {
535
- value = response.params[key].values
536
- .sort((a, b) => a.nr - b.nr)
537
- .map(val => (val && val.value) || '')
538
- .join('');
539
-
540
- if (response.params[key].charset) {
541
- // convert "%AB" to "=?charset?Q?=AB?=" and then to unicode
542
- response.params[key] = this.decodeWords(
543
- '=?' +
544
- response.params[key].charset +
545
- '?Q?' +
546
- value
547
- // fix invalidly encoded chars
548
- .replace(/[=?_\s]/g, s => {
549
- let c = s.charCodeAt(0).toString(16);
550
- if (s === ' ') {
551
- return '_';
552
- } else {
553
- return '%' + (c.length < 2 ? '0' : '') + c;
554
- }
555
- })
556
- // change from urlencoding to percent encoding
557
- .replace(/%/g, '=') +
558
- '?='
559
- );
560
- } else {
561
- response.params[key] = this.decodeWords(value);
562
- }
563
- }
564
- });
565
-
566
- return response;
567
- }
568
-
569
- /**
570
- * Encodes a string or an Buffer to an UTF-8 Parameter Value Continuation encoding (rfc2231)
571
- * Useful for splitting long parameter values.
572
- *
573
- * For example
574
- * title="unicode string"
575
- * becomes
576
- * title*0*=utf-8''unicode
577
- * title*1*=%20string
578
- *
579
- * @param {String|Buffer} data String to be encoded
580
- * @param {Number} [maxLength=50] Max length for generated chunks
581
- * @param {String} [fromCharset='UTF-8'] Source sharacter set
582
- * @return {Array} A list of encoded keys and headers
583
- */
584
- buildHeaderParam(key, data, maxLength, fromCharset) {
585
- let list = [];
586
- let encodedStr = typeof data === 'string' ? data : this.decode(data, fromCharset);
587
- let encodedStrArr;
588
- let chr, ord;
589
- let line;
590
- let startPos = 0;
591
- let isEncoded = false;
592
- let i, len;
593
-
594
- maxLength = maxLength || 50;
595
-
596
- // process ascii only text
597
- if (this.isPlainText(data)) {
598
- // check if conversion is even needed
599
- if (encodedStr.length <= maxLength) {
600
- return [
601
- {
602
- key,
603
- value: encodedStr
604
- }
605
- ];
606
- }
607
-
608
- encodedStr = encodedStr.replace(new RegExp('.{' + maxLength + '}', 'g'), str => {
609
- list.push({
610
- line: str
611
- });
612
- return '';
613
- });
614
-
615
- if (encodedStr) {
616
- list.push({
617
- line: encodedStr
618
- });
619
- }
620
- } else {
621
- if (/[\uD800-\uDBFF]/.test(encodedStr)) {
622
- // string containts surrogate pairs, so normalize it to an array of bytes
623
- encodedStrArr = [];
624
- for (i = 0, len = encodedStr.length; i < len; i++) {
625
- chr = encodedStr.charAt(i);
626
- ord = chr.charCodeAt(0);
627
- if (ord >= 0xd800 && ord <= 0xdbff && i < len - 1) {
628
- chr += encodedStr.charAt(i + 1);
629
- encodedStrArr.push(chr);
630
- i++;
631
- } else {
632
- encodedStrArr.push(chr);
633
- }
634
- }
635
- encodedStr = encodedStrArr;
636
- }
637
-
638
- // first line includes the charset and language info and needs to be encoded
639
- // even if it does not contain any unicode characters
640
- line = "utf-8''";
641
- isEncoded = true;
642
- startPos = 0;
643
-
644
- // process text with unicode or special chars
645
- for (i = 0, len = encodedStr.length; i < len; i++) {
646
- chr = encodedStr[i];
647
-
648
- if (isEncoded) {
649
- chr = this.safeEncodeURIComponent(chr);
650
- } else {
651
- // try to urlencode current char
652
- chr = chr === ' ' ? chr : this.safeEncodeURIComponent(chr);
653
- // By default it is not required to encode a line, the need
654
- // only appears when the string contains unicode or special chars
655
- // in this case we start processing the line over and encode all chars
656
- if (chr !== encodedStr[i]) {
657
- // Check if it is even possible to add the encoded char to the line
658
- // If not, there is no reason to use this line, just push it to the list
659
- // and start a new line with the char that needs encoding
660
- if ((this.safeEncodeURIComponent(line) + chr).length >= maxLength) {
661
- list.push({
662
- line,
663
- encoded: isEncoded
664
- });
665
- line = '';
666
- startPos = i - 1;
667
- } else {
668
- isEncoded = true;
669
- i = startPos;
670
- line = '';
671
- continue;
672
- }
673
- }
674
- }
675
-
676
- // if the line is already too long, push it to the list and start a new one
677
- if ((line + chr).length >= maxLength) {
678
- list.push({
679
- line,
680
- encoded: isEncoded
681
- });
682
- line = chr = encodedStr[i] === ' ' ? ' ' : this.safeEncodeURIComponent(encodedStr[i]);
683
- if (chr === encodedStr[i]) {
684
- isEncoded = false;
685
- startPos = i - 1;
686
- } else {
687
- isEncoded = true;
688
- }
689
- } else {
690
- line += chr;
691
- }
692
- }
693
-
694
- if (line) {
695
- list.push({
696
- line,
697
- encoded: isEncoded
698
- });
699
- }
700
- }
701
-
702
- return list.map((item, i) => ({
703
- // encoded lines: {name}*{part}*
704
- // unencoded lines: {name}*{part}
705
- // if any line needs to be encoded then the first line (part==0) is always encoded
706
- key: key + '*' + i + (item.encoded ? '*' : ''),
707
- value: item.line
708
- }));
709
- }
710
-
711
- /**
712
- * Returns file extension for a content type string. If no suitable extensions
713
- * are found, 'bin' is used as the default extension
714
- *
715
- * @param {String} mimeType Content type to be checked for
716
- * @return {String} File extension
717
- */
718
- detectExtension(mimeType) {
719
- mimeType = (mimeType || '').toString().toLowerCase().replace(/\s/g, '');
720
- if (!(mimeType in mimetypes.list)) {
721
- return 'bin';
722
- }
723
-
724
- if (typeof mimetypes.list[mimeType] === 'string') {
725
- return mimetypes.list[mimeType];
726
- }
727
-
728
- let mimeParts = mimeType.split('/');
729
-
730
- // search for name match
731
- for (let i = 0, len = mimetypes.list[mimeType].length; i < len; i++) {
732
- if (mimeParts[1] === mimetypes.list[mimeType][i]) {
733
- return mimetypes.list[mimeType][i];
734
- }
735
- }
736
-
737
- // use the first one
738
- return mimetypes.list[mimeType][0] !== '*' ? mimetypes.list[mimeType][0] : 'bin';
739
- }
740
-
741
- /**
742
- * Returns content type for a file extension. If no suitable content types
743
- * are found, 'application/octet-stream' is used as the default content type
744
- *
745
- * @param {String} extension Extension to be checked for
746
- * @return {String} File extension
747
- */
748
- detectMimeType(extension) {
749
- extension = (extension || '').toString().toLowerCase().replace(/\s/g, '').replace(/^\./g, '').split('.').pop();
750
-
751
- if (!(extension in mimetypes.extensions)) {
752
- return 'application/octet-stream';
753
- }
754
-
755
- if (typeof mimetypes.extensions[extension] === 'string') {
756
- return mimetypes.extensions[extension];
757
- }
758
-
759
- let mimeParts;
760
-
761
- // search for name match
762
- for (let i = 0, len = mimetypes.extensions[extension].length; i < len; i++) {
763
- mimeParts = mimetypes.extensions[extension][i].split('/');
764
- if (mimeParts[1] === extension) {
765
- return mimetypes.extensions[extension][i];
766
- }
767
- }
768
-
769
- // use the first one
770
- return mimetypes.extensions[extension][0];
771
- }
772
-
773
- /**
774
- * Folds long lines, useful for folding header lines (afterSpace=false) and
775
- * flowed text (afterSpace=true)
776
- *
777
- * @param {String} str String to be folded
778
- * @param {Number} [lineLength=76] Maximum length of a line
779
- * @param {Boolean} afterSpace If true, leave a space in th end of a line
780
- * @return {String} String with folded lines
781
- */
782
- foldLines(str, lineLength, afterSpace) {
783
- str = (str || '').toString();
784
- lineLength = lineLength || 76;
785
-
786
- let pos = 0,
787
- len = str.length,
788
- result = '',
789
- line,
790
- match;
791
-
792
- while (pos < len) {
793
- line = str.substr(pos, lineLength);
794
- if (line.length < lineLength) {
795
- result += line;
796
- break;
797
- }
798
- if ((match = line.match(/^[^\n\r]*(\r?\n|\r)/))) {
799
- line = match[0];
800
- result += line;
801
- pos += line.length;
802
- continue;
803
- } else if ((match = line.match(/(\s+)[^\s]*$/)) && match[0].length - (afterSpace ? (match[1] || '').length : 0) < line.length) {
804
- line = line.substr(0, line.length - (match[0].length - (afterSpace ? (match[1] || '').length : 0)));
805
- } else if ((match = str.substr(pos + line.length).match(/^[^\s]+(\s*)/))) {
806
- line = line + match[0].substr(0, match[0].length - (!afterSpace ? (match[1] || '').length : 0));
807
- }
808
-
809
- result += line;
810
- pos += line.length;
811
- if (pos < len) {
812
- result += '\r\n';
813
- }
814
- }
815
-
816
- return result;
817
- }
818
-
819
- /**
820
- * Splits a mime encoded string. Needed for dividing mime words into smaller chunks
821
- *
822
- * @param {String} str Mime encoded string to be split up
823
- * @param {Number} maxlen Maximum length of characters for one part (minimum 12)
824
- * @return {Array} Split string
825
- */
826
- splitMimeEncodedString(str, maxlen) {
827
- let curLine,
828
- match,
829
- chr,
830
- done,
831
- lines = [];
832
-
833
- // require at least 12 symbols to fit possible 4 octet UTF-8 sequences
834
- maxlen = Math.max(maxlen || 0, 12);
835
-
836
- while (str.length) {
837
- curLine = str.substr(0, maxlen);
838
-
839
- // move incomplete escaped char back to main
840
- if ((match = curLine.match(/[=][0-9A-F]?$/i))) {
841
- curLine = curLine.substr(0, match.index);
842
- }
843
-
844
- done = false;
845
- while (!done) {
846
- done = true;
847
- // check if not middle of a unicode char sequence
848
- if ((match = str.substr(curLine.length).match(/^[=]([0-9A-F]{2})/i))) {
849
- chr = parseInt(match[1], 16);
850
- // invalid sequence, move one char back anc recheck
851
- if (chr < 0xc2 && chr > 0x7f) {
852
- curLine = curLine.substr(0, curLine.length - 3);
853
- done = false;
854
- }
855
- }
856
- }
857
-
858
- if (curLine.length) {
859
- lines.push(curLine);
860
- }
861
- str = str.substr(curLine.length);
862
- }
863
-
864
- return lines;
865
- }
866
-
867
- encodeURICharComponent(chr) {
868
- let res = '';
869
- let ord = chr.charCodeAt(0).toString(16).toUpperCase();
870
-
871
- if (ord.length % 2) {
872
- ord = '0' + ord;
873
- }
874
-
875
- if (ord.length > 2) {
876
- for (let i = 0, len = ord.length / 2; i < len; i++) {
877
- res += '%' + ord.substr(i, 2);
878
- }
879
- } else {
880
- res += '%' + ord;
881
- }
882
-
883
- return res;
884
- }
885
-
886
- safeEncodeURIComponent(str) {
887
- str = (str || '').toString();
888
-
889
- try {
890
- // might throw if we try to encode invalid sequences, eg. partial emoji
891
- str = encodeURIComponent(str);
892
- } catch (E) {
893
- // should never run
894
- return str.replace(/[^\x00-\x1F *'()<>@,;:\\"[\]?=\u007F-\uFFFF]+/g, '');
895
- }
896
-
897
- // ensure chars that are not handled by encodeURICompent are converted as well
898
- return str.replace(/[\x00-\x1F *'()<>@,;:\\"[\]?=\u007F-\uFFFF]/g, chr => this.encodeURICharComponent(chr));
899
- }
900
- }
901
-
902
- module.exports = new Libmime();
903
- module.exports.Libmime = Libmime;