Browse Source

[Test] Adopt received framework to allow unit testing

tags/3.1
Vsevolod Stakhov 2 years ago
parent
commit
3fecc4efe4
2 changed files with 165 additions and 59 deletions
  1. 92
    55
      src/libmime/received.cxx
  2. 73
    4
      src/libmime/received.hxx

+ 92
- 55
src/libmime/received.cxx View File

}; };


static inline auto static inline auto
received_part_set_or_append(struct rspamd_task *task,
const gchar *begin,
gsize len,
mime_string &dest) -> void
received_part_set_or_append(const gchar *begin,
gsize len,
mime_string &dest) -> void
{ {
if (len == 0) { if (len == 0) {
return; return;
} }


static auto static auto
received_process_part(struct rspamd_task *task,
const std::string_view &data,
received_process_part(const std::string_view &data,
received_part_type type, received_part_type type,
std::ptrdiff_t &last, std::ptrdiff_t &last,
received_part &npart) -> bool received_part &npart) -> bool
if (p > c) { if (p > c) {
npart.comments.emplace_back(received_char_filter); npart.comments.emplace_back(received_char_filter);
auto &comment = npart.comments.back(); auto &comment = npart.comments.back();
received_part_set_or_append(task,
c, p - c,
received_part_set_or_append(c, p - c,
comment); comment);
} }
} }
if (*p == '(') { if (*p == '(') {
if (p > c) { if (p > c) {
if (type != received_part_type::RSPAMD_RECEIVED_PART_UNKNOWN) { if (type != received_part_type::RSPAMD_RECEIVED_PART_UNKNOWN) {
received_part_set_or_append(task,
c, p - c,
received_part_set_or_append(c, p - c,
npart.data); npart.data);
} }
} }
else if (g_ascii_isspace (*p)) { else if (g_ascii_isspace (*p)) {
if (p > c) { if (p > c) {
if (type != received_part_type::RSPAMD_RECEIVED_PART_UNKNOWN) { if (type != received_part_type::RSPAMD_RECEIVED_PART_UNKNOWN) {
received_part_set_or_append(task,
c, p - c,
received_part_set_or_append(c, p - c,
npart.data); npart.data);
} }
} }
/* It is actually delimiter of date part if not in the comments */ /* It is actually delimiter of date part if not in the comments */
if (p > c) { if (p > c) {
if (type != received_part_type::RSPAMD_RECEIVED_PART_UNKNOWN) { if (type != received_part_type::RSPAMD_RECEIVED_PART_UNKNOWN) {
received_part_set_or_append(task,
c, p - c,
received_part_set_or_append(c, p - c,
npart.data); npart.data);
} }
} }
break; break;
case read_tcpinfo: case read_tcpinfo:
if (*p == ']') { if (*p == ']') {
received_part_set_or_append(task,
c, p - c + 1,
received_part_set_or_append(c, p - c + 1,
npart.data); npart.data);
seen_tcpinfo = TRUE; seen_tcpinfo = TRUE;
state = skip_spaces; state = skip_spaces;
case read_data: case read_data:
if (p > c) { if (p > c) {
if (type != received_part_type::RSPAMD_RECEIVED_PART_UNKNOWN) { if (type != received_part_type::RSPAMD_RECEIVED_PART_UNKNOWN) {
received_part_set_or_append(task,
c, p - c,
received_part_set_or_append(c, p - c,
npart.data); npart.data);
} }


} }


static auto static auto
received_spill(struct rspamd_task *task,
const std::string_view &in,
received_spill(const std::string_view &in,
std::ptrdiff_t &date_pos) -> std::vector<received_part> std::ptrdiff_t &date_pos) -> std::vector<received_part>
{ {
std::vector<received_part> parts; std::vector<received_part> parts;
auto &rcvd_part = parts.back(); auto &rcvd_part = parts.back();
auto chunk = std::string_view{p, (std::size_t)(end - p)}; auto chunk = std::string_view{p, (std::size_t)(end - p)};


if (!received_process_part(task, chunk, what, pos, rcvd_part)) {
if (!received_process_part(chunk, what, pos, rcvd_part)) {
parts.pop_back(); parts.pop_back();


return false; return false;
(rspamd_inet_address_parse_flags)(RSPAMD_INET_ADDRESS_PARSE_REMOTE|RSPAMD_INET_ADDRESS_PARSE_NO_UNIX) (rspamd_inet_address_parse_flags)(RSPAMD_INET_ADDRESS_PARSE_REMOTE|RSPAMD_INET_ADDRESS_PARSE_NO_UNIX)


static auto static auto
received_process_rdns(struct rspamd_task *task,
const std::string_view &in,
mime_string &dest) -> bool
received_process_rdns(rspamd_mempool_t *pool,
const std::string_view &in,
mime_string &dest) -> bool
{ {
auto seen_dot = false; auto seen_dot = false;


/* We have enclosed ip address */ /* We have enclosed ip address */
auto *addr = rspamd_parse_inet_address_pool(p + 1, auto *addr = rspamd_parse_inet_address_pool(p + 1,
(end - p) - 2, (end - p) - 2,
task->task_pool,
pool,
RSPAMD_INET_ADDRESS_PARSE_RECEIVED); RSPAMD_INET_ADDRESS_PARSE_RECEIVED);


if (addr) { if (addr) {
} }


static auto static auto
received_process_host_tcpinfo(struct rspamd_task *task,
received_process_host_tcpinfo(rspamd_mempool_t *pool,
received_header &rh, received_header &rh,
const std::string_view &in) -> bool const std::string_view &in) -> bool
{ {
auto substr_addr = in.substr(1, brace_pos - 1); auto substr_addr = in.substr(1, brace_pos - 1);
addr = rspamd_parse_inet_address_pool(substr_addr.data(), addr = rspamd_parse_inet_address_pool(substr_addr.data(),
substr_addr.size(), substr_addr.size(),
task->task_pool,
pool,
RSPAMD_INET_ADDRESS_PARSE_RECEIVED); RSPAMD_INET_ADDRESS_PARSE_RECEIVED);


if (addr) { if (addr) {
if (g_ascii_isxdigit(in[0])) { if (g_ascii_isxdigit(in[0])) {
/* Try to parse IP address */ /* Try to parse IP address */
addr = rspamd_parse_inet_address_pool(in.data(), addr = rspamd_parse_inet_address_pool(in.data(),
in.size(), task->task_pool, RSPAMD_INET_ADDRESS_PARSE_RECEIVED);
in.size(), pool, RSPAMD_INET_ADDRESS_PARSE_RECEIVED);
if (addr) { if (addr) {
rh.addr = addr; rh.addr = addr;
rh.real_ip.assign_copy(std::string_view(rspamd_inet_address_to_string(addr))); rh.real_ip.assign_copy(std::string_view(rspamd_inet_address_to_string(addr)));
ebrace_pos - obrace_pos - 1); ebrace_pos - obrace_pos - 1);
addr = rspamd_parse_inet_address_pool(substr_addr.data(), addr = rspamd_parse_inet_address_pool(substr_addr.data(),
substr_addr.size(), substr_addr.size(),
task->task_pool,
pool,
RSPAMD_INET_ADDRESS_PARSE_RECEIVED); RSPAMD_INET_ADDRESS_PARSE_RECEIVED);


if (addr) { if (addr) {
/* Process with rDNS */ /* Process with rDNS */
auto rdns_substr = in.substr(0, obrace_pos); auto rdns_substr = in.substr(0, obrace_pos);


if (received_process_rdns(task,
rdns_substr,
rh.real_hostname)) {
if (received_process_rdns(pool,rdns_substr,rh.real_hostname)) {
ret = true; ret = true;
} }
} }
} }
else { else {
/* Hostname or some crap, sigh... */ /* Hostname or some crap, sigh... */
if (received_process_rdns(task, in, rh.real_hostname)) {
if (received_process_rdns(pool, in, rh.real_hostname)) {
ret = true; ret = true;
} }
} }
} }


static void static void
received_process_from(struct rspamd_task *task,
const received_part &rpart,
received_header &rh)
received_process_from(rspamd_mempool_t *pool,
const received_part &rpart,
received_header &rh)
{ {
if (rpart.data.size() > 0) { if (rpart.data.size() > 0) {
/* We have seen multiple cases: /* We have seen multiple cases:
if (!rpart.comments.empty()) { if (!rpart.comments.empty()) {
/* We can have info within comment as part of RFC */ /* We can have info within comment as part of RFC */
received_process_host_tcpinfo( received_process_host_tcpinfo(
task, rh,
pool, rh,
rpart.comments[0].as_view()); rpart.comments[0].as_view());
} }


if (rh.real_ip.size() == 0) { if (rh.real_ip.size() == 0) {
/* Try to do the same with data */ /* Try to do the same with data */
if (received_process_host_tcpinfo( if (received_process_host_tcpinfo(
task, rh,
pool, rh,
rpart.data.as_view())) { rpart.data.as_view())) {
seen_ip_in_data = true; seen_ip_in_data = true;
} }
if (!seen_ip_in_data) { if (!seen_ip_in_data) {
if (rh.real_ip.size() != 0) { if (rh.real_ip.size() != 0) {
/* Get anounced hostname (usually helo) */ /* Get anounced hostname (usually helo) */
received_process_rdns(task,
received_process_rdns(pool,
rpart.data.as_view(), rpart.data.as_view(),
rh.from_hostname); rh.from_hostname);
} }
else { else {
received_process_host_tcpinfo(task,
received_process_host_tcpinfo(pool,
rh, rpart.data.as_view()); rh, rpart.data.as_view());
} }
} }
/* rpart->dlen = 0 */ /* rpart->dlen = 0 */
if (!rpart.comments.empty()) { if (!rpart.comments.empty()) {
received_process_host_tcpinfo( received_process_host_tcpinfo(
task, rh,
pool, rh,
rpart.comments[0].as_view()); rpart.comments[0].as_view());
} }
} }
} }


static auto static auto
received_header_parse(struct rspamd_task *task, const std::string_view &in,
received_header_parse(received_header_chain &chain, rspamd_mempool_t *pool,
const std::string_view &in,
struct rspamd_mime_header *hdr) -> bool struct rspamd_mime_header *hdr) -> bool
{ {
std::ptrdiff_t date_pos = -1; std::ptrdiff_t date_pos = -1;
{"local", received_flags::LOCAL} {"local", received_flags::LOCAL}
}); });


auto parts = received_spill(task, in, date_pos);
auto parts = received_spill(in, date_pos);


if (parts.empty()) { if (parts.empty()) {
return false; return false;
} }


auto *recv_chain_ptr = static_cast<received_header_chain *>(MESSAGE_FIELD(task, received_headers));

if (recv_chain_ptr == nullptr) {
/* This constructor automatically registers dtor in mempool */
recv_chain_ptr = new received_header_chain(task);
MESSAGE_FIELD(task, received_headers) = (void *)recv_chain_ptr;
}

auto &rh = recv_chain_ptr->new_received();
auto &rh = chain.new_received();


rh.flags = received_flags::UNKNOWN; rh.flags = received_flags::UNKNOWN;
rh.hdr = hdr; rh.hdr = hdr;
for (const auto &part : parts) { for (const auto &part : parts) {
switch (part.type) { switch (part.type) {
case received_part_type::RSPAMD_RECEIVED_PART_FROM: case received_part_type::RSPAMD_RECEIVED_PART_FROM:
received_process_from(task, part, rh);
received_process_from(pool, part, rh);
break; break;
case received_part_type::RSPAMD_RECEIVED_PART_BY: case received_part_type::RSPAMD_RECEIVED_PART_BY:
received_process_rdns(task,
received_process_rdns(pool,
part.data.as_view(), part.data.as_view(),
rh.by_hostname); rh.by_hostname);
break; break;
const char *data, size_t sz, const char *data, size_t sz,
struct rspamd_mime_header *hdr) struct rspamd_mime_header *hdr)
{ {
return rspamd::mime::received_header_parse(task, std::string_view{data, sz}, hdr);
auto *recv_chain_ptr = static_cast<rspamd::mime::received_header_chain *>
(MESSAGE_FIELD(task, received_headers));

if (recv_chain_ptr == nullptr) {
/* This constructor automatically registers dtor in mempool */
recv_chain_ptr = new rspamd::mime::received_header_chain(task);
MESSAGE_FIELD(task, received_headers) = (void *)recv_chain_ptr;
}
return rspamd::mime::received_header_parse(*recv_chain_ptr, task->task_pool,
std::string_view{data, sz}, hdr);
} }


bool bool
return rspamd::mime::received_export_to_lua( return rspamd::mime::received_export_to_lua(
static_cast<rspamd::mime::received_header_chain *>(MESSAGE_FIELD(task, received_headers)), static_cast<rspamd::mime::received_header_chain *>(MESSAGE_FIELD(task, received_headers)),
L); L);
}

/* Tests part */
#define DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL
#include "doctest/doctest.h"

TEST_SUITE("received") {
TEST_CASE("parse received")
{
using namespace std::string_view_literals;
using map_type = robin_hood::unordered_flat_map<std::string_view, std::string_view>;
std::vector<std::pair<std::string_view, map_type>> cases{
{"from smtp11.mailtrack.pl (smtp11.mailtrack.pl [185.243.30.90])"sv,
{
{"real_ip", "185.243.30.90"},
{"from_ip", "185.243.30.90"},
{"real_hostname", "smtp11.mailtrack.pl"},
{"from_hostname", "smtp11.mailtrack.pl"}
}
}
};
rspamd_mempool_t *pool = rspamd_mempool_new_default("rcvd test", 0);

for (auto &&c : cases) {
SUBCASE(c.first.data()) {
rspamd::mime::received_header_chain chain;
auto ret = rspamd::mime::received_header_parse(chain, pool,
c.first, nullptr);
CHECK(ret == true);
auto &&rh = chain.get_received(0);
CHECK(rh.has_value());
auto res = rh.value().get().as_map();

for (const auto &expected : c.second) {
CHECK_MESSAGE(res.contains(expected.first), expected.first.data());
CHECK(res[expected.first] == expected.second);
}
for (const auto &existing : res) {
CHECK_MESSAGE(c.second.contains(existing.first), existing.first.data());
CHECK(c.second[existing.first] == existing.second);
}
}
}

rspamd_mempool_delete(pool);
}
} }

+ 73
- 4
src/libmime/received.hxx View File

#include "mime_string.hxx" #include "mime_string.hxx"
#include "libmime/email_addr.h" #include "libmime/email_addr.h"
#include "libserver/task.h" #include "libserver/task.h"
#include "contrib/robin-hood/robin_hood.h"
#include <vector> #include <vector>
#include <string_view> #include <string_view>
#include <utility> #include <utility>
received_header& operator=(received_header &&other) noexcept { received_header& operator=(received_header &&other) noexcept {
if (this != &other) { if (this != &other) {
from_hostname = std::move(other.from_hostname); from_hostname = std::move(other.from_hostname);
from_ip = std::move(other.from_ip);
from_ip = other.from_ip;
real_hostname = std::move(other.real_hostname); real_hostname = std::move(other.real_hostname);
real_ip = std::move(other.real_ip);
by_hostname = std::move(other.by_hostname); by_hostname = std::move(other.by_hostname);
for_mbox = std::move(other.for_mbox);
for_mbox = other.for_mbox;
timestamp = other.timestamp; timestamp = other.timestamp;
flags = other.flags; flags = other.flags;
std::swap(for_addr, other.for_addr); std::swap(for_addr, other.for_addr);
return *this; return *this;
} }


/* Unit tests helper */
static auto from_map(const robin_hood::unordered_flat_map<std::string_view, std::string_view> &map) -> received_header {
using namespace std::string_view_literals;
received_header rh;

if (map.contains("from_hostname")) {
rh.from_hostname.assign_copy(map.at("from_hostname"sv));
}
if (map.contains("real_hostname")) {
rh.real_hostname.assign_copy(map.at("real_hostname"sv));
}
if (map.contains("by_hostname")) {
rh.by_hostname.assign_copy(map.at("by_hostname"sv));
}
if (map.contains("real_ip")) {
rh.real_ip.assign_copy(map.at("real_ip"sv));
}
if (map.contains("from_ip")) {
rh.from_ip = map.at("from_ip"sv);
}
if (map.contains("for_mbox")) {
rh.for_mbox = map.at("for_mbox"sv);
}

return rh;
}

auto as_map() const -> robin_hood::unordered_flat_map<std::string_view, std::string_view>
{
robin_hood::unordered_flat_map<std::string_view, std::string_view> map;

if (!from_hostname.empty()) {
map["from_hostname"] = from_hostname.as_view();
}
if (!real_hostname.empty()) {
map["real_hostname"] = real_hostname.as_view();
}
if (!by_hostname.empty()) {
map["by_hostname"] = by_hostname.as_view();
}
if (!real_ip.empty()) {
map["real_ip"] = real_ip.as_view();
}
if (!from_ip.empty()) {
map["from_ip"] = from_ip;
}
if (!for_mbox.empty()) {
map["for_mbox"] = for_mbox;
}

return map;
}

~received_header() { ~received_header() {
if (for_addr) { if (for_addr) {
rspamd_email_address_free(for_addr); rspamd_email_address_free(for_addr);


class received_header_chain { class received_header_chain {
public: public:
explicit received_header_chain(struct rspamd_task *_task) : task(_task) {
explicit received_header_chain(struct rspamd_task *task) {
headers.reserve(2); headers.reserve(2);
rspamd_mempool_add_destructor(task->task_pool, rspamd_mempool_add_destructor(task->task_pool,
received_header_chain::received_header_chain_pool_dtor, this); received_header_chain::received_header_chain_pool_dtor, this);
} }
explicit received_header_chain() {
headers.reserve(2);
}


enum class append_type { enum class append_type {
append_tail, append_tail,
return headers.front(); return headers.front();
} }
} }
auto new_received(received_header &&hdr, append_type how = append_type::append_tail) -> received_header & {
if (how == append_type::append_tail) {
headers.emplace_back(std::move(hdr));

return headers.back();
}
else {
headers.insert(std::begin(headers), std::move(hdr));

return headers.front();
}
}
auto get_received(std::size_t nth) -> std::optional<std::reference_wrapper<received_header>>{ auto get_received(std::size_t nth) -> std::optional<std::reference_wrapper<received_header>>{
if (nth < headers.size()) { if (nth < headers.size()) {
return headers[nth]; return headers[nth];
delete static_cast<received_header_chain *>(ptr); delete static_cast<received_header_chain *>(ptr);
} }
std::vector<received_header> headers; std::vector<received_header> headers;
struct rspamd_task *task;
}; };


} }

Loading…
Cancel
Save