@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.
- package/dist/externalVersion.js +6 -6
- package/dist/node_modules/joi/dist/joi-browser.min.js +1 -1
- package/dist/node_modules/joi/lib/index.js +1 -1
- package/dist/node_modules/joi/lib/messages.js +35 -6
- package/dist/node_modules/joi/lib/types/keys.js +18 -1
- package/dist/node_modules/joi/lib/types/link.js +18 -2
- package/dist/node_modules/joi/lib/types/string.js +2 -2
- package/dist/node_modules/joi/package.json +1 -1
- package/dist/node_modules/nodemailer/.ncurc.js +2 -0
- package/dist/node_modules/nodemailer/.prettierignore +8 -0
- package/dist/node_modules/nodemailer/.prettierrc +12 -0
- package/dist/node_modules/nodemailer/.prettierrc.js +2 -0
- package/dist/node_modules/nodemailer/.release-please-config.json +9 -0
- package/dist/node_modules/nodemailer/eslint.config.js +88 -0
- package/dist/node_modules/nodemailer/lib/addressparser/index.js +314 -71
- package/dist/node_modules/nodemailer/lib/base64/index.js +9 -13
- package/dist/node_modules/nodemailer/lib/dkim/index.js +17 -18
- package/dist/node_modules/nodemailer/lib/dkim/message-parser.js +12 -13
- package/dist/node_modules/nodemailer/lib/dkim/relaxed-body.js +3 -3
- package/dist/node_modules/nodemailer/lib/dkim/sign.js +22 -18
- package/dist/node_modules/nodemailer/lib/errors.js +59 -0
- package/dist/node_modules/nodemailer/lib/fetch/cookies.js +14 -19
- package/dist/node_modules/nodemailer/lib/fetch/index.js +164 -27
- package/dist/node_modules/nodemailer/lib/json-transport/index.js +4 -4
- package/dist/node_modules/nodemailer/lib/mail-composer/index.js +229 -155
- package/dist/node_modules/nodemailer/lib/mailer/index.js +106 -54
- package/dist/node_modules/nodemailer/lib/mailer/mail-message.js +106 -68
- package/dist/node_modules/nodemailer/lib/mime-funcs/index.js +105 -59
- package/dist/node_modules/nodemailer/lib/mime-funcs/mime-types.js +21 -14
- package/dist/node_modules/nodemailer/lib/mime-node/index.js +389 -137
- package/dist/node_modules/nodemailer/lib/mime-node/last-newline.js +1 -1
- package/dist/node_modules/nodemailer/lib/mime-node/le-unix.js +4 -7
- package/dist/node_modules/nodemailer/lib/mime-node/le-windows.js +1 -4
- package/dist/node_modules/nodemailer/lib/nodemailer.js +1 -1
- package/dist/node_modules/nodemailer/lib/punycode/index.js +1 -1
- package/dist/node_modules/nodemailer/lib/qp/index.js +33 -22
- package/dist/node_modules/nodemailer/lib/sendmail-transport/index.js +54 -44
- package/dist/node_modules/nodemailer/lib/ses-transport/index.js +110 -193
- package/dist/node_modules/nodemailer/lib/shared/index.js +299 -240
- package/dist/node_modules/nodemailer/lib/shared/objects.js +42 -0
- package/dist/node_modules/nodemailer/lib/shared/url.js +151 -0
- package/dist/node_modules/nodemailer/lib/smtp-connection/data-stream.js +3 -6
- package/dist/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +50 -19
- package/dist/node_modules/nodemailer/lib/smtp-connection/index.js +415 -223
- package/dist/node_modules/nodemailer/lib/smtp-pool/index.js +27 -34
- package/dist/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +19 -16
- package/dist/node_modules/nodemailer/lib/smtp-transport/index.js +66 -53
- package/dist/node_modules/nodemailer/lib/stream-transport/index.js +16 -7
- package/dist/node_modules/nodemailer/lib/well-known/index.js +7 -7
- package/dist/node_modules/nodemailer/lib/well-known/services.json +328 -38
- package/dist/node_modules/nodemailer/lib/xoauth2/index.js +105 -33
- package/dist/node_modules/nodemailer/package.json +1 -1
- 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 (
|
|
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
|
-
|
|
26
|
-
if (!
|
|
39
|
+
const ifaces = module.exports.networkInterfaces;
|
|
40
|
+
if (!ifaces) {
|
|
27
41
|
// hope for the best
|
|
28
42
|
return true;
|
|
29
43
|
}
|
|
30
44
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
52
|
+
const resolve = (family, hostname, options, callback) => {
|
|
44
53
|
options = options || {};
|
|
45
|
-
const familySupported = isFamilySupported(family, options.allowInternalNetworkInterfaces);
|
|
46
54
|
|
|
47
|
-
if (!
|
|
55
|
+
if (!isFamilySupported(family, options.allowInternalNetworkInterfaces)) {
|
|
48
56
|
return callback(null, []);
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
const
|
|
52
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
169
|
+
resolve(6, options.host, options, (err, addresses) => {
|
|
161
170
|
if (err) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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 && !
|
|
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 (!
|
|
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
|
-
|
|
233
|
-
addresses:
|
|
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 (
|
|
274
|
+
} catch (lookupErr) {
|
|
250
275
|
if (cached) {
|
|
251
|
-
|
|
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:
|
|
285
|
+
error: lookupErr
|
|
257
286
|
})
|
|
258
287
|
);
|
|
259
288
|
}
|
|
260
|
-
return callback(
|
|
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
|
-
|
|
302
|
+
const options = {};
|
|
303
|
+
const url = urllib.parse(str, true);
|
|
274
304
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
317
|
+
if (!isNaN(url.port) && Number(url.port)) {
|
|
318
|
+
options.port = Number(url.port);
|
|
319
|
+
}
|
|
293
320
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
321
|
+
if (url.hostname) {
|
|
322
|
+
options.host = url.hostname;
|
|
323
|
+
}
|
|
297
324
|
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
302
|
-
|
|
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
|
-
|
|
306
|
-
|
|
338
|
+
if (!isNaN(value)) {
|
|
339
|
+
value = Number(value);
|
|
307
340
|
}
|
|
308
341
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
340
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
|
|
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
|
-
|
|
377
|
-
|
|
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
|
-
|
|
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
|
-
|
|
413
|
-
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
-
|
|
429
|
-
|
|
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
|
-
|
|
455
|
+
const data = uri.substring(commaPos + 1);
|
|
456
|
+
const metaStr = uri.substring('data:'.length, commaPos);
|
|
432
457
|
|
|
433
|
-
let
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
metaEntries.
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
461
|
-
} catch (
|
|
462
|
-
|
|
493
|
+
bufferData = Buffer.from(decodeURIComponent(data));
|
|
494
|
+
} catch (_decodeError) {
|
|
495
|
+
bufferData = Buffer.from(data);
|
|
463
496
|
}
|
|
464
|
-
|
|
497
|
+
}
|
|
498
|
+
} catch (_bufferError) {
|
|
499
|
+
bufferData = Buffer.alloc(0);
|
|
465
500
|
}
|
|
466
501
|
|
|
467
|
-
return {
|
|
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
|
-
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
|
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
|
-
|
|
548
|
-
|
|
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
|
-
|
|
556
|
-
|
|
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
|
-
|
|
642
|
+
const buf = Buffer.from(str);
|
|
579
643
|
let result = '';
|
|
580
644
|
for (let i = 0, len = buf.length; i < len; i++) {
|
|
581
|
-
|
|
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
|
-
|
|
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
|
-
|
|
643
|
-
|
|
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
|
-
|
|
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
|
-
|
|
741
|
+
const logger = {};
|
|
683
742
|
levels.forEach(level => {
|
|
684
743
|
logger[level] = print.bind(null, level);
|
|
685
744
|
});
|