@nocobase/plugin-notification-email 2.3.0-beta.12 → 2.3.0-beta.13

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 (48) hide show
  1. package/dist/client/index.js +1 -1
  2. package/dist/client-v2/locale.d.ts +1 -1
  3. package/dist/externalVersion.js +7 -7
  4. package/dist/node_modules/nodemailer/.ncurc.js +2 -0
  5. package/dist/node_modules/nodemailer/.prettierignore +8 -0
  6. package/dist/node_modules/nodemailer/.prettierrc +12 -0
  7. package/dist/node_modules/nodemailer/.prettierrc.js +2 -0
  8. package/dist/node_modules/nodemailer/.release-please-config.json +9 -0
  9. package/dist/node_modules/nodemailer/eslint.config.js +88 -0
  10. package/dist/node_modules/nodemailer/lib/addressparser/index.js +314 -71
  11. package/dist/node_modules/nodemailer/lib/base64/index.js +9 -13
  12. package/dist/node_modules/nodemailer/lib/dkim/index.js +17 -18
  13. package/dist/node_modules/nodemailer/lib/dkim/message-parser.js +12 -13
  14. package/dist/node_modules/nodemailer/lib/dkim/relaxed-body.js +3 -3
  15. package/dist/node_modules/nodemailer/lib/dkim/sign.js +23 -19
  16. package/dist/node_modules/nodemailer/lib/errors.js +59 -0
  17. package/dist/node_modules/nodemailer/lib/fetch/cookies.js +14 -19
  18. package/dist/node_modules/nodemailer/lib/fetch/index.js +164 -27
  19. package/dist/node_modules/nodemailer/lib/json-transport/index.js +4 -4
  20. package/dist/node_modules/nodemailer/lib/mail-composer/index.js +235 -161
  21. package/dist/node_modules/nodemailer/lib/mailer/index.js +106 -52
  22. package/dist/node_modules/nodemailer/lib/mailer/mail-message.js +106 -68
  23. package/dist/node_modules/nodemailer/lib/mime-funcs/index.js +105 -59
  24. package/dist/node_modules/nodemailer/lib/mime-funcs/mime-types.js +21 -14
  25. package/dist/node_modules/nodemailer/lib/mime-node/index.js +399 -138
  26. package/dist/node_modules/nodemailer/lib/mime-node/last-newline.js +1 -1
  27. package/dist/node_modules/nodemailer/lib/mime-node/le-unix.js +4 -7
  28. package/dist/node_modules/nodemailer/lib/mime-node/le-windows.js +1 -4
  29. package/dist/node_modules/nodemailer/lib/nodemailer.js +1 -1
  30. package/dist/node_modules/nodemailer/lib/punycode/index.js +460 -0
  31. package/dist/node_modules/nodemailer/lib/qp/index.js +33 -22
  32. package/dist/node_modules/nodemailer/lib/sendmail-transport/index.js +54 -44
  33. package/dist/node_modules/nodemailer/lib/ses-transport/index.js +110 -193
  34. package/dist/node_modules/nodemailer/lib/shared/index.js +316 -207
  35. package/dist/node_modules/nodemailer/lib/shared/objects.js +42 -0
  36. package/dist/node_modules/nodemailer/lib/shared/url.js +151 -0
  37. package/dist/node_modules/nodemailer/lib/smtp-connection/data-stream.js +3 -6
  38. package/dist/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +50 -19
  39. package/dist/node_modules/nodemailer/lib/smtp-connection/index.js +423 -218
  40. package/dist/node_modules/nodemailer/lib/smtp-pool/index.js +27 -34
  41. package/dist/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +19 -16
  42. package/dist/node_modules/nodemailer/lib/smtp-transport/index.js +66 -53
  43. package/dist/node_modules/nodemailer/lib/stream-transport/index.js +16 -7
  44. package/dist/node_modules/nodemailer/lib/well-known/index.js +7 -7
  45. package/dist/node_modules/nodemailer/lib/well-known/services.json +333 -38
  46. package/dist/node_modules/nodemailer/lib/xoauth2/index.js +105 -33
  47. package/dist/node_modules/nodemailer/package.json +1 -1
  48. package/package.json +3 -3
