@nocobase/plugin-workflow-mailer 2.2.16 → 2.2.18

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 (53) hide show
  1. package/dist/externalVersion.js +6 -6
  2. package/dist/node_modules/joi/dist/joi-browser.min.js +1 -1
  3. package/dist/node_modules/joi/lib/index.js +1 -1
  4. package/dist/node_modules/joi/lib/messages.js +35 -6
  5. package/dist/node_modules/joi/lib/types/keys.js +18 -1
  6. package/dist/node_modules/joi/lib/types/link.js +18 -2
  7. package/dist/node_modules/joi/lib/types/string.js +2 -2
  8. package/dist/node_modules/joi/package.json +1 -1
  9. package/dist/node_modules/nodemailer/.ncurc.js +2 -0
  10. package/dist/node_modules/nodemailer/.prettierignore +8 -0
  11. package/dist/node_modules/nodemailer/.prettierrc +12 -0
  12. package/dist/node_modules/nodemailer/.prettierrc.js +2 -0
  13. package/dist/node_modules/nodemailer/.release-please-config.json +9 -0
  14. package/dist/node_modules/nodemailer/eslint.config.js +88 -0
  15. package/dist/node_modules/nodemailer/lib/addressparser/index.js +314 -71
  16. package/dist/node_modules/nodemailer/lib/base64/index.js +9 -13
  17. package/dist/node_modules/nodemailer/lib/dkim/index.js +17 -18
  18. package/dist/node_modules/nodemailer/lib/dkim/message-parser.js +12 -13
  19. package/dist/node_modules/nodemailer/lib/dkim/relaxed-body.js +3 -3
  20. package/dist/node_modules/nodemailer/lib/dkim/sign.js +22 -18
  21. package/dist/node_modules/nodemailer/lib/errors.js +59 -0
  22. package/dist/node_modules/nodemailer/lib/fetch/cookies.js +14 -19
  23. package/dist/node_modules/nodemailer/lib/fetch/index.js +164 -27
  24. package/dist/node_modules/nodemailer/lib/json-transport/index.js +4 -4
  25. package/dist/node_modules/nodemailer/lib/mail-composer/index.js +229 -155
  26. package/dist/node_modules/nodemailer/lib/mailer/index.js +106 -54
  27. package/dist/node_modules/nodemailer/lib/mailer/mail-message.js +106 -68
  28. package/dist/node_modules/nodemailer/lib/mime-funcs/index.js +105 -59
  29. package/dist/node_modules/nodemailer/lib/mime-funcs/mime-types.js +21 -14
  30. package/dist/node_modules/nodemailer/lib/mime-node/index.js +389 -137
  31. package/dist/node_modules/nodemailer/lib/mime-node/last-newline.js +1 -1
  32. package/dist/node_modules/nodemailer/lib/mime-node/le-unix.js +4 -7
  33. package/dist/node_modules/nodemailer/lib/mime-node/le-windows.js +1 -4
  34. package/dist/node_modules/nodemailer/lib/nodemailer.js +1 -1
  35. package/dist/node_modules/nodemailer/lib/punycode/index.js +1 -1
  36. package/dist/node_modules/nodemailer/lib/qp/index.js +33 -22
  37. package/dist/node_modules/nodemailer/lib/sendmail-transport/index.js +54 -44
  38. package/dist/node_modules/nodemailer/lib/ses-transport/index.js +110 -193
  39. package/dist/node_modules/nodemailer/lib/shared/index.js +299 -240
  40. package/dist/node_modules/nodemailer/lib/shared/objects.js +42 -0
  41. package/dist/node_modules/nodemailer/lib/shared/url.js +151 -0
  42. package/dist/node_modules/nodemailer/lib/smtp-connection/data-stream.js +3 -6
  43. package/dist/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +50 -19
  44. package/dist/node_modules/nodemailer/lib/smtp-connection/index.js +415 -223
  45. package/dist/node_modules/nodemailer/lib/smtp-pool/index.js +27 -34
  46. package/dist/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +19 -16
  47. package/dist/node_modules/nodemailer/lib/smtp-transport/index.js +66 -53
  48. package/dist/node_modules/nodemailer/lib/stream-transport/index.js +16 -7
  49. package/dist/node_modules/nodemailer/lib/well-known/index.js +7 -7
  50. package/dist/node_modules/nodemailer/lib/well-known/services.json +328 -38
  51. package/dist/node_modules/nodemailer/lib/xoauth2/index.js +105 -33
  52. package/dist/node_modules/nodemailer/package.json +1 -1
  53. package/package.json +4 -4
