#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);
#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
}
-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;
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);
}
}
/* 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;
}
-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;
}
-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;
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);
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());
}