From 0df00b6af6775d67d92b15f71b9a485297deeb80 Mon Sep 17 00:00:00 2001 From: Amish Date: Fri, 20 Jan 2023 20:59:48 +0530 Subject: [PATCH] rspamc: add -R option for human readable report --- doc/rspamc.1 | 5 ++ doc/rspamc.1.md | 3 ++ src/client/rspamc.cxx | 105 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 99 insertions(+), 14 deletions(-) diff --git a/doc/rspamc.1 b/doc/rspamc.1 index 338f8053d..8989f98b2 100644 --- a/doc/rspamc.1 +++ b/doc/rspamc.1 @@ -149,6 +149,11 @@ Bind to specified ip address .RS .RE .TP +.B \-R, \-\-human +Output human readable report +.RS +.RE +.TP .B \-j, \-\-json Output formatted JSON .RS diff --git a/doc/rspamc.1.md b/doc/rspamc.1.md index afb256792..c6baa939c 100644 --- a/doc/rspamc.1.md +++ b/doc/rspamc.1.md @@ -86,6 +86,9 @@ requires input. -b *host:port*, \--bind=*host:port* : Bind to specified ip address +-R, \--human +: Output human readable report + -j, \--json : Output formatted JSON diff --git a/src/client/rspamc.cxx b/src/client/rspamc.cxx index 973bb58a4..57505cf66 100644 --- a/src/client/rspamc.cxx +++ b/src/client/rspamc.cxx @@ -71,6 +71,7 @@ static gboolean pass_all; static gboolean tty = FALSE; static gboolean verbose = FALSE; static gboolean print_commands = FALSE; +static gboolean humanreport = FALSE; static gboolean json = FALSE; static gboolean compact = FALSE; static gboolean headers = FALSE; @@ -134,6 +135,7 @@ static GOptionEntry entries[] = "Bind to specified ip address", nullptr}, {"commands", 0, 0, G_OPTION_ARG_NONE, &print_commands, "List available commands", nullptr}, + {"human", 'R', 0, G_OPTION_ARG_NONE, &humanreport, "Output human readable report", nullptr}, {"json", 'j', 0, G_OPTION_ARG_NONE, &json, "Output json reply", nullptr}, {"compact", '\0', 0, G_OPTION_ARG_NONE, &compact, "Output compact json reply", nullptr}, {"headers", 0, 0, G_OPTION_ARG_NONE, &headers, "Output HTTP headers", @@ -823,6 +825,59 @@ add_options(GQueue *opts) } } +static void +print_indented_line(FILE *out, std::string line, size_t maxlen, size_t indent) +{ + if (maxlen < 1) return; + + std::string s; + for (size_t pos = 0; pos < line.length(); pos += s.length()) { + s = line.substr(pos, pos ? (maxlen-indent) : maxlen); + if (indent && pos) fmt::print(out, "{:>{}}", " ", indent); + fmt::print(out, "{}\n", s); + } +} + +static void +rspamc_symbol_human_output(FILE *out, const ucl_object_t *obj) +{ + auto first = true; + double score = 0; + const char *key = nullptr, *desc = nullptr; + + const auto *val = ucl_object_lookup(obj, "score"); + if (val != nullptr) score = ucl_object_todouble(val); + + key = ucl_object_key(obj); + val = ucl_object_lookup(obj, "description"); + if (val != nullptr) desc = ucl_object_tostring(val); + + std::string line = fmt::format("{:>4.1f} {:<22} ", score, key); + if (desc != nullptr) line += desc; + + val = ucl_object_lookup(obj, "options"); + if (val != nullptr && val->type == UCL_ARRAY) { + ucl_object_iter_t it = nullptr; + const ucl_object_t *cur; + + line += fmt::format("{}[", desc == nullptr ? "" : " "); + + while ((cur = ucl_object_iterate (val, &it, true)) != nullptr) { + if (first) { + line += fmt::format("{}", ucl_object_tostring(cur)); + first = false; + } + else { + line += fmt::format(",{}", ucl_object_tostring(cur)); + } + } + line += ']'; + } + else if (desc == nullptr) line += '\n'; + + print_indented_line(out, line, 78, 28); +} + static void rspamc_symbol_output(FILE *out, const ucl_object_t *obj) { @@ -864,12 +919,12 @@ rspamc_metric_output(FILE *out, const ucl_object_t *obj) auto print_protocol_string = [&](const char *ucl_name, const char *output_message) { auto *elt = ucl_object_lookup(obj, ucl_name); if (elt) { - fmt::print(out, "{}: {}\n", output_message, + fmt::print(out, fmt::runtime(humanreport ? ",{}={}" : "{}: {}\n"), output_message, emphasis_argument(ucl_object_tostring(elt))); } }; - fmt::print(out, "[Metric: default]\n"); + if (!humanreport) fmt::print(out, "[Metric: default]\n"); const auto *elt = ucl_object_lookup(obj, "required_score"); @@ -885,6 +940,13 @@ rspamc_metric_output(FILE *out, const ucl_object_t *obj) got_scores++; } + if (humanreport) { + fmt::print(out, + "{}/{}", + emphasis_argument(score, 2), + emphasis_argument(required_score, 2)); + } + elt = ucl_object_lookup(obj, "action"); if (elt) { auto act = rspamd_action_from_str_rspamc(ucl_object_tostring(elt)); @@ -915,26 +977,36 @@ rspamc_metric_output(FILE *out, const ucl_object_t *obj) colorized_action = fmt::format(fmt::emphasis::bold, ucl_object_tostring(elt)); break; } - fmt::print(out, "Action: {}\n", colorized_action); + fmt::print(out, fmt::runtime(humanreport ? ",Action={}" : "Action: {}\n"), colorized_action); } - fmt::print(out, "Spam: {}\n", emphasis_argument(act.value() < METRIC_ACTION_GREYLIST ? - "true" : "false")); + fmt::print(out, fmt::runtime(humanreport ? ",Spam={}" : "Spam: {}\n"), + emphasis_argument(act.value() < METRIC_ACTION_GREYLIST ? + "true" : "false")); } else { print_protocol_string("action", "Action"); } } - print_protocol_string("subject", "Subject"); + if (!humanreport) print_protocol_string("subject", "Subject"); - if (got_scores == 2) { + if (humanreport) fmt::print(out, "\n"); + else if (got_scores == 2) { fmt::print(out, "Score: {} / {}\n", emphasis_argument(score, 2), emphasis_argument(required_score, 2)); } + if (humanreport) { + fmt::print(out, "Content analysis details: ({} points, {} required)\n\n", + emphasis_argument(score, 2), + emphasis_argument(required_score, 2)); + fmt::print(out, " pts rule name description\n"); + fmt::print(out, "---- ---------------------- --------------------------------------------------\n"); + } + elt = ucl_object_lookup(obj, "symbols"); if (elt) { @@ -949,9 +1021,11 @@ rspamc_metric_output(FILE *out, const ucl_object_t *obj) sort_ucl_container_with_default(symbols, "name"); for (const auto *sym_obj : symbols) { - rspamc_symbol_output(out, sym_obj); + if (humanreport) rspamc_symbol_human_output(out, sym_obj); + else rspamc_symbol_output(out, sym_obj); } } + if (humanreport) fmt::print(out, "\n"); } static void @@ -988,8 +1062,10 @@ rspamc_symbols_output(FILE *out, ucl_object_t *obj) } }; - print_protocol_string("message-id", "Message-ID"); - print_protocol_string("queue-id", "Queue-ID"); + if (!humanreport) { + print_protocol_string("message-id", "Message-ID"); + print_protocol_string("queue-id", "Queue-ID"); + } const auto *elt = ucl_object_lookup(obj, "urls"); @@ -1003,7 +1079,7 @@ rspamc_symbols_output(FILE *out, ucl_object_t *obj) emitted = (char *)ucl_object_emit(elt, UCL_EMIT_JSON); } - fmt::print(out, "Urls: {}\n", emitted); + if (emitted && strcmp(emitted, "[]")) fmt::print(out, "Urls: {}\n", emitted); free(emitted); } @@ -1018,11 +1094,12 @@ rspamc_symbols_output(FILE *out, ucl_object_t *obj) emitted = (char *)ucl_object_emit(elt, UCL_EMIT_JSON); } - fmt::print(out, "Emails: {}\n", emitted); + if (emitted && strcmp(emitted, "[]")) fmt::print(out, "Emails: {}\n", emitted); free(emitted); } print_protocol_string("error", "Scan error"); + if (humanreport) return; elt = ucl_object_lookup(obj, "messages"); if (elt && elt->type == UCL_OBJECT) { @@ -1646,13 +1723,13 @@ rspamc_client_cb(struct rspamd_client_connection *conn, } else { if (cmd.need_input && !json) { - if (!compact) { + if (!compact && !humanreport) { fmt::print(out, "Results for file: {} ({:.3} seconds)\n", emphasis_argument(cbdata->filename), diff); } } else { - if (!compact && !json) { + if (!compact && !json && !humanreport) { fmt::print(out, "Results for command: {} ({:.3} seconds)\n", emphasis_argument(cmd.name), diff); } -- 2.39.5