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:
1 parent
b0cadc3f08
commit
c1491b044b
115 files changed
+6174
-137
No files matched your search
@@ -0,0 +1,139 @@
|
||||
#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<std::pair<std::string, std::string>> __imapaccounts::decodeList(const std::string &encodedRaw) {
|
||||
std::vector<std::pair<std::string, std::string>> 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 colon = entry.find(':');
|
||||
if (colon == std::string::npos)
|
||||
continue;
|
||||
std::string userB64 = trimWhitespace(entry.substr(0, colon));
|
||||
std::string passB64 = trimWhitespace(entry.substr(colon + 1));
|
||||
std::string user = base64.decode(userB64).str();
|
||||
std::string password = base64.decode(passB64).str();
|
||||
entries.push_back(std::make_pair(user, password));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::string __imapaccounts::encodeList(const std::vector<std::pair<std::string, std::string>> &entries) {
|
||||
coreutils::Base64 base64;
|
||||
std::string result;
|
||||
for (size_t i = 0; i < entries.size(); i++) {
|
||||
if (i > 0)
|
||||
result += ";";
|
||||
std::string user = entries[i].first;
|
||||
std::string password = entries[i].second;
|
||||
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 user = trimWhitespace(resolveKeyword("user").str());
|
||||
std::string password = trimWhitespace(resolveKeyword("password").str());
|
||||
for (size_t i = 0; i < entries.size(); i++) {
|
||||
if (entries[i].first == user) {
|
||||
entries.erase(entries.begin() + (long)i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
entries.push_back(std::make_pair(user, password));
|
||||
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].first == 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].first == user) {
|
||||
password = entries[i].second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
out = coreutils::MString(password);
|
||||
} else if (action == "firstuser") {
|
||||
out = coreutils::MString(entries.empty() ? std::string() : entries[0].first);
|
||||
} 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].first) + "\"";
|
||||
}
|
||||
json += "],\"active\":\"" + jsonEscape(active) + "\"}";
|
||||
out = coreutils::MString(json);
|
||||
} else {
|
||||
throw coreutils::Exception("imapaccounts: unknown action \"" + action + "\"", __FILE__, __LINE__, 500);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user