Add IMAP webmail tag, admin/opensource subpage tiers, store/cart, htmleditor, uploads

New __imap*/__smtpsend/__eval Jet tags give templates real IMAP
(list/message/flag/delete/move/search/folders/accounts) and SMTP-send
capability, backing the new /email webmail client (email.jet/
email.menu, assets/images/email.js + newemail.js) and its
mainpagetemplate.jet-based page shell. Most of this tag family's
source was already being built into the live site (Tag.cpp already
registered __imap/__imaplist/__imapmessage/__imapflag/__imapdelete/
__eval) but had never actually been committed; this also registers
and commits the remaining five (__imapmove/__imapfolders/
__imapsearch/__smtpsend/__imapaccounts).

admin.page/admin.menu gain a real subpage tier (admin-email,
admin-newemail, admin-invoices, admin-products, admin-users) replacing
several previously top-level pages, mirrored by a similar
opensource.page subpage tier (opensource-overview/asm1802/barantmail/
jet/serverframework/synthmax) - old top-level pages retired via git
rename (asm1802.page, jet.page, serverframework.page,
admin_invoices.page); barantmail.page is dropped outright, folded into
opensource-barantmail.subpage.

New storefront (store.page, store-createstore.subpage,
barantstore.page) and cart flow (cart.jet, updatecartqty.proc,
removecartitem.proc, editproduct.page/selectproduct.proc/
update_product.proc for inventory maintenance against the barant
database).

New document upload/download manager (documents.jet, documents_api.jet,
documents_upload.proc, documents_delete.proc,
assets/images/documents.js + documents-page.js) with a persistent
floating panel, and a rich-text page editor (htmleditor.jet/.page,
createhtmleditor.proc, savehtmleditor.proc, assets/images/htmleditor.js
+ its icon/logo assets).

SESSIONCTL.tag: login now redirects back to the originally-requested
page instead of always landing on /account ($[start:0] vs $[start]
variable-name mismatch).

main.css/menu.bar/invoices.menu/jet_overview.section/login.menu:
styling and navigation updates for the above; new site assets (fonts,
images, audio) referenced by them.

jetc/build-assets.sh, list_jet_files.sh, lowercase_jet.py,
strip_fragments.py: small dev tooling used while building the above.
This commit is contained in:
Brad Arant committed 2026-09-06 04:14:00 +00:00
1 parent b0cadc3f08
commit c1491b044b
115 files changed
+6174 -137

No files matched your search

+112
View File
@@ -0,0 +1,112 @@
#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>.
coreutils::MString listMessages(const std::string &folder, int limit);
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<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