@@ -2,54 +2,62 @@
2
2
 
3
3
  'use strict';
4
4
 
5
- const urllib = require('url');
5
+ const urllib = require('./url');
6
6
  const util = require('util');
7
7
  const fs = require('fs');
8
8
  const nmfetch = require('../fetch');
9
+ const errors = require('../errors');
10
+ const objects = require('./objects');
9
11
  const dns = require('dns');
10
12
  const net = require('net');
11
13
  const os = require('os');
12
14
 
15
+ // re-exported for the callers that already depend on this module, see ./objects
16
+ const isProtoKey = (module.exports.isProtoKey = objects.isProtoKey);
17
+ module.exports.copyOwnKeys = objects.copyOwnKeys;
18
+
13
19
  const DNS_TTL = 5 * 60 * 1000;
20
+ const CACHE_CLEANUP_INTERVAL = 30 * 1000; // Minimum 30 seconds between cleanups
21
+ const MAX_CACHE_SIZE = 1000; // Maximum number of entries in cache
22
+
23
+ let lastCacheCleanup = 0;
24
+ module.exports._lastCacheCleanup = () => lastCacheCleanup;
25
+ module.exports._resetCacheCleanup = () => {
26
+ lastCacheCleanup = 0;
27
+ };
14
28
 
15
29
  let networkInterfaces;
16
30
  try {
17
31
  networkInterfaces = os.networkInterfaces();
18
- } catch (err) {
32
+ } catch (_err) {
19
33
  // fails on some systems
20
34
  }
21
35
 
22
36
  module.exports.networkInterfaces = networkInterfaces;
23
37
 
