zhuha 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of zhuha might be problematic. Click here for more details.
- package/06-02.html +81 -0
- package/06-02.js +72 -0
- package/06-03.js +7 -0
- package/06-04.js +7 -0
- package/AnswersLW5.pdf +0 -0
- package/m0603/m0603.js +30 -0
- package/m0603/node_modules/.package-lock.json +16 -0
- package/m0603/node_modules/nodemailer/.gitattributes +6 -0
- package/m0603/node_modules/nodemailer/.prettierrc.js +8 -0
- package/m0603/node_modules/nodemailer/CHANGELOG.md +725 -0
- package/m0603/node_modules/nodemailer/CODE_OF_CONDUCT.md +76 -0
- package/m0603/node_modules/nodemailer/CONTRIBUTING.md +67 -0
- package/m0603/node_modules/nodemailer/LICENSE +16 -0
- package/m0603/node_modules/nodemailer/README.md +97 -0
- package/m0603/node_modules/nodemailer/SECURITY.txt +22 -0
- package/m0603/node_modules/nodemailer/lib/addressparser/index.js +313 -0
- package/m0603/node_modules/nodemailer/lib/base64/index.js +142 -0
- package/m0603/node_modules/nodemailer/lib/dkim/index.js +251 -0
- package/m0603/node_modules/nodemailer/lib/dkim/message-parser.js +155 -0
- package/m0603/node_modules/nodemailer/lib/dkim/relaxed-body.js +154 -0
- package/m0603/node_modules/nodemailer/lib/dkim/sign.js +117 -0
- package/m0603/node_modules/nodemailer/lib/fetch/cookies.js +281 -0
- package/m0603/node_modules/nodemailer/lib/fetch/index.js +274 -0
- package/m0603/node_modules/nodemailer/lib/json-transport/index.js +82 -0
- package/m0603/node_modules/nodemailer/lib/mail-composer/index.js +558 -0
- package/m0603/node_modules/nodemailer/lib/mailer/index.js +427 -0
- package/m0603/node_modules/nodemailer/lib/mailer/mail-message.js +315 -0
- package/m0603/node_modules/nodemailer/lib/mime-funcs/index.js +625 -0
- package/m0603/node_modules/nodemailer/lib/mime-funcs/mime-types.js +2102 -0
- package/m0603/node_modules/nodemailer/lib/mime-node/index.js +1290 -0
- package/m0603/node_modules/nodemailer/lib/mime-node/last-newline.js +33 -0
- package/m0603/node_modules/nodemailer/lib/mime-node/le-unix.js +43 -0
- package/m0603/node_modules/nodemailer/lib/mime-node/le-windows.js +52 -0
- package/m0603/node_modules/nodemailer/lib/nodemailer.js +143 -0
- package/m0603/node_modules/nodemailer/lib/qp/index.js +219 -0
- package/m0603/node_modules/nodemailer/lib/sendmail-transport/index.js +210 -0
- package/m0603/node_modules/nodemailer/lib/ses-transport/index.js +349 -0
- package/m0603/node_modules/nodemailer/lib/shared/index.js +638 -0
- package/m0603/node_modules/nodemailer/lib/smtp-connection/data-stream.js +108 -0
- package/m0603/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +143 -0
- package/m0603/node_modules/nodemailer/lib/smtp-connection/index.js +1796 -0
- package/m0603/node_modules/nodemailer/lib/smtp-pool/index.js +648 -0
- package/m0603/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +253 -0
- package/m0603/node_modules/nodemailer/lib/smtp-transport/index.js +416 -0
- package/m0603/node_modules/nodemailer/lib/stream-transport/index.js +135 -0
- package/m0603/node_modules/nodemailer/lib/well-known/index.js +47 -0
- package/m0603/node_modules/nodemailer/lib/well-known/services.json +286 -0
- package/m0603/node_modules/nodemailer/lib/xoauth2/index.js +376 -0
- package/m0603/node_modules/nodemailer/package.json +46 -0
- package/m0603/node_modules/nodemailer/postinstall.js +101 -0
- package/m0603/package-lock.json +31 -0
- package/m0603/package.json +15 -0
- package/package.json +16 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const Transform = require('stream').Transform;
|
4
|
+
|
5
|
+
class LastNewline extends Transform {
|
6
|
+
constructor() {
|
7
|
+
super();
|
8
|
+
this.lastByte = false;
|
9
|
+
}
|
10
|
+
|
11
|
+
_transform(chunk, encoding, done) {
|
12
|
+
if (chunk.length) {
|
13
|
+
this.lastByte = chunk[chunk.length - 1];
|
14
|
+
}
|
15
|
+
|
16
|
+
this.push(chunk);
|
17
|
+
done();
|
18
|
+
}
|
19
|
+
|
20
|
+
_flush(done) {
|
21
|
+
if (this.lastByte === 0x0a) {
|
22
|
+
return done();
|
23
|
+
}
|
24
|
+
if (this.lastByte === 0x0d) {
|
25
|
+
this.push(Buffer.from('\n'));
|
26
|
+
return done();
|
27
|
+
}
|
28
|
+
this.push(Buffer.from('\r\n'));
|
29
|
+
return done();
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
module.exports = LastNewline;
|
@@ -0,0 +1,43 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const stream = require('stream');
|
4
|
+
const Transform = stream.Transform;
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Ensures that only <LF> is used for linebreaks
|
8
|
+
*
|
9
|
+
* @param {Object} options Stream options
|
10
|
+
*/
|
11
|
+
class LeWindows extends Transform {
|
12
|
+
constructor(options) {
|
13
|
+
super(options);
|
14
|
+
// init Transform
|
15
|
+
this.options = options || {};
|
16
|
+
}
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Escapes dots
|
20
|
+
*/
|
21
|
+
_transform(chunk, encoding, done) {
|
22
|
+
let buf;
|
23
|
+
let lastPos = 0;
|
24
|
+
|
25
|
+
for (let i = 0, len = chunk.length; i < len; i++) {
|
26
|
+
if (chunk[i] === 0x0d) {
|
27
|
+
// \n
|
28
|
+
buf = chunk.slice(lastPos, i);
|
29
|
+
lastPos = i + 1;
|
30
|
+
this.push(buf);
|
31
|
+
}
|
32
|
+
}
|
33
|
+
if (lastPos && lastPos < chunk.length) {
|
34
|
+
buf = chunk.slice(lastPos);
|
35
|
+
this.push(buf);
|
36
|
+
} else if (!lastPos) {
|
37
|
+
this.push(chunk);
|
38
|
+
}
|
39
|
+
done();
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
module.exports = LeWindows;
|
@@ -0,0 +1,52 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const stream = require('stream');
|
4
|
+
const Transform = stream.Transform;
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Ensures that only <CR><LF> sequences are used for linebreaks
|
8
|
+
*
|
9
|
+
* @param {Object} options Stream options
|
10
|
+
*/
|
11
|
+
class LeWindows extends Transform {
|
12
|
+
constructor(options) {
|
13
|
+
super(options);
|
14
|
+
// init Transform
|
15
|
+
this.options = options || {};
|
16
|
+
this.lastByte = false;
|
17
|
+
}
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Escapes dots
|
21
|
+
*/
|
22
|
+
_transform(chunk, encoding, done) {
|
23
|
+
let buf;
|
24
|
+
let lastPos = 0;
|
25
|
+
|
26
|
+
for (let i = 0, len = chunk.length; i < len; i++) {
|
27
|
+
if (chunk[i] === 0x0a) {
|
28
|
+
// \n
|
29
|
+
if ((i && chunk[i - 1] !== 0x0d) || (!i && this.lastByte !== 0x0d)) {
|
30
|
+
if (i > lastPos) {
|
31
|
+
buf = chunk.slice(lastPos, i);
|
32
|
+
this.push(buf);
|
33
|
+
}
|
34
|
+
this.push(Buffer.from('\r\n'));
|
35
|
+
lastPos = i + 1;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
if (lastPos && lastPos < chunk.length) {
|
41
|
+
buf = chunk.slice(lastPos);
|
42
|
+
this.push(buf);
|
43
|
+
} else if (!lastPos) {
|
44
|
+
this.push(chunk);
|
45
|
+
}
|
46
|
+
|
47
|
+
this.lastByte = chunk[chunk.length - 1];
|
48
|
+
done();
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
module.exports = LeWindows;
|
@@ -0,0 +1,143 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const Mailer = require('./mailer');
|
4
|
+
const shared = require('./shared');
|
5
|
+
const SMTPPool = require('./smtp-pool');
|
6
|
+
const SMTPTransport = require('./smtp-transport');
|
7
|
+
const SendmailTransport = require('./sendmail-transport');
|
8
|
+
const StreamTransport = require('./stream-transport');
|
9
|
+
const JSONTransport = require('./json-transport');
|
10
|
+
const SESTransport = require('./ses-transport');
|
11
|
+
const nmfetch = require('./fetch');
|
12
|
+
const packageData = require('../package.json');
|
13
|
+
|
14
|
+
const ETHEREAL_API = (process.env.ETHEREAL_API || 'https://api.nodemailer.com').replace(/\/+$/, '');
|
15
|
+
const ETHEREAL_WEB = (process.env.ETHEREAL_WEB || 'https://ethereal.email').replace(/\/+$/, '');
|
16
|
+
const ETHEREAL_CACHE = ['true', 'yes', 'y', '1'].includes((process.env.ETHEREAL_CACHE || 'yes').toString().trim().toLowerCase());
|
17
|
+
|
18
|
+
let testAccount = false;
|
19
|
+
|
20
|
+
module.exports.createTransport = function (transporter, defaults) {
|
21
|
+
let urlConfig;
|
22
|
+
let options;
|
23
|
+
let mailer;
|
24
|
+
|
25
|
+
if (
|
26
|
+
// provided transporter is a configuration object, not transporter plugin
|
27
|
+
(typeof transporter === 'object' && typeof transporter.send !== 'function') ||
|
28
|
+
// provided transporter looks like a connection url
|
29
|
+
(typeof transporter === 'string' && /^(smtps?|direct):/i.test(transporter))
|
30
|
+
) {
|
31
|
+
if ((urlConfig = typeof transporter === 'string' ? transporter : transporter.url)) {
|
32
|
+
// parse a configuration URL into configuration options
|
33
|
+
options = shared.parseConnectionUrl(urlConfig);
|
34
|
+
} else {
|
35
|
+
options = transporter;
|
36
|
+
}
|
37
|
+
|
38
|
+
if (options.pool) {
|
39
|
+
transporter = new SMTPPool(options);
|
40
|
+
} else if (options.sendmail) {
|
41
|
+
transporter = new SendmailTransport(options);
|
42
|
+
} else if (options.streamTransport) {
|
43
|
+
transporter = new StreamTransport(options);
|
44
|
+
} else if (options.jsonTransport) {
|
45
|
+
transporter = new JSONTransport(options);
|
46
|
+
} else if (options.SES) {
|
47
|
+
transporter = new SESTransport(options);
|
48
|
+
} else {
|
49
|
+
transporter = new SMTPTransport(options);
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
mailer = new Mailer(transporter, options, defaults);
|
54
|
+
|
55
|
+
return mailer;
|
56
|
+
};
|
57
|
+
|
58
|
+
module.exports.createTestAccount = function (apiUrl, callback) {
|
59
|
+
let promise;
|
60
|
+
|
61
|
+
if (!callback && typeof apiUrl === 'function') {
|
62
|
+
callback = apiUrl;
|
63
|
+
apiUrl = false;
|
64
|
+
}
|
65
|
+
|
66
|
+
if (!callback) {
|
67
|
+
promise = new Promise((resolve, reject) => {
|
68
|
+
callback = shared.callbackPromise(resolve, reject);
|
69
|
+
});
|
70
|
+
}
|
71
|
+
|
72
|
+
if (ETHEREAL_CACHE && testAccount) {
|
73
|
+
setImmediate(() => callback(null, testAccount));
|
74
|
+
return promise;
|
75
|
+
}
|
76
|
+
|
77
|
+
apiUrl = apiUrl || ETHEREAL_API;
|
78
|
+
|
79
|
+
let chunks = [];
|
80
|
+
let chunklen = 0;
|
81
|
+
|
82
|
+
let req = nmfetch(apiUrl + '/user', {
|
83
|
+
contentType: 'application/json',
|
84
|
+
method: 'POST',
|
85
|
+
body: Buffer.from(
|
86
|
+
JSON.stringify({
|
87
|
+
requestor: packageData.name,
|
88
|
+
version: packageData.version
|
89
|
+
})
|
90
|
+
)
|
91
|
+
});
|
92
|
+
|
93
|
+
req.on('readable', () => {
|
94
|
+
let chunk;
|
95
|
+
while ((chunk = req.read()) !== null) {
|
96
|
+
chunks.push(chunk);
|
97
|
+
chunklen += chunk.length;
|
98
|
+
}
|
99
|
+
});
|
100
|
+
|
101
|
+
req.once('error', err => callback(err));
|
102
|
+
|
103
|
+
req.once('end', () => {
|
104
|
+
let res = Buffer.concat(chunks, chunklen);
|
105
|
+
let data;
|
106
|
+
let err;
|
107
|
+
try {
|
108
|
+
data = JSON.parse(res.toString());
|
109
|
+
} catch (E) {
|
110
|
+
err = E;
|
111
|
+
}
|
112
|
+
if (err) {
|
113
|
+
return callback(err);
|
114
|
+
}
|
115
|
+
if (data.status !== 'success' || data.error) {
|
116
|
+
return callback(new Error(data.error || 'Request failed'));
|
117
|
+
}
|
118
|
+
delete data.status;
|
119
|
+
testAccount = data;
|
120
|
+
callback(null, testAccount);
|
121
|
+
});
|
122
|
+
|
123
|
+
return promise;
|
124
|
+
};
|
125
|
+
|
126
|
+
module.exports.getTestMessageUrl = function (info) {
|
127
|
+
if (!info || !info.response) {
|
128
|
+
return false;
|
129
|
+
}
|
130
|
+
|
131
|
+
let infoProps = new Map();
|
132
|
+
info.response.replace(/\[([^\]]+)\]$/, (m, props) => {
|
133
|
+
props.replace(/\b([A-Z0-9]+)=([^\s]+)/g, (m, key, value) => {
|
134
|
+
infoProps.set(key, value);
|
135
|
+
});
|
136
|
+
});
|
137
|
+
|
138
|
+
if (infoProps.has('STATUS') && infoProps.has('MSGID')) {
|
139
|
+
return (testAccount.web || ETHEREAL_WEB) + '/message/' + infoProps.get('MSGID');
|
140
|
+
}
|
141
|
+
|
142
|
+
return false;
|
143
|
+
};
|
@@ -0,0 +1,219 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const Transform = require('stream').Transform;
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Encodes a Buffer into a Quoted-Printable encoded string
|
7
|
+
*
|
8
|
+
* @param {Buffer} buffer Buffer to convert
|
9
|
+
* @returns {String} Quoted-Printable encoded string
|
10
|
+
*/
|
11
|
+
function encode(buffer) {
|
12
|
+
if (typeof buffer === 'string') {
|
13
|
+
buffer = Buffer.from(buffer, 'utf-8');
|
14
|
+
}
|
15
|
+
|
16
|
+
// usable characters that do not need encoding
|
17
|
+
let ranges = [
|
18
|
+
// https://tools.ietf.org/html/rfc2045#section-6.7
|
19
|
+
[0x09], // <TAB>
|
20
|
+
[0x0a], // <LF>
|
21
|
+
[0x0d], // <CR>
|
22
|
+
[0x20, 0x3c], // <SP>!"#$%&'()*+,-./0123456789:;
|
23
|
+
[0x3e, 0x7e] // >?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
|
24
|
+
];
|
25
|
+
let result = '';
|
26
|
+
let ord;
|
27
|
+
|
28
|
+
for (let i = 0, len = buffer.length; i < len; i++) {
|
29
|
+
ord = buffer[i];
|
30
|
+
// if the char is in allowed range, then keep as is, unless it is a WS in the end of a line
|
31
|
+
if (checkRanges(ord, ranges) && !((ord === 0x20 || ord === 0x09) && (i === len - 1 || buffer[i + 1] === 0x0a || buffer[i + 1] === 0x0d))) {
|
32
|
+
result += String.fromCharCode(ord);
|
33
|
+
continue;
|
34
|
+
}
|
35
|
+
result += '=' + (ord < 0x10 ? '0' : '') + ord.toString(16).toUpperCase();
|
36
|
+
}
|
37
|
+
|
38
|
+
return result;
|
39
|
+
}
|
40
|
+
|
41
|
+
/**
|
42
|
+
* Adds soft line breaks to a Quoted-Printable string
|
43
|
+
*
|
44
|
+
* @param {String} str Quoted-Printable encoded string that might need line wrapping
|
45
|
+
* @param {Number} [lineLength=76] Maximum allowed length for a line
|
46
|
+
* @returns {String} Soft-wrapped Quoted-Printable encoded string
|
47
|
+
*/
|
48
|
+
function wrap(str, lineLength) {
|
49
|
+
str = (str || '').toString();
|
50
|
+
lineLength = lineLength || 76;
|
51
|
+
|
52
|
+
if (str.length <= lineLength) {
|
53
|
+
return str;
|
54
|
+
}
|
55
|
+
|
56
|
+
let pos = 0;
|
57
|
+
let len = str.length;
|
58
|
+
let match, code, line;
|
59
|
+
let lineMargin = Math.floor(lineLength / 3);
|
60
|
+
let result = '';
|
61
|
+
|
62
|
+
// insert soft linebreaks where needed
|
63
|
+
while (pos < len) {
|
64
|
+
line = str.substr(pos, lineLength);
|
65
|
+
if ((match = line.match(/\r\n/))) {
|
66
|
+
line = line.substr(0, match.index + match[0].length);
|
67
|
+
result += line;
|
68
|
+
pos += line.length;
|
69
|
+
continue;
|
70
|
+
}
|
71
|
+
|
72
|
+
if (line.substr(-1) === '\n') {
|
73
|
+
// nothing to change here
|
74
|
+
result += line;
|
75
|
+
pos += line.length;
|
76
|
+
continue;
|
77
|
+
} else if ((match = line.substr(-lineMargin).match(/\n.*?$/))) {
|
78
|
+
// truncate to nearest line break
|
79
|
+
line = line.substr(0, line.length - (match[0].length - 1));
|
80
|
+
result += line;
|
81
|
+
pos += line.length;
|
82
|
+
continue;
|
83
|
+
} else if (line.length > lineLength - lineMargin && (match = line.substr(-lineMargin).match(/[ \t.,!?][^ \t.,!?]*$/))) {
|
84
|
+
// truncate to nearest space
|
85
|
+
line = line.substr(0, line.length - (match[0].length - 1));
|
86
|
+
} else if (line.match(/[=][\da-f]{0,2}$/i)) {
|
87
|
+
// push incomplete encoding sequences to the next line
|
88
|
+
if ((match = line.match(/[=][\da-f]{0,1}$/i))) {
|
89
|
+
line = line.substr(0, line.length - match[0].length);
|
90
|
+
}
|
91
|
+
|
92
|
+
// ensure that utf-8 sequences are not split
|
93
|
+
while (line.length > 3 && line.length < len - pos && !line.match(/^(?:=[\da-f]{2}){1,4}$/i) && (match = line.match(/[=][\da-f]{2}$/gi))) {
|
94
|
+
code = parseInt(match[0].substr(1, 2), 16);
|
95
|
+
if (code < 128) {
|
96
|
+
break;
|
97
|
+
}
|
98
|
+
|
99
|
+
line = line.substr(0, line.length - 3);
|
100
|
+
|
101
|
+
if (code >= 0xc0) {
|
102
|
+
break;
|
103
|
+
}
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
if (pos + line.length < len && line.substr(-1) !== '\n') {
|
108
|
+
if (line.length === lineLength && line.match(/[=][\da-f]{2}$/i)) {
|
109
|
+
line = line.substr(0, line.length - 3);
|
110
|
+
} else if (line.length === lineLength) {
|
111
|
+
line = line.substr(0, line.length - 1);
|
112
|
+
}
|
113
|
+
pos += line.length;
|
114
|
+
line += '=\r\n';
|
115
|
+
} else {
|
116
|
+
pos += line.length;
|
117
|
+
}
|
118
|
+
|
119
|
+
result += line;
|
120
|
+
}
|
121
|
+
|
122
|
+
return result;
|
123
|
+
}
|
124
|
+
|
125
|
+
/**
|
126
|
+
* Helper function to check if a number is inside provided ranges
|
127
|
+
*
|
128
|
+
* @param {Number} nr Number to check for
|
129
|
+
* @param {Array} ranges An Array of allowed values
|
130
|
+
* @returns {Boolean} True if the value was found inside allowed ranges, false otherwise
|
131
|
+
*/
|
132
|
+
function checkRanges(nr, ranges) {
|
133
|
+
for (let i = ranges.length - 1; i >= 0; i--) {
|
134
|
+
if (!ranges[i].length) {
|
135
|
+
continue;
|
136
|
+
}
|
137
|
+
if (ranges[i].length === 1 && nr === ranges[i][0]) {
|
138
|
+
return true;
|
139
|
+
}
|
140
|
+
if (ranges[i].length === 2 && nr >= ranges[i][0] && nr <= ranges[i][1]) {
|
141
|
+
return true;
|
142
|
+
}
|
143
|
+
}
|
144
|
+
return false;
|
145
|
+
}
|
146
|
+
|
147
|
+
/**
|
148
|
+
* Creates a transform stream for encoding data to Quoted-Printable encoding
|
149
|
+
*
|
150
|
+
* @constructor
|
151
|
+
* @param {Object} options Stream options
|
152
|
+
* @param {Number} [options.lineLength=76] Maximum length for lines, set to false to disable wrapping
|
153
|
+
*/
|
154
|
+
class Encoder extends Transform {
|
155
|
+
constructor(options) {
|
156
|
+
super();
|
157
|
+
|
158
|
+
// init Transform
|
159
|
+
this.options = options || {};
|
160
|
+
|
161
|
+
if (this.options.lineLength !== false) {
|
162
|
+
this.options.lineLength = this.options.lineLength || 76;
|
163
|
+
}
|
164
|
+
|
165
|
+
this._curLine = '';
|
166
|
+
|
167
|
+
this.inputBytes = 0;
|
168
|
+
this.outputBytes = 0;
|
169
|
+
}
|
170
|
+
|
171
|
+
_transform(chunk, encoding, done) {
|
172
|
+
let qp;
|
173
|
+
|
174
|
+
if (encoding !== 'buffer') {
|
175
|
+
chunk = Buffer.from(chunk, encoding);
|
176
|
+
}
|
177
|
+
|
178
|
+
if (!chunk || !chunk.length) {
|
179
|
+
return done();
|
180
|
+
}
|
181
|
+
|
182
|
+
this.inputBytes += chunk.length;
|
183
|
+
|
184
|
+
if (this.options.lineLength) {
|
185
|
+
qp = this._curLine + encode(chunk);
|
186
|
+
qp = wrap(qp, this.options.lineLength);
|
187
|
+
qp = qp.replace(/(^|\n)([^\n]*)$/, (match, lineBreak, lastLine) => {
|
188
|
+
this._curLine = lastLine;
|
189
|
+
return lineBreak;
|
190
|
+
});
|
191
|
+
|
192
|
+
if (qp) {
|
193
|
+
this.outputBytes += qp.length;
|
194
|
+
this.push(qp);
|
195
|
+
}
|
196
|
+
} else {
|
197
|
+
qp = encode(chunk);
|
198
|
+
this.outputBytes += qp.length;
|
199
|
+
this.push(qp, 'ascii');
|
200
|
+
}
|
201
|
+
|
202
|
+
done();
|
203
|
+
}
|
204
|
+
|
205
|
+
_flush(done) {
|
206
|
+
if (this._curLine) {
|
207
|
+
this.outputBytes += this._curLine.length;
|
208
|
+
this.push(this._curLine, 'ascii');
|
209
|
+
}
|
210
|
+
done();
|
211
|
+
}
|
212
|
+
}
|
213
|
+
|
214
|
+
// expose to the world
|
215
|
+
module.exports = {
|
216
|
+
encode,
|
217
|
+
wrap,
|
218
|
+
Encoder
|
219
|
+
};
|
@@ -0,0 +1,210 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const spawn = require('child_process').spawn;
|
4
|
+
const packageData = require('../../package.json');
|
5
|
+
const shared = require('../shared');
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Generates a Transport object for Sendmail
|
9
|
+
*
|
10
|
+
* Possible options can be the following:
|
11
|
+
*
|
12
|
+
* * **path** optional path to sendmail binary
|
13
|
+
* * **newline** either 'windows' or 'unix'
|
14
|
+
* * **args** an array of arguments for the sendmail binary
|
15
|
+
*
|
16
|
+
* @constructor
|
17
|
+
* @param {Object} optional config parameter for Sendmail
|
18
|
+
*/
|
19
|
+
class SendmailTransport {
|
20
|
+
constructor(options) {
|
21
|
+
options = options || {};
|
22
|
+
|
23
|
+
// use a reference to spawn for mocking purposes
|
24
|
+
this._spawn = spawn;
|
25
|
+
|
26
|
+
this.options = options || {};
|
27
|
+
|
28
|
+
this.name = 'Sendmail';
|
29
|
+
this.version = packageData.version;
|
30
|
+
|
31
|
+
this.path = 'sendmail';
|
32
|
+
this.args = false;
|
33
|
+
this.winbreak = false;
|
34
|
+
|
35
|
+
this.logger = shared.getLogger(this.options, {
|
36
|
+
component: this.options.component || 'sendmail'
|
37
|
+
});
|
38
|
+
|
39
|
+
if (options) {
|
40
|
+
if (typeof options === 'string') {
|
41
|
+
this.path = options;
|
42
|
+
} else if (typeof options === 'object') {
|
43
|
+
if (options.path) {
|
44
|
+
this.path = options.path;
|
45
|
+
}
|
46
|
+
if (Array.isArray(options.args)) {
|
47
|
+
this.args = options.args;
|
48
|
+
}
|
49
|
+
this.winbreak = ['win', 'windows', 'dos', '\r\n'].includes((options.newline || '').toString().toLowerCase());
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
/**
|
55
|
+
* <p>Compiles a mailcomposer message and forwards it to handler that sends it.</p>
|
56
|
+
*
|
57
|
+
* @param {Object} emailMessage MailComposer object
|
58
|
+
* @param {Function} callback Callback function to run when the sending is completed
|
59
|
+
*/
|
60
|
+
send(mail, done) {
|
61
|
+
// Sendmail strips this header line by itself
|
62
|
+
mail.message.keepBcc = true;
|
63
|
+
|
64
|
+
let envelope = mail.data.envelope || mail.message.getEnvelope();
|
65
|
+
let messageId = mail.message.messageId();
|
66
|
+
let args;
|
67
|
+
let sendmail;
|
68
|
+
let returned;
|
69
|
+
|
70
|
+
const hasInvalidAddresses = []
|
71
|
+
.concat(envelope.from || [])
|
72
|
+
.concat(envelope.to || [])
|
73
|
+
.some(addr => /^-/.test(addr));
|
74
|
+
if (hasInvalidAddresses) {
|
75
|
+
return done(new Error('Can not send mail. Invalid envelope addresses.'));
|
76
|
+
}
|
77
|
+
|
78
|
+
if (this.args) {
|
79
|
+
// force -i to keep single dots
|
80
|
+
args = ['-i'].concat(this.args).concat(envelope.to);
|
81
|
+
} else {
|
82
|
+
args = ['-i'].concat(envelope.from ? ['-f', envelope.from] : []).concat(envelope.to);
|
83
|
+
}
|
84
|
+
|
85
|
+
let callback = err => {
|
86
|
+
if (returned) {
|
87
|
+
// ignore any additional responses, already done
|
88
|
+
return;
|
89
|
+
}
|
90
|
+
returned = true;
|
91
|
+
if (typeof done === 'function') {
|
92
|
+
if (err) {
|
93
|
+
return done(err);
|
94
|
+
} else {
|
95
|
+
return done(null, {
|
96
|
+
envelope: mail.data.envelope || mail.message.getEnvelope(),
|
97
|
+
messageId,
|
98
|
+
response: 'Messages queued for delivery'
|
99
|
+
});
|
100
|
+
}
|
101
|
+
}
|
102
|
+
};
|
103
|
+
|
104
|
+
try {
|
105
|
+
sendmail = this._spawn(this.path, args);
|
106
|
+
} catch (E) {
|
107
|
+
this.logger.error(
|
108
|
+
{
|
109
|
+
err: E,
|
110
|
+
tnx: 'spawn',
|
111
|
+
messageId
|
112
|
+
},
|
113
|
+
'Error occurred while spawning sendmail. %s',
|
114
|
+
E.message
|
115
|
+
);
|
116
|
+
return callback(E);
|
117
|
+
}
|
118
|
+
|
119
|
+
if (sendmail) {
|
120
|
+
sendmail.on('error', err => {
|
121
|
+
this.logger.error(
|
122
|
+
{
|
123
|
+
err,
|
124
|
+
tnx: 'spawn',
|
125
|
+
messageId
|
126
|
+
},
|
127
|
+
'Error occurred when sending message %s. %s',
|
128
|
+
messageId,
|
129
|
+
err.message
|
130
|
+
);
|
131
|
+
callback(err);
|
132
|
+
});
|
133
|
+
|
134
|
+
sendmail.once('exit', code => {
|
135
|
+
if (!code) {
|
136
|
+
return callback();
|
137
|
+
}
|
138
|
+
let err;
|
139
|
+
if (code === 127) {
|
140
|
+
err = new Error('Sendmail command not found, process exited with code ' + code);
|
141
|
+
} else {
|
142
|
+
err = new Error('Sendmail exited with code ' + code);
|
143
|
+
}
|
144
|
+
|
145
|
+
this.logger.error(
|
146
|
+
{
|
147
|
+
err,
|
148
|
+
tnx: 'stdin',
|
149
|
+
messageId
|
150
|
+
},
|
151
|
+
'Error sending message %s to sendmail. %s',
|
152
|
+
messageId,
|
153
|
+
err.message
|
154
|
+
);
|
155
|
+
callback(err);
|
156
|
+
});
|
157
|
+
sendmail.once('close', callback);
|
158
|
+
|
159
|
+
sendmail.stdin.on('error', err => {
|
160
|
+
this.logger.error(
|
161
|
+
{
|
162
|
+
err,
|
163
|
+
tnx: 'stdin',
|
164
|
+
messageId
|
165
|
+
},
|
166
|
+
'Error occurred when piping message %s to sendmail. %s',
|
167
|
+
messageId,
|
168
|
+
err.message
|
169
|
+
);
|
170
|
+
callback(err);
|
171
|
+
});
|
172
|
+
|
173
|
+
let recipients = [].concat(envelope.to || []);
|
174
|
+
if (recipients.length > 3) {
|
175
|
+
recipients.push('...and ' + recipients.splice(2).length + ' more');
|
176
|
+
}
|
177
|
+
this.logger.info(
|
178
|
+
{
|
179
|
+
tnx: 'send',
|
180
|
+
messageId
|
181
|
+
},
|
182
|
+
'Sending message %s to <%s>',
|
183
|
+
messageId,
|
184
|
+
recipients.join(', ')
|
185
|
+
);
|
186
|
+
|
187
|
+
let sourceStream = mail.message.createReadStream();
|
188
|
+
sourceStream.once('error', err => {
|
189
|
+
this.logger.error(
|
190
|
+
{
|
191
|
+
err,
|
192
|
+
tnx: 'stdin',
|
193
|
+
messageId
|
194
|
+
},
|
195
|
+
'Error occurred when generating message %s. %s',
|
196
|
+
messageId,
|
197
|
+
err.message
|
198
|
+
);
|
199
|
+
sendmail.kill('SIGINT'); // do not deliver the message
|
200
|
+
callback(err);
|
201
|
+
});
|
202
|
+
|
203
|
+
sourceStream.pipe(sendmail.stdin);
|
204
|
+
} else {
|
205
|
+
return callback(new Error('sendmail was not found'));
|
206
|
+
}
|
207
|
+
}
|
208
|
+
}
|
209
|
+
|
210
|
+
module.exports = SendmailTransport;
|