UNPKG

@cull/imap

Version:

A simple, configurable javascript interface exposing mailboxes and messages via IMAP.

428 lines (427 loc) 15.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; result["default"] = mod; return result; }; Object.defineProperty(exports, "__esModule", { value: true }); const code_1 = __importDefault(require("./code")); const patterns_1 = __importStar(require("./patterns")); const header_1 = __importDefault(require("./header")); const envelope_1 = __importDefault(require("./envelope")); /** * Status Response * > Status responses are OK, NO, BAD, PREAUTH and BYE. * @link https://tools.ietf.org/html/rfc3501#section-7.1 */ var Status; (function (Status) { Status["OK"] = "OK"; Status["NO"] = "NO"; Status["BAD"] = "BAD"; Status["PREAUTH"] = "PREAUTH"; Status["BYE"] = "BYE"; })(Status = exports.Status || (exports.Status = {})); /** * Server and Mailbox Status Response * @link https://tools.ietf.org/html/rfc3501#section-7.2 */ var ServerStatus; (function (ServerStatus) { /** * `CAPABILITY` Response * @link https://tools.ietf.org/html/rfc3501#section-7.2.1 */ ServerStatus["CAPABILITY"] = "CAPABILITY"; /** * `LIST` Response * @link https://tools.ietf.org/html/rfc3501#section-7.2.2 */ ServerStatus["LIST"] = "LIST"; ServerStatus["LSUB"] = "LSUB"; ServerStatus["STATUS"] = "STATUS"; ServerStatus["SEARCH"] = "SEARCH"; ServerStatus["FLAGS"] = "FLAGS"; })(ServerStatus = exports.ServerStatus || (exports.ServerStatus = {})); /** * Mailbox Size Response * @link https://tools.ietf.org/html/rfc3501#section-7.3 */ var MailboxSizeUpdate; (function (MailboxSizeUpdate) { MailboxSizeUpdate["EXISTS"] = "EXISTS"; MailboxSizeUpdate["RECENT"] = "RECENT"; })(MailboxSizeUpdate = exports.MailboxSizeUpdate || (exports.MailboxSizeUpdate = {})); /** * Message Status Response * @link https://tools.ietf.org/html/rfc3501#section-7.4 */ var MessageStatus; (function (MessageStatus) { MessageStatus["EXPUNGE"] = "EXPUNGE"; MessageStatus["FETCH"] = "FETCH"; })(MessageStatus = exports.MessageStatus || (exports.MessageStatus = {})); /** * Message Data Items included a FETCH * @link https://tools.ietf.org/html/rfc3501#section-7.4.2 */ var MessageDataItem; (function (MessageDataItem) { MessageDataItem["BODY"] = "BODY"; MessageDataItem["BODYSTRUCTURE"] = "BODYSTRUCTURE"; MessageDataItem["ENVELOPE"] = "ENVELOPE"; MessageDataItem["FLAGS"] = "FLAGS"; MessageDataItem["INTERNALDATE"] = "INTERNALDATE"; MessageDataItem["RFC822"] = "RFC822"; MessageDataItem["HEADER"] = "RFC822.HEADER"; MessageDataItem["SIZE"] = "RFC822.SIZE"; MessageDataItem["TEXT"] = "RFC822.TEXT"; MessageDataItem["UID"] = "UID"; })(MessageDataItem = exports.MessageDataItem || (exports.MessageDataItem = {})); /** * __An IMAP Server Response__ * * Response parses a server response into a more accessible representation of its variable components. * * @link https://tools.ietf.org/html/rfc3501#section-2.2.2 * @link https://tools.ietf.org/html/rfc3501#section-7 */ class Response { constructor(buffer) { /** * Datetime Response object created; analgous to a "received" datetime */ this.received = new Date(); /** * Server provided data * @link https://tools.ietf.org/html/rfc3501#section-2.2.2 */ this.data = {}; /** * Server response codes * @link https://tools.ietf.org/html/rfc3501#section-7.1 */ this.codes = []; this.buffer = buffer; this.initialize(); } /** * Parse each line based on the first token. * @link https://tools.ietf.org/html/rfc3501#section-2.2.2 */ initialize() { this.lines.forEach(line => { let [token, data] = patterns_1.bisect(line); if (!token) { return; } if (token === '+') { this.continuation = true; this.text = data; return; } this.tag = token !== '*' ? token : undefined; try { this.parse(data); } catch (error) { throw error; } }); } parse(line) { var _a, _b; let [token, data] = patterns_1.bisect(line); if (!token) return; let n = parseInt(token, 10); let numeric = !isNaN(n); if (numeric) { let [head, tail] = patterns_1.bisect(data); token = head !== null && head !== void 0 ? head : data; data = tail !== null && tail !== void 0 ? tail : data; } switch (true) { case token in MailboxSizeUpdate: this.data[token] = n; break; case token === MessageStatus.EXPUNGE: if (this.data[token] === undefined) { this.data[token] = []; } (_a = this.data[token]) === null || _a === void 0 ? void 0 : _a.push(n); break; case token === MessageStatus.FETCH: if (this.data[token] === undefined) { this.data[token] = new Map(); } let message = parseFetchResponse(data); if (message !== undefined) { (_b = this.data[token]) === null || _b === void 0 ? void 0 : _b.set(n, message); } break; case token in Status: let status = token; let parsed = exports.parseStatusResponse(status, data); this.status = status; this.codes = this.codes.concat(parsed.codes); this.text = parsed.text; break; case token in ServerStatus: this.parseServerStatusResponse(token, data); break; default: throw new Error(`Unprocessed response: ${line}`); } } /** * Parse a Server and Mailbox Status response * @link https://tools.ietf.org/html/rfc3501#section-7.2 */ parseServerStatusResponse(state, data) { switch (state) { case ServerStatus.CAPABILITY: this.data[state] = data.split(' '); break; case ServerStatus.LIST: let m = data.match(/^\((.*)\)\s(\S+)\s(.+)$/); if (m) { if (this.data[state] === undefined) { this.data[state] = []; } let name = patterns_1.unquote(m[3]); let delimiter = patterns_1.unquote(m[2]); let attributes = m[1].split(' ').filter(a => a !== ''); this.data[ServerStatus.LIST].push({ name, delimiter, attributes }); } break; case ServerStatus.FLAGS: if (this.data[state] === undefined) { this.data[state] = []; } let m2 = data.match(/^\((.*)\)$/); this.data[ServerStatus.FLAGS] = m2 ? m2[1].split(' ') : [data]; break; default: throw new Error(`Unprocessed Server State Response: ${state} ${data}`); } } get lines() { var _a; if (this._lines === undefined) { let lines = exports.linesFromResponse(this.toString()); this._lines = lines.filter(line => line !== ''); } return (_a = this._lines) !== null && _a !== void 0 ? _a : []; } toString() { var _a; if (this._string === undefined) { this._string = this.buffer.toString('ascii'); } return (_a = this._string) !== null && _a !== void 0 ? _a : ''; } } exports.Response = Response; exports.default = Response; /** * Split response data into lines. * @link https://tools.ietf.org/html/rfc3501#section-2.2 */ exports.linesFromResponse = (data) => { let containsLiterals = data.match(patterns_1.default.stringLiteralPrefix); return containsLiterals === null ? data.split(`\r\n`) : exports.linesFromResponseWithStringLiterals(data); }; /** * Break single string multi-line response into array of strings by `\r\n`. * * Ignore `\r\n` when part of a string literal symbol, `{0}\r\n` or the string literal data. * @link https://tools.ietf.org/html/rfc3501#section-2.2 * @link https://tools.ietf.org/html/rfc3501#section-4.3 */ exports.linesFromResponseWithStringLiterals = (data) => { let lines = []; let buffer = ''; let literal = false; let octet = { target: 0, index: 0 }; let pattern = new RegExp(`${patterns_1.default.stringLiteralPrefix.source}$`); [...data].forEach(current => { let previous = buffer[buffer.length - 1]; buffer += current; if (literal) octet.index++; if (current === `\n` && previous === `\r` && !literal) { let match = buffer.match(pattern); if (match && match.groups !== undefined && match.groups.octets !== undefined) { literal = true; let target = parseInt(match.groups.octets, 10); if (target) { octet = { target, index: 0 }; } } else { lines.push(buffer.slice(0, -2)); buffer = ''; } } literal = literal && octet.index !== octet.target; }); if (buffer !== '') throw new Error(`Invalid/incomplete buffer (not terminated with CRLF?): ${buffer}`); return lines.map(patterns_1.deliteralize); }; /** * Parse a general Status response * @link https://tools.ietf.org/html/rfc3501#section-7.1 */ exports.parseStatusResponse = (status, data) => { let result = { codes: [] }; if (data) { let match = data.match(/^[\[](.+)[\]]\s{1}(.*)$/); if (match) { let [a, b] = patterns_1.bisect(match[1]); let code = a ? new code_1.default(status, a, b, match[2]) : new code_1.default(status, match[1], undefined, match[2]); result.codes.push(code); } else { result.text = data; } } return result; }; /** * Parse a FETCH response. * @link https://tools.ietf.org/html/rfc3501#section-7.4.2 */ let parseFetchResponse = (response) => { let data = {}; if (response !== undefined) { let extracted = exports.extractMessageData(response); let keys = Object.keys(extracted); keys.forEach(key => { var _a, _b; let item = key.toUpperCase(); let value = extracted[key]; if (item.indexOf(MessageDataItem.BODY) === 0) { item = MessageDataItem.BODY; } if (!(item in MessageDataItem)) { throw new Error(`Unknown message data item: ${key}`); } switch (item) { case MessageDataItem.ENVELOPE: data[item] = envelope_1.default.from(patterns_1.deparenthesize(value)); break; case MessageDataItem.FLAGS: data[item] = patterns_1.deparenthesize(value) .split(`\s`) .filter(flag => flag !== ''); break; case MessageDataItem.INTERNALDATE: data[item] = patterns_1.unquote(value); break; case MessageDataItem.HEADER: data[item] = new header_1.default(value); break; case MessageDataItem.BODY: value = patterns_1.unescape(patterns_1.unquote(value)); if (data[item] === undefined) { data[item] = {}; } let template = /^(BODY|BODY\.PEEK)\[(?<section>.+)\](\<(?<partial>.*)\>)?$/; let match = key.toUpperCase().match(template); if (match !== null && match.groups !== undefined) { if (((_a = match.groups) === null || _a === void 0 ? void 0 : _a.section) === 'HEADER') { value = new header_1.default(value); } data[item][(_b = match.groups) === null || _b === void 0 ? void 0 : _b.section] = value; } else { data[item] = value; } break; default: data[item] = value; break; } }); } return data; }; exports.extractMessageData = (data) => { let result = {}; let buffer = ''; let item; let start; let skip = 0; [...patterns_1.deparenthesize(data)].forEach(current => { let previous = buffer[buffer.length - 1]; buffer += current; switch (true) { case item === undefined: switch (true) { case current === ' ' && buffer === ' ': buffer = ''; break; case current === ' ': item = buffer.slice(0, -1); start = undefined; buffer = ''; skip = 0; break; } break; case start === undefined: start = current; break; // Parentheses case start === '(': switch (true) { case current === '(': skip++; break; case current === ')' && skip > 0: skip--; break; case current === ')' && skip === 0: result[item] = buffer; item = undefined; start = undefined; buffer = ''; break; } break; // Quotes case start === '"': if (current === '"' && previous !== `\\`) { result[item] = buffer; item = undefined; start = undefined; buffer = ''; } break; // Unquoted, non-whitespace string case current === ' ': result[item] = buffer.slice(0, -1); item = undefined; start = undefined; buffer = ''; break; } }); if (buffer !== '') throw new Error(`Invalid/incomplete buffer: ${buffer}`); return result; };