24
38
  const isFamilySupported = (family, allowInternal) => {
25
- let networkInterfaces = module.exports.networkInterfaces;
26
- if (!networkInterfaces) {
39
+ const ifaces = module.exports.networkInterfaces;
40
+ if (!ifaces) {
27
41
  // hope for the best
28
42
  return true;
29
43
  }
30
44
 
31
- const familySupported =
32
- // crux that replaces Object.values(networkInterfaces) as Object.values is not supported in nodejs v6
33
- Object.keys(networkInterfaces)
34
- .map(key => networkInterfaces[key])
35
- // crux that replaces .flat() as it is not supported in older Node versions (v10 and older)
36
- .reduce((acc, val) => acc.concat(val), [])
37
- .filter(i => !i.internal || allowInternal)
38
- .filter(i => i.family === 'IPv' + family || i.family === family).length > 0;
39
-
40
- return familySupported;
45
+ return Object.keys(ifaces)
46
+ .map(key => ifaces[key])
47
+ .reduce((acc, val) => acc.concat(val), [])
48
+ .filter(i => !i.internal || allowInternal)
49
+ .some(i => i.family === 'IPv' + family || i.family === family);
41
50
  };
42
51
 
43
- const resolver = (family, hostname, options, callback) => {
52
+ const resolve = (family, hostname, options, callback) => {
44
53
  options = options || {};
45
- const familySupported = isFamilySupported(family, options.allowInternalNetworkInterfaces);
46
54
 
47
- if (!familySupported) {
55
+ if (!isFamilySupported(family, options.allowInternalNetworkInterfaces)) {
48
56
  return callback(null, []);
49
57
  }
50
58
 
51
- const resolver = dns.Resolver ? new dns.Resolver(options) : dns;
52
- resolver['resolve' + family](hostname, (err, addresses) => {
59
+ const dnsResolver = dns.Resolver ? new dns.Resolver(options) : dns;
60
+ dnsResolver['resolve' + family](hostname, (err, addresses) => {
53
61
  if (err) {
54
62
  switch (err.code) {
55
63
  case dns.NODATA:
@@ -74,15 +82,17 @@ const formatDNSValue = (value, extra) => {
74
82
  return Object.assign({}, extra || {});
75
83
  }
76
84
 
85
+ const addresses = value.addresses || [];
86
+
87
+ // Select a random address from available addresses, or null if none
88
+ const host = addresses.length > 0 ? addresses[Math.floor(Math.random() * addresses.length)] : null;
89
+
77
90
  return Object.assign(
78
91
  {
79
92
  servername: value.servername,
80
- host:
81
- !value.addresses || !value.addresses.length
82
- ? null
83
- : value.addresses.length === 1
84
- ? value.addresses[0]
85
- : value.addresses[Math.floor(Math.random() * value.addresses.length)]
93
+ host,
94
+ // Include all addresses for connection fallback support
95
+ _addresses: addresses
86
96
  },
87
97
  extra || {}
88
98
  );
@@ -97,7 +107,7 @@ module.exports.resolveHostname = (options, callback) => {
97
107
 
98
108
  if (!options.host || net.isIP(options.host)) {
99
109
  // nothing to do here
100
- let value = {
110
+ const value = {
101
111
  addresses: [options.host],
102
112
  servername: options.servername || false
103
113
  };
@@ -113,7 +123,27 @@ module.exports.resolveHostname = (options, callback) => {
113
123
  if (dnsCache.has(options.host)) {
114
124
  cached = dnsCache.get(options.host);
115
125
 
116
- if (!cached.expires || cached.expires >= Date.now()) {
126
+ // Lazy cleanup with time throttling
127
+ const now = Date.now();
128
+ if (now - lastCacheCleanup > CACHE_CLEANUP_INTERVAL) {
129
+ lastCacheCleanup = now;
130
+
131
+ // Clean up expired entries
132
+ for (const [host, entry] of dnsCache.entries()) {
133
+ if (entry.expires && entry.expires < now) {
134
+ dnsCache.delete(host);
135
+ }
136
+ }
137
+
138
+ // If cache is still too large, remove oldest entries
139
+ if (dnsCache.size > MAX_CACHE_SIZE) {
140
+ const toDelete = Math.floor(MAX_CACHE_SIZE * 0.1); // Remove 10% of entries
141
+ const keys = Array.from(dnsCache.keys()).slice(0, toDelete);
142
+ keys.forEach(key => dnsCache.delete(key));
143
+ }
144
+ }
145
+
146
+ if (!cached.expires || cached.expires >= now) {
117
147
  return callback(
118
148
  null,
119
149
  formatDNSValue(cached.value, {
@@ -123,58 +153,32 @@ module.exports.resolveHostname = (options, callback) => {
123
153
  }
124
154
  }
125
155
 
126
- resolver(4, options.host, options, (err, addresses) => {
127
- if (err) {
128
- if (cached) {
129
- // ignore error, use expired value
130
- return callback(
131
- null,
132
- formatDNSValue(cached.value, {
133
- cached: true,
134
- error: err
135
- })
136
- );
137
- }
138
- return callback(err);
139
- }
140
-
141
- if (addresses && addresses.length) {
142
- let value = {
143
- addresses,
144
- servername: options.servername || options.host
145
- };
156
+ // Resolve both IPv4 and IPv6 addresses for fallback support
157
+ let ipv4Addresses = [];
158
+ let ipv6Addresses = [];
159
+ let ipv4Error = null;
160
+ let ipv6Error = null;
146
161
 
147
- dnsCache.set(options.host, {
148
- value,
149
- expires: Date.now() + (options.dnsTtl || DNS_TTL)
150
- });
151
-
152
- return callback(
153
- null,
154
- formatDNSValue(value, {
155
- cached: false
156
- })
157
- );
162
+ resolve(4, options.host, options, (err, addresses) => {
163
+ if (err) {
164
+ ipv4Error = err;
165
+ } else {
166
+ ipv4Addresses = addresses || [];
158
167
  }
159
168
 
160
- resolver(6, options.host, options, (err, addresses) => {
169
+ resolve(6, options.host, options, (err, addresses) => {
161
170
  if (err) {
162
- if (cached) {
163
- // ignore error, use expired value
164
- return callback(
165
- null,
166
- formatDNSValue(cached.value, {
167
- cached: true,
168
- error: err
169
- })
170
- );
171
- }
172
- return callback(err);
171
+ ipv6Error = err;
172
+ } else {
173
+ ipv6Addresses = addresses || [];
173
174
  }
174
175
 
175
- if (addresses && addresses.length) {
176
- let value = {
177
- addresses,
176
+ // Combine addresses: IPv4 first, then IPv6
177
+ const allAddresses = ipv4Addresses.concat(ipv6Addresses);
178
+
179
+ if (allAddresses.length) {
180
+ const value = {
181
+ addresses: allAddresses,
178
182
  servername: options.servername || options.host
179
183
  };
180
184
 
@@ -191,11 +195,34 @@ module.exports.resolveHostname = (options, callback) => {
191
195
  );
192
196
  }
193
197
 
198
+ // No addresses from resolve4/resolve6, try dns.lookup as fallback
199
+ if (ipv4Error && ipv6Error) {
200
+ // Both resolvers had errors
201
+ if (cached) {
202
+ dnsCache.set(options.host, {
203
+ value: cached.value,
204
+ expires: Date.now() + (options.dnsTtl || DNS_TTL)
205
+ });
206
+
207
+ return callback(
208
+ null,
209
+ formatDNSValue(cached.value, {
210
+ cached: true,
211
+ error: ipv4Error
212
+ })
213
+ );
214
+ }
215
+ }
216
+
194
217
  try {
195
218
  dns.lookup(options.host, { all: true }, (err, addresses) => {
196
219
  if (err) {
197
220
  if (cached) {
198
- // ignore error, use expired value
221
+ dnsCache.set(options.host, {
222
+ value: cached.value,
223
+ expires: Date.now() + (options.dnsTtl || DNS_TTL)
224
+ });
225
+
199
226
  return callback(
200
227
  null,
201
228
  formatDNSValue(cached.value, {
@@ -207,19 +234,17 @@ module.exports.resolveHostname = (options, callback) => {
207
234
  return callback(err);
208
235
  }
209
236
 
210
- let address = addresses
211
- ? addresses
212
- .filter(addr => isFamilySupported(addr.family))
213
- .map(addr => addr.address)
214
- .shift()
215
- : false;
237
+ // Get all supported addresses from dns.lookup
238
+ const supportedAddresses = addresses
239
+ ? addresses.filter(addr => isFamilySupported(addr.family)).map(addr => addr.address)
240
+ : [];
216
241
 
217
- if (addresses && addresses.length && !address) {
242
+ if (addresses && addresses.length && !supportedAddresses.length) {
218
243
  // there are addresses but none can be used
219
244
  console.warn(`Failed to resolve IPv${addresses[0].family} addresses with current network`);
220
245
  }
221
246
 
222
- if (!address && cached) {
247
+ if (!supportedAddresses.length && cached) {
223
248
  // nothing was found, fallback to cached value
224
249
  return callback(
225
250
  null,
@@ -229,8 +254,8 @@ module.exports.resolveHostname = (options, callback) => {
229
254
  );
230
255
  }
231
256
 
232
- let value = {
233
- addresses: address ? [address] : [options.host],
257
+ const value = {
258
+ addresses: supportedAddresses.length ? supportedAddresses : [options.host],
234
259
  servername: options.servername || options.host
235
260
  };
236
261
 
@@ -246,18 +271,22 @@ module.exports.resolveHostname = (options, callback) => {
246
271
  })
247
272
  );
248
273
  });
249
- } catch (err) {
274
+ } catch (lookupErr) {
250
275
  if (cached) {
251
- // ignore error, use expired value
276
+ dnsCache.set(options.host, {
277
+ value: cached.value,
278
+ expires: Date.now() + (options.dnsTtl || DNS_TTL)
279
+ });
280
+
252
281
  return callback(
253
282
  null,
254
283
  formatDNSValue(cached.value, {
255
284
  cached: true,
256
- error: err
285
+ error: lookupErr
257
286
  })
258
287
  );
259
288
  }
260
- return callback(err);
289
+ return callback(ipv4Error || ipv6Error || lookupErr);
261
290
  }
262
291
  });
263
292
  });
@@ -270,97 +299,91 @@ module.exports.resolveHostname = (options, callback) => {
270
299
  */
271
300
  module.exports.parseConnectionUrl = str => {
272
301
  str = str || '';
273
- let options = {};
302
+ const options = {};
303
+ const url = urllib.parse(str, true);
274
304
 
275
- [urllib.parse(str, true)].forEach(url => {
276
- let auth;
277
-
278
- switch (url.protocol) {
279
- case 'smtp:':
280
- options.secure = false;
281
- break;
282
- case 'smtps:':
283
- options.secure = true;
284
- break;
285
- case 'direct:':
286
- options.direct = true;
287
- break;
288
- }
305
+ switch (url.protocol) {
306
+ case 'smtp:':
307
+ options.secure = false;
308
+ break;
309
+ case 'smtps:':
310
+ options.secure = true;
311
+ break;
312
+ case 'direct:':
313
+ options.direct = true;
314
+ break;
315
+ }
289
316
 
290
- if (!isNaN(url.port) && Number(url.port)) {
291
- options.port = Number(url.port);
292
- }
317
+ if (!isNaN(url.port) && Number(url.port)) {
318
+ options.port = Number(url.port);
319
+ }
293
320
 
294
- if (url.hostname) {
295
- options.host = url.hostname;
296
- }
321
+ if (url.hostname) {
322
+ options.host = url.hostname;
323
+ }
297
324
 
298
- if (url.auth) {
299
- auth = url.auth.split(':');
325
+ if (url.auth) {
326
+ const auth = url.auth.split(':');
327
+ options.auth = {
328
+ user: auth.shift(),
329
+ pass: auth.join(':')
330
+ };
331
+ }
300
332
 
301
- if (!options.auth) {
302
- options.auth = {};
303
- }
333
+ Object.keys(url.query || {}).forEach(key => {
334
+ let obj = options;
335
+ let lKey = key;
336
+ let value = url.query[key];
304
337
 
305
- options.auth.user = auth.shift();
306
- options.auth.pass = auth.join(':');
338
+ if (!isNaN(value)) {
339
+ value = Number(value);
307
340
  }
308
341
 
309
- Object.keys(url.query || {}).forEach(key => {
310
- let obj = options;
311
- let lKey = key;
312
- let value = url.query[key];
313
-
314
- if (!isNaN(value)) {
315
- value = Number(value);
316
- }
317
-
318
- switch (value) {
319
- case 'true':
320
- value = true;
321
- break;
322
- case 'false':
323
- value = false;
324
- break;
325
- }
342
+ switch (value) {
343
+ case 'true':
344
+ value = true;
345
+ break;
346
+ case 'false':
347
+ value = false;
348
+ break;
349
+ }
326
350
 
327
- // tls is nested object
328
- if (key.indexOf('tls.') === 0) {
329
- lKey = key.substr(4);
330
- if (!options.tls) {
331
- options.tls = {};
332
- }
333
- obj = options.tls;
334
- } else if (key.indexOf('.') >= 0) {
335
- // ignore nested properties besides tls
336
- return;
351
+ // tls is nested object
352
+ if (key.indexOf('tls.') === 0) {
353
+ lKey = key.substr(4);
354
+ if (!options.tls) {
355
+ options.tls = {};
337
356
  }
357
+ obj = options.tls;
358
+ } else if (key.indexOf('.') >= 0) {
359
+ // ignore nested properties besides tls
360
+ return;
361
+ }
338
362
 
339
- if (!(lKey in obj)) {
340
- obj[lKey] = value;
341
- }
342
- });
363
+ // `in` already keeps "__proto__" out, but only as a side effect of it being an
364
+ // Object.prototype member. Say it, so the protection survives a change to the check
365
+ if (!isProtoKey(lKey) && !(lKey in obj)) {
366
+ obj[lKey] = value;
367
+ }
343
368
  });
344
369
 
345
370
  return options;
346
371
  };
347
372
 
348
373
  module.exports._logFunc = (logger, level, defaults, data, message, ...args) => {
349
- let entry = {};
350
-
351
- Object.keys(defaults || {}).forEach(key => {
352
- if (key !== 'level') {
353
- entry[key] = defaults[key];
354
- }
355
- });
356
-
357
- Object.keys(data || {}).forEach(key => {
358
- if (key !== 'level') {
359
- entry[key] = data[key];
360
- }
361
- });
374
+ const entry = Object.assign({}, defaults || {}, data || {});
375
+ delete entry.level;
376
+
377
+ let logLevel = level;
378
+ if (typeof logger[logLevel] !== 'function') {
379
+ // Provided logger does not implement this level. Fall back to a
380
+ // lower-severity handler instead of throwing.
381
+ logLevel = ['info', 'debug', 'log', 'trace', 'warn', 'error'].find(name => typeof logger[name] === 'function');
382
+ }
362
383
 
363
- logger[level](entry, message, ...args);
384
+ if (logLevel) {
385
+ logger[logLevel](entry, message, ...args);
386
+ }
364
387
  };
365
388
 
366
389
  /**
@@ -373,8 +396,8 @@ module.exports._logFunc = (logger, level, defaults, data, message, ...args) => {
373
396
  module.exports.getLogger = (options, defaults) => {
374
397
  options = options || {};
375
398
 
376
- let response = {};
377
- let levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
399
+ const response = {};
400
+ const levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
378
401
 
379
402
  if (!options.logger) {
380
403
  // use vanity logger
@@ -384,12 +407,7 @@ module.exports.getLogger = (options, defaults) => {
384
407
  return response;
385
408
  }
386
409
 
387
- let logger = options.logger;
388
-
389
- if (options.logger === true) {
390
- // create console logger
391
- logger = createDefaultLogger(levels);
392
- }
410
+ const logger = options.logger === true ? createDefaultLogger(levels) : options.logger;
393
411
 
394
412
  levels.forEach(level => {
395
413
  response[level] = (data, message, ...args) => {
@@ -409,8 +427,8 @@ module.exports.getLogger = (options, defaults) => {
409
427
  */
410
428
  module.exports.callbackPromise = (resolve, reject) =>
411
429
  function () {
412
- let args = Array.from(arguments);
413
- let err = args.shift();
430
+ const args = Array.from(arguments);
431
+ const err = args.shift();
414
432
  if (err) {
415
433
  reject(err);
416
434
  } else {
@@ -419,52 +437,74 @@ module.exports.callbackPromise = (resolve, reject) =>
419
437
  };
420
438
 
421
439
  module.exports.parseDataURI = uri => {
422
- let input = uri;
423
- let commaPos = input.indexOf(',');
424
- if (!commaPos) {
425
- return uri;
440
+ if (typeof uri !== 'string') {
441
+ return null;
442
+ }
443
+
444
+ // Early return for non-data URIs to avoid unnecessary processing
445
+ if (!uri.startsWith('data:')) {
446
+ return null;
426
447
  }
427
448
 
428
- let data = input.substring(commaPos + 1);
429
- let metaStr = input.substring('data:'.length, commaPos);
449
+ // Find the first comma safely - this prevents ReDoS
450
+ const commaPos = uri.indexOf(',');
451
+ if (commaPos === -1) {
452
+ return null;
453
+ }
430
454
 
431
- let encoding;
455
+ const data = uri.substring(commaPos + 1);
456
+ const metaStr = uri.substring('data:'.length, commaPos);
432
457
 
433
- let metaEntries = metaStr.split(';');
434
- let lastMetaEntry = metaEntries.length > 1 ? metaEntries[metaEntries.length - 1] : false;
435
- if (lastMetaEntry && lastMetaEntry.indexOf('=') < 0) {
436
- encoding = lastMetaEntry.toLowerCase();
437
- metaEntries.pop();
458
+ let encoding;
459
+ const metaEntries = metaStr.split(';');
460
+
461
+ if (metaEntries.length > 0) {
462
+ const lastEntry = metaEntries[metaEntries.length - 1].toLowerCase().trim();
463
+ // Only recognize valid encoding types to prevent manipulation
464
+ if (['base64', 'utf8', 'utf-8'].includes(lastEntry) && lastEntry.indexOf('=') === -1) {
465
+ encoding = lastEntry;
466
+ metaEntries.pop();
467
+ }
438
468
  }
439
469
 
440
- let contentType = metaEntries.shift() || 'application/octet-stream';
441
- let params = {};
442
- for (let entry of metaEntries) {
443
- let sep = entry.indexOf('=');
444
- if (sep >= 0) {
445
- let key = entry.substring(0, sep);
446
- let value = entry.substring(sep + 1);
447
- params[key] = value;
470
+ const contentType = metaEntries.length > 0 ? metaEntries.shift() : 'application/octet-stream';
471
+ const params = {};
472
+
473
+ for (let i = 0; i < metaEntries.length; i++) {
474
+ const entry = metaEntries[i];
475
+ const sepPos = entry.indexOf('=');
476
+ if (sepPos > 0) {
477
+ // Ensure there's a key before the '='
478
+ const key = entry.substring(0, sepPos).trim();
479
+ const value = entry.substring(sepPos + 1).trim();
480
+ if (key && !isProtoKey(key)) {
481
+ params[key] = value;
482
+ }
448
483
  }
449
484
  }
450
485
 
451
- switch (encoding) {
452
- case 'base64':
453
- data = Buffer.from(data, 'base64');
454
- break;
455
- case 'utf8':
456
- data = Buffer.from(data);
457
- break;
458
- default:
486
+ // Decode data based on encoding with proper error handling
487
+ let bufferData;
488
+ try {
489
+ if (encoding === 'base64') {
490
+ bufferData = Buffer.from(data, 'base64');
491
+ } else {
459
492
  try {
460
- data = Buffer.from(decodeURIComponent(data));
461
- } catch (err) {
462
- data = Buffer.from(data);
493
+ bufferData = Buffer.from(decodeURIComponent(data));
494
+ } catch (_decodeError) {
495
+ bufferData = Buffer.from(data);
463
496
  }
464
- data = Buffer.from(data);
497
+ }
498
+ } catch (_bufferError) {
499
+ bufferData = Buffer.alloc(0);
465
500
  }
466
501
 
467
- return { data, encoding, contentType, params };
502
+ return {
503
+ data: bufferData,
504
+ encoding: encoding || null,
505
+ contentType: contentType || 'application/octet-stream',
506
+ params
507
+ };
468
508
  };
469
509
 
470
510
  /**
@@ -478,9 +518,17 @@ module.exports.parseDataURI = uri => {
478
518
  *
479
519
  * @param {Object} data An object or an Array you want to resolve an element for
480
520
  * @param {String|Number} key Property name or an Array index
521
+ * @param {Object} [options] Optional access policy: { disableFileAccess, disableUrlAccess }
481
522
  * @param {Function} callback Callback function with (err, value)
482
523
  */
483
- module.exports.resolveContent = (data, key, callback) => {
524
+ module.exports.resolveContent = (data, key, options, callback) => {
525
+ // options is optional; support the legacy resolveContent(data, key, callback) signature
526
+ if (!callback && typeof options === 'function') {
527
+ callback = options;
528
+ options = false;
529
+ }
530
+ options = options || {};
531
+
484
532
  let promise;
485
533
 
486
534
  if (!callback) {
@@ -489,9 +537,14 @@ module.exports.resolveContent = (data, key, callback) => {
489
537
  });
490
538
  }
491
539
 
540
+ resolveContentValue(data, key, options, callback);
541
+
542
+ return promise;
543
+ };
544
+
545
+ function resolveContentValue(data, key, options, callback) {
492
546
  let content = (data && data[key] && data[key].content) || data[key];
493
- let contentStream;
494
- let encoding = ((typeof data[key] === 'object' && data[key].encoding) || 'utf8')
547
+ const encoding = ((typeof data[key] === 'object' && data[key].encoding) || 'utf8')
495
548
  .toString()
496
549
  .toLowerCase()
497
550
  .replace(/[-_\s]/g, '');
@@ -515,17 +568,32 @@ module.exports.resolveContent = (data, key, callback) => {
515
568
  }
516
569
  callback(null, value);
517
570
  });
518
- } else if (/^https?:\/\//i.test(content.path || content.href)) {
519
- contentStream = nmfetch(content.path || content.href);
520
- return resolveStream(contentStream, callback);
521
571
  } else if (/^data:/i.test(content.path || content.href)) {
522
- let parsedDataUri = module.exports.parseDataURI(content.path || content.href);
523
-
524
- if (!parsedDataUri || !parsedDataUri.data) {
525
- return callback(null, Buffer.from(0));
572
+ const parsedDataUri = module.exports.parseDataURI(content.path || content.href);
573
+
574
+ return callback(null, parsedDataUri && parsedDataUri.data ? parsedDataUri.data : Buffer.alloc(0));
575
+ } else if (content.href || /^https?:\/\//i.test(content.path)) {
576
+ // An href is always a URL, and so is a path that looks like one. Let nmfetch
577
+ // decide whether it is fetchable, it validates the parsed URL. Testing the raw
578
+ // string here instead would let a file: href fall through to the "return as is"
579
+ // default below and travel on inside the resolved message.
580
+ const url = content.href || content.path;
581
+ if (options.disableUrlAccess) {
582
+ return setImmediate(() => {
583
+ const err = new Error('Url access rejected for ' + url);
584
+ err.code = errors.EURLACCESS;
585
+ callback(err);
586
+ });
526
587
  }
527
- return callback(null, parsedDataUri.data);
588
+ return resolveStream(nmfetch(url, { headers: content.httpHeaders, tls: content.tls }), callback);
528
589
  } else if (content.path) {
590
+ if (options.disableFileAccess) {
591
+ return setImmediate(() => {
592
+ const err = new Error('File access rejected for ' + content.path);
593
+ err.code = errors.EFILEACCESS;
594
+ callback(err);
595
+ });
596
+ }
529
597
  return resolveStream(fs.createReadStream(content.path), callback);
530
598
  }
531
599
  }
@@ -536,29 +604,25 @@ module.exports.resolveContent = (data, key, callback) => {
536
604
 
537
605
  // default action, return as is
538
606
  setImmediate(() => callback(null, content));
539
-
540
- return promise;
541
- };
607
+ }
542
608
 
543
609
  /**
544
610
  * Copies properties from source objects to target objects
545
611
  */
546
612
  module.exports.assign = function (/* target, ... sources */) {
547
- let args = Array.from(arguments);
548
- let target = args.shift() || {};
613
+ const args = Array.from(arguments);
614
+ const target = args.shift() || {};
549
615
 
550
616
  args.forEach(source => {
551
617
  Object.keys(source || {}).forEach(key => {
618
+ if (isProtoKey(key)) {
619
+ return;
620
+ }
552
621
  if (['tls', 'auth'].includes(key) && source[key] && typeof source[key] === 'object') {
553
622
  // tls and auth are special keys that need to be enumerated separately
554
- // other objects are passed as is
555
- if (!target[key]) {
556
- // ensure that target has this key
557
- target[key] = {};
558
- }
559
- Object.keys(source[key]).forEach(subKey => {
560
- target[key][subKey] = source[key][subKey];
561
- });
623
+ // other objects are passed as is. Enumerating is a copy of user supplied
624
+ // keys just like the loop above, so it gets the same treatment
625
+ target[key] = module.exports.copyOwnKeys(target[key] || {}, source[key]);
562
626
  } else {
563
627
  target[key] = source[key];
564
628
  }
@@ -575,10 +639,10 @@ module.exports.encodeXText = str => {
575
639
  if (!/[^\x21-\x2A\x2C-\x3C\x3E-\x7E]/.test(str)) {
576
640
  return str;
577
641
  }
578
- let buf = Buffer.from(str);
642
+ const buf = Buffer.from(str);
579
643
  let result = '';
580
644
  for (let i = 0, len = buf.length; i < len; i++) {
581
- let c = buf[i];
645
+ const c = buf[i];
582
646
  if (c < 0x21 || c > 0x7e || c === 0x2b || c === 0x3d) {
583
647
  result += '+' + (c < 0x10 ? '0' : '') + c.toString(16).toUpperCase();
584
648
  } else {
@@ -596,7 +660,7 @@ module.exports.encodeXText = str => {
596
660
  */
597
661
  function resolveStream(stream, callback) {
598
662
  let responded = false;
599
- let chunks = [];
663
+ const chunks = [];
600
664
  let chunklen = 0;
601
665
 
602
666
  stream.on('error', err => {
@@ -639,13 +703,8 @@ function resolveStream(stream, callback) {
639
703
  * @returns {Object} Bunyan logger instance
640
704
  */
641
705
  function createDefaultLogger(levels) {
642
- let levelMaxLen = 0;
643
- let levelNames = new Map();
644
- levels.forEach(level => {
645
- if (level.length > levelMaxLen) {
646
- levelMaxLen = level.length;
647
- }
648
- });
706
+ const levelMaxLen = levels.reduce((max, level) => Math.max(max, level.length), 0);
707
+ const levelNames = new Map();
649
708
 
650
709
  levels.forEach(level => {
651
710
  let levelName = level.toUpperCase();
@@ -655,7 +714,7 @@ function createDefaultLogger(levels) {
655
714
  levelNames.set(level, levelName);
656
715
  });
657
716
 
658
- let print = (level, entry, message, ...args) => {
717
+ const print = (level, entry, message, ...args) => {
659
718
  let prefix = '';
660
719
  if (entry) {
661
720
  if (entry.tnx === 'server') {
@@ -679,7 +738,7 @@ function createDefaultLogger(levels) {
679
738
  });
680
739
  };
681
740
 
682
- let logger = {};
741
+ const logger = {};
683
742
  levels.forEach(level => {
684
743
  logger[level] = print.bind(null, level);
685
744
  });