Files
JetCore/__imapaccounts.cpp
Brad ArantandClaude Sonnet 5 b8f7793773 Add global music player, invoicing/payment system, account subpages, mail account admin, and the qualityiron.vegas Jet 2.0 port
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>
2026-10-06 17:42:01 +00:00

164 lines
6.2 KiB
C++

#include "__imapaccounts.h"
#include "Base64.h"
#include "Exception.h"
#include <cstdio>
namespace jet {
std::string __imapaccounts::jsonEscape(const std::string &s) {
std::string out;
out.reserve(s.size() + 8);
for (unsigned char c : s) {
switch (c) {
case '"': out += "\\\""; break;
case '\\': out += "\\\\"; break;
case '\n': out += "\\n"; break;
case '\r': continue;
case '\t': out += "\\t"; break;
default:
if (c < 0x20) {
char buf[8];
snprintf(buf, sizeof(buf), "\\u%04x", c);
out += buf;
} else {
out += (char)c;
}
}
}
return out;
}
static std::string trimWhitespace(const std::string &s) {
size_t start = s.find_first_not_of(" \t\r\n");
if (start == std::string::npos)
return std::string();
size_t end = s.find_last_not_of(" \t\r\n");
return s.substr(start, end - start + 1);
}
std::vector<__imapaccounts::Account> __imapaccounts::decodeList(const std::string &encodedRaw) {
std::vector<Account> entries;
coreutils::Base64 base64;
// The list value round-trips through <eval>/<set> template
// captures, which include whatever whitespace/newlines surround
// the tag call in the .jet source - trimming here (rather than
// insisting every template call site stays whitespace-free) is
// the robust fix, since a base64 decoder that stops at the first
// invalid character would otherwise silently truncate to nothing
// when a stray leading newline precedes real, valid content.
std::string encoded = trimWhitespace(encodedRaw);
size_t pos = 0;
while (pos < encoded.size()) {
size_t semi = encoded.find(';', pos);
std::string entry = (semi == std::string::npos) ? encoded.substr(pos) : encoded.substr(pos, semi - pos);
pos = (semi == std::string::npos) ? encoded.size() : semi + 1;
entry = trimWhitespace(entry);
if (entry.empty())
continue;
size_t colon1 = entry.find(':');
if (colon1 == std::string::npos)
continue;
size_t colon2 = entry.find(':', colon1 + 1);
if (colon2 == std::string::npos)
continue;
std::string hostB64 = trimWhitespace(entry.substr(0, colon1));
std::string userB64 = trimWhitespace(entry.substr(colon1 + 1, colon2 - colon1 - 1));
std::string passB64 = trimWhitespace(entry.substr(colon2 + 1));
Account account;
account.host = base64.decode(hostB64).str();
account.user = base64.decode(userB64).str();
account.password = base64.decode(passB64).str();
entries.push_back(account);
}
return entries;
}
std::string __imapaccounts::encodeList(const std::vector<Account> &entries) {
coreutils::Base64 base64;
std::string result;
for (size_t i = 0; i < entries.size(); i++) {
if (i > 0)
result += ";";
std::string host = entries[i].host;
std::string user = entries[i].user;
std::string password = entries[i].password;
result += base64.encode(host).str();
result += ":";
result += base64.encode(user).str();
result += ":";
result += base64.encode(password).str();
}
return result;
}
__imapaccounts::__imapaccounts(coreutils::ZString &in, coreutils::MString &parentOut, Global &global, Tag *parent, Tag *local) : Tag(in, parentOut, global, parent, local) {
std::string action = trimWhitespace(resolveKeyword("action").str());
std::string listStr = resolveKeyword("list").str();
auto entries = decodeList(listStr);
if (action == "add") {
std::string host = trimWhitespace(resolveKeyword("host").str());
std::string user = trimWhitespace(resolveKeyword("user").str());
std::string password = trimWhitespace(resolveKeyword("password").str());
for (size_t i = 0; i < entries.size(); i++) {
if (entries[i].user == user) {
entries.erase(entries.begin() + (long)i);
break;
}
}
Account account;
account.host = host;
account.user = user;
account.password = password;
entries.push_back(account);
out = coreutils::MString(encodeList(entries));
} else if (action == "remove") {
std::string user = trimWhitespace(resolveKeyword("user").str());
for (size_t i = 0; i < entries.size(); i++) {
if (entries[i].user == user) {
entries.erase(entries.begin() + (long)i);
break;
}
}
out = coreutils::MString(encodeList(entries));
} else if (action == "getpassword") {
std::string user = trimWhitespace(resolveKeyword("user").str());
std::string password;
for (size_t i = 0; i < entries.size(); i++) {
if (entries[i].user == user) {
password = entries[i].password;
break;
}
}
out = coreutils::MString(password);
} else if (action == "gethost") {
std::string user = trimWhitespace(resolveKeyword("user").str());
std::string host;
for (size_t i = 0; i < entries.size(); i++) {
if (entries[i].user == user) {
host = entries[i].host;
break;
}
}
out = coreutils::MString(host);
} else if (action == "firstuser") {
out = coreutils::MString(entries.empty() ? std::string() : entries[0].user);
} else if (action == "list") {
std::string active = resolveKeyword("active").str();
std::string json = "{\"accounts\":[";
for (size_t i = 0; i < entries.size(); i++) {
if (i > 0)
json += ",";
json += "\"" + jsonEscape(entries[i].user) + "\"";
}
json += "],\"active\":\"" + jsonEscape(active) + "\"}";
out = coreutils::MString(json);
} else {
throw coreutils::Exception("imapaccounts: unknown action \"" + action + "\"", __FILE__, __LINE__, 500);
}
}
}