First go at code reorganization.

fork
Brian White 12 years ago
parent 0b1c66178c
commit 5cd24335e4

File diff suppressed because it is too large Load Diff

@ -0,0 +1,283 @@
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, literalData, 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];
else if (Array.isArray(result[i]) && typeof result[i][0] === 'string' &&
result[i][0].indexOf('HEADER') === 0 && literalData) {
var headers = literalData.split(/\r\n(?=[\w])/), header;
fetchData.headers = {};
for (var j=0,len2=headers.length; j<len2; ++j) {
header = headers[j].substring(0, headers[j].indexOf(': ')).toLowerCase();
if (!fetchData.headers[header])
fetchData.headers[header] = [];
fetchData.headers[header].push(headers[j].substr(headers[j]
.indexOf(': ')+2)
.replace(/\r\n/g, '').trim());
}
}
}
}
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']]
if (Array.isArray(cur[next])) {
part.disposition = {};
if (Array.isArray(cur[next][1])) {
for (var i=0,len=cur[next][1].length; i<len; i+=2)
part.disposition[cur[next][1][i].toLowerCase()] = cur[next][1][i+1];
} else
part.disposition[cur[next][0]] = cur[next][1];
} else
part.disposition = cur[next];
++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,391 @@
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] = 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 (' + buildSearchQuery(args[0], extensions, true) + ') ('
+ buildSearchQuery(args[1], extensions, true) + ')'
} 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);
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;
}
}

@ -2,7 +2,7 @@
"version": "0.3.2",
"author": "Brian White <mscdex@mscdex.net>",
"description": "An IMAP module for node.js that makes communicating with IMAP servers easy",
"main": "./imap",
"main": "./lib/imap",
"engines": { "node" : ">=0.4.0" },
"keywords": [ "imap", "mail", "email", "reader", "client" ],
"licenses": [ { "type": "MIT", "url": "http://github.com/mscdex/node-imap/raw/master/LICENSE" } ],

Loading…
Cancel
Save