@nocobase/plugin-notification-email 2.3.0-beta.11 → 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.
- package/dist/client/index.js +1 -1
- package/dist/client-v2/locale.d.ts +1 -1
- package/dist/externalVersion.js +7 -7
- 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 +23 -19
- 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 +235 -161
- package/dist/node_modules/nodemailer/lib/mailer/index.js +106 -52
- 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 +399 -138
- 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 +460 -0
- 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 +316 -207
- 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 +423 -218
- 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 +333 -38
- package/dist/node_modules/nodemailer/lib/xoauth2/index.js +105 -33
- package/dist/node_modules/nodemailer/package.json +1 -1
- 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 (
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
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 {
|
|
@@ -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
|
-
|
|
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
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
|
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
|
-
|
|
498
|
-
|
|
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
|
-
|
|
506
|
-
|
|
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
|
-
|
|
642
|
+
const buf = Buffer.from(str);
|
|
529
643
|
let result = '';
|
|
530
644
|
for (let i = 0, len = buf.length; i < len; i++) {
|
|
531
|
-
|
|
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
|
-
|
|
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
|
-
|
|
593
|
-
|
|
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
|
-
|
|
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
|
-
|
|
741
|
+
const logger = {};
|
|
633
742
|
levels.forEach(level => {
|
|
634
743
|
logger[level] = print.bind(null, level);
|
|
635
744
|
});
|