Loading src/store/ConfigStore.cpp +52 −4 Changes for src/store/ConfigStore.cpp: 52 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -92,8 +92,9 @@ private: size_t _pos = 0; }; // Deliberately does not touch r.priority -- see the HttpRoutePriority comment in Records.h for // why that field is persisted as its own section instead of appended here. // Deliberately does not touch r.priority/r.forceHttpsRedirect -- see the HttpRoutePriority // comment in Records.h for why fields added after the initial schema are persisted as their // own section instead of appended here. void encodeHttpRoute(ByteWriter &w, const HttpRouteRecord &r) { w.str(r.id); w.str(r.domain); Loading @@ -104,7 +105,6 @@ void encodeHttpRoute(ByteWriter &w, const HttpRouteRecord &r) { w.u8(r.tlsVerifyUpstream ? 1 : 0); w.u32(static_cast<uint32_t>(r.tlsPinnedFingerprintsSha256.size())); for (const auto &fp : r.tlsPinnedFingerprintsSha256) w.str(fp); w.u8(r.forceHttpsRedirect ? 1 : 0); } HttpRouteRecord decodeHttpRoute(ByteReader &r) { HttpRouteRecord rec; Loading @@ -118,7 +118,6 @@ HttpRouteRecord decodeHttpRoute(ByteReader &r) { uint32_t fpCount = r.u32(); rec.tlsPinnedFingerprintsSha256.reserve(fpCount); for (uint32_t i = 0; i < fpCount; ++i) rec.tlsPinnedFingerprintsSha256.push_back(r.str()); rec.forceHttpsRedirect = r.u8() != 0; return rec; } Loading @@ -132,6 +131,20 @@ std::pair<std::string, int32_t> decodeHttpRoutePriority(ByteReader &r) { return {routeId, priority}; } // Same reasoning as HttpRoutePriority above: its own section rather than a field appended to // encodeHttpRoute/decodeHttpRoute, so an older WAL/snapshot with no entries here just means // every route defaults to forceHttpsRedirect=false (see listHttpRoutes()'s join) instead of // desyncing the shared multi-record buffer or throwing on a truncated WAL frame. void encodeHttpRouteForceHttpsRedirect(ByteWriter &w, const std::string &routeId, bool value) { w.str(routeId); w.u8(value ? 1 : 0); } std::pair<std::string, bool> decodeHttpRouteForceHttpsRedirect(ByteReader &r) { std::string routeId = r.str(); bool value = r.u8() != 0; return {routeId, value}; } void encodeSmtpRule(ByteWriter &w, const SmtpRelayRuleRecord &r) { w.str(r.id); w.str(r.domain); Loading Loading @@ -441,6 +454,14 @@ void ConfigStore::applyRecord(RecordTable table, RecordOp op, const std::vector< _httpRoutePriorities.erase(r.str()); } break; case RecordTable::HttpRouteForceHttpsRedirect: if (op == RecordOp::Put) { auto [routeId, value] = decodeHttpRouteForceHttpsRedirect(r); _httpRouteForceHttpsRedirect[routeId] = value; } else { _httpRouteForceHttpsRedirect.erase(r.str()); } break; } ++_version; } Loading @@ -456,6 +477,11 @@ void ConfigStore::putHttpRoute(const HttpRouteRecord &rec) { encodeHttpRoutePriority(pw, rec.id, rec.priority); appendWal(RecordTable::HttpRoutePriority, RecordOp::Put, pw.buffer()); _httpRoutePriorities[rec.id] = rec.priority; ByteWriter fw; encodeHttpRouteForceHttpsRedirect(fw, rec.id, rec.forceHttpsRedirect); appendWal(RecordTable::HttpRouteForceHttpsRedirect, RecordOp::Put, fw.buffer()); _httpRouteForceHttpsRedirect[rec.id] = rec.forceHttpsRedirect; } void ConfigStore::deleteHttpRoute(const std::string &id) { Loading @@ -469,6 +495,11 @@ void ConfigStore::deleteHttpRoute(const std::string &id) { pw.str(id); appendWal(RecordTable::HttpRoutePriority, RecordOp::Delete, pw.buffer()); _httpRoutePriorities.erase(id); ByteWriter fw; fw.str(id); appendWal(RecordTable::HttpRouteForceHttpsRedirect, RecordOp::Delete, fw.buffer()); _httpRouteForceHttpsRedirect.erase(id); } std::vector<HttpRouteRecord> ConfigStore::listHttpRoutes() const { Loading @@ -478,6 +509,8 @@ std::vector<HttpRouteRecord> ConfigStore::listHttpRoutes() const { out.push_back(rec); auto pit = _httpRoutePriorities.find(id); out.back().priority = (pit != _httpRoutePriorities.end()) ? pit->second : 0; auto fit = _httpRouteForceHttpsRedirect.find(id); out.back().forceHttpsRedirect = (fit != _httpRouteForceHttpsRedirect.end()) ? fit->second : false; } // HttpProxyController::RequestEvent picks the first match off this list -- lowest priority // first, id as a stable tiebreak (most commonly every route sitting at the default 0). Loading Loading @@ -706,6 +739,11 @@ std::vector<uint8_t> ConfigStore::snapshot() const { w.u32(static_cast<uint32_t>(_httpRoutePriorities.size())); for (const auto &[routeId, priority] : _httpRoutePriorities) encodeHttpRoutePriority(w, routeId, priority); w.u32(static_cast<uint32_t>(_httpRouteForceHttpsRedirect.size())); for (const auto &[routeId, value] : _httpRouteForceHttpsRedirect) { encodeHttpRouteForceHttpsRedirect(w, routeId, value); } return w.buffer(); } Loading @@ -728,6 +766,7 @@ bool ConfigStore::applySnapshotIfNewer(const std::vector<uint8_t> &blob) { std::unordered_map<std::string, DkimKeyRecord> dkimKeys; std::unordered_map<std::string, ListenerTlsAssignmentRecord> listenerTlsAssignments; std::unordered_map<std::string, int32_t> httpRoutePriorities; std::unordered_map<std::string, bool> httpRouteForceHttpsRedirect; uint32_t n = r.u32(); for (uint32_t i = 0; i < n; ++i) { Loading Loading @@ -790,6 +829,14 @@ bool ConfigStore::applySnapshotIfNewer(const std::vector<uint8_t> &blob) { httpRoutePriorities[routeId] = priority; } } // HTTP route forceHttpsRedirect flags were added after priorities; same guard. if (!r.atEnd()) { n = r.u32(); for (uint32_t i = 0; i < n; ++i) { auto [routeId, value] = decodeHttpRouteForceHttpsRedirect(r); httpRouteForceHttpsRedirect[routeId] = value; } } _httpRoutes = std::move(httpRoutes); _smtpRelayRules = std::move(smtpRules); Loading @@ -800,6 +847,7 @@ bool ConfigStore::applySnapshotIfNewer(const std::vector<uint8_t> &blob) { _dkimKeys = std::move(dkimKeys); _listenerTlsAssignments = std::move(listenerTlsAssignments); _httpRoutePriorities = std::move(httpRoutePriorities); _httpRouteForceHttpsRedirect = std::move(httpRouteForceHttpsRedirect); _version = incomingVersion; return true; } Loading src/store/ConfigStore.h +1 −0 Changes for src/store/ConfigStore.h: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -89,6 +89,7 @@ private: std::unordered_map<std::string, HttpRouteRecord> _httpRoutes; std::unordered_map<std::string, int32_t> _httpRoutePriorities; // routeId -> HttpRouteRecord::priority std::unordered_map<std::string, bool> _httpRouteForceHttpsRedirect; // routeId -> HttpRouteRecord::forceHttpsRedirect std::unordered_map<std::string, SmtpRelayRuleRecord> _smtpRelayRules; std::unordered_map<std::string, PluginConfigRecord> _pluginConfigs; std::unordered_map<std::string, PermissionRegistryRecord> _permissions; Loading src/store/Records.h +6 −0 Changes for src/store/Records.h: 6 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -38,6 +38,9 @@ struct HttpRouteRecord { // When true, a request matching this route that arrived over a plain (non-TLS) listener // gets a 301 redirect to the same host+path under "https://" instead of being proxied -- // see HttpProxyController::RequestEvent. No effect on a listener that's already TLS. // Persisted separately from the rest of this record, same as priority above (see // ConfigStore::encodeHttpRoute's comment) -- round-trips through // ConfigStore::listHttpRoutes(), not through encodeHttpRoute/decodeHttpRoute directly. bool forceHttpsRedirect = false; }; Loading Loading @@ -178,6 +181,9 @@ enum class RecordTable : uint8_t { // section, guarded the same way TlsProfile/UpstreamPool/DkimKey/ListenerTlsAssignment were // added, has no such ambiguity. HttpRoutePriority = 9, // HttpRouteRecord::forceHttpsRedirect's storage -- same reasoning as HttpRoutePriority // just above (own section, not a field appended to encodeHttpRoute/decodeHttpRoute). HttpRouteForceHttpsRedirect = 10, }; enum class RecordOp : uint8_t { Loading Loading
src/store/ConfigStore.cpp +52 −4 Changes for src/store/ConfigStore.cpp: 52 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -92,8 +92,9 @@ private: size_t _pos = 0; }; // Deliberately does not touch r.priority -- see the HttpRoutePriority comment in Records.h for // why that field is persisted as its own section instead of appended here. // Deliberately does not touch r.priority/r.forceHttpsRedirect -- see the HttpRoutePriority // comment in Records.h for why fields added after the initial schema are persisted as their // own section instead of appended here. void encodeHttpRoute(ByteWriter &w, const HttpRouteRecord &r) { w.str(r.id); w.str(r.domain); Loading @@ -104,7 +105,6 @@ void encodeHttpRoute(ByteWriter &w, const HttpRouteRecord &r) { w.u8(r.tlsVerifyUpstream ? 1 : 0); w.u32(static_cast<uint32_t>(r.tlsPinnedFingerprintsSha256.size())); for (const auto &fp : r.tlsPinnedFingerprintsSha256) w.str(fp); w.u8(r.forceHttpsRedirect ? 1 : 0); } HttpRouteRecord decodeHttpRoute(ByteReader &r) { HttpRouteRecord rec; Loading @@ -118,7 +118,6 @@ HttpRouteRecord decodeHttpRoute(ByteReader &r) { uint32_t fpCount = r.u32(); rec.tlsPinnedFingerprintsSha256.reserve(fpCount); for (uint32_t i = 0; i < fpCount; ++i) rec.tlsPinnedFingerprintsSha256.push_back(r.str()); rec.forceHttpsRedirect = r.u8() != 0; return rec; } Loading @@ -132,6 +131,20 @@ std::pair<std::string, int32_t> decodeHttpRoutePriority(ByteReader &r) { return {routeId, priority}; } // Same reasoning as HttpRoutePriority above: its own section rather than a field appended to // encodeHttpRoute/decodeHttpRoute, so an older WAL/snapshot with no entries here just means // every route defaults to forceHttpsRedirect=false (see listHttpRoutes()'s join) instead of // desyncing the shared multi-record buffer or throwing on a truncated WAL frame. void encodeHttpRouteForceHttpsRedirect(ByteWriter &w, const std::string &routeId, bool value) { w.str(routeId); w.u8(value ? 1 : 0); } std::pair<std::string, bool> decodeHttpRouteForceHttpsRedirect(ByteReader &r) { std::string routeId = r.str(); bool value = r.u8() != 0; return {routeId, value}; } void encodeSmtpRule(ByteWriter &w, const SmtpRelayRuleRecord &r) { w.str(r.id); w.str(r.domain); Loading Loading @@ -441,6 +454,14 @@ void ConfigStore::applyRecord(RecordTable table, RecordOp op, const std::vector< _httpRoutePriorities.erase(r.str()); } break; case RecordTable::HttpRouteForceHttpsRedirect: if (op == RecordOp::Put) { auto [routeId, value] = decodeHttpRouteForceHttpsRedirect(r); _httpRouteForceHttpsRedirect[routeId] = value; } else { _httpRouteForceHttpsRedirect.erase(r.str()); } break; } ++_version; } Loading @@ -456,6 +477,11 @@ void ConfigStore::putHttpRoute(const HttpRouteRecord &rec) { encodeHttpRoutePriority(pw, rec.id, rec.priority); appendWal(RecordTable::HttpRoutePriority, RecordOp::Put, pw.buffer()); _httpRoutePriorities[rec.id] = rec.priority; ByteWriter fw; encodeHttpRouteForceHttpsRedirect(fw, rec.id, rec.forceHttpsRedirect); appendWal(RecordTable::HttpRouteForceHttpsRedirect, RecordOp::Put, fw.buffer()); _httpRouteForceHttpsRedirect[rec.id] = rec.forceHttpsRedirect; } void ConfigStore::deleteHttpRoute(const std::string &id) { Loading @@ -469,6 +495,11 @@ void ConfigStore::deleteHttpRoute(const std::string &id) { pw.str(id); appendWal(RecordTable::HttpRoutePriority, RecordOp::Delete, pw.buffer()); _httpRoutePriorities.erase(id); ByteWriter fw; fw.str(id); appendWal(RecordTable::HttpRouteForceHttpsRedirect, RecordOp::Delete, fw.buffer()); _httpRouteForceHttpsRedirect.erase(id); } std::vector<HttpRouteRecord> ConfigStore::listHttpRoutes() const { Loading @@ -478,6 +509,8 @@ std::vector<HttpRouteRecord> ConfigStore::listHttpRoutes() const { out.push_back(rec); auto pit = _httpRoutePriorities.find(id); out.back().priority = (pit != _httpRoutePriorities.end()) ? pit->second : 0; auto fit = _httpRouteForceHttpsRedirect.find(id); out.back().forceHttpsRedirect = (fit != _httpRouteForceHttpsRedirect.end()) ? fit->second : false; } // HttpProxyController::RequestEvent picks the first match off this list -- lowest priority // first, id as a stable tiebreak (most commonly every route sitting at the default 0). Loading Loading @@ -706,6 +739,11 @@ std::vector<uint8_t> ConfigStore::snapshot() const { w.u32(static_cast<uint32_t>(_httpRoutePriorities.size())); for (const auto &[routeId, priority] : _httpRoutePriorities) encodeHttpRoutePriority(w, routeId, priority); w.u32(static_cast<uint32_t>(_httpRouteForceHttpsRedirect.size())); for (const auto &[routeId, value] : _httpRouteForceHttpsRedirect) { encodeHttpRouteForceHttpsRedirect(w, routeId, value); } return w.buffer(); } Loading @@ -728,6 +766,7 @@ bool ConfigStore::applySnapshotIfNewer(const std::vector<uint8_t> &blob) { std::unordered_map<std::string, DkimKeyRecord> dkimKeys; std::unordered_map<std::string, ListenerTlsAssignmentRecord> listenerTlsAssignments; std::unordered_map<std::string, int32_t> httpRoutePriorities; std::unordered_map<std::string, bool> httpRouteForceHttpsRedirect; uint32_t n = r.u32(); for (uint32_t i = 0; i < n; ++i) { Loading Loading @@ -790,6 +829,14 @@ bool ConfigStore::applySnapshotIfNewer(const std::vector<uint8_t> &blob) { httpRoutePriorities[routeId] = priority; } } // HTTP route forceHttpsRedirect flags were added after priorities; same guard. if (!r.atEnd()) { n = r.u32(); for (uint32_t i = 0; i < n; ++i) { auto [routeId, value] = decodeHttpRouteForceHttpsRedirect(r); httpRouteForceHttpsRedirect[routeId] = value; } } _httpRoutes = std::move(httpRoutes); _smtpRelayRules = std::move(smtpRules); Loading @@ -800,6 +847,7 @@ bool ConfigStore::applySnapshotIfNewer(const std::vector<uint8_t> &blob) { _dkimKeys = std::move(dkimKeys); _listenerTlsAssignments = std::move(listenerTlsAssignments); _httpRoutePriorities = std::move(httpRoutePriorities); _httpRouteForceHttpsRedirect = std::move(httpRouteForceHttpsRedirect); _version = incomingVersion; return true; } Loading
src/store/ConfigStore.h +1 −0 Changes for src/store/ConfigStore.h: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -89,6 +89,7 @@ private: std::unordered_map<std::string, HttpRouteRecord> _httpRoutes; std::unordered_map<std::string, int32_t> _httpRoutePriorities; // routeId -> HttpRouteRecord::priority std::unordered_map<std::string, bool> _httpRouteForceHttpsRedirect; // routeId -> HttpRouteRecord::forceHttpsRedirect std::unordered_map<std::string, SmtpRelayRuleRecord> _smtpRelayRules; std::unordered_map<std::string, PluginConfigRecord> _pluginConfigs; std::unordered_map<std::string, PermissionRegistryRecord> _permissions; Loading
src/store/Records.h +6 −0 Changes for src/store/Records.h: 6 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -38,6 +38,9 @@ struct HttpRouteRecord { // When true, a request matching this route that arrived over a plain (non-TLS) listener // gets a 301 redirect to the same host+path under "https://" instead of being proxied -- // see HttpProxyController::RequestEvent. No effect on a listener that's already TLS. // Persisted separately from the rest of this record, same as priority above (see // ConfigStore::encodeHttpRoute's comment) -- round-trips through // ConfigStore::listHttpRoutes(), not through encodeHttpRoute/decodeHttpRoute directly. bool forceHttpsRedirect = false; }; Loading Loading @@ -178,6 +181,9 @@ enum class RecordTable : uint8_t { // section, guarded the same way TlsProfile/UpstreamPool/DkimKey/ListenerTlsAssignment were // added, has no such ambiguity. HttpRoutePriority = 9, // HttpRouteRecord::forceHttpsRedirect's storage -- same reasoning as HttpRoutePriority // just above (own section, not a field appended to encodeHttpRoute/decodeHttpRoute). HttpRouteForceHttpsRedirect = 10, }; enum class RecordOp : uint8_t { Loading