@@ -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 = {};
274
-
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
- }
302
+ const options = {};
303
+ const url = urllib.parse(str, true);
304
+
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 {
@@ -418,6 +436,77 @@ module.exports.callbackPromise = (resolve, reject) =>
418
436
  }
419
437
  };
420
438
 
439
+ module.exports.parseDataURI = 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;
447
+ }
448
+
449
+ // Find the first comma safely - this prevents ReDoS
450
+ const commaPos = uri.indexOf(',');
451
+ if (commaPos === -1) {
452
+ return null;
453
+ }
454
+
455
+ const data = uri.substring(commaPos + 1);
456
+ const metaStr = uri.substring('data:'.length, commaPos);
457
+
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
+ }
468
+ }
469
+
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
+ }
483
+ }
484
+ }
485
+
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 {
492
+ try {
493
+ bufferData = Buffer.from(decodeURIComponent(data));
494
+ } catch (_decodeError) {
495
+ bufferData = Buffer.from(data);
496
+ }
497
+ }
498
+ } catch (_bufferError) {
499
+ bufferData = Buffer.alloc(0);
500
+ }
501
+
502
+ return {
503
+ data: bufferData,
504
+ encoding: encoding || null,
505
+ contentType: contentType || 'application/octet-stream',
506
+ params
507
+ };
508
+ };
509
+
421
510
  /**
422
511
  * Resolves a String or a Buffer value for content value. Useful if the value
423
512
  * is a Stream or a file or an URL. If the value is a Stream, overwrites
@@ -429,9 +518,17 @@ module.exports.callbackPromise = (resolve, reject) =>
429
518
  *
430
519
  * @param {Object} data An object or an Array you want to resolve an element for
431
520
  * @param {String|Number} key Property name or an Array index
521
+ * @param {Object} [options] Optional access policy: { disableFileAccess, disableUrlAccess }
432
522
  * @param {Function} callback Callback function with (err, value)
433
523
  */
434
- 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
+
435
532
  let promise;
436
533
 
437
534
  if (!callback) {
@@ -440,9 +537,14 @@ module.exports.resolveContent = (data, key, callback) => {
440
537
  });
441
538
  }
442
539
 
540
+ resolveContentValue(data, key, options, callback);
541
+
542
+ return promise;
543
+ };
544
+
545
+ function resolveContentValue(data, key, options, callback) {
443
546
  let content = (data && data[key] && data[key].content) || data[key];
444
- let contentStream;
445
- let encoding = ((typeof data[key] === 'object' && data[key].encoding) || 'utf8')
547
+ const encoding = ((typeof data[key] === 'object' && data[key].encoding) || 'utf8')
446
548
  .toString()
447
549
  .toLowerCase()
448
550
  .replace(/[-_\s]/g, '');
@@ -466,16 +568,32 @@ module.exports.resolveContent = (data, key, callback) => {
466
568
  }
467
569
  callback(null, value);
468
570
  });
469
- } else if (/^https?:\/\//i.test(content.path || content.href)) {
470
- contentStream = nmfetch(content.path || content.href);
471
- return resolveStream(contentStream, callback);
472
571
  } else if (/^data:/i.test(content.path || content.href)) {
473
- let parts = (content.path || content.href).match(/^data:((?:[^;]*;)*(?:[^,]*)),(.*)$/i);
474
- if (!parts) {
475
- 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
+ });
476
587
  }
477
- return callback(null, /\bbase64$/i.test(parts[1]) ? Buffer.from(parts[2], 'base64') : Buffer.from(decodeURIComponent(parts[2])));
588
+ return resolveStream(nmfetch(url, { headers: content.httpHeaders, tls: content.tls }), callback);
478
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
+ }
479
597
  return resolveStream(fs.createReadStream(content.path), callback);
