Coverage Report

Created: 2025-04-22 06:17

/src/neomutt/email/thread.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Create/manipulate threading in emails
4
 *
5
 * @authors
6
 * Copyright (C) 2018-2023 Richard Russon <rich@flatcap.org>
7
 * Copyright (C) 2019 Federico Kircheis <federico.kircheis@gmail.com>
8
 * Copyright (C) 2020 Pietro Cerutti <gahr@gahr.ch>
9
 * Copyright (C) 2021 Eric Blake <eblake@redhat.com>
10
 *
11
 * @copyright
12
 * This program is free software: you can redistribute it and/or modify it under
13
 * the terms of the GNU General Public License as published by the Free Software
14
 * Foundation, either version 2 of the License, or (at your option) any later
15
 * version.
16
 *
17
 * This program is distributed in the hope that it will be useful, but WITHOUT
18
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
20
 * details.
21
 *
22
 * You should have received a copy of the GNU General Public License along with
23
 * this program.  If not, see <http://www.gnu.org/licenses/>.
24
 */
25
26
/**
27
 * @page email_thread Email threading
28
 *
29
 * Create/manipulate threading in emails
30
 */
31
32
#include "config.h"
33
#include <stdbool.h>
34
#include <stdlib.h>
35
#include "mutt/lib.h"
36
#include "thread.h"
37
#include "email.h"
38
#include "envelope.h"
39
40
/**
41
 * is_descendant - Is one thread a descendant of another
42
 * @param a Parent thread
43
 * @param b Child thread
44
 * @retval true b is a descendent of a (child, grandchild, etc)
45
 */
46
bool is_descendant(const struct MuttThread *a, const struct MuttThread *b)
47
0
{
48
0
  while (a)
49
0
  {
50
0
    if (a == b)
51
0
      return true;
52
0
    a = a->parent;
53
0
  }
54
0
  return false;
55
0
}
56
57
/**
58
 * unlink_message - Break the message out of the thread
59
 * @param[in,out] old Root of thread
60
 * @param[in]     cur Child thread to separate
61
 *
62
 * Remove cur and its descendants from their current location.  Also make sure
63
 * ancestors of cur no longer are sorted by the fact that cur is their
64
 * descendant.
65
 */
66
void unlink_message(struct MuttThread **old, struct MuttThread *cur)
67
0
{
68
0
  if (!old || !cur)
69
0
    return;
70
71
0
  struct MuttThread *tmp = NULL;
72
73
0
  if (cur->prev)
74
0
    cur->prev->next = cur->next;
75
0
  else
76
0
    *old = cur->next;
77
78
0
  if (cur->next)
79
0
    cur->next->prev = cur->prev;
80
81
0
  if (cur->sort_thread_key)
82
0
  {
83
0
    for (tmp = cur->parent;
84
0
         tmp && (tmp->sort_thread_key == cur->sort_thread_key); tmp = tmp->parent)
85
0
    {
86
0
      tmp->sort_thread_key = NULL;
87
0
    }
88
0
  }
89
0
  if (cur->sort_aux_key)
90
0
  {
91
0
    for (tmp = cur->parent; tmp && (tmp->sort_aux_key == cur->sort_aux_key); tmp = tmp->parent)
92
0
      tmp->sort_aux_key = NULL;
93
0
  }
94
0
}
95
96
/**
97
 * insert_message - Insert a message into a thread
98
 * @param[in,out] add    New thread to add
99
 * @param[in]     parent Parent of new thread
100
 * @param[in]     cur    Current thread to add after
101
 *
102
 * add cur as a prior sibling of *add, with parent parent
103
 */
104
void insert_message(struct MuttThread **add, struct MuttThread *parent, struct MuttThread *cur)
105
0
{
106
0
  if (!cur || !add)
107
0
    return;
108
109
0
  if (*add)
110
0
    (*add)->prev = cur;
111
112
0
  cur->parent = parent;
113
0
  cur->next = *add;
114
0
  cur->prev = NULL;
115
0
  *add = cur;
116
0
}
117
118
/**
119
 * find_virtual - Find an email with a Virtual message number
120
 * @param cur     Thread to search
121
 * @param reverse If true, reverse the direction of the search
122
 * @retval ptr Matching Email
123
 */
