]> git.ipfire.org Git - thirdparty/mlmmj.git/commitdiff
rewind_thread_list: imrpove
authorBaptiste Daroussin <bapt@FreeBSD.org>
Tue, 10 Mar 2026 21:07:32 +0000 (22:07 +0100)
committerBaptiste Daroussin <bapt@FreeBSD.org>
Tue, 10 Mar 2026 21:10:24 +0000 (22:10 +0100)
Add test and rework the code:
- only read mail headers
- improvement memory management
- cleanup code
- make the code testable

include/send_digest.h
src/send_digest.c
tests/mlmmj.c

index 47ed15be6c9b61d1ef6b4c3caeada55c1ebf77d5..f9130cf6e0aed938919a8a6bcb2488ac1d349634 100644 (file)
 
 #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);
index da5972cb5b443b8059318fb7bc03757076650f0f..249ff187c84911d4ec0476604dcb13273e143d37 100644 (file)
 #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<num_threads; j++) {
-                       /* subject is stored with trailing \n for display,
-                        * compare only the meaningful part */
-                       if (strncmp(tmp, threads[j].subject,
-                           strlen(tmp)) == 0 &&
-                           threads[j].subject[strlen(tmp)] == '\n') {
+                       if (strncmp(tmp, threads[j].subject, tmplen) == 0 &&
+                           threads[j].subject[tmplen] == '\n') {
                                thread_idx = j;
                                break;
                        }
                }
                if (thread_idx == -1) {
+                       if (num_threads == threads_alloc) {
+                               threads_alloc = threads_alloc ? threads_alloc * 2 : 16;
+                               threads = xrealloc(threads,
+                                               threads_alloc*sizeof(struct digest_thread));
+                       }
+                       xasprintf(&(threads[num_threads].subject), "%s\n", tmp);
+                       threads[num_threads].num_mails = 0;
+                       threads[num_threads].mails = NULL;
+                       thread_idx = num_threads;
                        num_threads++;
-                       threads = xrealloc(threads,
-                                       num_threads*sizeof(struct thread));
-                       xasprintf(&(threads[num_threads-1].subject), "%s\n", tmp);
-                       threads[num_threads-1].num_mails = 0;
-                       threads[num_threads-1].mails = NULL;
-                       thread_idx = num_threads-1;
                }
 
                tmp = from;
-               for (;;) {
-                       if (isspace(*tmp)) {
-                               tmp++;
-                               continue;
-                       }
-                       break;
-               }
+               while (isspace(*tmp))
+                       tmp++;
                /* tmp is now left-trimmed from */
 
                threads[thread_idx].num_mails++;
-               threads[thread_idx].mails = xrealloc(threads[thread_idx].mails,
-                               threads[thread_idx].num_mails*sizeof(struct mail));
+               if (threads[thread_idx].num_mails > 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;
index 782b2a03962afab4f46ea801a0832baddead36cb..7a89a8902d6478ed18641ddfd23751e214b57288 100644 (file)
@@ -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());
 }