1240 lines
45 KiB
JavaScript
1240 lines
45 KiB
JavaScript
/*eslint no-unused-expressions: 0, prefer-arrow-callback: 0 */
|
|
|
|
'use strict';
|
|
|
|
const { parser } = require('../lib/handler/imap-handler');
|
|
const mimetorture = require('./fixtures/serialized-mimetorture');
|
|
|
|
let asyncWrapper = (test, handler) => {
|
|
handler(test)
|
|
.then(() => test.done())
|
|
.catch(err => {
|
|
test.ifError(err);
|
|
test.done();
|
|
});
|
|
};
|
|
|
|
let asyncWrapperFail = (test, handler) => {
|
|
handler(test)
|
|
.then(() => {
|
|
test.ok(false);
|
|
test.done();
|
|
})
|
|
.catch(err => {
|
|
test.ok(err);
|
|
test.done();
|
|
});
|
|
};
|
|
|
|
module.exports['IMAP Parser: Tags: get TAG'] = test => asyncWrapper(test, async test => test.equal((await parser('TAG1 CMD')).tag, 'TAG1'));
|
|
module.exports['IMAP Parser: Tags: space before TAG'] = test => asyncWrapperFail(test, async test => test.ok(await parser(' TAG CMD')));
|
|
module.exports['IMAP Parser: Tags: fail empty TAG'] = test => asyncWrapperFail(test, async test => test.ok(await parser('')));
|
|
module.exports['IMAP Parser: Tags: * TAG'] = test => asyncWrapper(test, async test => test.equal((await parser('* CMD')).tag, '*'));
|
|
module.exports['IMAP Parser: Tags: + TAG'] = test => asyncWrapper(test, async test => test.equal((await parser('+ CMD')).tag, '+'));
|
|
module.exports['IMAP Parser: Tags: fail TAG only'] = test => asyncWrapperFail(test, async test => test.ok(await parser('TAG1')));
|
|
module.exports['IMAP Parser: Tags: fail invalid char in TAG'] = test => asyncWrapperFail(test, async test => test.ok(await parser('TAG"1 CMD')));
|
|
|
|
module.exports['IMAP Parser: Command: single'] = test => asyncWrapper(test, async test => test.equal((await parser('TAG1 CMD')).command, 'CMD'));
|
|
module.exports['IMAP Parser: Command: multi word'] = test =>
|
|
asyncWrapper(test, async test => test.equal((await parser('TAG1 UID FETCH')).command, 'UID FETCH'));
|
|
module.exports['IMAP Parser: Command: fail extra ws'] = test => asyncWrapperFail(test, async test => test.ok(await parser('TAG1 CMD')));
|
|
module.exports['IMAP Parser: Command: fail empty command'] = test => asyncWrapperFail(test, async test => test.ok(await parser('TAG1 ')));
|
|
module.exports['IMAP Parser: Command: fail invalid char in command'] = test => asyncWrapperFail(test, async test => test.ok(await parser('TAG1 CM=D')));
|
|
|
|
module.exports['IMAP Parser: Args: allow trailing whitespace and empty arguments'] = test =>
|
|
asyncWrapper(test, async test => test.deepEqual(await parser('* SEARCH '), { tag: '*', command: 'SEARCH' }));
|
|
|
|
module.exports['IMAP Parser: Attributes: single atom'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD FED')).attributes, [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'FED'
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Attributes: multiple atoms'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD FED TED')).attributes, [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'FED'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'TED'
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Attributes: special char in atom'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD %')).attributes, [
|
|
{
|
|
type: 'ATOM',
|
|
value: '%'
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Attributes: escaped char in atom'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD \\*')).attributes, [
|
|
{
|
|
type: 'ATOM',
|
|
value: '\\*'
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Attributes: sub list'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('12.82 STATUS [Gmail].Trash (UIDNEXT UNSEEN HIGHESTMODSEQ)')).attributes, [
|
|
{
|
|
type: 'ATOM',
|
|
value: '[Gmail].Trash'
|
|
},
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: 'UIDNEXT'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'UNSEEN'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'HIGHESTMODSEQ'
|
|
}
|
|
]
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Attributes: single string'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD "ABCDE"')).attributes, [
|
|
{
|
|
type: 'STRING',
|
|
value: 'ABCDE'
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Attributes: multiple strings'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD "ABCDE" "DEFGH"')).attributes, [
|
|
{
|
|
type: 'STRING',
|
|
value: 'ABCDE'
|
|
},
|
|
{
|
|
type: 'STRING',
|
|
value: 'DEFGH'
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Attributes: invalid char in string'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('* 1 FETCH (BODY[] "\xc2")')).attributes, [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'FETCH'
|
|
},
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: 'BODY',
|
|
section: []
|
|
},
|
|
{
|
|
type: 'STRING',
|
|
value: '\xc2'
|
|
}
|
|
]
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Lists: single atom'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD (1234)')).attributes, [
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: '1234'
|
|
}
|
|
]
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Lists: multiple atoms'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD (1234 TERE)')).attributes, [
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: '1234'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'TERE'
|
|
}
|
|
]
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Lists: multiple lists'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD (1234)(TERE)')).attributes, [
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: '1234'
|
|
}
|
|
],
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: 'TERE'
|
|
}
|
|
]
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Lists: extra whitespace in start of list'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD ( 1234)')).attributes, [
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: '1234'
|
|
}
|
|
]
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Lists: extra whitespace in end of list'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD (1234 )')).attributes, [
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: '1234'
|
|
}
|
|
]
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Lists: extra whitespace after list'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD (1234) ')).attributes, [
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: '1234'
|
|
}
|
|
]
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Lists: nested list 1'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD (((TERE)) VANA)')).attributes, [
|
|
[
|
|
[
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: 'TERE'
|
|
}
|
|
]
|
|
],
|
|
{
|
|
type: 'ATOM',
|
|
value: 'VANA'
|
|
}
|
|
]
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Lists: nested list 2'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD (( (TERE)) VANA)')).attributes, [
|
|
[
|
|
[
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: 'TERE'
|
|
}
|
|
]
|
|
],
|
|
{
|
|
type: 'ATOM',
|
|
value: 'VANA'
|
|
}
|
|
]
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Lists: nested list 3'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD (((TERE) ) VANA)')).attributes, [
|
|
[
|
|
[
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: 'TERE'
|
|
}
|
|
]
|
|
],
|
|
{
|
|
type: 'ATOM',
|
|
value: 'VANA'
|
|
}
|
|
]
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Literals: single literal'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD {4}\r\n', { literals: [Buffer.from('abcd')] })).attributes, [
|
|
{
|
|
type: 'LITERAL',
|
|
value: Buffer.from('abcd')
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Literals: literal with NULL'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD {4}\r\n', { literals: [Buffer.from('ab\x00d')] })).attributes, [
|
|
{
|
|
type: 'LITERAL',
|
|
value: Buffer.from('ab\x00d')
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Literals: literal8'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD ~{4}\r\n', { literals: [Buffer.from('ab\x00d')] })).attributes, [
|
|
{
|
|
type: 'LITERAL',
|
|
value: Buffer.from('ab\x00d')
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Literals: unexpected literal8 prefix'] = test =>
|
|
asyncWrapper(test, async test => {
|
|
try {
|
|
await parser('TAG1 CMD ~\r\n');
|
|
test.ok(false, 'Must throw');
|
|
} catch (err) {
|
|
test.ok(err, 'Error must exist');
|
|
}
|
|
});
|
|
|
|
module.exports['IMAP Parser: Literals: multiple literals'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD {4}\r\n {4}\r\n', { literals: [Buffer.from('abcd'), Buffer.from('kere')] })).attributes, [
|
|
{
|
|
type: 'LITERAL',
|
|
value: Buffer.from('abcd')
|
|
},
|
|
{
|
|
type: 'LITERAL',
|
|
value: Buffer.from('kere')
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Literals: list'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD ({4}\r\n {4}\r\n)', { literals: [Buffer.from('abcd'), Buffer.from('kere')] })).attributes, [
|
|
[
|
|
{
|
|
type: 'LITERAL',
|
|
value: Buffer.from('abcd')
|
|
},
|
|
{
|
|
type: 'LITERAL',
|
|
value: Buffer.from('kere')
|
|
}
|
|
]
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Tags: fail extra ws after literal'] = test =>
|
|
asyncWrapperFail(test, async test => test.ok(await parser('TAG1 CMD {4}\r\n{4} \r\n', { literals: [Buffer.from('abcd'), Buffer.from('kere')] })));
|
|
|
|
module.exports['IMAP Parser: Literals: allow zero length literal in the end of a list'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD ({0}\r\n)')).attributes, [
|
|
[
|
|
{
|
|
type: 'LITERAL',
|
|
value: Buffer.from('')
|
|
}
|
|
]
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Section: empty'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD BODY[]')).attributes, [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'BODY',
|
|
section: []
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Section: list'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD BODY[(KERE)]')).attributes, [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'BODY',
|
|
section: [
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: 'KERE'
|
|
}
|
|
]
|
|
]
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Section: allow trailing ws'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD BODY[HEADER.FIELDS (Subject From) ]')).attributes, [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'BODY',
|
|
section: [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'HEADER.FIELDS'
|
|
},
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: 'Subject'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'From'
|
|
}
|
|
]
|
|
]
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Tags: fail unknown section'] = test => asyncWrapperFail(test, async test => test.ok(await parser('TAG1 CMD KODY[]')));
|
|
|
|
module.exports['IMAP Parser: Readable: simple'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual(await parser('* OK Hello world!'), {
|
|
command: 'OK',
|
|
tag: '*',
|
|
attributes: [
|
|
{
|
|
type: 'TEXT',
|
|
value: 'Hello world!'
|
|
}
|
|
]
|
|
})
|
|
);
|
|
|
|
module.exports['IMAP Parser: Readable: section'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual(await parser('* OK [CAPABILITY IDLE] Hello world!'), {
|
|
command: 'OK',
|
|
tag: '*',
|
|
attributes: [
|
|
{
|
|
section: [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'CAPABILITY'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'IDLE'
|
|
}
|
|
],
|
|
type: 'ATOM',
|
|
value: ''
|
|
},
|
|
{
|
|
type: 'TEXT',
|
|
value: 'Hello world!'
|
|
}
|
|
]
|
|
})
|
|
);
|
|
|
|
// USEATTR is from RFC6154; we are testing that just an ATOM
|
|
// on its own will parse successfully here. (All of the
|
|
// RFC5530 codes are also single atoms.)
|
|
module.exports['IMAP Parser: Section: USEATTR'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual(await parser('TAG1 OK [USEATTR] \\All not supported'), {
|
|
tag: 'TAG1',
|
|
command: 'OK',
|
|
attributes: [
|
|
{
|
|
type: 'ATOM',
|
|
value: '',
|
|
section: [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'USEATTR'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
type: 'TEXT',
|
|
value: '\\All not supported'
|
|
}
|
|
]
|
|
})
|
|
);
|
|
|
|
// RFC5267 defines the NOUPDATE error. Including for quote /
|
|
// string coverage.
|
|
module.exports['IMAP Parser: Section: NOUPDATE'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual(await parser('* NO [NOUPDATE "B02"] Too many contexts'), {
|
|
tag: '*',
|
|
command: 'NO',
|
|
attributes: [
|
|
{
|
|
type: 'ATOM',
|
|
value: '',
|
|
section: [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'NOUPDATE'
|
|
},
|
|
{
|
|
type: 'STRING',
|
|
value: 'B02'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
type: 'TEXT',
|
|
value: 'Too many contexts'
|
|
}
|
|
]
|
|
})
|
|
);
|
|
|
|
// RFC5464 defines the METADATA response code; adding this to
|
|
// ensure the transition for when '2199' hits ']' is handled
|
|
// safely.
|
|
module.exports['IMAP Parser: Section: METADATA'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual(await parser('TAG1 OK [METADATA LONGENTRIES 2199] GETMETADATA complete'), {
|
|
tag: 'TAG1',
|
|
command: 'OK',
|
|
attributes: [
|
|
{
|
|
type: 'ATOM',
|
|
value: '',
|
|
section: [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'METADATA'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'LONGENTRIES'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: '2199'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
type: 'TEXT',
|
|
value: 'GETMETADATA complete'
|
|
}
|
|
]
|
|
})
|
|
);
|
|
|
|
// RFC4467 defines URLMECH. Included because of the example
|
|
// third atom involves base64-encoding which is somewhat unusual
|
|
module.exports['IMAP Parser: Section: URLMECH'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual(await parser('TAG1 OK [URLMECH INTERNAL XSAMPLE=P34OKhO7VEkCbsiYY8rGEg==] done'), {
|
|
tag: 'TAG1',
|
|
command: 'OK',
|
|
attributes: [
|
|
{
|
|
type: 'ATOM',
|
|
value: '',
|
|
section: [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'URLMECH'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'INTERNAL'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'XSAMPLE=P34OKhO7VEkCbsiYY8rGEg=='
|
|
}
|
|
]
|
|
},
|
|
{
|
|
type: 'TEXT',
|
|
value: 'done'
|
|
}
|
|
]
|
|
})
|
|
);
|
|
|
|
// RFC2221 defines REFERRAL where the argument is an imapurl
|
|
// (defined by RFC2192 which is obsoleted by RFC5092) which
|
|
// is significantly more complicated than the rest of the IMAP
|
|
// grammar and which was based on the RFC2060 grammar where
|
|
// resp_text_code included:
|
|
// atom [SPACE 1*<any TEXT_CHAR except ']'>]
|
|
// So this is just a test case of our explicit special-casing
|
|
// of REFERRAL.
|
|
module.exports['IMAP Parser: Section: REFERRAL'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual(await parser('TAG1 NO [REFERRAL IMAP://user;AUTH=*@SERVER2/] Remote Server'), {
|
|
tag: 'TAG1',
|
|
command: 'NO',
|
|
attributes: [
|
|
{
|
|
type: 'ATOM',
|
|
value: '',
|
|
section: [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'REFERRAL'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'IMAP://user;AUTH=*@SERVER2/'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
type: 'TEXT',
|
|
value: 'Remote Server'
|
|
}
|
|
]
|
|
})
|
|
);
|
|
|
|
// PERMANENTFLAGS is from RFC3501. Its syntax is also very
|
|
// similar to BADCHARSET, except BADCHARSET has astrings
|
|
// inside the list.
|
|
module.exports['IMAP Parser: Section: PERMANENTFLAGS'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual(await parser('* OK [PERMANENTFLAGS (de:hacking $label kt-evalution [css3-page] \\*)] Flags permitted.'), {
|
|
tag: '*',
|
|
command: 'OK',
|
|
attributes: [
|
|
{
|
|
type: 'ATOM',
|
|
value: '',
|
|
section: [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'PERMANENTFLAGS'
|
|
},
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: 'de:hacking'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: '$label'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'kt-evalution'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: '[css3-page]'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: '\\*'
|
|
}
|
|
]
|
|
]
|
|
},
|
|
{
|
|
type: 'TEXT',
|
|
value: 'Flags permitted.'
|
|
}
|
|
]
|
|
})
|
|
);
|
|
|
|
// COPYUID is from RFC4315 and included the previously failing
|
|
// parsing situation of a sequence terminated by ']' rather than
|
|
// whitespace.
|
|
module.exports['IMAP Parser: Section: COPYUID'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual(await parser('TAG1 OK [COPYUID 4 1417051618:1417051620 1421730687:1421730689] COPY completed'), {
|
|
tag: 'TAG1',
|
|
command: 'OK',
|
|
attributes: [
|
|
{
|
|
type: 'ATOM',
|
|
value: '',
|
|
section: [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'COPYUID'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: '4'
|
|
},
|
|
{
|
|
type: 'SEQUENCE',
|
|
value: '1417051618:1417051620'
|
|
},
|
|
{
|
|
type: 'SEQUENCE',
|
|
value: '1421730687:1421730689'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
type: 'TEXT',
|
|
value: 'COPY completed'
|
|
}
|
|
]
|
|
})
|
|
);
|
|
|
|
// MODIFIED is from RFC4551 and is basically the same situation
|
|
// as the COPYUID case, but in this case our example sequences
|
|
// have commas in them. (Note that if there was no comma, the
|
|
// '7,9' payload would end up an ATOM.)
|
|
module.exports['IMAP Parser: Section: MODIFIED'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual(await parser('TAG1 OK [MODIFIED 7,9] Conditional STORE failed'), {
|
|
tag: 'TAG1',
|
|
command: 'OK',
|
|
attributes: [
|
|
{
|
|
type: 'ATOM',
|
|
value: '',
|
|
section: [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'MODIFIED'
|
|
},
|
|
{
|
|
type: 'SEQUENCE',
|
|
value: '7,9'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
type: 'TEXT',
|
|
value: 'Conditional STORE failed'
|
|
}
|
|
]
|
|
})
|
|
);
|
|
|
|
module.exports['IMAP Parser: Partial: Start'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD BODY[]<0>')).attributes, [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'BODY',
|
|
section: [],
|
|
partial: [0]
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Partial: Start.End'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD BODY[]<12.45>')).attributes, [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'BODY',
|
|
section: [],
|
|
partial: [12, 45]
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Partial: Section'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD BODY[HEADER.FIELDS (Subject From)]<12.45>')).attributes, [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'BODY',
|
|
section: [
|
|
// keep indentation
|
|
{
|
|
type: 'ATOM',
|
|
value: 'HEADER.FIELDS'
|
|
},
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: 'Subject'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'From'
|
|
}
|
|
]
|
|
],
|
|
partial: [12, 45]
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Partial: fail unknown section'] = test => asyncWrapperFail(test, async test => test.ok(await parser('TAG1 CMD KODY<0.123>')));
|
|
|
|
module.exports['IMAP Parser: Partial: fail zero prefix for start'] = test => asyncWrapperFail(test, async test => test.ok(await parser('TAG1 CMD BODY[]<01>')));
|
|
|
|
module.exports['IMAP Parser: Partial: fail zero prefix for end'] = test => asyncWrapperFail(test, async test => test.ok(await parser('TAG1 CMD BODY[]<0.01>')));
|
|
|
|
module.exports['IMAP Parser: Partial: fail extra separator'] = test => asyncWrapperFail(test, async test => test.ok(await parser('TAG1 CMD BODY[]<0.1.>')));
|
|
|
|
module.exports['IMAP Parser: Sequence: mixed'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD *:4,5:7 TEST')).attributes, [
|
|
{
|
|
type: 'SEQUENCE',
|
|
value: '*:4,5:7'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'TEST'
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Sequence: range'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD 1:* TEST')).attributes, [
|
|
{
|
|
type: 'SEQUENCE',
|
|
value: '1:*'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'TEST'
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Sequence: limited range'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('TAG1 CMD *:4 TEST')).attributes, [
|
|
{
|
|
type: 'SEQUENCE',
|
|
value: '*:4'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'TEST'
|
|
}
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: Sequence: fail partial range'] = test => asyncWrapperFail(test, async test => test.ok(await parser('TAG1 CMD *:4,5:')));
|
|
|
|
module.exports['IMAP Parser: Sequence: fail invalid chars'] = test => asyncWrapperFail(test, async test => test.ok(await parser('TAG1 CMD *:4,5:TEST TEST')));
|
|
|
|
module.exports['IMAP Parser: Sequence: fail partial range w/ args'] = test =>
|
|
asyncWrapperFail(test, async test => test.ok(await parser('TAG1 CMD *:4,5: TEST')));
|
|
|
|
module.exports['IMAP Parser: Sequence: fail missing colon'] = test => asyncWrapperFail(test, async test => test.ok(await parser('TAG1 CMD *4,5 TEST')));
|
|
|
|
module.exports['IMAP Parser: Sequence: fail non-range wildchar 1'] = test => asyncWrapperFail(test, async test => test.ok(await parser('TAG1 CMD *,5 TEST')));
|
|
|
|
module.exports['IMAP Parser: Sequence: fail non-range wildchar 2'] = test => asyncWrapperFail(test, async test => test.ok(await parser('TAG1 CMD 5,* TEST')));
|
|
|
|
module.exports['IMAP Parser: Sequence: failextra comma'] = test => asyncWrapperFail(test, async test => test.ok(await parser('TAG1 CMD 5, TEST')));
|
|
|
|
module.exports['IMAP Parser: escaped quotes'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('* 331 FETCH (ENVELOPE ("=?ISO-8859-1?Q?\\"G=FCnter__Hammerl\\"?="))')).attributes, [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'FETCH'
|
|
},
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: 'ENVELOPE'
|
|
},
|
|
[
|
|
{
|
|
type: 'STRING',
|
|
value: '=?ISO-8859-1?Q?"G=FCnter__Hammerl"?='
|
|
}
|
|
]
|
|
]
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser: mimetorture'] = test => asyncWrapper(test, async test => test.deepEqual(await parser(mimetorture.input), mimetorture.output));
|
|
|
|
module.exports['IMAP Parser, unicode select'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('F OK [READ-WRITE] [Gmail]/Visi laiškai selected. (Success) [THROTTLED]')).attributes, [
|
|
{
|
|
type: 'ATOM',
|
|
value: '',
|
|
section: [{ type: 'ATOM', value: 'READ-WRITE' }]
|
|
},
|
|
{ type: 'TEXT', value: '[Gmail]/Visi laiškai selected. (Success) [THROTTLED]' }
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser, unicode select 2'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('E OK [READ-WRITE] [Gmail]/Вся почта selected. (Success) [THROTTLED]')).attributes, [
|
|
{
|
|
type: 'ATOM',
|
|
value: '',
|
|
section: [{ type: 'ATOM', value: 'READ-WRITE' }]
|
|
},
|
|
{ type: 'TEXT', value: '[Gmail]/Вся почта selected. (Success) [THROTTLED]' }
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser, single quote in atom'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('* LIST (HasNoChildren UnMarked) "/" \'a')).attributes, [
|
|
[
|
|
{ type: 'ATOM', value: 'HasNoChildren' },
|
|
{ type: 'ATOM', value: 'UnMarked' }
|
|
],
|
|
{ type: 'STRING', value: '/' },
|
|
{ type: 'ATOM', value: "'a" }
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser, unicode status 1'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('* STATUS Segregator/Społeczności (MESSAGES 0 UIDNEXT 1 UIDVALIDITY 1)')).attributes, [
|
|
{ type: 'ATOM', value: 'Segregator/Społeczności' },
|
|
[
|
|
{ type: 'ATOM', value: 'MESSAGES' },
|
|
{ type: 'ATOM', value: '0' },
|
|
{ type: 'ATOM', value: 'UIDNEXT' },
|
|
{ type: 'ATOM', value: '1' },
|
|
{ type: 'ATOM', value: 'UIDVALIDITY' },
|
|
{ type: 'ATOM', value: '1' }
|
|
]
|
|
])
|
|
);
|
|
module.exports['IMAP Parser, unicode status 12'] = test =>
|
|
asyncWrapper(test, async test =>
|
|
test.deepEqual((await parser('* STATUS Šegregator/Społeczności (MESSAGES 0 UIDNEXT 1 UIDVALIDITY 1)')).attributes, [
|
|
{ type: 'ATOM', value: 'Šegregator/Społeczności' },
|
|
[
|
|
{ type: 'ATOM', value: 'MESSAGES' },
|
|
{ type: 'ATOM', value: '0' },
|
|
{ type: 'ATOM', value: 'UIDNEXT' },
|
|
{ type: 'ATOM', value: '1' },
|
|
{ type: 'ATOM', value: 'UIDVALIDITY' },
|
|
{ type: 'ATOM', value: '1' }
|
|
]
|
|
])
|
|
);
|
|
|
|
module.exports['IMAP Parser, NO with a dot'] = test =>
|
|
asyncWrapper(test, async test => {
|
|
let parsed = await parser('X NO Server Unavailable. 15');
|
|
test.equal(parsed.command, 'NO');
|
|
test.deepEqual(parsed.attributes, [{ type: 'TEXT', value: 'Server Unavailable. 15' }]);
|
|
});
|
|
|
|
module.exports['IMAP Parser, no tag or response (Exchange)'] = test =>
|
|
asyncWrapper(test, async test => {
|
|
let parsed = await parser('Server Unavailable. 15');
|
|
console.log(parsed);
|
|
test.equal(parsed.command, 'BAD');
|
|
test.deepEqual(parsed.attributes, [{ type: 'TEXT', value: 'Server Unavailable. 15' }]);
|
|
});
|
|
|
|
module.exports['IMAP Parser, BAD with throttling'] = test =>
|
|
asyncWrapper(test, async test => {
|
|
let parsed = await parser('X BAD Request is throttled. Suggested Backoff Time: 92415 milliseconds');
|
|
test.equal(parsed.command, 'BAD');
|
|
test.deepEqual(parsed.attributes, [{ type: 'TEXT', value: 'Request is throttled. Suggested Backoff Time: 92415 milliseconds' }]);
|
|
});
|
|
|
|
module.exports['IMAP Parser, subfolder square bracket'] = test =>
|
|
asyncWrapper(test, async test => {
|
|
let parsed = await parser('* LIST (\\UnMarked) "." INBOX.[Airmail].Snooze');
|
|
test.deepEqual(parsed.attributes, [
|
|
[{ type: 'ATOM', value: '\\UnMarked' }],
|
|
{ type: 'STRING', value: '.' },
|
|
{ type: 'ATOM', value: 'INBOX.[Airmail].Snooze' }
|
|
]);
|
|
});
|
|
|
|
module.exports['IMAP Parser, FETCH with full range'] = test =>
|
|
asyncWrapper(test, async test => {
|
|
let parsed = await parser('* 32 FETCH (UID 32 RFC822.SIZE 3991 BODY[2.MIME] "(* 61B literal *)" BODY[2]<0.65536> "(* 6B literal *)")');
|
|
test.deepEqual(parsed.attributes, [
|
|
{
|
|
type: 'ATOM',
|
|
value: 'FETCH'
|
|
},
|
|
[
|
|
{
|
|
type: 'ATOM',
|
|
value: 'UID'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: '32'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'RFC822.SIZE'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: '3991'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'BODY',
|
|
section: [
|
|
{
|
|
type: 'ATOM',
|
|
value: '2.MIME'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
type: 'STRING',
|
|
value: '(* 61B literal *)'
|
|
},
|
|
{
|
|
type: 'ATOM',
|
|
value: 'BODY',
|
|
section: [
|
|
{
|
|
type: 'ATOM',
|
|
value: '2'
|
|
}
|
|
],
|
|
partial: [0, 65536]
|
|
},
|
|
{
|
|
type: 'STRING',
|
|
value: '(* 6B literal *)'
|
|
}
|
|
]
|
|
]);
|
|
});
|
|
|
|
module.exports['IMAP Parser, FETCH with BODYSTRUCTURE'] = test =>
|
|
asyncWrapper(test, async test => {
|
|
let parsed = await parser(
|
|
'* 1013 FETCH (UID 2986 MODSEQ (4960) BODYSTRUCTURE (("text" "plain" ("charset" "us-ascii") NIL NIL "7bit" 16 1 NIL NIL NIL NIL)("message" "rfc822" ("name" "Tellimuse Microsoft 365 Business Standard arve vaatamine.eml") NIL NIL "7bit" 370684 ("Mon, 16 Dec 2024 03:28:28 +0000" "Tellimuse Microsoft 365 Business Standard arve vaatamine" (("Microsoft" NIL "microsoft-noreply" "microsoft.com")) (("Microsoft" NIL "microsoft-noreply" "microsoft.com")) (("Microsoft" NIL "microsoft-noreply" "microsoft.com")) ((NIL NIL "andris.reinman" "gmail.com")) NIL NIL NIL "<58710631-775f-4c07-96ff-28a776f44d90@az.eastus2.microsoft.com>") ((("text" "plain" ("charset" "utf-8") NIL NIL "quoted-printable" 2866 80 NIL NIL NIL NIL)("text" "html" ("charset" "utf-8") NIL NIL "quoted-printable" 78392 1770 NIL NIL NIL NIL) "alternative" ("boundary" "=-EcWGOW6mwE+0T4lm385OWw==") NIL NIL NIL)("application" "octet-stream" ("name" "52482541500.pdf") NIL NIL "base64" 279430 NIL ("attachment" ("filename" "52482541500.pdf")) NIL NIL) "mixed" ("boundary" "=-1wJq2CLBJ6H+Zk2GPX9FKw==") NIL NIL NIL) 5580 NIL ("attachment" ("filename" "Tellimuse Microsoft 365 Business Standard arve vaatamine.eml")) NIL NIL) "mixed" ("boundary" "Apple-Mail=_F700EE9B-43B1-4EF1-95EE-CAA13391B333") NIL NIL NIL))'
|
|
);
|
|
|
|
test.deepEqual(parsed.attributes, [
|
|
{ type: 'ATOM', value: 'FETCH' },
|
|
[
|
|
{ type: 'ATOM', value: 'UID' },
|
|
{ type: 'ATOM', value: '2986' },
|
|
{ type: 'ATOM', value: 'MODSEQ' },
|
|
[{ type: 'ATOM', value: '4960' }],
|
|
{ type: 'ATOM', value: 'BODYSTRUCTURE' },
|
|
[
|
|
[
|
|
{ type: 'STRING', value: 'text' },
|
|
{ type: 'STRING', value: 'plain' },
|
|
[
|
|
{ type: 'STRING', value: 'charset' },
|
|
{ type: 'STRING', value: 'us-ascii' }
|
|
],
|
|
null,
|
|
null,
|
|
{ type: 'STRING', value: '7bit' },
|
|
{ type: 'ATOM', value: '16' },
|
|
{ type: 'ATOM', value: '1' },
|
|
null,
|
|
null,
|
|
null,
|
|
null
|
|
],
|
|
[
|
|
{ type: 'STRING', value: 'message' },
|
|
{ type: 'STRING', value: 'rfc822' },
|
|
[
|
|
{ type: 'STRING', value: 'name' },
|
|
{ type: 'STRING', value: 'Tellimuse Microsoft 365 Business Standard arve vaatamine.eml' }
|
|
],
|
|
null,
|
|
null,
|
|
{ type: 'STRING', value: '7bit' },
|
|
{ type: 'ATOM', value: '370684' },
|
|
[
|
|
{ type: 'STRING', value: 'Mon, 16 Dec 2024 03:28:28 +0000' },
|
|
{ type: 'STRING', value: 'Tellimuse Microsoft 365 Business Standard arve vaatamine' },
|
|
[
|
|
[
|
|
{ type: 'STRING', value: 'Microsoft' },
|
|
null,
|
|
{ type: 'STRING', value: 'microsoft-noreply' },
|
|
{ type: 'STRING', value: 'microsoft.com' }
|
|
]
|
|
],
|
|
[
|
|
[
|
|
{ type: 'STRING', value: 'Microsoft' },
|
|
null,
|
|
{ type: 'STRING', value: 'microsoft-noreply' },
|
|
{ type: 'STRING', value: 'microsoft.com' }
|
|
]
|
|
],
|
|
[
|
|
[
|
|
{ type: 'STRING', value: 'Microsoft' },
|
|
null,
|
|
{ type: 'STRING', value: 'microsoft-noreply' },
|
|
{ type: 'STRING', value: 'microsoft.com' }
|
|
]
|
|
],
|
|
[[null, null, { type: 'STRING', value: 'andris.reinman' }, { type: 'STRING', value: 'gmail.com' }]],
|
|
null,
|
|
null,
|
|
null,
|
|
{ type: 'STRING', value: '<58710631-775f-4c07-96ff-28a776f44d90@az.eastus2.microsoft.com>' }
|
|
],
|
|
[
|
|
[
|
|
[
|
|
{ type: 'STRING', value: 'text' },
|
|
{ type: 'STRING', value: 'plain' },
|
|
[
|
|
{ type: 'STRING', value: 'charset' },
|
|
{ type: 'STRING', value: 'utf-8' }
|
|
],
|
|
null,
|
|
null,
|
|
{ type: 'STRING', value: 'quoted-printable' },
|
|
{ type: 'ATOM', value: '2866' },
|
|
{ type: 'ATOM', value: '80' },
|
|
null,
|
|
null,
|
|
null,
|
|
null
|
|
],
|
|
[
|
|
{ type: 'STRING', value: 'text' },
|
|
{ type: 'STRING', value: 'html' },
|
|
[
|
|
{ type: 'STRING', value: 'charset' },
|
|
{ type: 'STRING', value: 'utf-8' }
|
|
],
|
|
null,
|
|
null,
|
|
{ type: 'STRING', value: 'quoted-printable' },
|
|
{ type: 'ATOM', value: '78392' },
|
|
{ type: 'ATOM', value: '1770' },
|
|
null,
|
|
null,
|
|
null,
|
|
null
|
|
],
|
|
{ type: 'STRING', value: 'alternative' },
|
|
[
|
|
{ type: 'STRING', value: 'boundary' },
|
|
{ type: 'STRING', value: '=-EcWGOW6mwE+0T4lm385OWw==' }
|
|
],
|
|
null,
|
|
null,
|
|
null
|
|
],
|
|
[
|
|
{ type: 'STRING', value: 'application' },
|
|
{ type: 'STRING', value: 'octet-stream' },
|
|
[
|
|
{ type: 'STRING', value: 'name' },
|
|
{ type: 'STRING', value: '52482541500.pdf' }
|
|
],
|
|
null,
|
|
null,
|
|
{ type: 'STRING', value: 'base64' },
|
|
{ type: 'ATOM', value: '279430' },
|
|
null,
|
|
[
|
|
{ type: 'STRING', value: 'attachment' },
|
|
[
|
|
{ type: 'STRING', value: 'filename' },
|
|
{ type: 'STRING', value: '52482541500.pdf' }
|
|
]
|
|
],
|
|
null,
|
|
null
|
|
],
|
|
{ type: 'STRING', value: 'mixed' },
|
|
[
|
|
{ type: 'STRING', value: 'boundary' },
|
|
{ type: 'STRING', value: '=-1wJq2CLBJ6H+Zk2GPX9FKw==' }
|
|
],
|
|
null,
|
|
null,
|
|
null
|
|
],
|
|
{ type: 'ATOM', value: '5580' },
|
|
null,
|
|
[
|
|
{ type: 'STRING', value: 'attachment' },
|
|
[
|
|
{ type: 'STRING', value: 'filename' },
|
|
{ type: 'STRING', value: 'Tellimuse Microsoft 365 Business Standard arve vaatamine.eml' }
|
|
]
|
|
],
|
|
null,
|
|
null
|
|
],
|
|
{ type: 'STRING', value: 'mixed' },
|
|
[
|
|
{ type: 'STRING', value: 'boundary' },
|
|
{ type: 'STRING', value: 'Apple-Mail=_F700EE9B-43B1-4EF1-95EE-CAA13391B333' }
|
|
],
|
|
null,
|
|
null,
|
|
null
|
|
]
|
|
]
|
|
]);
|
|
});
|