124
struct Email *find_virtual(struct MuttThread *cur, bool reverse)
125
0
{
126
0
  if (!cur)
127
0
    return NULL;
128
129
0
  struct MuttThread *top = NULL;
130
131
0
  if (cur->message && (cur->message->vnum >= 0))
132
0
    return cur->message;
133
134
0
  top = cur;
135
0
  cur = cur->child;
136
0
  if (!cur)
137
0
    return NULL;
138
139
0
  while (reverse && cur->next)
140
0
    cur = cur->next;
141
142
0
  while (true)
143
0
  {
144
0
    if (cur->message && (cur->message->vnum >= 0))
145
0
      return cur->message;
146
147
0
    if (cur->child)
148
0
    {
149
0
      cur = cur->child;
150
151
0
      while (reverse && cur->next)
152
0
        cur = cur->next;
153
0
    }
154
0
    else if (reverse ? cur->prev : cur->next)
155
0
    {
156
0
      cur = reverse ? cur->prev : cur->next;
157
0
    }
158
0
    else
159
0
    {
160
0
      while (!(reverse ? cur->prev : cur->next))
161
0
      {
162
0
        cur = cur->parent;
163
0
        if (cur == top)
164
0
          return NULL;
165
0
      }
166
0
      cur = reverse ? cur->prev : cur->next;
167
0
    }
168
    /* not reached */
169
0
  }
170
0
}
171
172
/**
173
 * clean_references - Update email references for a broken Thread
174
 * @param brk Broken thread
175
 * @param cur Current thread
176
 */
177
void clean_references(struct MuttThread *brk, struct MuttThread *cur)
178
0
{
179
0
  struct ListNode *ref = NULL;
180
0
  bool done = false;
181
182
0
  for (; cur; cur = cur->next, done = false)
183
0
  {
184
    /* parse subthread recursively */
185
0
    clean_references(brk, cur->child);
186
187
0
    if (!cur->message)
188
0
      break; /* skip pseudo-message */
189
190
    /* Looking for the first bad reference according to the new threading.
191
     * Optimal since NeoMutt stores the references in reverse order, and the
192
     * first loop should match immediately for mails respecting RFC2822. */
193
0
    for (struct MuttThread *p = brk; !done && p; p = p->parent)
194
0
    {
195
0
      for (ref = STAILQ_FIRST(&cur->message->env->references);
196
0
           p->message && ref; ref = STAILQ_NEXT(ref, entries))
197
0
      {
198
0
        if (mutt_istr_equal(ref->data, p->message->env->message_id))
199
0
        {
200
0
          done = true;
201
0
          break;
202
0
        }
203
0
      }
204
0
    }
205
206
0
    if (done)
207
0
    {
208
0
      struct Email *e = cur->message;
209
210
      /* clearing the References: header from obsolete Message-ID(s) */
211
0
      struct ListNode *np = NULL;
212
0
      while ((np = STAILQ_NEXT(ref, entries)))
213
0
      {
214
0
        STAILQ_REMOVE_AFTER(&cur->message->env->references, ref, entries);
215
0
        FREE(&np->data);
216
0
        FREE(&np);
217
0
      }
218
219
0
      e->changed = true;
220
0
      e->env->changed |= MUTT_ENV_CHANGED_REFS;
221
0
    }
222
0
  }
223
0
}
224
225
/**
226
 * mutt_break_thread - Break the email Thread
227
 * @param e Email to break at
228
 */
229
void mutt_break_thread(struct Email *e)
230
0
{
231
0
  if (!e)
232
0
    return;
233
234
0
  mutt_list_free(&e->env->in_reply_to);
235
0
  mutt_list_free(&e->env->references);
236
0
  e->changed = true;
237
0
  e->env->changed |= (MUTT_ENV_CHANGED_IRT | MUTT_ENV_CHANGED_REFS);
238
239
0
  clean_references(e->thread, e->thread->child);
240
0
}