221 lines
8.2 KiB
JavaScript
221 lines
8.2 KiB
JavaScript
'use strict';
|
|
|
|
const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
|
|
|
|
// Selects a mailbox
|
|
module.exports = async (connection, path, options) => {
|
|
if (![connection.states.AUTHENTICATED, connection.states.SELECTED].includes(connection.state)) {
|
|
// nothing to do here
|
|
return;
|
|
}
|
|
options = options || {};
|
|
|
|
path = normalizePath(connection, path);
|
|
|
|
if (!connection.folders.has(path)) {
|
|
let folders = await connection.run('LIST', '', path);
|
|
if (!folders) {
|
|
throw new Error('Failed to fetch folders');
|
|
}
|
|
folders.forEach(folder => {
|
|
connection.folders.set(folder.path, folder);
|
|
});
|
|
}
|
|
|
|
let folderListData = connection.folders.has(path) ? connection.folders.get(path) : false;
|
|
|
|
let response;
|
|
try {
|
|
let map = { path };
|
|
if (folderListData) {
|
|
['delimiter', 'specialUse', 'subscribed', 'listed'].forEach(key => {
|
|
if (folderListData[key]) {
|
|
map[key] = folderListData[key];
|
|
}
|
|
});
|
|
}
|
|
|
|
let extraArgs = [];
|
|
if (connection.enabled.has('QRESYNC') && options.changedSince && options.uidValidity) {
|
|
extraArgs.push([
|
|
{ type: 'ATOM', value: 'QRESYNC' },
|
|
[
|
|
{ type: 'ATOM', value: options.uidValidity?.toString() },
|
|
{ type: 'ATOM', value: options.changedSince.toString() }
|
|
]
|
|
]);
|
|
map.qresync = true;
|
|
}
|
|
|
|
let encodedPath = encodePath(connection, path);
|
|
|
|
let selectCommand = {
|
|
command: !options.readOnly ? 'SELECT' : 'EXAMINE',
|
|
arguments: [{ type: encodedPath.indexOf('&') >= 0 ? 'STRING' : 'ATOM', value: encodedPath }].concat(extraArgs || [])
|
|
};
|
|
|
|
response = await connection.exec(selectCommand.command, selectCommand.arguments, {
|
|
untagged: {
|
|
OK: async untagged => {
|
|
if (!untagged.attributes || !untagged.attributes.length) {
|
|
return;
|
|
}
|
|
let section = !untagged.attributes[0].value && untagged.attributes[0].section;
|
|
if (section && section.length > 1 && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
|
|
let key = section[0].value.toLowerCase();
|
|
let value;
|
|
|
|
if (typeof section[1].value === 'string') {
|
|
value = section[1].value;
|
|
} else if (Array.isArray(section[1])) {
|
|
value = section[1].map(entry => (typeof entry.value === 'string' ? entry.value : false)).filter(entry => entry);
|
|
}
|
|
|
|
switch (key) {
|
|
case 'highestmodseq':
|
|
key = 'highestModseq';
|
|
if (/^[0-9]+$/.test(value)) {
|
|
value = BigInt(value);
|
|
}
|
|
break;
|
|
|
|
case 'mailboxid':
|
|
key = 'mailboxId';
|
|
if (Array.isArray(value) && value.length) {
|
|
value = value[0];
|
|
}
|
|
break;
|
|
|
|
case 'permanentflags':
|
|
key = 'permanentFlags';
|
|
value = new Set(value);
|
|
break;
|
|
|
|
case 'uidnext':
|
|
key = 'uidNext';
|
|
value = Number(value);
|
|
break;
|
|
|
|
case 'uidvalidity':
|
|
key = 'uidValidity';
|
|
if (/^[0-9]+$/.test(value)) {
|
|
value = BigInt(value);
|
|
}
|
|
break;
|
|
}
|
|
|
|
map[key] = value;
|
|
}
|
|
|
|
if (section && section.length === 1 && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
|
|
let key = section[0].value.toLowerCase();
|
|
switch (key) {
|
|
case 'nomodseq':
|
|
key = 'noModseq';
|
|
map[key] = true;
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
FLAGS: async untagged => {
|
|
if (!untagged.attributes || (!untagged.attributes.length && Array.isArray(untagged.attributes[0]))) {
|
|
return;
|
|
}
|
|
let flags = untagged.attributes[0].map(flag => (typeof flag.value === 'string' ? flag.value : false)).filter(flag => flag);
|
|
map.flags = new Set(flags);
|
|
},
|
|
EXISTS: async untagged => {
|
|
let num = Number(untagged.command);
|
|
if (isNaN(num)) {
|
|
return false;
|
|
}
|
|
|
|
map.exists = num;
|
|
},
|
|
VANISHED: async untagged => {
|
|
await connection.untaggedVanished(
|
|
untagged,
|
|
// mailbox is not yet open, so use a dummy mailbox object
|
|
{ path, uidNext: false, uidValidity: false }
|
|
);
|
|
},
|
|
// we should only get an untagged FETCH for a SELECT/EXAMINE if QRESYNC was asked for
|
|
FETCH: async untagged => {
|
|
await connection.untaggedFetch(
|
|
untagged,
|
|
// mailbox is not yet open, so use a dummy mailbox object
|
|
{ path, uidNext: false, uidValidity: false }
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
let section = !response.response.attributes[0].value && response.response.attributes[0].section;
|
|
if (section && section.length && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
|
|
switch (section[0].value.toUpperCase()) {
|
|
case 'READ-ONLY':
|
|
map.readOnly = true;
|
|
break;
|
|
case 'READ-WRITE':
|
|
default:
|
|
map.readOnly = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (
|
|
map.qresync &&
|
|
// UIDVALIDITY must be the same
|
|
(options.uidValidity !== map.uidValidity ||
|
|
// HIGHESTMODSEQ response must be present
|
|
!map.highestModseq ||
|
|
// NOMODSEQ is not allowed
|
|
map.noModseq)
|
|
) {
|
|
// QRESYNC does not apply here, so unset it
|
|
map.qresync = false;
|
|
}
|
|
|
|
let currentMailbox = connection.mailbox;
|
|
connection.mailbox = false;
|
|
|
|
if (currentMailbox && currentMailbox.path !== path) {
|
|
connection.emit('mailboxClose', currentMailbox);
|
|
}
|
|
|
|
connection.mailbox = map;
|
|
connection.currentSelectCommand = selectCommand;
|
|
connection.state = connection.states.SELECTED;
|
|
|
|
if (!currentMailbox || currentMailbox.path !== path) {
|
|
connection.emit('mailboxOpen', connection.mailbox);
|
|
}
|
|
|
|
response.next();
|
|
return map;
|
|
} catch (err) {
|
|
let errorCode = getStatusCode(err.response);
|
|
if (errorCode) {
|
|
err.serverResponseCode = errorCode;
|
|
}
|
|
err.response = await getErrorText(err.response);
|
|
|
|
if (connection.state === connection.states.SELECTED) {
|
|
// reset selected state
|
|
|
|
let currentMailbox = connection.mailbox;
|
|
|
|
connection.mailbox = false;
|
|
connection.currentSelectCommand = false;
|
|
connection.state = connection.states.AUTHENTICATED;
|
|
|
|
if (currentMailbox) {
|
|
connection.emit('mailboxClose', currentMailbox);
|
|
}
|
|
}
|
|
|
|
connection.log.warn({ err, cid: connection.id });
|
|
throw err;
|
|
}
|
|
};
|