Jet interpreter (Tag.cpp, __call.cpp, __imap*.cpp/h, __smtpsend.cpp/h, __imapemptyfolder.cpp/h - new): assorted parser/tag fixes found across this work - a stray literal-tag-in-comment and backslash-quote parser corruption class, a dangling-pointer fix, a __call.cpp fd leak, IMAP folder-management tags, multi-account/per-host IMAP support, and a smtpsend Message-ID fix. barant.com site (jetc/barant-site/): - A site-wide persistent music player (header band, playlist/queue, stereo FFT EQ, session-persisted state) fed by new library/ ianslibrary/music song-list subpages. - /account split into info/phones/invoices subpages; a full invoicing and Square payment system (create/edit/copy/pay/email invoice); admin invoice management and mail-account management subpages. - /services split into overview/hosting/siptrunking/documents subpages, retiring the old standalone pages. - SIPPHONE.tag: a Jet 2.0 WebRTC softphone tag. - main.css/spa.js/email.js: the SPA loading indicator, the music player band styling, and assorted fixes accumulated alongside this work. qualityiron-site/: the qualityiron.vegas job/contractor/customer/ document management app, ported from its original Jet 1.4 codebase to Jet 2.0 as barant.coms APIServers second multi-tenant site. wcwc/wcwc.jet: the WCWC site Jet source (wcwc.jtc build output not included). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
124 lines
5.9 KiB
C++
124 lines
5.9 KiB
C++
#ifndef ____imap_h__
|
|
# define ____imap_h__
|
|
|
|
# include "Tag.h"
|
|
# include "ZString.h"
|
|
# include "MString.h"
|
|
# include <cstdint>
|
|
# include <openssl/ssl.h>
|
|
# include <string>
|
|
# include <vector>
|
|
|
|
namespace jet {
|
|
|
|
///
|
|
/// <imap config="./email.cfg" user="..." password="..." sessionid="">
|
|
///
|
|
/// 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 <sql> finds its
|
|
/// <database> 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 <sql>.
|
|
/// 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);
|
|
// Permanently empties an entire folder - every message, not one at
|
|
// a time. "1:*" is IMAP's standard UID-range syntax for "every UID
|
|
// in the selected mailbox" (well-defined and a no-op, not an error,
|
|
// on an already-empty folder - verified directly against
|
|
// BARANTMail rather than assumed from the RFC), so this is a
|
|
// single mark-all-deleted STORE plus one EXPUNGE, not a loop over
|
|
// deleteMessage() - the whole point is one IMAP round trip instead
|
|
// of one per message.
|
|
coreutils::MString emptyFolder(const std::string &folder);
|
|
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<std::string> 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<std::string> findFlagsField(const std::string &fetchLine);
|
|
static long findSizeField(const std::string &fetchLine);
|
|
// Extracts the literal content immediately following a
|
|
// "BODY[...]" (or "BODY[...]<n>") 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<std::string> parseSummaryLines(const std::vector<std::string> &lines);
|
|
};
|
|
|
|
}
|
|
|
|
#endif
|