mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-01-07 16:38:17 +08:00
263 lines
7.6 KiB
JavaScript
263 lines
7.6 KiB
JavaScript
/* eslint new-cap: 0 */
|
|
|
|
'use strict';
|
|
|
|
const imapFormalSyntax = require('./imap-formal-syntax');
|
|
const streams = require('stream');
|
|
const PassThrough = streams.PassThrough;
|
|
const LengthLimiter = require('../length-limiter');
|
|
|
|
/**
|
|
* Compiles an input object into a streamed IMAP response
|
|
*/
|
|
module.exports = function(response, isLogging) {
|
|
let output = new PassThrough();
|
|
|
|
let resp = (response.tag || '') + (response.command ? ' ' + response.command : '');
|
|
let lr = resp; // this value is going to store last known `resp` state for later usage
|
|
|
|
let val, lastType;
|
|
|
|
let waiting = false;
|
|
let queue = [];
|
|
let ended = false;
|
|
|
|
let emit = function(stream, expectedLength, startFrom, maxLength) {
|
|
expectedLength = expectedLength || 0;
|
|
startFrom = startFrom || 0;
|
|
maxLength = maxLength || 0;
|
|
|
|
if (resp.length) {
|
|
queue.push(Buffer.from(resp, 'binary'));
|
|
lr = resp;
|
|
resp = '';
|
|
}
|
|
|
|
if (stream) {
|
|
queue.push({
|
|
type: 'stream',
|
|
stream,
|
|
expectedLength,
|
|
startFrom,
|
|
maxLength
|
|
});
|
|
}
|
|
|
|
if (waiting) {
|
|
return;
|
|
}
|
|
|
|
if (!queue.length) {
|
|
if (ended) {
|
|
output.end();
|
|
}
|
|
return;
|
|
}
|
|
|
|
let value = queue.shift();
|
|
if (value.type === 'stream') {
|
|
if (!value.expectedLength) {
|
|
return emit();
|
|
}
|
|
|
|
if (value.stream && value.stream.errored) {
|
|
let err = value.stream.errored;
|
|
value.stream.errored = false;
|
|
return output.emit('error', err);
|
|
}
|
|
|
|
waiting = true;
|
|
let expectedLength = value.maxLength ? Math.min(value.expectedLength, value.startFrom + value.maxLength) : value.expectedLength;
|
|
let startFrom = value.startFrom;
|
|
|
|
let limiter = new LengthLimiter(expectedLength, ' ', startFrom);
|
|
|
|
value.stream.pipe(limiter).pipe(output, {
|
|
end: false
|
|
});
|
|
|
|
// pass errors to output
|
|
value.stream.once('error', err => {
|
|
output.emit('error', err);
|
|
});
|
|
|
|
limiter.once('end', () => {
|
|
waiting = false;
|
|
return emit();
|
|
});
|
|
} else if (Buffer.isBuffer(value)) {
|
|
output.write(value);
|
|
return emit();
|
|
} else {
|
|
if (typeof value === 'number') {
|
|
value = value.toString();
|
|
} else if (typeof value !== 'string') {
|
|
value = (value || '').toString();
|
|
}
|
|
output.write(Buffer.from(value, 'binary'));
|
|
return emit();
|
|
}
|
|
};
|
|
|
|
let walk = function(node, callback) {
|
|
if (lastType === 'LITERAL' || (['(', '<', '['].indexOf((resp || lr).substr(-1)) < 0 && (resp || lr).length)) {
|
|
resp += ' ';
|
|
}
|
|
|
|
if (node && node.buffer && !Buffer.isBuffer(node)) {
|
|
// mongodb binary
|
|
node = node.buffer;
|
|
}
|
|
|
|
if (Array.isArray(node)) {
|
|
lastType = 'LIST';
|
|
resp += '(';
|
|
|
|
let pos = 0;
|
|
let next = () => {
|
|
if (pos >= node.length) {
|
|
resp += ')';
|
|
return setImmediate(callback);
|
|
}
|
|
walk(node[pos++], next);
|
|
};
|
|
|
|
return setImmediate(next);
|
|
}
|
|
|
|
if (!node && typeof node !== 'string' && typeof node !== 'number') {
|
|
resp += 'NIL';
|
|
return setImmediate(callback);
|
|
}
|
|
|
|
if (typeof node === 'string' || Buffer.isBuffer(node)) {
|
|
if (isLogging && node.length > 20) {
|
|
resp += '"(* ' + node.length + 'B string *)"';
|
|
} else {
|
|
resp += JSON.stringify(node.toString('binary'));
|
|
}
|
|
return setImmediate(callback);
|
|
}
|
|
|
|
if (typeof node === 'number') {
|
|
resp += Math.round(node) || 0; // Only integers allowed
|
|
return setImmediate(callback);
|
|
}
|
|
|
|
lastType = node.type;
|
|
|
|
if (isLogging && node.sensitive) {
|
|
resp += '"(* value hidden *)"';
|
|
return setImmediate(callback);
|
|
}
|
|
|
|
switch (node.type.toUpperCase()) {
|
|
case 'LITERAL': {
|
|
let nval = node.value;
|
|
|
|
if (typeof nval === 'number') {
|
|
nval = nval.toString();
|
|
}
|
|
|
|
let len;
|
|
|
|
if (nval && typeof nval.pipe === 'function') {
|
|
len = node.expectedLength || 0;
|
|
if (node.startFrom) {
|
|
len -= node.startFrom;
|
|
}
|
|
if (node.maxLength) {
|
|
len = Math.min(len, node.maxLength);
|
|
}
|
|
} else {
|
|
len = (nval || '').toString().length;
|
|
}
|
|
|
|
if (isLogging) {
|
|
resp += '"(* ' + len + 'B literal *)"';
|
|
} else {
|
|
resp += '{' + len + '}\r\n';
|
|
emit();
|
|
|
|
if (nval && typeof nval.pipe === 'function') {
|
|
//value is a stream object
|
|
emit(nval, node.expectedLength, node.startFrom, node.maxLength);
|
|
} else {
|
|
resp = (nval || '').toString('binary');
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 'STRING':
|
|
if (isLogging && node.value.length > 20) {
|
|
resp += '"(* ' + node.value.length + 'B string *)"';
|
|
} else {
|
|
resp += JSON.stringify((node.value || '').toString('binary'));
|
|
}
|
|
break;
|
|
case 'TEXT':
|
|
case 'SEQUENCE':
|
|
resp += (node.value || '').toString('binary');
|
|
break;
|
|
|
|
case 'NUMBER':
|
|
resp += node.value || 0;
|
|
break;
|
|
|
|
case 'ATOM':
|
|
case 'SECTION': {
|
|
val = (node.value || '').toString('binary');
|
|
|
|
if (imapFormalSyntax.verify(val.charAt(0) === '\\' ? val.substr(1) : val, imapFormalSyntax['ATOM-CHAR']()) >= 0) {
|
|
val = JSON.stringify(val);
|
|
}
|
|
|
|
resp += val;
|
|
|
|
let finalize = () => {
|
|
if (node.partial) {
|
|
resp += '<' + node.partial[0] + '>';
|
|
}
|
|
setImmediate(callback);
|
|
};
|
|
|
|
if (node.section) {
|
|
resp += '[';
|
|
|
|
let pos = 0;
|
|
let next = () => {
|
|
if (pos >= node.section.length) {
|
|
resp += ']';
|
|
return setImmediate(finalize);
|
|
}
|
|
walk(node.section[pos++], next);
|
|
};
|
|
|
|
return setImmediate(next);
|
|
}
|
|
|
|
return finalize();
|
|
}
|
|
}
|
|
setImmediate(callback);
|
|
};
|
|
|
|
let finalize = () => {
|
|
ended = true;
|
|
emit();
|
|
};
|
|
let pos = 0;
|
|
let attribs = [].concat(response.attributes || []);
|
|
let next = () => {
|
|
if (pos >= attribs.length) {
|
|
return setImmediate(finalize);
|
|
}
|
|
walk(attribs[pos++], next);
|
|
};
|
|
setImmediate(next);
|
|
|
|
return output;
|
|
};
|
|
|
|
// expose for testing
|
|
module.exports.LengthLimiter = LengthLimiter;
|