#include "vdm/rules/match.hpp" #include #include #include #include namespace vdm::rules { namespace { char lower_ascii(char c) { return static_cast(std::tolower(static_cast(c))); } bool ieq(std::string_view a, std::string_view b) { return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), [](char x, char y) { return lower_ascii(x) == lower_ascii(y); }); } // A rule's `extensions` entries are documented with no leading '.', but be lenient about // one showing up anyway (a hand-edited rule table, an older client) rather than let a // clause that never matches silently swallow a whole category. std::string_view strip_leading_dot(std::string_view s) { return (!s.empty() && s.front() == '.') ? s.substr(1) : s; } bool match_clause(const RuleMatch &m, const MatchInput &in) { if (m.extensions) { bool any = std::any_of(m.extensions->begin(), m.extensions->end(), [&](const auto &e) { return ieq(strip_leading_dot(e), in.extension); }); if (!any) return false; } if (m.mime_types) { bool any = std::any_of(m.mime_types->begin(), m.mime_types->end(), [&](const auto &t) { return ieq(t, in.mime_type); }); if (!any) return false; } if (m.host_pattern && !glob_match(*m.host_pattern, in.host)) return false; if (m.url_pattern && !glob_match(*m.url_pattern, in.url)) return false; if (m.min_size_bytes) { if (!in.size_bytes || *in.size_bytes < *m.min_size_bytes) return false; } if (m.max_size_bytes) { if (!in.size_bytes || *in.size_bytes > *m.max_size_bytes) return false; } return true; } } // namespace bool glob_match(std::string_view pattern, std::string_view text) noexcept { // Classic iterative wildcard match (single backtrack point at the most recent '*'), not // the naive recursive version — bounded work on any input, including a pattern that is // nothing but repeated '*'s against a long `text`. std::size_t p = 0, t = 0; std::size_t star = std::string_view::npos, mark = 0; while (t < text.size()) { if (p < pattern.size() && (pattern[p] == '?' || lower_ascii(pattern[p]) == lower_ascii(text[t]))) { ++p; ++t; } else if (p < pattern.size() && pattern[p] == '*') { star = p++; mark = t; } else if (star != std::string_view::npos) { p = star + 1; t = ++mark; } else { return false; } } while (p < pattern.size() && pattern[p] == '*') ++p; return p == pattern.size(); } std::optional match_rules(const std::vector &rules, const MatchInput &input) { std::vector order(rules.size()); std::iota(order.begin(), order.end(), 0); // Stable by construction: std::stable_sort keeps table order among equal priorities. std::stable_sort(order.begin(), order.end(), [&](std::size_t a, std::size_t b) { return rules[a].priority < rules[b].priority; }); for (auto i : order) { const Rule &r = rules[i]; if (!r.enabled) continue; if (match_clause(r.match, input)) return r.action; } return std::nullopt; } } // namespace vdm::rules