480
598
  }
481
599
  }
@@ -486,29 +604,25 @@ module.exports.resolveContent = (data, key, callback) => {
486
604
 
487
605
  // default action, return as is
488
606
  setImmediate(() => callback(null, content));
489
-
490
- return promise;
491
- };
607
+ }
492
608
 
493
609
  /**
494
610
  * Copies properties from source objects to target objects
495
611
  */
496
612
  module.exports.assign = function (/* target, ... sources */) {
497
- let args = Array.from(arguments);
498
- let target = args.shift() || {};
613
+ const args = Array.from(arguments);
614
+ const target = args.shift() || {};
499
615
 
500
616
  args.forEach(source => {
501
617
  Object.keys(source || {}).forEach(key => {
618
+ if (isProtoKey(key)) {
619
+ return;
620
+ }
502
621
  if (['tls', 'auth'].includes(key) && source[key] && typeof source[key] === 'object') {
503
622
  // tls and auth are special keys that need to be enumerated separately
504
- // other objects are passed as is
505
- if (!target[key]) {
506
- // ensure that target has this key
507
- target[key] = {};
508
- }
509
- Object.keys(source[key]).forEach(subKey => {
510
- target[key][subKey] = source[key][subKey];
511
- });
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]);
512
626
  } else {
513
627
  target[key] = source[key];
514
628
  }
@@ -525,10 +639,10 @@ module.exports.encodeXText = str => {
525
639
  if (!/[^\x21-\x2A\x2C-\x3C\x3E-\x7E]/.test(str)) {
526
640
  return str;
527
641
  }
528
- let buf = Buffer.from(str);
642
+ const buf = Buffer.from(str);
529
643
  let result = '';
530
644
  for (let i = 0, len = buf.length; i < len; i++) {
531
- let c = buf[i];
645
+ const c = buf[i];
532
646
  if (c < 0x21 || c > 0x7e || c === 0x2b || c === 0x3d) {
533
647
  result += '+' + (c < 0x10 ? '0' : '') + c.toString(16).toUpperCase();
534
648
  } else {
@@ -546,7 +660,7 @@ module.exports.encodeXText = str => {
546
660
  */
547
661
  function resolveStream(stream, callback) {
548
662
  let responded = false;
549
- let chunks = [];
663
+ const chunks = [];
550
664
  let chunklen = 0;
551
665
 
552
666
  stream.on('error', err => {
@@ -589,13 +703,8 @@ function resolveStream(stream, callback) {
589
703
  * @returns {Object} Bunyan logger instance
590
704
  */
591
705
  function createDefaultLogger(levels) {
592
- let levelMaxLen = 0;
593
- let levelNames = new Map();
594
- levels.forEach(level => {
595
- if (level.length > levelMaxLen) {
596
- levelMaxLen = level.length;
597
- }
598
- });
706
+ const levelMaxLen = levels.reduce((max, level) => Math.max(max, level.length), 0);
707
+ const levelNames = new Map();
599
708
 
600
709
  levels.forEach(level => {
601
710
  let levelName = level.toUpperCase();
@@ -605,7 +714,7 @@ function createDefaultLogger(levels) {
605
714
  levelNames.set(level, levelName);
606
715
  });
607
716
 
608
- let print = (level, entry, message, ...args) => {
717
+ const print = (level, entry, message, ...args) => {
609
718
  let prefix = '';
610
719
  if (entry) {
611
720
  if (entry.tnx === 'server') {
@@ -629,7 +738,7 @@ function createDefaultLogger(levels) {
629
738
  });
630
739
  };
631
740
 
632
- let logger = {};
741
+ const logger = {};
633
742
  levels.forEach(level => {
634
743
  logger[level] = print.bind(null, level);
635
744
  });