pop3-to-imap/imap/logic.js

212 lines
5.2 KiB
JavaScript

class IMAPController {
constructor (socket, tag) {
this.socket = socket;
this.tag = tag;
}
out (answer) {
console.debug("IMAP response:", answer);
this.socket.write(`${this.tag} ${answer}`);
}
untaggedOut (answer) {
console.debug("IMAP response:", answer);
this.socket.write(answer);
}
close () {
this.socket.end();
}
}
const messages = [
{
id: 1,
uid: 101,
flags: ["\\Seen"],
raw: `From: Example <ex@example.com>\r\nTo: You <you@example.com>\r\nSubject: Hello\r\n\r\nThis is a test email.`
}
];
const parseUidRange = (input) => {
if (input === '*') return messages.map(msg => msg.uid);
if (input.includes(':')) {
const [start, end] = input.split(':').map(Number);
return messages
.map(msg => msg.uid)
.filter(uid => uid >= start && uid <= (isNaN(end) ? Infinity : end));
}
return [Number(input)];
};
class Commands {
constructor (client) {
this.client = client;
}
// Methods
// Required commands
async capability (args, controller) {
/*if (args.length > 0)
return out();*/
controller.untaggedOut("* CAPABILITY IMAP4rev1 IDLE\r\n");
controller.out("OK CAPABILITY completed\r\n");
}
async logout (args, controller) {
controller.untaggedOut("* BYE Logging out\r\n");
controller.out("OK LOGOUT completed\r\n");
controller.close();
}
async noop(args, controller) {
controller.out("OK NOOP completed\r\n");
}
async login(args, controller) {
//this.client.state = "authenticated";
controller.out("OK LOGIN completed\r\n");
}
// Mailposts
async list(args, controller) {
controller.untaggedOut(`* LIST (\\HasNoChildren) "." "INBOX"\r\n`);
controller.out("OK LIST completed\r\n");
}
async select(args, controller) {
const messageCount = messages.length;
controller.untaggedOut("* FLAGS (\\Seen \\Answered \\Flagged \\Deleted \\Draft)\r\n");
controller.untaggedOut(`* ${messageCount} EXISTS\r\n`);
controller.untaggedOut(`* ${messageCount} RECENT\r\n`);
controller.untaggedOut("* OK [UNSEEN 1] First unseen\r\n");
controller.untaggedOut("* OK [UIDVALIDITY 1] UIDs valid\r\n");
controller.untaggedOut(`* OK [UIDNEXT ${messageCount + 1}] Predicted next UID\r\n`);
controller.out("OK [READ-WRITE] SELECT completed\r\n");
//this.client.state = "selected";
}
// Mailpost's messages
async fetch(args, controller) {
if (!args || args.length < 2) {
return controller.out("BAD FETCH requires message sequence and data item\r\n");
}
const seq = args[0];
const item = args.slice(1).join(" ").toUpperCase();
if (seq !== "1") {
return controller.out("NO FETCH only supports message 1 for now\r\n");
}
if (!item.includes("BODY[]")) {
return controller.out("NO FETCH only supports BODY[] for now\r\n");
}
const msg = messages.find(m => m.id === 1);
if (!msg) {
return controller.out("NO Message not found\r\n");
}
const body = msg.raw;
controller.untaggedOut(`* 1 FETCH (BODY[] {${body.length}}\r\n${body}\r\n)\r\n`);
controller.out("OK FETCH completed\r\n");
}
async uid(args, controller) {
if (!args || args.length < 2) {
return controller.out("BAD UID requires subcommand and arguments\r\n");
}
const subCommand = args[0].toUpperCase();
if (subCommand === "FETCH") {
const uidArg = args[1];
const fetchFields = args.slice(2).join(" ").toUpperCase();
const msg = messages.find(m => m.uid === Number(uidArg));
if (!msg) {
return controller.out("NO No such message\r\n");
}
const headersOnly = msg.raw.split("\r\n\r\n")[0] + "\r\n";
const headerLength = headersOnly.length;
const bodySize = msg.raw.length;
const flags = msg.flags.join(" ");
controller.untaggedOut(
`* ${msg.id} FETCH (` +
`UID ${msg.uid} ` +
`RFC822.SIZE ${bodySize} ` +
`FLAGS (${flags}) ` +
`BODY[HEADER.FIELDS (From To Subject Date Message-ID)] {${headerLength}}\r\n` +
headersOnly +
")\r\n"
);
return controller.out("OK UID FETCH completed\r\n");
}
controller.out("BAD UID subcommand not supported\r\n");
}
async store(args, controller) {
controller.out("OK STORE not yet implemented\r\n");
}
async expunge(args, controller) {
controller.out("OK EXPUNGE not yet implemented\r\n");
}
async copy(args, controller) {
controller.out("OK COPY not yet implemented\r\n");
}
async close(args, controller) {
controller.out("OK CLOSE not yet implemented\r\n");
}
async search(args, controller) {
controller.out("* SEARCH\r\n");
controller.out("OK SEARCH completed\r\n");
}
async examine(args, controller) {
controller.out("OK EXAMINE not yet implemented\r\n");
}
async append(args, controller) {
controller.out("OK APPEND not yet implemented\r\n");
}
// Push-notifications (IDLE)
async idle(args, controller) {
controller.untaggedOut("+ idling\r\n");
controller.out("OK IDLE not yet implemented\r\n");
}
}
class Client {
constructor (socket) {
this.socket = socket;
this.commands = new Commands(this);
}
command (tag, command, args) {
//console.debug("this >>", this, this.socket, this.commands);
const cmd = this.commands[command.toLowerCase()];
if (cmd === undefined) {
return this.socket.write(`${tag} BAD unknown command\r\n`);
}
cmd.call(this.commands, args, new IMAPController(this.socket, tag));
}
}
module.exports = { Client };