commit
3ef5ad8f18
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,277 @@
|
||||
var utils = require('./imap.utilities');
|
||||
|
||||
exports.convStr = function(str) {
|
||||
if (str[0] === '"')
|
||||
return str.substring(1, str.length-1);
|
||||
else if (str === 'NIL')
|
||||
return null;
|
||||
else if (/^\d+$/.test(str)) {
|
||||
// some IMAP extensions utilize large (64-bit) integers, which JavaScript
|
||||
// can't handle natively, so we'll just keep it as a string if it's too big
|
||||
var val = parseInt(str, 10);
|
||||
return (val.toString() === str ? val : str);
|
||||
} else
|
||||
return str;
|
||||
}
|
||||
|
||||
exports.parseNamespaces = function(str, namespaces) {
|
||||
var result = exports.parseExpr(str);
|
||||
for (var grp=0; grp<3; ++grp) {
|
||||
if (Array.isArray(result[grp])) {
|
||||
var vals = [];
|
||||
for (var i=0,len=result[grp].length; i<len; ++i) {
|
||||
var val = { prefix: result[grp][i][0], delim: result[grp][i][1] };
|
||||
if (result[grp][i].length > 2) {
|
||||
// extension data
|
||||
val.extensions = [];
|
||||
for (var j=2,len2=result[grp][i].length; j<len2; j+=2) {
|
||||
val.extensions.push({
|
||||
name: result[grp][i][j],
|
||||
flags: result[grp][i][j+1]
|
||||
});
|
||||
}
|
||||
}
|
||||
vals.push(val);
|
||||
}
|
||||
if (grp === 0)
|
||||
namespaces.personal = vals;
|
||||
else if (grp === 1)
|
||||
namespaces.other = vals;
|
||||
else if (grp === 2)
|
||||
namespaces.shared = vals;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.parseFetch = function(str, fetchData) {
|
||||
var key, idxNext, result = exports.parseExpr(str);
|
||||
for (var i=0,len=result.length; i<len; i+=2) {
|
||||
if (result[i] === 'UID')
|
||||
fetchData.id = parseInt(result[i+1], 10);
|
||||
else if (result[i] === 'INTERNALDATE')
|
||||
fetchData.date = result[i+1];
|
||||
else if (result[i] === 'FLAGS')
|
||||
fetchData.flags = result[i+1].filter(utils.isNotEmpty);
|
||||
else if (result[i] === 'BODYSTRUCTURE')
|
||||
fetchData.structure = exports.parseBodyStructure(result[i+1]);
|
||||
else if (typeof result[i] === 'string') // simple extensions
|
||||
fetchData[result[i].toLowerCase()] = result[i+1];
|
||||
}
|
||||
}
|
||||
|
||||
exports.parseBodyStructure = function(cur, prefix, partID) {
|
||||
var ret = [];
|
||||
if (prefix === undefined) {
|
||||
var result = (Array.isArray(cur) ? cur : exports.parseExpr(cur));
|
||||
if (result.length)
|
||||
ret = exports.parseBodyStructure(result, '', 1);
|
||||
} else {
|
||||
var part, partLen = cur.length, next;
|
||||
if (Array.isArray(cur[0])) { // multipart
|
||||
next = -1;
|
||||
while (Array.isArray(cur[++next])) {
|
||||
ret.push(exports.parseBodyStructure(cur[next], prefix
|
||||
+ (prefix !== '' ? '.' : '')
|
||||
+ (partID++).toString(), 1));
|
||||
}
|
||||
part = { type: cur[next++].toLowerCase() };
|
||||
if (partLen > next) {
|
||||
if (Array.isArray(cur[next])) {
|
||||
part.params = {};
|
||||
for (var i=0,len=cur[next].length; i<len; i+=2)
|
||||
part.params[cur[next][i].toLowerCase()] = cur[next][i+1];
|
||||
} else
|
||||
part.params = cur[next];
|
||||
++next;
|
||||
}
|
||||
} else { // single part
|
||||
next = 7;
|
||||
if (typeof cur[1] === 'string') {
|
||||
part = {
|
||||
// the path identifier for this part, useful for fetching specific
|
||||
// parts of a message
|
||||
partID: (prefix !== '' ? prefix : '1'),
|
||||
|
||||
// required fields as per RFC 3501 -- null or otherwise
|
||||
type: cur[0].toLowerCase(), subtype: cur[1].toLowerCase(),
|
||||
params: null, id: cur[3], description: cur[4], encoding: cur[5],
|
||||
size: cur[6]
|
||||
}
|
||||
} else {
|
||||
// type information for malformed multipart body
|
||||
part = { type: cur[0].toLowerCase(), params: null };
|
||||
cur.splice(1, 0, null);
|
||||
++partLen;
|
||||
next = 2;
|
||||
}
|
||||
if (Array.isArray(cur[2])) {
|
||||
part.params = {};
|
||||
for (var i=0,len=cur[2].length; i<len; i+=2)
|
||||
part.params[cur[2][i].toLowerCase()] = cur[2][i+1];
|
||||
if (cur[1] === null)
|
||||
++next;
|
||||
}
|
||||
if (part.type === 'message' && part.subtype === 'rfc822') {
|
||||
// envelope
|
||||
if (partLen > next && Array.isArray(cur[next])) {
|
||||
part.envelope = {};
|
||||
for (var i=0,field,len=cur[next].length; i<len; ++i) {
|
||||
if (i === 0)
|
||||
part.envelope.date = cur[next][i];
|
||||
else if (i === 1)
|
||||
part.envelope.subject = cur[next][i];
|
||||
else if (i >= 2 && i <= 7) {
|
||||
var val = cur[next][i];
|
||||
if (Array.isArray(val)) {
|
||||
var addresses = [], inGroup = false, curGroup;
|
||||
for (var j=0,len2=val.length; j<len2; ++j) {
|
||||
if (val[j][3] === null) { // start group addresses
|
||||
inGroup = true;
|
||||
curGroup = {
|
||||
group: val[j][2],
|
||||
addresses: []
|
||||
};
|
||||
} else if (val[j][2] === null) { // end of group addresses
|
||||
inGroup = false;
|
||||
addresses.push(curGroup);
|
||||
} else { // regular user address
|
||||
var info = {
|
||||
name: val[j][0],
|
||||
mailbox: val[j][2],
|
||||
host: val[j][3]
|
||||
};
|
||||
if (inGroup)
|
||||
curGroup.addresses.push(info);
|
||||
else
|
||||
addresses.push(info);
|
||||
}
|
||||
}
|
||||
val = addresses;
|
||||
}
|
||||
if (i === 2)
|
||||
part.envelope.from = val;
|
||||
else if (i === 3)
|
||||
part.envelope.sender = val;
|
||||
else if (i === 4)
|
||||
part.envelope['reply-to'] = val;
|
||||
else if (i === 5)
|
||||
part.envelope.to = val;
|
||||
else if (i === 6)
|
||||
part.envelope.cc = val;
|
||||
else if (i === 7)
|
||||
part.envelope.bcc = val;
|
||||
} else if (i === 8)
|
||||
// message ID being replied to
|
||||
part.envelope['in-reply-to'] = cur[next][i];
|
||||
else if (i === 9)
|
||||
part.envelope['message-id'] = cur[next][i];
|
||||
else
|
||||
break;
|
||||
}
|
||||
} else
|
||||
part.envelope = null;
|
||||
++next;
|
||||
|
||||
// body
|
||||
if (partLen > next && Array.isArray(cur[next])) {
|
||||
part.body = exports.parseBodyStructure(cur[next], prefix
|
||||
+ (prefix !== '' ? '.' : '')
|
||||
+ (partID++).toString(), 1);
|
||||
} else
|
||||
part.body = null;
|
||||
++next;
|
||||
}
|
||||
if ((part.type === 'text'
|
||||
|| (part.type === 'message' && part.subtype === 'rfc822'))
|
||||
&& partLen > next)
|
||||
part.lines = cur[next++];
|
||||
if (typeof cur[1] === 'string' && partLen > next)
|
||||
part.md5 = cur[next++];
|
||||
}
|
||||
// add any extra fields that may or may not be omitted entirely
|
||||
exports.parseStructExtra(part, partLen, cur, next);
|
||||
ret.unshift(part);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
exports.parseStructExtra = function(part, partLen, cur, next) {
|
||||
if (partLen > next) {
|
||||
// disposition
|
||||
// null or a special k/v list with these kinds of values:
|
||||
// e.g.: ['Foo', null]
|
||||
// ['Foo', ['Bar', 'Baz']]
|
||||
// ['Foo', ['Bar', 'Baz', 'Bam', 'Pow']]
|
||||
var disposition = { type: null, params: null };
|
||||
if (Array.isArray(cur[next])) {
|
||||
disposition.type = cur[next][0];
|
||||
if (Array.isArray(cur[next][1])) {
|
||||
disposition.params = {};
|
||||
for (var i=0,len=cur[next][1].length; i<len; i+=2)
|
||||
disposition.params[cur[next][1][i].toLowerCase()] = cur[next][1][i+1];
|
||||
}
|
||||
} else if (cur[next] !== null)
|
||||
disposition.type = cur[next];
|
||||
|
||||
if (disposition.type === null)
|
||||
part.disposition = null;
|
||||
else
|
||||
part.disposition = disposition;
|
||||
|
||||
++next;
|
||||
}
|
||||
if (partLen > next) {
|
||||
// language can be a string or a list of one or more strings, so let's
|
||||
// make this more consistent ...
|
||||
if (cur[next] !== null)
|
||||
part.language = (Array.isArray(cur[next]) ? cur[next] : [cur[next]]);
|
||||
else
|
||||
part.language = null;
|
||||
++next;
|
||||
}
|
||||
if (partLen > next)
|
||||
part.location = cur[next++];
|
||||
if (partLen > next) {
|
||||
// extension stuff introduced by later RFCs
|
||||
// this can really be any value: a string, number, or (un)nested list
|
||||
// let's not parse it for now ...
|
||||
part.extensions = cur[next];
|
||||
}
|
||||
}
|
||||
|
||||
exports.parseExpr = function(o, result, start) {
|
||||
start = start || 0;
|
||||
var inQuote = false, lastPos = start - 1, isTop = false;
|
||||
if (!result)
|
||||
result = new Array();
|
||||
if (typeof o === 'string') {
|
||||
var state = new Object();
|
||||
state.str = o;
|
||||
o = state;
|
||||
isTop = true;
|
||||
}
|
||||
for (var i=start,len=o.str.length; i<len; ++i) {
|
||||
if (!inQuote) {
|
||||
if (o.str[i] === '"')
|
||||
inQuote = true;
|
||||
else if (o.str[i] === ' ' || o.str[i] === ')' || o.str[i] === ']') {
|
||||
if (i - (lastPos+1) > 0)
|
||||
result.push(exports.convStr(o.str.substring(lastPos+1, i)));
|
||||
if (o.str[i] === ')' || o.str[i] === ']')
|
||||
return i;
|
||||
lastPos = i;
|
||||
} else if (o.str[i] === '(' || o.str[i] === '[') {
|
||||
var innerResult = [];
|
||||
i = exports.parseExpr(o, innerResult, i+1);
|
||||
lastPos = i;
|
||||
result.push(innerResult);
|
||||
}
|
||||
} else if (o.str[i] === '"' &&
|
||||
(o.str[i-1] &&
|
||||
(o.str[i-1] !== '\\' || (o.str[i-2] && o.str[i-2] === '\\'))))
|
||||
inQuote = false;
|
||||
if (i+1 === len && len - (lastPos+1) > 0)
|
||||
result.push(exports.convStr(o.str.substring(lastPos+1)));
|
||||
}
|
||||
return (isTop ? result : start);
|
||||
}
|
@ -0,0 +1,394 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
var inherits = require('util').inherits,
|
||||
EventEmitter = require('events').EventEmitter;
|
||||
|
||||
var PARSE_HEADER_NAME = 0,
|
||||
PARSE_HEADER_VAL = 1,
|
||||
PARSE_BODY = 2;
|
||||
|
||||
var CR = 13,
|
||||
LF = 10,
|
||||
COLON = 58,
|
||||
SPACE = 32,
|
||||
TAB = 9,
|
||||
REGEXP_FOLD = /\r\n\s+/g;
|
||||
|
||||
var MIMEParser = module.exports = function() {
|
||||
this._state = PARSE_HEADER_NAME;
|
||||
this._hdrname = '';
|
||||
this._hdrval = '';
|
||||
this._sawCR = false;
|
||||
this._sawLF = false;
|
||||
this._needUnfold = false;
|
||||
};
|
||||
inherits(MIMEParser, EventEmitter);
|
||||
|
||||
MIMEParser.prototype.execute = function(b, start, end) {
|
||||
if (this._state == PARSE_BODY) {
|
||||
var chunk;
|
||||
if ((start === undefined && end === undefined)
|
||||
|| (start === 0 && end === b.length))
|
||||
chunk = b;
|
||||
else
|
||||
chunk = b.slice(start, end);
|
||||
return this.emit('data', chunk);
|
||||
}
|
||||
start || (start = 0);
|
||||
end || (end = b.length);
|
||||
|
||||
var i = start, finished = false;
|
||||
while (i < end) {
|
||||
if (this._state === PARSE_HEADER_NAME) {
|
||||
if (i > start)
|
||||
start = i;
|
||||
while (i < end) {
|
||||
if (b[i] === COLON) {
|
||||
this._state = PARSE_HEADER_VAL;
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
if (this._state === PARSE_HEADER_NAME)
|
||||
this._hdrname += b.toString('ascii', start, end);
|
||||
else if (finished) {
|
||||
this._hdrname += b.toString('ascii', start, (i < end ? i : end));
|
||||
finished = false;
|
||||
++i;
|
||||
}
|
||||
} else if (this._state === PARSE_HEADER_VAL) {
|
||||
if (i > start)
|
||||
start = i;
|
||||
while (i < end) {
|
||||
if (b[i] === CR) {
|
||||
if (!(this._sawCR && this._sawLF)) {
|
||||
this._sawCR = true;
|
||||
this._sawLF = false;
|
||||
}
|
||||
} else if (b[i] === LF && this._sawCR) {
|
||||
if (this._sawLF) {
|
||||
this._state = PARSE_BODY;
|
||||
this._sawCR = false;
|
||||
this._sawLF = false;
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
this._sawLF = true;
|
||||
} else {
|
||||
if (this._sawCR && this._sawLF) {
|
||||
if (b[i] !== SPACE && b[i] !== TAB) {
|
||||
this._state = PARSE_HEADER_NAME;
|
||||
this._sawCR = false;
|
||||
this._sawLF = false;
|
||||
finished = true;
|
||||
break;
|
||||
} else {
|
||||
this._needUnfold = true;
|
||||
// unfold
|
||||
/*this._hdrval += b.toString('ascii', start, (i < end ? i - 2 : end));
|
||||
start = i;*/
|
||||
}
|
||||
}
|
||||
this._sawCR = false;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
if (this._state === PARSE_HEADER_VAL)
|
||||
this._hdrval += b.toString('ascii', start, (i < end ? i : end));
|
||||
else if (finished) {
|
||||
this._hdrval += b.toString('ascii', start, (i < end ? i - 2 : end));
|
||||
if (this._needUnfold)
|
||||
this._hdrval = this._hdrval.replace(REGEXP_FOLD, ' ');
|
||||
this.emit('header', this._hdrname, this._hdrval.trim());
|
||||
this._hdrname = '';
|
||||
this._hdrval = '';
|
||||
this._needUnfold = false;
|
||||
finished = false;
|
||||
if (this._state === PARSE_BODY) {
|
||||
if ((i + 1) < end)
|
||||
this.emit('data', b.slice(i + 1, end));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
MIMEParser.prototype.finish = function() {
|
||||
this._state = PARSE_HEADER_NAME;
|
||||
this._hdrname = '';
|
||||
this._hdrval = '';
|
||||
this._sawCR = false;
|
||||
this._sawLF = false;
|
||||
this._needUnfold = false;
|
||||
};
|
@ -0,0 +1,236 @@
|
||||
var assert = require('assert');
|
||||
|
||||
var parseFetch = require('../lib/imap.parsers').parseFetch;
|
||||
|
||||
var tests = {
|
||||
simple: [
|
||||
['', {}],
|
||||
|
||||
['FLAGS (\\Seen) RFC822.SIZE 44827',
|
||||
{ flags: [ '\\Seen' ], 'rfc822.size': 44827 }],
|
||||
|
||||
['FLAGS (\\Seen) UID 4827313',
|
||||
{ flags: [ '\\Seen' ], id: 4827313 }],
|
||||
|
||||
['FLAGS (\\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700"\
|
||||
RFC822.SIZE 4286 ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)"\
|
||||
"IMAP4rev1 WG mtg summary and minutes"\
|
||||
(("Terry Gray" NIL "gray" "cac.washington.edu"))\
|
||||
(("Terry Gray" NIL "gray" "cac.washington.edu"))\
|
||||
(("Terry Gray" NIL "gray" "cac.washington.edu"))\
|
||||
((NIL NIL "imap" "cac.washington.edu"))\
|
||||
((NIL NIL "minutes" "CNRI.Reston.VA.US")\
|
||||
("John Klensin" NIL "KLENSIN" "MIT.EDU")) NIL NIL\
|
||||
"<B27397-0100000@cac.washington.edu>")\
|
||||
BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 3028\
|
||||
92)',
|
||||
{ flags: [ '\\Seen' ],
|
||||
date: '17-Jul-1996 02:44:25 -0700',
|
||||
'rfc822.size': 4286,
|
||||
envelope: [
|
||||
'Wed, 17 Jul 1996 02:23:25 -0700 (PDT)',
|
||||
'IMAP4rev1 WG mtg summary and minutes',
|
||||
[ [ 'Terry Gray', null, 'gray', 'cac.washington.edu' ] ],
|
||||
[ [ 'Terry Gray', null, 'gray', 'cac.washington.edu' ] ],
|
||||
[ [ 'Terry Gray', null, 'gray', 'cac.washington.edu' ] ],
|
||||
[ [ null, null, 'imap', 'cac.washington.edu' ] ],
|
||||
[ [ null, null, 'minutes', 'CNRI.Reston.VA.US' ],
|
||||
[ 'John Klensin', null, 'KLENSIN', 'MIT.EDU' ] ],
|
||||
null,
|
||||
null,
|
||||
'<B27397-0100000@cac.washington.edu>'
|
||||
],
|
||||
body: [
|
||||
'TEXT',
|
||||
'PLAIN',
|
||||
[ 'CHARSET', 'US-ASCII' ],
|
||||
null,
|
||||
null,
|
||||
'7BIT',
|
||||
3028,
|
||||
92
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
['BODYSTRUCTURE (("text" "plain" ("charset" "us-ascii") NIL NIL "quoted-printable" 370 19 NIL NIL NIL NIL)("application" "pdf" ("x-mac-hide-extension" "yes" "x-unix-mode" "0644" "name" "filename.pdf") NIL NIL "base64" 2067794 NIL ("inline" ("filename" "filename.pdf")) NIL NIL) "mixed" ("boundary" "Apple-Mail=_801866EE-56A0-4F30-8DB3-2496094971D1") NIL NIL NIL)',
|
||||
{ structure:
|
||||
[ { type: 'mixed',
|
||||
params: { boundary: 'Apple-Mail=_801866EE-56A0-4F30-8DB3-2496094971D1' },
|
||||
|
||||
disposition: null,
|
||||
language: null,
|
||||
location: null
|
||||
},
|
||||
[ { partID: '1',
|
||||
type: 'text',
|
||||
subtype: 'plain',
|
||||
params: { charset: 'us-ascii' },
|
||||
id: null,
|
||||
description: null,
|
||||
encoding: 'quoted-printable',
|
||||
size: 370,
|
||||
lines: 19,
|
||||
md5: null,
|
||||
disposition: null,
|
||||
language: null,
|
||||
location: null
|
||||
} ],
|
||||
[ { partID: '2',
|
||||
type: 'application',
|
||||
subtype: 'pdf',
|
||||
params:
|
||||
{ 'x-mac-hide-extension': 'yes',
|
||||
'x-unix-mode': '0644',
|
||||
name: 'filename.pdf' },
|
||||
id: null,
|
||||
description: null,
|
||||
encoding: 'base64',
|
||||
size: 2067794,
|
||||
md5: null,
|
||||
disposition: { type: 'inline', params: { filename: 'filename.pdf' } },
|
||||
language: null,
|
||||
location: null
|
||||
} ]
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
['BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "utf-8") NIL NIL "7BIT" 266 16 NIL ("INLINE" NIL) NIL)("TEXT" "HTML" ("CHARSET" "utf-8") NIL NIL "7BIT" 7343 65 NIL ("INLINE" NIL) NIL) "ALTERNATIVE" ("BOUNDARY" "--4c81da7e1fb95-MultiPart-Mime-Boundary") NIL NIL)',
|
||||
{ structure:
|
||||
[ { type: 'alternative',
|
||||
params: { boundary: '--4c81da7e1fb95-MultiPart-Mime-Boundary' },
|
||||
disposition: null,
|
||||
language: null
|
||||
},
|
||||
[ { partID: '1',
|
||||
type: 'text',
|
||||
subtype: 'plain',
|
||||
params: { charset: 'utf-8' },
|
||||
id: null,
|
||||
description: null,
|
||||
encoding: '7BIT',
|
||||
size: 266,
|
||||
lines: 16,
|
||||
md5: null,
|
||||
disposition: { type: 'INLINE', params: null },
|
||||
language: null
|
||||
} ],
|
||||
[ { partID: '2',
|
||||
type: 'text',
|
||||
subtype: 'html',
|
||||
params: { charset: 'utf-8' },
|
||||
id: null,
|
||||
description: null,
|
||||
encoding: '7BIT',
|
||||
size: 7343,
|
||||
lines: 65,
|
||||
md5: null,
|
||||
disposition: { type: 'INLINE', params: null },
|
||||
language: null
|
||||
} ]
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
['BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "ISO-8859-1") NIL NIL "7BIT" 935 46 NIL NIL NIL) ("TEXT" "HTML" ("CHARSET" "ISO-8859-1") NIL NIL "QUOTED-PRINTABLE" 1962 33 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "000e0cd294e80dc83c0475bf339b") NIL NIL)',
|
||||
{ structure:
|
||||
[ { type: 'alternative',
|
||||
params: { boundary: '000e0cd294e80dc83c0475bf339b' },
|
||||
disposition: null,
|
||||
language: null
|
||||
},
|
||||
[ { partID: '1',
|
||||
type: 'text',
|
||||
subtype: 'plain',
|
||||
params: { charset: 'ISO-8859-1' },
|
||||
id: null,
|
||||
description: null,
|
||||
encoding: '7BIT',
|
||||
size: 935,
|
||||
lines: 46,
|
||||
md5: null,
|
||||
disposition: null,
|
||||
language: null
|
||||
} ],
|
||||
[ { partID: '2',
|
||||
type: 'text',
|
||||
subtype: 'html',
|
||||
params: { charset: 'ISO-8859-1' },
|
||||
id: null,
|
||||
description: null,
|
||||
encoding: 'QUOTED-PRINTABLE',
|
||||
size: 1962,
|
||||
lines: 33,
|
||||
md5: null,
|
||||
disposition: null,
|
||||
language: null
|
||||
} ]
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
['BODYSTRUCTURE ((("TEXT" "PLAIN" ("CHARSET" "ISO-8859-1") NIL NIL "7BIT" 935 46 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "ISO-8859-1") NIL NIL "QUOTED-PRINTABLE" 1962 33 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "000e0cd294e80dc83c0475bf339b") NIL NIL)("APPLICATION" "OCTET-STREAM" ("NAME" "license") NIL NIL "BASE64" 98 NIL ("ATTACHMENT" ("FILENAME" "license")) NIL) "MIXED" ("BOUNDARY" "000e0cd294e80dc84c0475bf339d") NIL NIL)',
|
||||
{ structure:
|
||||
[ { type: 'mixed',
|
||||
params: { boundary: '000e0cd294e80dc84c0475bf339d' },
|
||||
disposition: null,
|
||||
language: null
|
||||
},
|
||||
[ { type: 'alternative',
|
||||
params: { boundary: '000e0cd294e80dc83c0475bf339b' },
|
||||
disposition: null,
|
||||
language: null
|
||||
},
|
||||
[ { partID: '1.1',
|
||||
type: 'text',
|
||||
subtype: 'plain',
|
||||
params: { charset: 'ISO-8859-1' },
|
||||
id: null,
|
||||
description: null,
|
||||
encoding: '7BIT',
|
||||
size: 935,
|
||||
lines: 46,
|
||||
md5: null,
|
||||
disposition: null,
|
||||
language: null
|
||||
} ],
|
||||
[ { partID: '1.2',
|
||||
type: 'text',
|
||||
subtype: 'html',
|
||||
params: { charset: 'ISO-8859-1' },
|
||||
id: null,
|
||||
description: null,
|
||||
encoding: 'QUOTED-PRINTABLE',
|
||||
size: 1962,
|
||||
lines: 33,
|
||||
md5: null,
|
||||
disposition: null,
|
||||
language: null
|
||||
} ]
|
||||
],
|
||||
[ { partID: '2',
|
||||
type: 'application',
|
||||
subtype: 'octet-stream',
|
||||
params: { name: 'license' },
|
||||
id: null,
|
||||
description: null,
|
||||
encoding: 'BASE64',
|
||||
size: 98,
|
||||
md5: null,
|
||||
disposition: { type: 'ATTACHMENT', params: { filename: 'license' } },
|
||||
language: null
|
||||
} ]
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
var result;
|
||||
|
||||
for (var i=0,len=tests.simple.length; i<len; ++i) {
|
||||
result = {};
|
||||
parseFetch(tests.simple[i][0], result);
|
||||
assert.deepEqual(tests.simple[i][1], result);
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
var assert = require('assert');
|
||||
|
||||
var parseNamespaces = require('../lib/imap.parsers').parseNamespaces;
|
||||
|
||||
var tests = {
|
||||
simple: [
|
||||
['', {}],
|
||||
|
||||
['(("" "/")) NIL NIL',
|
||||
{ personal: [ { prefix: '', delim: '/' } ] }],
|
||||
|
||||
['(("INBOX." ".")) NIL NIL',
|
||||
{ personal: [ { prefix: 'INBOX.', delim: '.' } ] }],
|
||||
|
||||
['NIL NIL (("" "."))',
|
||||
{ shared: [ { prefix: '', delim: '.' } ] }],
|
||||
|
||||
['(("" "/")) NIL (("Public Folders/" "/"))',
|
||||
{ personal: [ { prefix: '', delim: '/' } ],
|
||||
shared: [ { prefix: 'Public Folders/', delim: '/' } ] }
|
||||
],
|
||||
|
||||
['(("" "/")) (("~" "/")) (("#shared/" "/")("#public/" "/")("#ftp/" "/")("#news." "."))',
|
||||
{ personal: [ { prefix: '', delim: '/' } ],
|
||||
other: [ { prefix: '~', delim: '/' } ],
|
||||
shared:
|
||||
[ { prefix: '#shared/', delim: '/' },
|
||||
{ prefix: '#public/', delim: '/' },
|
||||
{ prefix: '#ftp/', delim: '/' },
|
||||
{ prefix: '#news.', delim: '.' }
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
['(("" "/")("#mh/" "/" "X-PARAM" ("FLAG1" "FLAG2"))) NIL NIL',
|
||||
{ personal:
|
||||
[ { prefix: '', delim: '/' },
|
||||
{ prefix: '#mh/',
|
||||
delim: '/',
|
||||
extensions: [ { name: 'X-PARAM', flags: [ 'FLAG1', 'FLAG2' ] } ]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
var result;
|
||||
|
||||
for (var i=0,len=tests.simple.length; i<len; ++i) {
|
||||
result = {};
|
||||
parseNamespaces(tests.simple[i][0], result);
|
||||
assert.deepEqual(tests.simple[i][1], result);
|
||||
}
|
Loading…
Reference in New Issue