|
|
|
var tls = require('tls');
|
|
|
|
|
|
|
|
exports.MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
|
|
|
|
'Oct', 'Nov', 'Dec'];
|
|
|
|
|
|
|
|
exports.setSecure = function(tcpSocket) {
|
|
|
|
var pair = tls.createSecurePair(),
|
|
|
|
cleartext;
|
|
|
|
|
|
|
|
pair.encrypted.pipe(tcpSocket);
|
|
|
|
tcpSocket.pipe(pair.encrypted);
|
|
|
|
pair.fd = tcpSocket.fd;
|
|
|
|
|
|
|
|
cleartext = pair.cleartext;
|
|
|
|
cleartext.socket = tcpSocket;
|
|
|
|
cleartext.encrypted = pair.encrypted;
|
|
|
|
|
|
|
|
function onerror(e) {
|
|
|
|
if (cleartext._controlReleased)
|
|
|
|
cleartext.emit('error', e);
|
|
|
|
}
|
|
|
|
|
|
|
|
function onclose() {
|
|
|
|
tcpSocket.removeListener('error', onerror);
|
|
|
|
tcpSocket.removeListener('close', onclose);
|
|
|
|
}
|
|
|
|
|
|
|
|
tcpSocket.on('error', onerror);
|
|
|
|
tcpSocket.on('close', onclose);
|
|
|
|
|
|
|
|
pair.on('secure', function() {
|
|
|
|
process.nextTick(function() { cleartext.socket.emit('secure'); });
|
|
|
|
});
|
|
|
|
|
|
|
|
cleartext._controlReleased = true;
|
|
|
|
return cleartext;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adopted from jquery's extend method. Under the terms of MIT License.
|
|
|
|
*
|
|
|
|
* http://code.jquery.com/jquery-1.4.2.js
|
|
|
|
*
|
|
|
|
* Modified by Brian White to use Array.isArray instead of the custom isArray
|
|
|
|
* method
|
|
|
|
*/
|
|
|
|
exports.extend = function() {
|
|
|
|
// copy reference to target object
|
|
|
|
var target = arguments[0] || {},
|
|
|
|
i = 1,
|
|
|
|
length = arguments.length,
|
|
|
|
deep = false,
|
|
|
|
options,
|
|
|
|
name,
|
|
|
|
src,
|
|
|
|
copy;
|
|
|
|
|
|
|
|
// Handle a deep copy situation
|
|
|
|
if (typeof target === "boolean") {
|
|
|
|
deep = target;
|
|
|
|
target = arguments[1] || {};
|
|
|
|
// skip the boolean and the target
|
|
|
|
i = 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle case when target is a string or something (possible in deep copy)
|
|
|
|
if (typeof target !== "object" && !typeof target === 'function')
|
|
|
|
target = {};
|
|
|
|
|
|
|
|
var isPlainObject = function(obj) {
|
|
|
|
// Must be an Object.
|
|
|
|
// Because of IE, we also have to check the presence of the constructor
|
|
|
|
// property.
|
|
|
|
// Make sure that DOM nodes and window objects don't pass through, as well
|
|
|
|
if (!obj || toString.call(obj) !== "[object Object]" || obj.nodeType
|
|
|
|
|| obj.setInterval)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
var has_own_constructor = hasOwnProperty.call(obj, "constructor");
|
|
|
|
var has_is_prop_of_method = hasOwnProperty.call(obj.constructor.prototype,
|
|
|
|
"isPrototypeOf");
|
|
|
|
// Not own constructor property must be Object
|
|
|
|
if (obj.constructor && !has_own_constructor && !has_is_prop_of_method)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Own properties are enumerated firstly, so to speed up,
|
|
|
|
// if last one is own, then all properties are own.
|
|
|
|
|
|
|
|
var last_key;
|
|
|
|
for (var key in obj)
|
|
|
|
last_key = key;
|
|
|
|
|
|
|
|
return last_key === undefined || hasOwnProperty.call(obj, last_key);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
for (; i < length; i++) {
|
|
|
|
// Only deal with non-null/undefined values
|
|
|
|
if ((options = arguments[i]) !== null) {
|
|
|
|
// Extend the base object
|
|
|
|
for (name in options) {
|
|
|
|
src = target[name];
|
|
|
|
copy = options[name];
|
|
|
|
|
|
|
|
// Prevent never-ending loop
|
|
|
|
if (target === copy)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Recurse if we're merging object literal values or arrays
|
|
|
|
if (deep && copy && (isPlainObject(copy) || Array.isArray(copy))) {
|
|
|
|
var clone = src && (isPlainObject(src) || Array.isArray(src)
|
|
|
|
? src : (Array.isArray(copy) ? [] : {}));
|
|
|
|
|
|
|
|
// Never move original objects, clone them
|
|
|
|
target[name] = exports.extend(deep, clone, copy);
|
|
|
|
|
|
|
|
// Don't bring in undefined values
|
|
|
|
} else if (copy !== undefined)
|
|
|
|
target[name] = copy;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the modified object
|
|
|
|
return target;
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.bufferAppend = function(buf1, buf2) {
|
|
|
|
var newBuf = new Buffer(buf1.length + buf2.length);
|
|
|
|
buf1.copy(newBuf, 0, 0);
|
|
|
|
if (Buffer.isBuffer(buf2))
|
|
|
|
buf2.copy(newBuf, buf1.length, 0);
|
|
|
|
else if (Array.isArray(buf2)) {
|
|
|
|
for (var i=buf1.length, len=buf2.length; i<len; i++)
|
|
|
|
newBuf[i] = buf2[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
return newBuf;
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.bufferSplit = function(buf, str) {
|
|
|
|
if ((typeof str !== 'string' && !Array.isArray(str))
|
|
|
|
|| str.length === 0 || str.length > buf.length)
|
|
|
|
return [buf];
|
|
|
|
var search = !Array.isArray(str)
|
|
|
|
? str.split('').map(function(el) { return el.charCodeAt(0); })
|
|
|
|
: str,
|
|
|
|
searchLen = search.length,
|
|
|
|
ret = [], pos, start = 0;
|
|
|
|
|
|
|
|
while ((pos = exports.bufferIndexOf(buf, search, start)) > -1) {
|
|
|
|
ret.push(buf.slice(start, pos));
|
|
|
|
start = pos + searchLen;
|
|
|
|
}
|
|
|
|
if (!ret.length)
|
|
|
|
ret = [buf];
|
|
|
|
else if (start < buf.length)
|
|
|
|
ret.push(buf.slice(start));
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.bufferIndexOf = function(buf, str, start) {
|
|
|
|
if (str.length > buf.length)
|
|
|
|
return -1;
|
|
|
|
var search = !Array.isArray(str)
|
|
|
|
? str.split('').map(function(el) { return el.charCodeAt(0); })
|
|
|
|
: str,
|
|
|
|
searchLen = search.length,
|
|
|
|
ret = -1, i, j, len;
|
|
|
|
for (i=start||0,len=buf.length; i<len; ++i) {
|
|
|
|
if (buf[i] === search[0] && (len-i) >= searchLen) {
|
|
|
|
if (searchLen > 1) {
|
|
|
|
for (j=1; j<searchLen; ++j) {
|
|
|
|
if (buf[i+j] !== search[j])
|
|
|
|
break;
|
|
|
|
else if (j === searchLen - 1) {
|
|
|
|
ret = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
ret = i;
|
|
|
|
if (ret > -1)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.explode = function(str, delimiter, limit) {
|
|
|
|
if (arguments.length < 2 || arguments[0] === undefined
|
|
|
|
|| arguments[1] === undefined
|
|
|
|
|| !delimiter || delimiter === '' || typeof delimiter === 'function'
|
|
|
|
|| typeof delimiter === 'object')
|
|
|
|
return false;
|
|
|
|
|
|
|
|
delimiter = (delimiter === true ? '1' : delimiter.toString());
|
|
|
|
|
|
|
|
if (!limit || limit === 0)
|
|
|
|
return str.split(delimiter);
|
|
|
|
else if (limit < 0)
|
|
|
|
return false;
|
|
|
|
else if (limit > 0) {
|
|
|
|
var splitted = str.split(delimiter);
|
|
|
|
var partA = splitted.splice(0, limit - 1);
|
|
|
|
var partB = splitted.join(delimiter);
|
|
|
|
partA.push(partB);
|
|
|
|
return partA;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.isNotEmpty = function(str) {
|
|
|
|
return str.trim().length > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.escape = function(str) {
|
|
|
|
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.unescape = function(str) {
|
|
|
|
return str.replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.buildSearchQuery = function(options, extensions, isOrChild) {
|
|
|
|
var searchargs = '';
|
|
|
|
for (var i=0,len=options.length; i<len; i++) {
|
|
|
|
var criteria = (isOrChild ? options : options[i]),
|
|
|
|
args = null,
|
|
|
|
modifier = (isOrChild ? '' : ' ');
|
|
|
|
if (typeof criteria === 'string')
|
|
|
|
criteria = criteria.toUpperCase();
|
|
|
|
else if (Array.isArray(criteria)) {
|
|
|
|
if (criteria.length > 1)
|
|
|
|
args = criteria.slice(1);
|
|
|
|
if (criteria.length > 0)
|
|
|
|
criteria = criteria[0].toUpperCase();
|
|
|
|
} else
|
|
|
|
throw new Error('Unexpected search option data type. '
|
|
|
|
+ 'Expected string or array. Got: ' + typeof criteria);
|
|
|
|
if (criteria === 'OR') {
|
|
|
|
if (args.length !== 2)
|
|
|
|
throw new Error('OR must have exactly two arguments');
|
|
|
|
searchargs += ' OR (';
|
|
|
|
searchargs += buildSearchQuery(args[0], extensions, true);
|
|
|
|
searchargs += ') (';
|
|
|
|
searchargs += buildSearchQuery(args[1], extensions, true);
|
|
|
|
searchargs += ')';
|
|
|
|
} else {
|
|
|
|
if (criteria[0] === '!') {
|
|
|
|
modifier += 'NOT ';
|
|
|
|
criteria = criteria.substr(1);
|
|
|
|
}
|
|
|
|
switch(criteria) {
|
|
|
|
// -- Standard criteria --
|
|
|
|
case 'ALL':
|
|
|
|
case 'ANSWERED':
|
|
|
|
case 'DELETED':
|
|
|
|
case 'DRAFT':
|
|
|
|
case 'FLAGGED':
|
|
|
|
case 'NEW':
|
|
|
|
case 'SEEN':
|
|
|
|
case 'RECENT':
|
|
|
|
case 'OLD':
|
|
|
|
case 'UNANSWERED':
|
|
|
|
case 'UNDELETED':
|
|
|
|
case 'UNDRAFT':
|
|
|
|
case 'UNFLAGGED':
|
|
|
|
case 'UNSEEN':
|
|
|
|
searchargs += modifier + criteria;
|
|
|
|
break;
|
|
|
|
case 'BCC':
|
|
|
|
case 'BODY':
|
|
|
|
case 'CC':
|
|
|
|
case 'FROM':
|
|
|
|
case 'SUBJECT':
|
|
|
|
case 'TEXT':
|
|
|
|
case 'TO':
|
|
|
|
if (!args || args.length !== 1)
|
|
|
|
throw new Error('Incorrect number of arguments for search option: '
|
|
|
|
+ criteria);
|
|
|
|
searchargs += modifier + criteria + ' "' + escape(''+args[0]) + '"';
|
|
|
|
break;
|
|
|
|
case 'BEFORE':
|
|
|
|
case 'ON':
|
|
|
|
case 'SENTBEFORE':
|
|
|
|
case 'SENTON':
|
|
|
|
case 'SENTSINCE':
|
|
|
|
case 'SINCE':
|
|
|
|
if (!args || args.length !== 1)
|
|
|
|
throw new Error('Incorrect number of arguments for search option: '
|
|
|
|
+ criteria);
|
|
|
|
else if (!(args[0] instanceof Date)) {
|
|
|
|
if ((args[0] = new Date(args[0])).toString() === 'Invalid Date')
|
|
|
|
throw new Error('Search option argument must be a Date object'
|
|
|
|
+ ' or a parseable date string');
|
|
|
|
}
|
|
|
|
searchargs += modifier + criteria + ' ' + args[0].getDate() + '-'
|
|
|
|
+ exports.MONTHS[args[0].getMonth()] + '-'
|
|
|
|
+ args[0].getFullYear();
|
|
|
|
break;
|
|
|
|
case 'KEYWORD':
|
|
|
|
case 'UNKEYWORD':
|
|
|
|
if (!args || args.length !== 1)
|
|
|
|
throw new Error('Incorrect number of arguments for search option: '
|
|
|
|
+ criteria);
|
|
|
|
searchargs += modifier + criteria + ' ' + args[0];
|
|
|
|
break;
|
|
|
|
case 'LARGER':
|
|
|
|
case 'SMALLER':
|
|
|
|
if (!args || args.length !== 1)
|
|
|
|
throw new Error('Incorrect number of arguments for search option: '
|
|
|
|
+ criteria);
|
|
|
|
var num = parseInt(args[0]);
|
|
|
|
if (isNaN(num))
|
|
|
|
throw new Error('Search option argument must be a number');
|
|
|
|
searchargs += modifier + criteria + ' ' + args[0];
|
|
|
|
break;
|
|
|
|
case 'HEADER':
|
|
|
|
if (!args || args.length !== 2)
|
|
|
|
throw new Error('Incorrect number of arguments for search option: '
|
|
|
|
+ criteria);
|
|
|
|
searchargs += modifier + criteria + ' "' + escape(''+args[0]) + '" "'
|
|
|
|
+ escape(''+args[1]) + '"';
|
|
|
|
break;
|
|
|
|
case 'UID':
|
|
|
|
if (!args)
|
|
|
|
throw new Error('Incorrect number of arguments for search option: '
|
|
|
|
+ criteria);
|
|
|
|
exports.validateUIDList(args);
|
|
|
|
searchargs += modifier + criteria + ' ' + args.join(',');
|
|
|
|
break;
|
|
|
|
// -- Extensions criteria --
|
|
|
|
case 'X-GM-MSGID': // Gmail unique message ID
|
|
|
|
case 'X-GM-THRID': // Gmail thread ID
|
|
|
|
if (extensions.indexOf('X-GM-EXT-1') === -1)
|
|
|
|
throw new Error('IMAP extension not available: ' + criteria);
|
|
|
|
var val;
|
|
|
|
if (!args || args.length !== 1)
|
|
|
|
throw new Error('Incorrect number of arguments for search option: '
|
|
|
|
+ criteria);
|
|
|
|
else {
|
|
|
|
val = ''+args[0];
|
|
|
|
if (!(/^\d+$/.test(args[0])))
|
|
|
|
throw new Error('Invalid value');
|
|
|
|
}
|
|
|
|
searchargs += modifier + criteria + ' ' + val;
|
|
|
|
break;
|
|
|
|
case 'X-GM-RAW': // Gmail search syntax
|
|
|
|
if (extensions.indexOf('X-GM-EXT-1') === -1)
|
|
|
|
throw new Error('IMAP extension not available: ' + criteria);
|
|
|
|
if (!args || args.length !== 1)
|
|
|
|
throw new Error('Incorrect number of arguments for search option: '
|
|
|
|
+ criteria);
|
|
|
|
searchargs += modifier + criteria + ' "' + escape(''+args[0]) + '"';
|
|
|
|
break;
|
|
|
|
case 'X-GM-LABELS': // Gmail labels
|
|
|
|
if (extensions.indexOf('X-GM-EXT-1') === -1)
|
|
|
|
throw new Error('IMAP extension not available: ' + criteria);
|
|
|
|
if (!args || args.length !== 1)
|
|
|
|
throw new Error('Incorrect number of arguments for search option: '
|
|
|
|
+ criteria);
|
|
|
|
searchargs += modifier + criteria + ' ' + args[0];
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error('Unexpected search option: ' + criteria);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (isOrChild)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return searchargs;
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.validateUIDList = function(uids) {
|
|
|
|
for (var i=0,len=uids.length,intval; i<len; i++) {
|
|
|
|
if (typeof uids[i] === 'string') {
|
|
|
|
if (uids[i] === '*' || uids[i] === '*:*') {
|
|
|
|
if (len > 1)
|
|
|
|
uids = ['*'];
|
|
|
|
break;
|
|
|
|
} else if (/^(?:[\d]+|\*):(?:[\d]+|\*)$/.test(uids[i]))
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
intval = parseInt(''+uids[i]);
|
|
|
|
if (isNaN(intval)) {
|
|
|
|
throw new Error('Message ID/number must be an integer, "*", or a range: '
|
|
|
|
+ uids[i]);
|
|
|
|
} else if (typeof uids[i] !== 'number')
|
|
|
|
uids[i] = intval;
|
|
|
|
}
|
|
|
|
}
|