From: Baptiste Daroussin Date: Tue, 10 Mar 2026 21:07:32 +0000 (+0100) Subject: rewind_thread_list: imrpove X-Git-Tag: RELEASE_1_8_0~9 X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=32fa1e2d4a195f76b1c2e22dd47ff24872777cab;p=thirdparty%2Fmlmmj.git rewind_thread_list: imrpove Add test and rework the code: - only read mail headers - improvement memory management - cleanup code - make the code testable --- diff --git a/include/send_digest.h b/include/send_digest.h index 47ed15be..f9130cf6 100644 --- a/include/send_digest.h +++ b/include/send_digest.h @@ -23,5 +23,34 @@ #pragma once +struct digest_mail { + int idx; + char *from; +}; + +struct digest_thread { + char *subject; + int num_mails; + struct digest_mail *mails; +}; + +struct thread_list_state; +typedef struct thread_list_state thread_list_state; +struct thread_list_state { + const char *listdir; + int firstindex; + int lastindex; + int num_threads; + struct digest_thread *threads; + int cur_thread; + int cur_mail; +}; + +thread_list_state *init_thread_list(const char *listdir, + int firstindex, int lastindex); +void rewind_thread_list(void *state); +const char *get_thread_list_line(void *state); +void finish_thread_list(thread_list_state *s); + int send_digest(struct ml *ml, int lastindex, int index, int issue, const char *addr, const char *mlmmjsend); diff --git a/src/send_digest.c b/src/send_digest.c index da5972cb..249ff187 100644 --- a/src/send_digest.c +++ b/src/send_digest.c @@ -42,32 +42,8 @@ #include "utils.h" -struct mail { - int idx; - char *from; -}; - -struct thread { - char *subject; - int num_mails; - struct mail *mails; -}; - - -struct thread_list_state; -typedef struct thread_list_state thread_list_state; -struct thread_list_state { - const char *listdir; - int firstindex; - int lastindex; - int num_threads; - struct thread *threads; - int cur_thread; - int cur_mail; -}; - - -static thread_list_state *init_thread_list( + +thread_list_state *init_thread_list( const char *listdir, int firstindex, int lastindex) { /* We use a static variable rather than dynamic allocation as @@ -83,16 +59,16 @@ static thread_list_state *init_thread_list( } -static void rewind_thread_list(void * state) +void rewind_thread_list(void * state) { thread_list_state *s = (thread_list_state *)state; FILE *archivef; int i, j, thread_idx; char *line = NULL, *tmp, *subj, *from, *tmpfrom; - size_t linecap = 0; + size_t linecap = 0, tmplen; char *archivename; - int num_threads = 0; - struct thread *threads = NULL; + int num_threads = 0, threads_alloc = 0; + struct digest_thread *threads = NULL; if (s->cur_thread != -1) { /* We have gathered the data already; just rewind */ s->cur_thread = 0; @@ -112,11 +88,14 @@ static void rewind_thread_list(void * state) from = NULL; while (getline(&line, &linecap, archivef) > 0) { + /* stop at end of headers */ + if (line[0] == '\n' || + (line[0] == '\r' && line[1] == '\n')) + break; if (strncasecmp(line, "Subject:", 8) == 0) { free(subj); subj = unistr_header_to_utf8(line + 8); - } - if (strncasecmp(line, "From:", 5) == 0) { + } else if (strncasecmp(line, "From:", 5) == 0) { free(from); from = unistr_header_to_utf8(line + 5); } @@ -144,40 +123,42 @@ static void rewind_thread_list(void * state) } /* tmp is now the clean subject */ + tmplen = strlen(tmp); thread_idx = -1; for (j=0; j 1 && + (threads[thread_idx].num_mails & (threads[thread_idx].num_mails - 1)) == 0) { + /* power of 2: double the allocation */ + threads[thread_idx].mails = xrealloc(threads[thread_idx].mails, + threads[thread_idx].num_mails*2*sizeof(struct digest_mail)); + } else if (threads[thread_idx].num_mails == 1) { + threads[thread_idx].mails = xmalloc(sizeof(struct digest_mail)); + } threads[thread_idx].mails[threads[thread_idx].num_mails-1].idx = i; xasprintf(&tmpfrom, " %d - %s\n", i, tmp); threads[thread_idx].mails[threads[thread_idx].num_mails-1].from = tmpfrom; @@ -196,7 +177,7 @@ static void rewind_thread_list(void * state) } -static const char *get_thread_list_line(void * state) +const char *get_thread_list_line(void * state) { thread_list_state *s = (thread_list_state *)state; @@ -217,7 +198,7 @@ static const char *get_thread_list_line(void * state) } -static void finish_thread_list(thread_list_state * s) +void finish_thread_list(thread_list_state * s) { int i, j; if (s->threads == NULL) return; diff --git a/tests/mlmmj.c b/tests/mlmmj.c index 782b2a03..7a89a890 100644 --- a/tests/mlmmj.c +++ b/tests/mlmmj.c @@ -4369,6 +4369,94 @@ ATF_TC_BODY(digest_thread_grouping, tc) free(queuepath); } +ATF_TC_WITHOUT_HEAD(digest_thread_stops_at_headers); +ATF_TC_BODY(digest_thread_stops_at_headers, tc) +{ + thread_list_state *tls; + const char *line; + + /* + * Create archive files where the body contains lines that look like + * headers. The thread builder must stop at the blank line and never + * parse the body, so the fake "Subject:" in the body must be ignored. + */ + mkdir("fakelist", 0755); + mkdir("fakelist/archive", 0755); + atf_utils_create_file("fakelist/archive/1", + "From: alice@test\n" + "Subject: Real subject\n" + "\n" + "Subject: Fake subject in body\n" + "From: fake@body\n"); + + atf_utils_create_file("fakelist/archive/2", + "From: bob@test\n" + "Subject: Re: Real subject\n" + "\n" + "body text\n"); + + atf_utils_create_file("fakelist/archive/3", + "From: charlie@test\n" + "Subject: Other topic\n" + "\n" + "Subject: Decoy\n"); + + tls = init_thread_list("fakelist", 1, 3); + rewind_thread_list(tls); + + /* Thread 1: "Real subject" (mails 1 and 2 grouped via Re:) */ + line = get_thread_list_line(tls); + ATF_REQUIRE_MSG(line != NULL, "expected thread 1 subject"); + ATF_CHECK_MSG(strstr(line, "Real subject") != NULL, + "thread 1 subject should be 'Real subject', got: %s", line); + + /* Mail 1 in thread 1: alice */ + line = get_thread_list_line(tls); + ATF_REQUIRE(line != NULL); + ATF_CHECK_MSG(strstr(line, "1 - ") != NULL && strstr(line, "alice") != NULL, + "expected mail 1 from alice, got: %s", line); + + /* Mail 2 in thread 1: bob (Re: grouped) */ + line = get_thread_list_line(tls); + ATF_REQUIRE(line != NULL); + ATF_CHECK_MSG(strstr(line, "2 - ") != NULL && strstr(line, "bob") != NULL, + "expected mail 2 from bob, got: %s", line); + + /* Separator between threads */ + line = get_thread_list_line(tls); + ATF_REQUIRE(line != NULL); + ATF_CHECK_MSG(strcmp(line, "\n") == 0, + "expected blank separator, got: %s", line); + + /* Thread 2: "Other topic" (only mail 3) */ + line = get_thread_list_line(tls); + ATF_REQUIRE(line != NULL); + ATF_CHECK_MSG(strstr(line, "Other topic") != NULL, + "thread 2 subject should be 'Other topic', got: %s", line); + + /* Mail 3 in thread 2: charlie */ + line = get_thread_list_line(tls); + ATF_REQUIRE(line != NULL); + ATF_CHECK_MSG(strstr(line, "3 - ") != NULL && strstr(line, "charlie") != NULL, + "expected mail 3 from charlie, got: %s", line); + + /* Separator */ + line = get_thread_list_line(tls); + ATF_REQUIRE(line != NULL); + ATF_CHECK(strcmp(line, "\n") == 0); + + /* End */ + line = get_thread_list_line(tls); + ATF_CHECK(line == NULL); + + /* "Fake subject in body" must NOT have created a third thread */ + ATF_CHECK_MSG(tls->num_threads == 2, + "expected 2 threads (body headers ignored), got %d", + tls->num_threads); + + finish_thread_list(tls); +} + ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, random_int); @@ -4487,6 +4575,7 @@ ATF_TP_ADD_TCS(tp) ATF_TP_ADD_TC(tp, checkwait_smtpreply_closed); ATF_TP_ADD_TC(tp, digest_thread_list_format); ATF_TP_ADD_TC(tp, digest_thread_grouping); + ATF_TP_ADD_TC(tp, digest_thread_stops_at_headers); return (atf_no_error()); }