Conflicts:
	imap.js
fork
Chotiwat Chawannakul 12 years ago
commit 3ef5ad8f18

@ -89,7 +89,7 @@ node-imap exposes one object: **ImapConnection**.
* **seqno** - An Integer that designates this message's sequence number. This number changes when messages with smaller sequence numbers are deleted for example (see the ImapConnection's 'deleted' event).
* **flags** - An Array containing the flags currently set on this message.
* **date** - A String containing the internal server date for the message (always represented in GMT?)
* **headers** - An Object containing the headers of the message, **if headers were requested when calling fetch().** Note: The value of each property in the object is an Array containing the value(s) for that particular header name (just in case there are duplicate headers).
* **headers** - An Object containing the headers of the message, **if headers were requested when calling fetch().** Note: Duplicate headers are dealt with by storing the duplicated values in an array keyed on the header name (e.g. { to: ['foo@bar.com', 'bar@baz.com'] }).
* **structure** - An Array containing the structure of the message, **if the structure was requested when calling fetch().** See below for an explanation of the format of this property.
* Events:
* **data**(String) - Emitted for each message body chunk if a message body is being fetched
@ -367,10 +367,10 @@ ImapConnection Functions
* **fetch**(Integer/String/Array, Object) - _ImapFetch_ - Fetches the message(s) identified by the first parameter, in the currently open mailbox. The first parameter can either be an Integer for a single message ID, a String for a message ID range (e.g. '2504:2507' or '\*' or '2504:\*'), or an Array containing any number of the aforementioned Integers and/or Strings. The second (Object) parameter is a set of options used to determine how and what exactly to fetch. The valid options are:
* **markSeen** - A Boolean indicating whether to mark the message(s) as read when fetching it. **Default:** false
* **request** - An Object indicating what to fetch (at least **headers** OR **body** must be set to false -- in other words, you can only fetch one aspect of the message at a time):
* **request** - An Object indicating what to fetch (at least **headers** OR **body** must be set to false -- in other words, you can only fetch one aspect of the message at a time, the exception being if both are set to true which is the same as fetching a 'full' body, but with parsed headers):
* **struct** - A Boolean indicating whether to fetch the structure of the message. **Default:** true
* **headers** - A Boolean/Array value. A value of true fetches all message headers. An Array containing specific message headers to retrieve can also be specified. **Default:** true
* **body** - A Boolean/String/Array value. A Boolean value of true fetches the entire raw message body. A String value containing a valid partID (see _FetchResult_'s structure property) fetches the entire body/content of that particular part, or a String value of 'full' fetches the entire email message, including the headers. An Array value of length 2 can be specified if you wish to request a byte range of the content, where the first item is a Boolean/String as previously described and the second item is a String indicating the byte range, for example, to fetch the first 500 bytes: '0-500'. **Default:** false
* **body** - A Boolean/String/Array value. A Boolean value of true fetches the entire raw message body. A String value containing a valid partID (see _FetchResult_'s structure property) fetches the entire body/content of that particular part, or a String value of 'full' fetches the entire email message, including the headers (unparsed). An Array value of length 2 can be specified if you wish to request a byte range of the content, where the first item is a Boolean/String as previously described and the second item is a String indicating the byte range, for example, to fetch the first 500 bytes: '0-500'. **Default:** false
* **copy**(Integer/String/Array, String, Function) - _(void)_ - Copies the message(s) with the message ID(s) identified by the first parameter, in the currently open mailbox, to the mailbox specified by the second parameter. The first parameter can either be an Integer for a single message ID, a String for a message ID range (e.g. '2504:2507' or '\*' or '2504:\*'), or an Array containing any number of the aforementioned Integers and/or Strings. The Function parameter is the callback with one parameter: the error (null if none).

1713
imap.js

File diff suppressed because it is too large Load Diff

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;
};

@ -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" } ],

@ -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…
Cancel
Save