#ifndef ____imap_h__ # define ____imap_h__ # include "Tag.h" # include "ZString.h" # include "MString.h" # include # include # include # include namespace jet { /// /// /// /// Connects to the IMAP host named in the JSON file `config` (keys: /// host, port, caCertificate - see email.cfg), TLS-handshakes /// (implicit TLS, matching IMAP's standard port 993 - no STARTTLS /// negotiation), and LOGINs with user/password. Throws /// coreutils::Exception (with errorNumber set to a real HTTP status - /// 401 for a bad login, 502 for a connection/protocol failure) on any /// failure, exactly like __mysql throws on a failed /// mysql_real_connect() - see ApiSession.cpp's exception->HTTP-status /// mapping, which both this tag and __mysql rely on identically. /// /// Registers itself into Global::imapSessions under sessionid (a /// SEPARATE map from __mysql's Global::sessions - the two backends /// aren't interchangeable and Global::sessions is concretely typed /// __mysql*, not something a second backend could share without /// widening every existing __mysql-only call site), the same way /// __mysql registers into Global::sessions - later __imap* -family /// tags (__imaplist, __imapmessage, __imapflag, __imapdelete) look /// the live connection up by sessionid, mirroring how finds its /// by sessionid. /// /// Connection pooling: a thread_local pool keyed by sessionid+user, /// reused across requests on the same APIServer worker thread (an /// IMAP TCP+TLS+LOGIN round trip is too expensive to redo on every /// AJAX call the webmail UI makes) - directly mirrors __mysql.cpp's /// own thread_local connectionPool, including using a cheap /// keepalive probe (NOOP, IMAP's equivalent of mysql_ping()) to /// detect and discard a connection the server itself already dropped /// rather than handing a dead socket to the first real command. /// class __imap : public Tag { public: __imap(coreutils::ZString &in, coreutils::MString &parentOut, Global &global, Tag *parent, Tag *local); ~__imap(); // Each returns a complete, ready-to-emit JSON value (object or // array) - the __imap*-family wrapper tags (__imaplist.cpp etc.) // just write whatever this returns straight into their own `out`, // the same one-call-does-everything shape __sql's query() call // has relative to . /// offset counts back from the newest message (0 = the newest /// page); the returned JSON is {"total":N,"messages":[...]}. coreutils::MString listMessages(const std::string &folder, int limit, int offset); coreutils::MString searchMessages(const std::string &folder, const std::string &query); coreutils::MString fetchMessage(const std::string &folder, uint32_t uid); coreutils::MString setFlag(const std::string &folder, uint32_t uid, const std::string &flag, bool add); coreutils::MString deleteMessage(const std::string &folder, uint32_t uid); coreutils::MString moveMessage(const std::string &folder, uint32_t uid, const std::string &destFolder); coreutils::MString folderCounts(); coreutils::MString sessionId; private: SSL_CTX *ctx = nullptr; SSL *ssl = nullptr; int fd = -1; int tagCounter = 0; std::string recvBuffer; size_t recvPos = 0; std::string selectedFolder; std::string nextTag(); void sendLine(const std::string &line); char readByte(); // Reads one IMAP response line, INCLUDING any literal {n} content // that line announces (rejoined into the same returned string, // separated by "\r\n" markers) - see the .cpp for the full // rationale; this is a pragmatic, purpose-built reader for the // specific, known response shapes this client's own requests // provoke from BARANTMail (whose own FETCH implementation - built // earlier this same session - always uses literals for content, // never quoted strings), not a fully general IMAP parser. std::string readResponseLine(); // Reads response lines until the tagged completion line for `tag` // (built earlier via nextTag()) appears; returns every line // (untagged and the final tagged one) collected along the way. // Throws if the tagged line's status isn't OK. std::vector readUntilTagged(const std::string &tag); bool selectFolderIfNeeded(const std::string &folder); static std::string jsonEscape(const std::string &s); static uint32_t findUidField(const std::string &fetchLine); static std::vector findFlagsField(const std::string &fetchLine); static long findSizeField(const std::string &fetchLine); // Extracts the literal content immediately following a // "BODY[...]" (or "BODY[...]") marker in an already- // literal-rejoined response line - see readResponseLine()'s own // doc comment for the "\r\n"-separated rejoining this depends on. static std::string findBodyLiteral(const std::string &fetchLine); // Shared by listMessages() and searchMessages() - see its own // doc comment in the .cpp. static std::vector parseSummaryLines(const std::vector &lines); }; } #endif