module rewrite
parent
ed7203022c
commit
5eb8334553
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,582 @@
|
||||
var EventEmitter = require('events').EventEmitter,
|
||||
ReadableStream = require('stream').Readable || require('readable-stream'),
|
||||
inherits = require('util').inherits,
|
||||
inspect = require('util').inspect,
|
||||
utf7 = require('utf7').imap;
|
||||
|
||||
var CH_LF = 10,
|
||||
LITPLACEHOLDER = String.fromCharCode(0),
|
||||
EMPTY_READCB = function(n) {},
|
||||
RE_INTEGER = /^\d+$/,
|
||||
RE_PRECEDING = /^(?:\*|A\d+|\+) /,
|
||||
RE_BODYLITERAL = /BODY\[(.*)\] \{(\d+)\}$/i,
|
||||
RE_SEQNO = /^\* (\d+)/,
|
||||
RE_LISTCONTENT = /^\((.*)\)$/,
|
||||
RE_LITERAL = /\{(\d+)\}$/,
|
||||
RE_UNTAGGED = /^\* (?:(OK|NO|BAD|BYE|FLAGS|LIST|LSUB|SEARCH|STATUS|CAPABILITY|NAMESPACE|PREAUTH|SORT)|(\d+) (EXPUNGE|FETCH|RECENT|EXISTS))(?: (?:\[([^\]]+)\] )?(.+))?$/i,
|
||||
RE_TAGGED = /^A(\d+) (OK|NO|BAD) (?:\[([^\]]+)\] )?(.+)$/i,
|
||||
RE_CONTINUE = /^\+ (?:\[([^\]]+)\] )?(.+)$/i;
|
||||
|
||||
function Parser(stream, debug) {
|
||||
if (!(this instanceof Parser))
|
||||
return new Parser(stream, debug);
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
this._stream = stream;
|
||||
this._body = undefined;
|
||||
this._literallen = 0;
|
||||
this._literals = [];
|
||||
this._buffer = '';
|
||||
this.debug = debug;
|
||||
|
||||
var self = this;
|
||||
function cb() {
|
||||
if (self._literallen > 0)
|
||||
self._tryread(self._literallen);
|
||||
else
|
||||
self._tryread();
|
||||
}
|
||||
this._stream.on('readable', cb);
|
||||
process.nextTick(cb);
|
||||
}
|
||||
inherits(Parser, EventEmitter);
|
||||
|
||||
Parser.prototype._tryread = function(n) {
|
||||
var r = this._stream.read(n);
|
||||
r && this._parse(r);
|
||||
};
|
||||
|
||||
Parser.prototype._parse = function(data) {
|
||||
var i = 0, datalen = data.length, idxlf;
|
||||
|
||||
if (this._literallen > 0) {
|
||||
if (this._body) {
|
||||
var body = this._body;
|
||||
if (datalen > this._literallen) {
|
||||
var litlen = this._literallen;
|
||||
|
||||
i = this._literallen;
|
||||
this._literallen = 0;
|
||||
this._body = undefined;
|
||||
|
||||
body.push(data.slice(0, litlen));
|
||||
} else {
|
||||
this._literallen -= datalen;
|
||||
var r = body.push(data);
|
||||
if (!r && this._literallen > 0)
|
||||
return;
|
||||
i = datalen;
|
||||
}
|
||||
if (this._literallen === 0) {
|
||||
body._read = EMPTY_READCB;
|
||||
body.push(null);
|
||||
}
|
||||
} else {
|
||||
if (datalen > this._literallen)
|
||||
this._literals.push(data.slice(0, this._literallen));
|
||||
else
|
||||
this._literals.push(data);
|
||||
i = this._literallen;
|
||||
this._literallen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
while (i < datalen) {
|
||||
idxlf = indexOfCh(data, datalen, i, CH_LF);
|
||||
if (idxlf === -1) {
|
||||
this._buffer += data.toString('utf8');
|
||||
break;
|
||||
} else {
|
||||
this._buffer += data.toString('utf8', i, idxlf);
|
||||
this._buffer = this._buffer.trim();
|
||||
i = idxlf + 1;
|
||||
|
||||
this.debug && this.debug('<= ' + inspect(this._buffer));
|
||||
|
||||
var clearBuffer = false;
|
||||
|
||||
if (RE_PRECEDING.test(this._buffer)) {
|
||||
var firstChar = this._buffer[0];
|
||||
if (firstChar === '*')
|
||||
clearBuffer = this._resUntagged();
|
||||
else if (firstChar === 'A')
|
||||
clearBuffer = this._resTagged();
|
||||
else if (firstChar === '+')
|
||||
clearBuffer = this._resContinue();
|
||||
|
||||
if (this._literallen > 0 && i < datalen) {
|
||||
// literal data included in this chunk -- put it back onto stream
|
||||
this._stream.unshift(data.slice(i));
|
||||
i = datalen;
|
||||
if (!this._body && this._literallen > 0) {
|
||||
// check if unshifted contents satisfies non-body literal length
|
||||
this._tryread(this._literallen);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.emit('other', this._buffer);
|
||||
clearBuffer = true;
|
||||
}
|
||||
|
||||
if (clearBuffer)
|
||||
this._buffer = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (this._literallen === 0 || this._body)
|
||||
this._tryread();
|
||||
};
|
||||
|
||||
Parser.prototype._resTagged = function() {
|
||||
var ret = false;
|
||||
if (m = RE_LITERAL.exec(this._buffer)) {
|
||||
// non-BODY literal -- buffer it
|
||||
this._buffer = this._buffer.replace(RE_LITERAL, LITPLACEHOLDER);
|
||||
this._literallen = parseInt(m[1], 10);
|
||||
this._tryread(this._literallen);
|
||||
} else {
|
||||
var m = RE_TAGGED.exec(this._buffer),
|
||||
tagnum = parseInt(m[1], 10),
|
||||
type = m[2].toLowerCase(),
|
||||
textCode = (m[3] ? parseExpr(m[3], this._literals) : m[3]),
|
||||
text = m[4];
|
||||
|
||||
this._literals = [];
|
||||
|
||||
this.emit('tagged', {
|
||||
type: type,
|
||||
tagnum: tagnum,
|
||||
textCode: textCode,
|
||||
text: text
|
||||
});
|
||||
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
Parser.prototype._resUntagged = function() {
|
||||
var ret = false, self = this, m;
|
||||
if (m = RE_BODYLITERAL.exec(this._buffer)) {
|
||||
// BODY literal -- stream it
|
||||
var which = m[1], size = parseInt(m[2], 10);
|
||||
this._literallen = size;
|
||||
this._body = new ReadableStream();
|
||||
this._body._read = function bodyread(n) {
|
||||
self._tryread();
|
||||
};
|
||||
m = RE_SEQNO.exec(this._buffer);
|
||||
this._buffer = this._buffer.replace(RE_BODYLITERAL, '');
|
||||
this.emit('body', this._body, {
|
||||
seqno: parseInt(m[1], 10),
|
||||
which: which,
|
||||
size: size
|
||||
});
|
||||
} else if (m = RE_LITERAL.exec(this._buffer)) {
|
||||
// non-BODY literal -- buffer it
|
||||
this._buffer = this._buffer.replace(RE_LITERAL, LITPLACEHOLDER);
|
||||
this._literallen = parseInt(m[1], 10);
|
||||
this._tryread(this._literallen);
|
||||
} else if (m = RE_UNTAGGED.exec(this._buffer)) {
|
||||
// normal single line response
|
||||
|
||||
// m[1] or m[3] = response type
|
||||
// if m[3] is set, m[2] = sequence number (for FETCH) or count
|
||||
// m[4] = response text code (optional)
|
||||
// m[5] = response text (optional)
|
||||
|
||||
var type, num, textCode, val;
|
||||
if (m[2] !== undefined)
|
||||
num = parseInt(m[2], 10);
|
||||
if (m[4] !== undefined)
|
||||
textCode = parseTextCode(m[4], this._literals);
|
||||
|
||||
type = (m[1] || m[3]).toLowerCase();
|
||||
|
||||
if (type === 'flags'
|
||||
|| type === 'search'
|
||||
|| type === 'capability'
|
||||
|| type === 'sort') {
|
||||
if (m[5][0] === '(')
|
||||
val = RE_LISTCONTENT.exec(m[5])[1].split(' ');
|
||||
else
|
||||
val = m[5].split(' ');
|
||||
if (type === 'search' || type === 'sort')
|
||||
val = val.map(function(v) { return parseInt(v, 10); });
|
||||
} else if (type === 'list' || type === 'lsub')
|
||||
val = parseBoxList(m[5], this._literals);
|
||||
else if (type === 'status')
|
||||
val = parseStatus(m[5], this._literals);
|
||||
else if (type === 'fetch')
|
||||
val = parseFetch(m[5], this._literals);
|
||||
else if (type === 'namespace')
|
||||
val = parseNamespaces(m[5], this._literals);
|
||||
else
|
||||
val = m[5];
|
||||
|
||||
this._literals = [];
|
||||
|
||||
this.emit('untagged', {
|
||||
type: type,
|
||||
num: num,
|
||||
textCode: textCode,
|
||||
text: val
|
||||
});
|
||||
ret = true;
|
||||
} else
|
||||
ret = true;
|
||||
return ret;
|
||||
};
|
||||
|
||||
Parser.prototype._resContinue = function() {
|
||||
var m = RE_CONTINUE.exec(this._buffer), textCode, text = m[2];
|
||||
|
||||
if (m[1] !== undefined)
|
||||
textCode = parseTextCode(m[1], this._literals);
|
||||
|
||||
this.emit('continue', {
|
||||
textCode: textCode,
|
||||
text: text
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
function indexOfCh(buffer, len, i, ch) {
|
||||
var r = -1;
|
||||
for (; i < len; ++i) {
|
||||
if (buffer[i] === ch) {
|
||||
r = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
function parseTextCode(text, literals) {
|
||||
var r = parseExpr(text, literals);
|
||||
if (r.length === 1)
|
||||
return r[0];
|
||||
else
|
||||
return { key: r[0], val: r[1] };
|
||||
}
|
||||
|
||||
function parseBoxList(text, literals) {
|
||||
var r = parseExpr(text, literals);
|
||||
return {
|
||||
flags: r[0],
|
||||
delimiter: r[1],
|
||||
name: utf7.decode(''+r[2])
|
||||
};
|
||||
}
|
||||
|
||||
function parseNamespaces(text, literals) {
|
||||
var r = parseExpr(text, literals), i, len, j, len2, ns, nsobj, namespaces, n;
|
||||
|
||||
for (n = 0; n < 3; ++n) {
|
||||
if (r[n]) {
|
||||
namespaces = [];
|
||||
for (i = 0, len = r[n].length; i < len; ++i) {
|
||||
ns = r[n][i];
|
||||
nsobj = {
|
||||
prefix: ns[0],
|
||||
delimiter: ns[1],
|
||||
extensions: undefined
|
||||
};
|
||||
if (ns.length > 2)
|
||||
nsobj.extensions = {};
|
||||
for (j = 2, len2 = ns.length; j < len2; j += 2)
|
||||
nsobj.extensions[ns[j]] = ns[j + 1];
|
||||
namespaces.push(nsobj);
|
||||
}
|
||||
r[n] = namespaces;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
personal: r[0],
|
||||
other: r[1],
|
||||
shared: r[2]
|
||||
};
|
||||
}
|
||||
|
||||
function parseStatus(text, literals) {
|
||||
var r = parseExpr(text, literals), attrs = {};
|
||||
// r[1] is [KEY1, VAL1, KEY2, VAL2, .... KEYn, VALn]
|
||||
for (var i = 0, len = r[1].length; i < len; i += 2)
|
||||
attrs[r[1][i].toLowerCase()] = r[1][i + 1];
|
||||
return {
|
||||
name: utf7.decode(''+r[0]),
|
||||
attrs: attrs
|
||||
};
|
||||
}
|
||||
|
||||
function parseFetch(text, literals) {
|
||||
var list = parseExpr(text, literals)[0], attrs = {};
|
||||
// list is [KEY1, VAL1, KEY2, VAL2, .... KEYn, VALn]
|
||||
for (var i = 0, len = list.length, key, val; i < len; i += 2) {
|
||||
key = list[i].toLowerCase();
|
||||
val = list[i + 1];
|
||||
if (key === 'envelope')
|
||||
val = parseFetchEnvelope(val);
|
||||
else if (key === 'internaldate')
|
||||
val = new Date(val);
|
||||
else if (key === 'body')
|
||||
val = parseBodyStructure(val);
|
||||
attrs[key] = val;
|
||||
}
|
||||
return attrs;
|
||||
}
|
||||
|
||||
function parseBodyStructure(cur, literals, prefix, partID) {
|
||||
var ret = [], i, len;
|
||||
if (prefix === undefined) {
|
||||
var result = (Array.isArray(cur) ? cur : parseExpr(cur, literals));
|
||||
if (result.length)
|
||||
ret = parseBodyStructure(result, literals, '', 1);
|
||||
} else {
|
||||
var part, partLen = cur.length, next;
|
||||
if (Array.isArray(cur[0])) { // multipart
|
||||
next = -1;
|
||||
while (Array.isArray(cur[++next])) {
|
||||
ret.push(parseBodyStructure(cur[next],
|
||||
literals,
|
||||
prefix + (prefix !== '' ? '.' : '')
|
||||
+ (partID++).toString(), 1));
|
||||
}
|
||||
part = { type: cur[next++].toLowerCase() };
|
||||
if (partLen > next) {
|
||||
if (Array.isArray(cur[next])) {
|
||||
part.params = {};
|
||||
for (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 (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 = parseFetchEnvelope(cur[next]);
|
||||
else
|
||||
part.envelope = null;
|
||||
++next;
|
||||
|
||||
// body
|
||||
if (partLen > next && Array.isArray(cur[next]))
|
||||
part.body = parseBodyStructure(cur[next], literals, prefix, 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
|
||||
parseStructExtra(part, partLen, cur, next);
|
||||
ret.unshift(part);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function parseStructExtra(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, key; i < len; i += 2) {
|
||||
key = cur[next][1][i].toLowerCase();
|
||||
disposition.params[key] = 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];
|
||||
}
|
||||
}
|
||||
|
||||
function parseFetchEnvelope(list) {
|
||||
return {
|
||||
date: new Date(list[0]),
|
||||
subject: list[1],
|
||||
from: parseEnvelopeAddresses(list[2]),
|
||||
sender: parseEnvelopeAddresses(list[3]),
|
||||
replyTo: parseEnvelopeAddresses(list[4]),
|
||||
to: parseEnvelopeAddresses(list[5]),
|
||||
cc: parseEnvelopeAddresses(list[6]),
|
||||
bcc: parseEnvelopeAddresses(list[7]),
|
||||
inReplyTo: list[8],
|
||||
messageId: list[9]
|
||||
};
|
||||
}
|
||||
|
||||
function parseEnvelopeAddresses(list) {
|
||||
var addresses = null;
|
||||
if (Array.isArray(list)) {
|
||||
addresses = [];
|
||||
var inGroup = false, curGroup;
|
||||
for (var i = 0, len = list.length, addr; i < len; ++i) {
|
||||
addr = list[i];
|
||||
if (addr[2] === null) { // end of group addresses
|
||||
inGroup = false;
|
||||
if (curGroup) {
|
||||
addresses.push(curGroup);
|
||||
curGroup = undefined;
|
||||
}
|
||||
} else if (addr[3] === null) { // start of group addresses
|
||||
inGroup = true;
|
||||
curGroup = {
|
||||
group: addr[2],
|
||||
addresses: []
|
||||
};
|
||||
} else { // regular user address
|
||||
var info = {
|
||||
name: addr[0],
|
||||
mailbox: addr[2],
|
||||
host: addr[3]
|
||||
};
|
||||
if (inGroup)
|
||||
curGroup.addresses.push(info);
|
||||
else if (!inGroup)
|
||||
addresses.push(info);
|
||||
}
|
||||
list[i] = addr;
|
||||
}
|
||||
if (inGroup) {
|
||||
// no end of group found, assume implicit end
|
||||
addresses.push(curGroup);
|
||||
}
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
function parseExpr(o, literals, result, start, useBrackets) {
|
||||
start = start || 0;
|
||||
var inQuote = false, lastPos = start - 1, isTop = false, val;
|
||||
|
||||
if (useBrackets === undefined)
|
||||
useBrackets = true;
|
||||
if (!result)
|
||||
result = [];
|
||||
if (typeof o === 'string') {
|
||||
o = { str: o };
|
||||
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] === ')'
|
||||
|| (useBrackets && o.str[i] === ']')) {
|
||||
if (i - (lastPos + 1) > 0) {
|
||||
val = convStr(o.str.substring(lastPos + 1, i), literals);
|
||||
result.push(val);
|
||||
}
|
||||
if ((o.str[i] === ')' || (useBrackets && o.str[i] === ']')) && !isTop)
|
||||
return i;
|
||||
lastPos = i;
|
||||
} else if ((o.str[i] === '(' || (useBrackets && o.str[i] === '['))) {
|
||||
var innerResult = [];
|
||||
i = parseExpr(o, literals, innerResult, i + 1, useBrackets);
|
||||
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(convStr(o.str.substring(lastPos + 1), literals));
|
||||
}
|
||||
return (isTop ? result : start);
|
||||
}
|
||||
|
||||
function convStr(str, literals) {
|
||||
if (str[0] === '"')
|
||||
return str.substring(1, str.length - 1);
|
||||
else if (str === 'NIL')
|
||||
return null;
|
||||
else if (RE_INTEGER.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 if (literals && literals.length && str === LITPLACEHOLDER) {
|
||||
var l = literals.shift();
|
||||
if (Buffer.isBuffer(l))
|
||||
l = l.toString('utf8');
|
||||
return l;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
exports.Parser = Parser;
|
||||
exports.parseExpr = parseExpr;
|
||||
exports.parseEnvelopeAddresses = parseEnvelopeAddresses;
|
||||
exports.parseBodyStructure = parseBodyStructure;
|
File diff suppressed because it is too large
Load Diff
@ -1,348 +0,0 @@
|
||||
var utils = require('./imap.utilities');
|
||||
|
||||
var RE_CRLF = /\r\n/g,
|
||||
RE_HDR = /^([^:]+):[ \t]?(.+)?$/;
|
||||
|
||||
exports.convStr = function(str, literals) {
|
||||
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 if (literals && literals.lp < literals.length && /^\{\d+\}$/.test(str))
|
||||
return literals[literals.lp++];
|
||||
else
|
||||
return str;
|
||||
};
|
||||
|
||||
exports.parseHeaders = function(str) {
|
||||
var lines = str.split(RE_CRLF),
|
||||
headers = {}, m;
|
||||
|
||||
for (var i = 0, h, len = lines.length; i < len; ++i) {
|
||||
if (lines[i].length === 0)
|
||||
continue;
|
||||
if (lines[i][0] === '\t' || lines[i][0] === ' ') {
|
||||
// folded header content
|
||||
// RFC2822 says to just remove the CRLF and not the whitespace following
|
||||
// it, so we follow the RFC and include the leading whitespace ...
|
||||
headers[h][headers[h].length - 1] += lines[i];
|
||||
} else {
|
||||
m = RE_HDR.exec(lines[i]);
|
||||
h = m[1].toLowerCase();
|
||||
if (m[2]) {
|
||||
if (headers[h] === undefined)
|
||||
headers[h] = [m[2]];
|
||||
else
|
||||
headers[h].push(m[2]);
|
||||
} else
|
||||
headers[h] = [''];
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
};
|
||||
|
||||
exports.parseNamespaces = function(str, literals) {
|
||||
var result, vals;
|
||||
if (str.length === 3 && str.toUpperCase() === 'NIL')
|
||||
vals = null;
|
||||
else {
|
||||
result = exports.parseExpr(str, literals);
|
||||
vals = [];
|
||||
for (var i = 0, len = result.length; i < len; ++i) {
|
||||
var val = {
|
||||
prefix: result[i][0],
|
||||
delimiter: result[i][1]
|
||||
};
|
||||
if (result[i].length > 2) {
|
||||
// extension data
|
||||
val.extensions = [];
|
||||
for (var j = 2, len2 = result[i].length; j < len2; j += 2) {
|
||||
val.extensions.push({
|
||||
name: result[i][j],
|
||||
flags: result[i][j + 1]
|
||||
});
|
||||
}
|
||||
}
|
||||
vals.push(val);
|
||||
}
|
||||
}
|
||||
return vals;
|
||||
};
|
||||
|
||||
exports.parseFetchBodies = function(str, literals) {
|
||||
literals.lp = 0;
|
||||
var result = exports.parseExpr(str, literals),
|
||||
bodies;
|
||||
for (var i = 0, len = result.length; i < len; i += 2) {
|
||||
if (Array.isArray(result[i])) {
|
||||
if (result[i].length === 0)
|
||||
result[i].push('');
|
||||
else if (result[i].length > 1) {
|
||||
// HEADER.FIELDS (foo) or HEADER.FIELDS (foo bar baz)
|
||||
result[i][0] += ' (';
|
||||
if (Array.isArray(result[i][1]))
|
||||
result[i][0] += result[i][1].join(' ');
|
||||
else
|
||||
result[i][0] += result[i].slice(1).join(' ');
|
||||
result[i][0] += ')';
|
||||
}
|
||||
if (bodies === undefined)
|
||||
bodies = ['BODY[' + result[i][0] + ']', result[i + 1]];
|
||||
else {
|
||||
bodies.push('BODY[' + result[i][0] + ']');
|
||||
bodies.push(result[i + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return bodies;
|
||||
};
|
||||
|
||||
exports.parseFetch = function(str, literals, fetchData) {
|
||||
literals.lp = 0;
|
||||
var result = exports.parseExpr(str, literals, false, 0, false);
|
||||
for (var i = 0, len = result.length; i < len; i += 2) {
|
||||
result[i] = result[i].toUpperCase();
|
||||
if (/^BODY\[/.test(result[i]))
|
||||
continue;
|
||||
if (result[i] === 'UID')
|
||||
fetchData.uid = 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], literals);
|
||||
else if (result[i] === 'RFC822.SIZE')
|
||||
fetchData.size = parseInt(result[i + 1], 10);
|
||||
else if (typeof result[i] === 'string') // simple extensions
|
||||
fetchData[result[i].toLowerCase()] = result[i + 1];
|
||||
}
|
||||
};
|
||||
|
||||
exports.parseBodyStructure = function(cur, literals, prefix, partID) {
|
||||
var ret = [], i, len;
|
||||
if (prefix === undefined) {
|
||||
var result = (Array.isArray(cur) ? cur : exports.parseExpr(cur, literals));
|
||||
if (result.length)
|
||||
ret = exports.parseBodyStructure(result, literals, '', 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], literals, prefix
|
||||
+ (prefix !== '' ? '.' : '')
|
||||
+ (partID++).toString(), 1));
|
||||
}
|
||||
part = { type: cur[next++].toLowerCase() };
|
||||
if (partLen > next) {
|
||||
if (Array.isArray(cur[next])) {
|
||||
part.params = {};
|
||||
for (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 (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 (i = 0, 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], literals, prefix, 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, key; i < len; i += 2) {
|
||||
key = cur[next][1][i].toLowerCase();
|
||||
disposition.params[key] = 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, literals, result, start, useBrackets) {
|
||||
start = start || 0;
|
||||
var inQuote = false, lastPos = start - 1, isTop = false, val;
|
||||
|
||||
if (useBrackets === undefined)
|
||||
useBrackets = true;
|
||||
if (!result)
|
||||
result = [];
|
||||
if (typeof o === 'string') {
|
||||
o = { str: o };
|
||||
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] === ')'
|
||||
|| (useBrackets && o.str[i] === ']')) {
|
||||
if (i - (lastPos + 1) > 0) {
|
||||
val = exports.convStr(o.str.substring(lastPos + 1, i), literals);
|
||||
result.push(val);
|
||||
}
|
||||
if ((o.str[i] === ')' || (useBrackets && o.str[i] === ']')) && !isTop)
|
||||
return i;
|
||||
lastPos = i;
|
||||
} else if ((o.str[i] === '(' || (useBrackets && o.str[i] === '['))) {
|
||||
var innerResult = [];
|
||||
i = exports.parseExpr(o, literals, innerResult, i + 1, useBrackets);
|
||||
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), literals));
|
||||
}
|
||||
return (isTop ? result : start);
|
||||
};
|
@ -1,242 +0,0 @@
|
||||
exports.MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
|
||||
'Oct', 'Nov', 'Dec'];
|
||||
|
||||
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 += exports.buildSearchQuery(args[0], extensions, true);
|
||||
searchargs += ') (';
|
||||
searchargs += exports.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 + ' "' + exports.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], 10);
|
||||
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 + ' "' + exports.escape(''+args[0])
|
||||
+ '" "' + exports.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 + ' "' + exports.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:
|
||||
try {
|
||||
// last hope it's a seqno set
|
||||
// http://tools.ietf.org/html/rfc3501#section-6.4.4
|
||||
var seqnos = (args ? [criteria].concat(args) : [criteria]);
|
||||
exports.validateUIDList(seqnos);
|
||||
searchargs += modifier + seqnos.join(',');
|
||||
} catch(e) {
|
||||
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], 10);
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
var CHARR_CRLF = [13, 10];
|
||||
function line(b, s) {
|
||||
var len = b.length, p = b.p, start = p, ret = false, retest = false;
|
||||
while (p < len && !ret) {
|
||||
if (b[p] === CHARR_CRLF[s.p]) {
|
||||
if (++s.p === 2)
|
||||
ret = true;
|
||||
} else {
|
||||
retest = (s.p > 0);
|
||||
s.p = 0;
|
||||
if (retest)
|
||||
continue;
|
||||
}
|
||||
++p;
|
||||
}
|
||||
if (ret === false) {
|
||||
if (s.ret)
|
||||
s.ret += b.toString('ascii', start);
|
||||
else
|
||||
s.ret = b.toString('ascii', start);
|
||||
} else {
|
||||
var iCR = p - 2;
|
||||
if (iCR < 0) {
|
||||
// the CR is at the end of s.ret
|
||||
if (s.ret && s.ret.length > 1)
|
||||
ret = s.ret.substr(0, s.ret.length - 1);
|
||||
else
|
||||
ret = '';
|
||||
} else {
|
||||
// the entire CRLF is in b
|
||||
if (iCR === 0)
|
||||
ret = (s.ret ? s.ret : '');
|
||||
else {
|
||||
if (s.ret) {
|
||||
ret = s.ret;
|
||||
ret += b.toString('ascii', start, iCR);
|
||||
} else
|
||||
ret = b.toString('ascii', start, iCR);
|
||||
}
|
||||
}
|
||||
s.p = 0;
|
||||
s.ret = undefined;
|
||||
}
|
||||
b.p = p;
|
||||
return ret;
|
||||
}
|
||||
|
||||
exports.line = line;
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,72 @@
|
||||
var parseBodyStructure = require('../lib/Parser').parseBodyStructure;
|
||||
|
||||
var assert = require('assert'),
|
||||
inspect = require('util').inspect;
|
||||
|
||||
[
|
||||
{ source: '("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 1152 23)'
|
||||
+ '("TEXT" "PLAIN" ("CHARSET" "US-ASCII" "NAME" "cc.diff")'
|
||||
+ ' "<960723163407.20117h@cac.washington.edu>" "Compiler diff"'
|
||||
+ ' "BASE64" 4554 73)'
|
||||
+ '"MIXED"',
|
||||
expected: [ { type: 'mixed' },
|
||||
[ { partID: '1',
|
||||
type: 'text',
|
||||
subtype: 'plain',
|
||||
params: { charset: 'US-ASCII' },
|
||||
id: null,
|
||||
description: null,
|
||||
encoding: '7BIT',
|
||||
size: 1152,
|
||||
lines: 23
|
||||
}
|
||||
],
|
||||
[ { partID: '2',
|
||||
type: 'text',
|
||||
subtype: 'plain',
|
||||
params: { charset: 'US-ASCII', name: 'cc.diff' },
|
||||
id: '<960723163407.20117h@cac.washington.edu>',
|
||||
description: 'Compiler diff',
|
||||
encoding: 'BASE64',
|
||||
size: 4554,
|
||||
lines: 73
|
||||
}
|
||||
]
|
||||
],
|
||||
what: 'RFC3501 example #1'
|
||||
},
|
||||
{ source: '"TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 3028 92',
|
||||
expected: [ { partID: '1',
|
||||
type: 'text',
|
||||
subtype: 'plain',
|
||||
params: { charset: 'US-ASCII' },
|
||||
id: null,
|
||||
description: null,
|
||||
encoding: '7BIT',
|
||||
size: 3028,
|
||||
lines: 92
|
||||
}
|
||||
],
|
||||
what: 'RFC3501 example #2'
|
||||
},
|
||||
].forEach(function(v) {
|
||||
var result;
|
||||
try {
|
||||
result = parseBodyStructure(v.source);
|
||||
} catch (e) {
|
||||
console.log(makeMsg(v.what, 'JS Exception: ' + e.stack));
|
||||
return;
|
||||
}
|
||||
assert.deepEqual(result,
|
||||
v.expected,
|
||||
makeMsg(v.what,
|
||||
'Result mismatch:'
|
||||
+ '\nParsed: ' + inspect(result, false, 10)
|
||||
+ '\nExpected: ' + inspect(v.expected, false, 10)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
function makeMsg(what, msg) {
|
||||
return '[' + what + ']: ' + msg;
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
var parseExpr = require('../lib/Parser').parseExpr,
|
||||
parseEnvelopeAddresses = require('../lib/Parser').parseEnvelopeAddresses;
|
||||
|
||||
var assert = require('assert'),
|
||||
inspect = require('util').inspect;
|
||||
|
||||
[
|
||||
{ source: '("Terry Gray" NIL "gray" "cac.washington.edu")',
|
||||
expected: [ { name: 'Terry Gray',
|
||||
mailbox: 'gray',
|
||||
host: 'cac.washington.edu'
|
||||
}
|
||||
],
|
||||
what: 'RFC3501 example #1'
|
||||
},
|
||||
{ source: '(NIL NIL "imap" "cac.washington.edu")',
|
||||
expected: [ { name: null,
|
||||
mailbox: 'imap',
|
||||
host: 'cac.washington.edu'
|
||||
}
|
||||
],
|
||||
what: 'RFC3501 example #2'
|
||||
},
|
||||
{ source: '(NIL NIL "imap" NIL)'
|
||||
+ '(NIL NIL NIL NIL)',
|
||||
expected: [ { group: 'imap',
|
||||
addresses: []
|
||||
}
|
||||
],
|
||||
what: 'Zero-length group'
|
||||
},
|
||||
{ source: '(NIL NIL "imap" NIL)'
|
||||
+ '("Terry Gray" NIL "gray" "cac.washington.edu")'
|
||||
+ '(NIL NIL NIL NIL)',
|
||||
expected: [ { group: 'imap',
|
||||
addresses: [
|
||||
{ name: 'Terry Gray',
|
||||
mailbox: 'gray',
|
||||
host: 'cac.washington.edu'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
what: 'One-length group'
|
||||
},
|
||||
{ source: '(NIL NIL "imap" NIL)'
|
||||
+ '("Terry Gray" NIL "gray" "cac.washington.edu")'
|
||||
+ '(NIL NIL NIL NIL)'
|
||||
+ '(NIL NIL "imap" "cac.washington.edu")',
|
||||
expected: [ { group: 'imap',
|
||||
addresses: [
|
||||
{ name: 'Terry Gray',
|
||||
mailbox: 'gray',
|
||||
host: 'cac.washington.edu'
|
||||
}
|
||||
]
|
||||
},
|
||||
{ name: null,
|
||||
mailbox: 'imap',
|
||||
host: 'cac.washington.edu'
|
||||
}
|
||||
],
|
||||
what: 'One-length group and address'
|
||||
},
|
||||
{ source: '(NIL NIL "imap" NIL)'
|
||||
+ '("Terry Gray" NIL "gray" "cac.washington.edu")',
|
||||
expected: [ { group: 'imap',
|
||||
addresses: [
|
||||
{ name: 'Terry Gray',
|
||||
mailbox: 'gray',
|
||||
host: 'cac.washington.edu'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
what: 'Implicit group end'
|
||||
},
|
||||
{ source: '("Terry Gray" NIL "gray" "cac.washington.edu")'
|
||||
+ '(NIL NIL NIL NIL)',
|
||||
expected: [ { name: 'Terry Gray',
|
||||
mailbox: 'gray',
|
||||
host: 'cac.washington.edu'
|
||||
}
|
||||
],
|
||||
what: 'Group end without start'
|
||||
},
|
||||
].forEach(function(v) {
|
||||
var result;
|
||||
|
||||
try {
|
||||
result = parseEnvelopeAddresses(parseExpr(v.source));
|
||||
} catch (e) {
|
||||
console.log(makeMsg(v.what, 'JS Exception: ' + e.stack));
|
||||
return;
|
||||
}
|
||||
|
||||
assert.deepEqual(result,
|
||||
v.expected,
|
||||
makeMsg(v.what,
|
||||
'Result mismatch:'
|
||||
+ '\nParsed: ' + inspect(result, false, 10)
|
||||
+ '\nExpected: ' + inspect(v.expected, false, 10)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
function makeMsg(what, msg) {
|
||||
return '[' + what + ']: ' + msg;
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
var parseExpr = require('../lib/Parser').parseExpr;
|
||||
|
||||
var assert = require('assert'),
|
||||
inspect = require('util').inspect;
|
||||
|
||||
[
|
||||
{ source: '',
|
||||
expected: [],
|
||||
what: 'Empty value'
|
||||
},
|
||||
{ source: 'FLAGS NIL RFC822.SIZE 44827',
|
||||
expected: ['FLAGS', null, 'RFC822.SIZE', 44827],
|
||||
what: 'Simple, two key-value pairs with nil'
|
||||
},
|
||||
{ source: 'FLAGS (\\Seen) RFC822.SIZE 44827',
|
||||
expected: ['FLAGS', ['\\Seen'], 'RFC822.SIZE', 44827],
|
||||
what: 'Simple, two key-value pairs with list'
|
||||
},
|
||||
{ source: 'RFC822.SIZE 9007199254740993',
|
||||
expected: ['RFC822.SIZE', '9007199254740993'],
|
||||
what: 'Integer exceeding JavaScript max int size'
|
||||
},
|
||||
{ source: 'FLAGS (\\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700"',
|
||||
expected: ['FLAGS', ['\\Seen'], 'INTERNALDATE', '17-Jul-1996 02:44:25 -0700'],
|
||||
what: 'Quoted string'
|
||||
},
|
||||
{ source: '("Foo")("Bar") ("Baz")',
|
||||
expected: [['Foo'], ['Bar'], ['Baz']],
|
||||
what: 'Lists with varying spacing'
|
||||
},
|
||||
{ source: '""',
|
||||
expected: [''],
|
||||
what: 'Empty quoted string'
|
||||
},
|
||||
].forEach(function(v) {
|
||||
var result;
|
||||
|
||||
try {
|
||||
result = parseExpr(v.source);
|
||||
} catch (e) {
|
||||
console.log(makeMsg(v.what, 'JS Exception: ' + e.stack));
|
||||
return;
|
||||
}
|
||||
|
||||
assert.deepEqual(result,
|
||||
v.expected,
|
||||
makeMsg(v.what,
|
||||
'Result mismatch:'
|
||||
+ '\nParsed: ' + inspect(result, false, 10)
|
||||
+ '\nExpected: ' + inspect(v.expected, false, 10)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
function makeMsg(what, msg) {
|
||||
return '[' + what + ']: ' + msg;
|
||||
}
|
@ -1,236 +0,0 @@
|
||||
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);
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
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);
|
||||
}
|
@ -0,0 +1,422 @@
|
||||
var Parser = require('../lib/Parser').Parser;
|
||||
|
||||
var assert = require('assert'),
|
||||
crypto = require('crypto'),
|
||||
inspect = require('util').inspect;
|
||||
|
||||
var CR = '\r', LF = '\n', CRLF = CR + LF;
|
||||
|
||||
[
|
||||
{ source: ['A1 OK LOGIN completed', CRLF],
|
||||
expected: [ { type: 'ok',
|
||||
tagnum: 1,
|
||||
textCode: undefined,
|
||||
text: 'LOGIN completed'
|
||||
}
|
||||
],
|
||||
what: 'Tagged OK'
|
||||
},
|
||||
{ source: ['IDLE OK IDLE terminated', CRLF],
|
||||
expected: [ 'IDLE OK IDLE terminated' ],
|
||||
what: 'Unknown line'
|
||||
},
|
||||
{ source: ['+ idling', CRLF],
|
||||
expected: [ { textCode: undefined,
|
||||
text: 'idling'
|
||||
}
|
||||
],
|
||||
what: 'Continuation'
|
||||
},
|
||||
{ source: ['+ [ALERT] idling', CRLF],
|
||||
expected: [ { textCode: 'ALERT',
|
||||
text: 'idling'
|
||||
}
|
||||
],
|
||||
what: 'Continuation with text code'
|
||||
},
|
||||
{ source: ['* NAMESPACE ',
|
||||
'(("" "/")) ',
|
||||
'(("~" "/")) ',
|
||||
'(("#shared/" "/")("#public/" "/")("#ftp/" "/")("#news." "."))',
|
||||
CRLF],
|
||||
expected: [ { type: 'namespace',
|
||||
num: undefined,
|
||||
textCode: undefined,
|
||||
text: {
|
||||
personal: [
|
||||
{ prefix: '',
|
||||
delimiter: '/',
|
||||
extensions: undefined
|
||||
}
|
||||
],
|
||||
other: [
|
||||
{ prefix: '~',
|
||||
delimiter: '/',
|
||||
extensions: undefined
|
||||
}
|
||||
],
|
||||
shared: [
|
||||
{ prefix: '#shared/',
|
||||
delimiter: '/',
|
||||
extensions: undefined
|
||||
},
|
||||
{ prefix: '#public/',
|
||||
delimiter: '/',
|
||||
extensions: undefined
|
||||
},
|
||||
{ prefix: '#ftp/',
|
||||
delimiter: '/',
|
||||
extensions: undefined
|
||||
},
|
||||
{ prefix: '#news.',
|
||||
delimiter: '.',
|
||||
extensions: undefined
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
what: 'Multiple namespaces'
|
||||
},
|
||||
{ source: ['* NAMESPACE ',
|
||||
'(("" "/" "X-PARAM" ("FLAG1" "FLAG2"))) ',
|
||||
'NIL ',
|
||||
'NIL',
|
||||
CRLF],
|
||||
expected: [ { type: 'namespace',
|
||||
num: undefined,
|
||||
textCode: undefined,
|
||||
text: {
|
||||
personal: [
|
||||
{ prefix: '',
|
||||
delimiter: '/',
|
||||
extensions: {
|
||||
'X-PARAM': [ 'FLAG1', 'FLAG2' ]
|
||||
}
|
||||
}
|
||||
],
|
||||
other: null,
|
||||
shared: null
|
||||
}
|
||||
}
|
||||
],
|
||||
what: 'Multiple namespaces'
|
||||
},
|
||||
{ source: ['* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)', CRLF],
|
||||
expected: [ { type: 'flags',
|
||||
num: undefined,
|
||||
textCode: undefined,
|
||||
text: [
|
||||
'\\Answered',
|
||||
'\\Flagged',
|
||||
'\\Deleted',
|
||||
'\\Seen',
|
||||
'\\Draft'
|
||||
]
|
||||
}
|
||||
],
|
||||
what: 'Flags'
|
||||
},
|
||||
{ source: ['* SEARCH 2 3 6', CRLF],
|
||||
expected: [ { type: 'search',
|
||||
num: undefined,
|
||||
textCode: undefined,
|
||||
text: [ 2, 3, 6 ]
|
||||
}
|
||||
],
|
||||
what: 'Search'
|
||||
},
|
||||
{ source: ['* LIST (\\Noselect) "/" ~/Mail/foo', CRLF],
|
||||
expected: [ { type: 'list',
|
||||
num: undefined,
|
||||
textCode: undefined,
|
||||
text: {
|
||||
flags: [ '\\Noselect' ],
|
||||
delimiter: '/',
|
||||
name: '~/Mail/foo'
|
||||
}
|
||||
}
|
||||
],
|
||||
what: 'List'
|
||||
},
|
||||
{ source: ['* STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)', CRLF],
|
||||
expected: [ { type: 'status',
|
||||
num: undefined,
|
||||
textCode: undefined,
|
||||
text: {
|
||||
name: 'blurdybloop',
|
||||
attrs: { messages: 231, uidnext: 44292 }
|
||||
}
|
||||
}
|
||||
],
|
||||
what: 'Status'
|
||||
},
|
||||
{ source: ['* OK [UNSEEN 17] Message 17 is the first unseen message', CRLF],
|
||||
expected: [ { type: 'ok',
|
||||
num: undefined,
|
||||
textCode: {
|
||||
key: 'UNSEEN',
|
||||
val: 17
|
||||
},
|
||||
text: 'Message 17 is the first unseen message'
|
||||
}
|
||||
],
|
||||
what: 'Untagged OK (with text code, with text)'
|
||||
},
|
||||
{ source: ['* OK [PERMANENTFLAGS (\\Deleted \\Seen \\*)] Limited', CRLF],
|
||||
expected: [ { type: 'ok',
|
||||
num: undefined,
|
||||
textCode: {
|
||||
key: 'PERMANENTFLAGS',
|
||||
val: [ '\\Deleted', '\\Seen', '\\*' ]
|
||||
},
|
||||
text: 'Limited'
|
||||
}
|
||||
],
|
||||
what: 'Untagged OK (with text code, with text)'
|
||||
},
|
||||
{ source: ['* OK [UNSEEN 17]', CRLF],
|
||||
expected: [ { type: 'ok',
|
||||
num: undefined,
|
||||
textCode: undefined,
|
||||
text: '[UNSEEN 17]'
|
||||
}
|
||||
],
|
||||
what: 'Untagged OK (no text code, with text) (RFC violation)'
|
||||
},
|
||||
{ source: ['* OK IMAP4rev1 Service Ready', CRLF],
|
||||
expected: [ { type: 'ok',
|
||||
num: undefined,
|
||||
textCode: undefined,
|
||||
text: 'IMAP4rev1 Service Ready'
|
||||
}
|
||||
],
|
||||
what: 'Untagged OK (no text code, with text)'
|
||||
},
|
||||
{ source: ['* OK', CRLF], // I have seen servers that send stuff like this ..
|
||||
expected: [ { type: 'ok',
|
||||
num: undefined,
|
||||
textCode: undefined,
|
||||
text: undefined
|
||||
}
|
||||
],
|
||||
what: 'Untagged OK (no text code, no text) (RFC violation)'
|
||||
},
|
||||
{ source: ['* 18 EXISTS', CRLF],
|
||||
expected: [ { type: 'exists',
|
||||
num: 18,
|
||||
textCode: undefined,
|
||||
text: undefined
|
||||
}
|
||||
],
|
||||
what: 'Untagged EXISTS'
|
||||
},
|
||||
{ source: ['* 2 RECENT', CRLF],
|
||||
expected: [ { type: 'recent',
|
||||
num: 2,
|
||||
textCode: undefined,
|
||||
text: undefined
|
||||
}
|
||||
],
|
||||
what: 'Untagged RECENT'
|
||||
},
|
||||
{ source: ['* 12 FETCH (BODY[HEADER] {342}', CRLF,
|
||||
'Date: Wed, 17 Jul 1996 02:23:25 -0700 (PDT)', CRLF,
|
||||
'From: Terry Gray <gray@cac.washington.edu>', CRLF,
|
||||
'Subject: IMAP4rev1 WG mtg summary and minutes', CRLF,
|
||||
'To: imap@cac.washington.edu', CRLF,
|
||||
'cc: minutes@CNRI.Reston.VA.US, John Klensin <KLENSIN@MIT.EDU>', CRLF,
|
||||
'Message-Id: <B27397-0100000@cac.washington.edu>', CRLF,
|
||||
'MIME-Version: 1.0', CRLF,
|
||||
'Content-Type: TEXT/PLAIN; CHARSET=US-ASCII', CRLF, CRLF,
|
||||
')', CRLF],
|
||||
expected: [ { seqno: 12,
|
||||
which: 'HEADER',
|
||||
size: 342
|
||||
},
|
||||
{ type: 'fetch',
|
||||
num: 12,
|
||||
textCode: undefined,
|
||||
text: {}
|
||||
}
|
||||
],
|
||||
bodySHA1s: ['1f96faf50f6410f99237791f9e3b89454bf93fa7'],
|
||||
what: 'Untagged FETCH (body)'
|
||||
},
|
||||
{ source: ['* 12 FETCH (INTERNALDATE {26}', CRLF,
|
||||
'17-Jul-1996 02:44:25 -0700)' + CRLF],
|
||||
expected: [ { type: 'fetch',
|
||||
num: 12,
|
||||
textCode: undefined,
|
||||
text: {
|
||||
internaldate: new Date('17-Jul-1996 02:44:25 -0700')
|
||||
}
|
||||
}
|
||||
],
|
||||
what: 'Untagged FETCH with non-body literal'
|
||||
},
|
||||
{ source: ['* 12 FETCH (INTERNALDATE {2',
|
||||
'6}' + CRLF + '17-Jul-1996 02:44:25 -0700)' + CRLF],
|
||||
expected: [ { type: 'fetch',
|
||||
num: 12,
|
||||
textCode: undefined,
|
||||
text: {
|
||||
internaldate: new Date('17-Jul-1996 02:44:25 -0700')
|
||||
}
|
||||
}
|
||||
],
|
||||
what: 'Untagged FETCH with non-body literal (length split)'
|
||||
},
|
||||
{ source: ['* 12 FETCH (INTERNALDATE {26}', CRLF,
|
||||
'17-Jul-1996 02:44:25 -0700)' + CR,
|
||||
LF],
|
||||
expected: [ { type: 'fetch',
|
||||
num: 12,
|
||||
textCode: undefined,
|
||||
text: {
|
||||
internaldate: new Date('17-Jul-1996 02:44:25 -0700')
|
||||
}
|
||||
}
|
||||
],
|
||||
what: 'Untagged FETCH with non-body literal (split CRLF)'
|
||||
},
|
||||
{ source: ['* 12 FETCH (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))',
|
||||
CRLF],
|
||||
expected: [ { type: 'fetch',
|
||||
num: 12,
|
||||
textCode: undefined,
|
||||
text: {
|
||||
flags: [ '\\Seen' ],
|
||||
internaldate: new Date('17-Jul-1996 02:44:25 -0700'),
|
||||
'rfc822.size': 4286,
|
||||
envelope: {
|
||||
date: new Date('Wed, 17 Jul 1996 02:23:25 -0700 (PDT)'),
|
||||
subject: 'IMAP4rev1 WG mtg summary and minutes',
|
||||
from: [
|
||||
{ name: 'Terry Gray',
|
||||
mailbox: 'gray',
|
||||
host: 'cac.washington.edu'
|
||||
}
|
||||
],
|
||||
sender: [
|
||||
{ name: 'Terry Gray',
|
||||
mailbox: 'gray',
|
||||
host: 'cac.washington.edu'
|
||||
}
|
||||
],
|
||||
replyTo: [
|
||||
{ name: 'Terry Gray',
|
||||
mailbox: 'gray',
|
||||
host: 'cac.washington.edu'
|
||||
}
|
||||
],
|
||||
to: [
|
||||
{ name: null,
|
||||
mailbox: 'imap',
|
||||
host: 'cac.washington.edu'
|
||||
}
|
||||
],
|
||||
cc: [
|
||||
{ name: null,
|
||||
mailbox: 'minutes',
|
||||
host: 'CNRI.Reston.VA.US'
|
||||
},
|
||||
{ name: 'John Klensin',
|
||||
mailbox: 'KLENSIN',
|
||||
host: 'MIT.EDU'
|
||||
}
|
||||
],
|
||||
bcc: null,
|
||||
inReplyTo: null,
|
||||
messageId: '<B27397-0100000@cac.washington.edu>'
|
||||
},
|
||||
body: [
|
||||
{ partID: '1',
|
||||
type: 'text',
|
||||
subtype: 'plain',
|
||||
params: { charset: 'US-ASCII' },
|
||||
id: null,
|
||||
description: null,
|
||||
encoding: '7BIT',
|
||||
size: 3028,
|
||||
lines: 92
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
what: 'Untagged FETCH (flags, date, size, envelope, body[structure])'
|
||||
},
|
||||
].forEach(function(v) {
|
||||
var ss = new require('stream').Readable(), p, result = [];
|
||||
ss._read = function(){};
|
||||
|
||||
p = new Parser(ss);
|
||||
p.on('tagged', function(info) {
|
||||
result.push(info);
|
||||
});
|
||||
p.on('untagged', function(info) {
|
||||
result.push(info);
|
||||
});
|
||||
p.on('continue', function(info) {
|
||||
result.push(info);
|
||||
});
|
||||
p.on('other', function(line) {
|
||||
result.push(line);
|
||||
});
|
||||
p.on('body', function(stream, info) {
|
||||
result.push(info);
|
||||
if (Array.isArray(v.bodySHA1s)) {
|
||||
var hash = crypto.createHash('sha1');
|
||||
stream.on('data', function(d) {
|
||||
hash.update(d);
|
||||
});
|
||||
stream.on('end', function() {
|
||||
var calculated = hash.digest('hex'),
|
||||
expected = v.bodySHA1s.shift();
|
||||
assert.equal(calculated,
|
||||
expected,
|
||||
makeMsg(v.what,
|
||||
'Body SHA1 mismatch:'
|
||||
+ '\nCalculated: ' + calculated
|
||||
+ '\nExpected: ' + expected
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
v.source.forEach(function(chunk) {
|
||||
ss.push(chunk);
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(makeMsg(v.what, 'JS Exception: ' + e.stack));
|
||||
return;
|
||||
}
|
||||
assert.deepEqual(result,
|
||||
v.expected,
|
||||
makeMsg(v.what,
|
||||
'Result mismatch:'
|
||||
+ '\nParsed: ' + inspect(result, false, 10)
|
||||
+ '\nExpected: ' + inspect(v.expected, false, 10)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
function makeMsg(what, msg) {
|
||||
return '[' + what + ']: ' + msg;
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
require('fs').readdirSync(__dirname).forEach(function(f) {
|
||||
if (f.substr(0, 5) === 'test-')
|
||||
require('./' + f);
|
||||
});
|
Loading…
Reference in New Issue