#include "IMAPArgs.h" #include "MailFolder.h" #include #include #include #include #include namespace mail { namespace IMAPArgs { coreutils::ZString nextArgument(coreutils::ZString &request) { request.skipWhitespace(); if (request.eod()) return coreutils::ZString(); char c = request.getChar(); if (c == '"' || c == '(' || c == '[' || c == '{') return request.getContainer(); return request.getTokenExclude(" "); } bool isKeyword(coreutils::ZString &argument, const char *keyword) { std::string a = argument.str(); std::string b = keyword; if (a.size() != b.size()) return false; for (size_t i = 0; i < a.size(); ++i) if (std::toupper((unsigned char)a[i]) != std::toupper((unsigned char)b[i])) return false; return true; } std::string unwrap(coreutils::ZString &argument) { std::string value = argument.str(); if (value.size() >= 2) { char first = value.front(); char last = value.back(); if ((first == '"' && last == '"') || (first == '(' && last == ')') || (first == '[' && last == ']') || (first == '{' && last == '}')) return value.substr(1, value.size() - 2); } return value; } std::vector parseSequenceSet(coreutils::ZString &request, MailFolder &folder, bool uidMode) { std::vector result; // Always resolves to a concrete UID, regardless of uidMode - // callers below (including the range-walking code, which // re-derives sequence numbers from these UIDs via folder.seq()) // depend on that. auto resolveNumber = [&](const std::string &token) -> uint32_t { if (token == "*") return folder.size() == 0 ? 0 : folder.messages().back().uid; uint32_t value = (uint32_t)std::strtoul(token.c_str(), nullptr, 10); if (uidMode) return folder.find(value) ? value : 0; return folder.uidForSeq(value); }; coreutils::ZString set = nextArgument(request); std::string setStr = set.str(); std::istringstream stream(setStr); std::string part; while (std::getline(stream, part, ',')) { if (part.empty()) continue; size_t colon = part.find(':'); if (colon == std::string::npos) { uint32_t uid = resolveNumber(part); if (uid != 0) result.push_back(uid); } else { std::string fromStr = part.substr(0, colon); std::string toStr = part.substr(colon + 1); uint32_t fromUid = resolveNumber(fromStr); uint32_t toUid = resolveNumber(toStr); if (fromUid == 0 || toUid == 0) continue; if (fromUid > toUid) std::swap(fromUid, toUid); // resolveNumber() already turned both ends into concrete // UIDs regardless of uidMode - walking sequence-number- // by-sequence-number (not uid-by-uid) from here is // deliberate: a UID range like "5:9" must include every // message whose UID falls in that range, not just // messages whose UID happens to equal one of the // integers 5-9 (UIDs aren't dense/contiguous once // anything's been expunged). uint32_t fromSeq = folder.seq(fromUid); uint32_t toSeq = folder.seq(toUid); if (fromSeq == 0 || toSeq == 0) continue; for (uint32_t s = fromSeq; s <= toSeq; ++s) result.push_back(folder.uidForSeq(s)); } } std::sort(result.begin(), result.end()); result.erase(std::unique(result.begin(), result.end()), result.end()); return result; } std::vector parseFlagList(coreutils::ZString &request) { coreutils::ZString arg = nextArgument(request); std::string text = unwrap(arg); std::vector flags; std::istringstream stream(text); std::string token; while (stream >> token) flags.push_back(token); return flags; } time_t parseDate(coreutils::ZString &argument) { std::string text = unwrap(argument); struct tm tmValue = {}; // Try the full APPEND date-time form first, then fall back to // SEARCH's bare date form. if (strptime(text.c_str(), "%d-%b-%Y %H:%M:%S %z", &tmValue) != nullptr) return timegm(&tmValue) - tmValue.tm_gmtoff; tmValue = {}; if (strptime(text.c_str(), "%d-%b-%Y", &tmValue) != nullptr) return timegm(&tmValue); return (time_t)-1; } bool isValidFolderName(const std::string &name) { if (name.empty()) return false; if (name.find('/') != std::string::npos) return false; if (name[0] == '.') return false; return true; } std::string normalizeFolderName(const std::string &name) { if (name.size() == 5) { std::string upper = name; for (char &c : upper) c = std::toupper((unsigned char)c); if (upper == "INBOX") return "Inbox"; } return name; } } }