166 lines
5.7 KiB
JavaScript
166 lines
5.7 KiB
JavaScript
/* eslint new-cap: 0 */
|
|
|
|
'use strict';
|
|
|
|
const imapFormalSyntax = require('./imap-formal-syntax');
|
|
|
|
const { TokenParser } = require('./token-parser');
|
|
|
|
class ParserInstance {
|
|
constructor(input, options) {
|
|
this.input = (input || '').toString();
|
|
this.options = options || {};
|
|
this.remainder = this.input;
|
|
this.pos = 0;
|
|
}
|
|
|
|
async getTag() {
|
|
if (!this.tag) {
|
|
this.tag = await this.getElement(imapFormalSyntax.tag() + '*+', true);
|
|
}
|
|
return this.tag;
|
|
}
|
|
|
|
async getCommand() {
|
|
if (this.tag === '+') {
|
|
// special case
|
|
this.humanReadable = this.remainder.trim();
|
|
this.remainder = '';
|
|
|
|
return '';
|
|
}
|
|
|
|
if (!this.command) {
|
|
this.command = await this.getElement(imapFormalSyntax.command());
|
|
}
|
|
|
|
switch ((this.command || '').toString().toUpperCase()) {
|
|
case 'OK':
|
|
case 'NO':
|
|
case 'BAD':
|
|
case 'PREAUTH':
|
|
case 'BYE':
|
|
{
|
|
let match = this.remainder.match(/^\s+\[/);
|
|
if (match) {
|
|
let nesting = 1;
|
|
for (let i = match[0].length; i <= this.remainder.length; i++) {
|
|
let c = this.remainder[i];
|
|
|
|
if (c === '[') {
|
|
nesting++;
|
|
} else if (c === ']') {
|
|
nesting--;
|
|
}
|
|
if (!nesting) {
|
|
this.humanReadable = this.remainder.substring(i + 1).trim();
|
|
this.remainder = this.remainder.substring(0, i + 1);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
this.humanReadable = this.remainder.trim();
|
|
this.remainder = '';
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return this.command;
|
|
}
|
|
|
|
async getElement(syntax) {
|
|
let match, element, errPos;
|
|
|
|
if (this.remainder.match(/^\s/)) {
|
|
let error = new Error(`Unexpected whitespace at position ${this.pos} [E1]`);
|
|
error.code = 'ParserError1';
|
|
error.parserContext = { input: this.input, pos: this.pos };
|
|
throw error;
|
|
}
|
|
|
|
if ((match = this.remainder.match(/^\s*[^\s]+(?=\s|$)/))) {
|
|
element = match[0];
|
|
if ((errPos = imapFormalSyntax.verify(element, syntax)) >= 0) {
|
|
if (this.tag === 'Server' && element === 'Unavailable.') {
|
|
// Exchange error
|
|
let error = new Error(`Server returned an error: ${this.input}`);
|
|
error.code = 'ParserErrorExchange';
|
|
error.parserContext = {
|
|
input: this.input,
|
|
element,
|
|
pos: this.pos,
|
|
value: {
|
|
tag: '*',
|
|
command: 'BAD',
|
|
attributes: [{ type: 'TEXT', value: this.input }]
|
|
}
|
|
};
|
|
throw error;
|
|
}
|
|
|
|
let error = new Error(`Unexpected char at position ${this.pos + errPos} [E2: ${JSON.stringify(element.charAt(errPos))}]`);
|
|
error.code = 'ParserError2';
|
|
error.parserContext = { input: this.input, element, pos: this.pos };
|
|
throw error;
|
|
}
|
|
} else {
|
|
let error = new Error(`Unexpected end of input at position ${this.pos} [E3]`);
|
|
error.code = 'ParserError3';
|
|
error.parserContext = { input: this.input, pos: this.pos };
|
|
throw error;
|
|
}
|
|
|
|
this.pos += match[0].length;
|
|
this.remainder = this.remainder.substr(match[0].length);
|
|
|
|
return element;
|
|
}
|
|
|
|
async getSpace() {
|
|
if (!this.remainder.length) {
|
|
if (this.tag === '+' && this.pos === 1) {
|
|
// special case, empty + response
|
|
return;
|
|
}
|
|
|
|
let error = new Error(`Unexpected end of input at position ${this.pos} [E4]`);
|
|
error.code = 'ParserError4';
|
|
error.parserContext = { input: this.input, pos: this.pos };
|
|
throw error;
|
|
}
|
|
|
|
if (imapFormalSyntax.verify(this.remainder.charAt(0), imapFormalSyntax.SP()) >= 0) {
|
|
let error = new Error(`Unexpected char at position ${this.pos} [E5: ${JSON.stringify(this.remainder.charAt(0))}]`);
|
|
error.code = 'ParserError5';
|
|
error.parserContext = { input: this.input, element: this.remainder, pos: this.pos };
|
|
throw error;
|
|
}
|
|
|
|
this.pos++;
|
|
this.remainder = this.remainder.substr(1);
|
|
}
|
|
|
|
async getAttributes() {
|
|
if (!this.remainder.length) {
|
|
let error = new Error(`Unexpected end of input at position ${this.pos} [E6]`);
|
|
error.code = 'ParserError6';
|
|
error.parserContext = { input: this.input, pos: this.pos };
|
|
throw error;
|
|
}
|
|
|
|
if (this.remainder.match(/^\s/)) {
|
|
let error = new Error(`Unexpected whitespace at position ${this.pos} [E7]`);
|
|
error.code = 'ParserError7';
|
|
error.parserContext = { input: this.input, element: this.remainder, pos: this.pos };
|
|
throw error;
|
|
}
|
|
|
|
const tokenParser = new TokenParser(this, this.pos, this.remainder, this.options);
|
|
|
|
return await tokenParser.getAttributes();
|
|
}
|
|
}
|
|
|
|
module.exports.ParserInstance = ParserInstance;
|