1 /**
2  * @file
3  * The "currently-open" mailbox
4  *
5  * @authors
6  * Copyright (C) 2018 Richard Russon <rich@flatcap.org>
7  *
8  * @copyright
9  * This program is free software: you can redistribute it and/or modify it under
10  * the terms of the GNU General Public License as published by the Free Software
11  * Foundation, either version 2 of the License, or (at your option) any later
12  * version.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17  * details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /**
24  * @page neo_ctx The "currently-open" mailbox
25  *
26  * The "currently-open" mailbox
27  */
28 
29 #include "config.h"
30 #include <string.h>
31 #include "mutt/lib.h"
32 #include "config/lib.h"
33 #include "email/lib.h"
34 #include "core/lib.h"
35 #include "context.h"
36 #include "imap/lib.h"
37 #include "ncrypt/lib.h"
38 #include "pattern/lib.h"
39 #include "mutt_header.h"
40 #include "mutt_thread.h"
41 #include "mx.h"
42 #include "score.h"
43 #include "sort.h"
44 
45 /**
46  * ctx_free - Free a Context
47  * @param[out] ptr Context to free
48  */
ctx_free(struct Context ** ptr)49 void ctx_free(struct Context **ptr)
50 {
51   if (!ptr || !*ptr)
52     return;
53 
54   struct Context *ctx = *ptr;
55 
56   struct EventContext ev_c = { ctx };
57   mutt_debug(LL_NOTIFY, "NT_CONTEXT_DELETE: %p\n", ctx);
58   notify_send(ctx->notify, NT_CONTEXT, NT_CONTEXT_DELETE, &ev_c);
59 
60   if (ctx->mailbox)
61     notify_observer_remove(ctx->mailbox->notify, ctx_mailbox_observer, ctx);
62 
63   mutt_thread_ctx_free(&ctx->threads);
64   notify_free(&ctx->notify);
65   FREE(&ctx->pattern);
66   mutt_pattern_free(&ctx->limit_pattern);
67 
68   FREE(&ctx);
69   *ptr = NULL;
70 }
71 
72 /**
73  * ctx_new - Create a new Context
74  * @param m Mailbox
75  * @retval ptr New Context
76  */
ctx_new(struct Mailbox * m)77 struct Context *ctx_new(struct Mailbox *m)
78 {
79   if (!m)
80     return NULL;
81 
82   struct Context *ctx = mutt_mem_calloc(1, sizeof(struct Context));
83 
84   ctx->notify = notify_new();
85   notify_set_parent(ctx->notify, NeoMutt->notify);
86   struct EventContext ev_c = { ctx };
87   mutt_debug(LL_NOTIFY, "NT_CONTEXT_ADD: %p\n", ctx);
88   notify_send(ctx->notify, NT_CONTEXT, NT_CONTEXT_ADD, &ev_c);
89   // If the Mailbox is closed, ctx->mailbox must be set to NULL
90   notify_observer_add(m->notify, NT_MAILBOX, ctx_mailbox_observer, ctx);
91 
92   ctx->mailbox = m;
93   ctx->threads = mutt_thread_ctx_init(m);
94   ctx->msg_in_pager = -1;
95   ctx->collapsed = false;
96   ctx_update(ctx);
97 
98   return ctx;
99 }
100 
101 /**
102  * ctx_cleanup - Release memory and initialize a Context object
103  * @param ctx Context to cleanup
104  */
ctx_cleanup(struct Context * ctx)105 static void ctx_cleanup(struct Context *ctx)
106 {
107   FREE(&ctx->pattern);
108   mutt_pattern_free(&ctx->limit_pattern);
109   if (ctx->mailbox)
110     notify_observer_remove(ctx->mailbox->notify, ctx_mailbox_observer, ctx);
111 
112   struct Notify *notify = ctx->notify;
113   struct Mailbox *m = ctx->mailbox;
114   memset(ctx, 0, sizeof(struct Context));
115   ctx->notify = notify;
116   ctx->mailbox = m;
117 }
118 
119 /**
120  * ctx_update - Update the Context's message counts
121  * @param ctx          Mailbox
122  *
123  * this routine is called to update the counts in the context structure
124  */
ctx_update(struct Context * ctx)125 void ctx_update(struct Context *ctx)
126 {
127   if (!ctx || !ctx->mailbox)
128     return;
129 
130   struct Mailbox *m = ctx->mailbox;
131 
132   mutt_hash_free(&m->subj_hash);
133   mutt_hash_free(&m->id_hash);
134 
135   /* reset counters */
136   m->msg_unread = 0;
137   m->msg_flagged = 0;
138   m->msg_new = 0;
139   m->msg_deleted = 0;
140   m->msg_tagged = 0;
141   m->vcount = 0;
142   m->changed = false;
143 
144   mutt_clear_threads(ctx->threads);
145 
146   const bool c_score = cs_subset_bool(NeoMutt->sub, "score");
147   struct Email *e = NULL;
148   for (int msgno = 0; msgno < m->msg_count; msgno++)
149   {
150     e = m->emails[msgno];
151     if (!e)
152       continue;
153 
154     if (WithCrypto)
155     {
156       /* NOTE: this _must_ be done before the check for mailcap! */
157       e->security = crypt_query(e->body);
158     }
159 
160     if (ctx_has_limit(ctx))
161     {
162       e->vnum = -1;
163     }
164     else
165     {
166       m->v2r[m->vcount] = msgno;
167       e->vnum = m->vcount++;
168     }
169     e->msgno = msgno;
170 
171     if (e->env->supersedes)
172     {
173       struct Email *e2 = NULL;
174 
175       if (!m->id_hash)
176         m->id_hash = mutt_make_id_hash(m);
177 
178       e2 = mutt_hash_find(m->id_hash, e->env->supersedes);
179       if (e2)
180       {
181         e2->superseded = true;
182         if (c_score)
183           mutt_score_message(ctx->mailbox, e2, true);
184       }
185     }
186 
187     /* add this message to the hash tables */
188     if (m->id_hash && e->env->message_id)
189       mutt_hash_insert(m->id_hash, e->env->message_id, e);
190     if (m->subj_hash && e->env->real_subj)
191       mutt_hash_insert(m->subj_hash, e->env->real_subj, e);
192     mutt_label_hash_add(m, e);
193 
194     if (c_score)
195       mutt_score_message(ctx->mailbox, e, false);
196 
197     if (e->changed)
198       m->changed = true;
199     if (e->flagged)
200       m->msg_flagged++;
201     if (e->deleted)
202       m->msg_deleted++;
203     if (e->tagged)
204       m->msg_tagged++;
205     if (!e->read)
206     {
207       m->msg_unread++;
208       if (!e->old)
209         m->msg_new++;
210     }
211   }
212 
213   /* rethread from scratch */
214   mutt_sort_headers(ctx->mailbox, ctx->threads, true, &ctx->vsize);
215 }
216 
217 /**
218  * update_tables - Update a Context structure's internal tables
219  * @param ctx        Mailbox
220  */
update_tables(struct Context * ctx)221 static void update_tables(struct Context *ctx)
222 {
223   if (!ctx || !ctx->mailbox)
224     return;
225 
226   struct Mailbox *m = ctx->mailbox;
227 
228   int i, j, padding;
229 
230   /* update memory to reflect the new state of the mailbox */
231   m->vcount = 0;
232   ctx->vsize = 0;
233   m->msg_tagged = 0;
234   m->msg_deleted = 0;
235   m->msg_new = 0;
236   m->msg_unread = 0;
237   m->changed = false;
238   m->msg_flagged = 0;
239   padding = mx_msg_padding_size(m);
240   for (i = 0, j = 0; i < m->msg_count; i++)
241   {
242     if (!m->emails[i])
243       break;
244     const bool c_maildir_trash = cs_subset_bool(NeoMutt->sub, "maildir_trash");
245     if (!m->emails[i]->quasi_deleted &&
246         (!m->emails[i]->deleted || ((m->type == MUTT_MAILDIR) && c_maildir_trash)))
247     {
248       if (i != j)
249       {
250         m->emails[j] = m->emails[i];
251         m->emails[i] = NULL;
252       }
253       m->emails[j]->msgno = j;
254       if (m->emails[j]->vnum != -1)
255       {
256         m->v2r[m->vcount] = j;
257         m->emails[j]->vnum = m->vcount++;
258         struct Body *b = m->emails[j]->body;
259         ctx->vsize += b->length + b->offset - b->hdr_offset + padding;
260       }
261 
262       m->emails[j]->changed = false;
263       m->emails[j]->env->changed = false;
264 
265       if ((m->type == MUTT_MAILDIR) && c_maildir_trash)
266       {
267         if (m->emails[j]->deleted)
268           m->msg_deleted++;
269       }
270 
271       if (m->emails[j]->tagged)
272         m->msg_tagged++;
273       if (m->emails[j]->flagged)
274         m->msg_flagged++;
275       if (!m->emails[j]->read)
276       {
277         m->msg_unread++;
278         if (!m->emails[j]->old)
279           m->msg_new++;
280       }
281 
282       j++;
283     }
284     else
285     {
286       if ((m->type == MUTT_NOTMUCH) || (m->type == MUTT_MH) ||
287           (m->type == MUTT_MAILDIR) || (m->type == MUTT_IMAP))
288       {
289         mailbox_size_sub(m, m->emails[i]);
290       }
291       /* remove message from the hash tables */
292       if (m->subj_hash && m->emails[i]->env->real_subj)
293         mutt_hash_delete(m->subj_hash, m->emails[i]->env->real_subj, m->emails[i]);
294       if (m->id_hash && m->emails[i]->env->message_id)
295         mutt_hash_delete(m->id_hash, m->emails[i]->env->message_id, m->emails[i]);
296       mutt_label_hash_remove(m, m->emails[i]);
297 
298 #ifdef USE_IMAP
299       if (m->type == MUTT_IMAP)
300         imap_notify_delete_email(m, m->emails[i]);
301 #endif
302 
303       mailbox_gc_add(m->emails[i]);
304       m->emails[i] = NULL;
305     }
306   }
307   m->msg_count = j;
308 }
309 
310 /**
311  * ctx_mailbox_observer - Notification that a Mailbox has changed - Implements ::observer_t - @ingroup observer_api
312  */
ctx_mailbox_observer(struct NotifyCallback * nc)313 int ctx_mailbox_observer(struct NotifyCallback *nc)
314 {
315   if ((nc->event_type != NT_MAILBOX) || !nc->global_data)
316     return -1;
317 
318   struct Context *ctx = nc->global_data;
319 
320   switch (nc->event_subtype)
321   {
322     case NT_MAILBOX_DELETE:
323       mutt_clear_threads(ctx->threads);
324       ctx_cleanup(ctx);
325       break;
326     case NT_MAILBOX_INVALID:
327       ctx_update(ctx);
328       break;
329     case NT_MAILBOX_UPDATE:
330       update_tables(ctx);
331       break;
332     case NT_MAILBOX_RESORT:
333       mutt_sort_headers(ctx->mailbox, ctx->threads, true, &ctx->vsize);
334       break;
335     default:
336       return 0;
337   }
338 
339   mutt_debug(LL_DEBUG5, "mailbox done\n");
340   return 0;
341 }
342 
343 /**
344  * message_is_tagged - Is a message in the index tagged (and within limit)
345  * @param e   Email
346  * @retval true The message is both tagged and within limit
347  *
348  * If a limit is in effect, the message must be visible within it.
349  */
message_is_tagged(struct Email * e)350 bool message_is_tagged(struct Email *e)
351 {
352   return e->visible && e->tagged;
353 }
354 
355 /**
356  * el_add_tagged - Get a list of the tagged Emails
357  * @param el         Empty EmailList to populate
358  * @param ctx        Current Mailbox
359  * @param e          Current Email
360  * @param use_tagged Use tagged Emails
361  * @retval num Number of selected emails
362  * @retval -1  Error
363  */
el_add_tagged(struct EmailList * el,struct Context * ctx,struct Email * e,bool use_tagged)364 int el_add_tagged(struct EmailList *el, struct Context *ctx, struct Email *e, bool use_tagged)
365 {
366   int count = 0;
367 
368   if (use_tagged)
369   {
370     if (!ctx || !ctx->mailbox || !ctx->mailbox->emails)
371       return -1;
372 
373     struct Mailbox *m = ctx->mailbox;
374     for (size_t i = 0; i < m->msg_count; i++)
375     {
376       e = m->emails[i];
377       if (!e)
378         break;
379       if (!message_is_tagged(e))
380         continue;
381 
382       struct EmailNode *en = mutt_mem_calloc(1, sizeof(*en));
383       en->email = e;
384       STAILQ_INSERT_TAIL(el, en, entries);
385       count++;
386     }
387   }
388   else
389   {
390     if (!e)
391       return -1;
392 
393     struct EmailNode *en = mutt_mem_calloc(1, sizeof(*en));
394     en->email = e;
395     STAILQ_INSERT_TAIL(el, en, entries);
396     count = 1;
397   }
398 
399   return count;
400 }
401 
402 /**
403  * mutt_get_virt_email - Get a virtual Email
404  * @param m    Mailbox
405  * @param vnum Virtual index number
406  * @retval ptr  Email
407  * @retval NULL No Email selected, or bad index values
408  *
409  * This safely gets the result of the following:
410  * - `mailbox->emails[mailbox->v2r[vnum]]`
411  */
mutt_get_virt_email(struct Mailbox * m,int vnum)412 struct Email *mutt_get_virt_email(struct Mailbox *m, int vnum)
413 {
414   if (!m || !m->emails || !m->v2r)
415     return NULL;
416 
417   if ((vnum < 0) || (vnum >= m->vcount))
418     return NULL;
419 
420   int inum = m->v2r[vnum];
421   if ((inum < 0) || (inum >= m->msg_count))
422     return NULL;
423 
424   return m->emails[inum];
425 }
426 
427 /**
428  * ctx_has_limit - Is a limit active?
429  * @param ctx Context
430  * @retval true A limit is active
431  * @retval false No limit is active
432  */
ctx_has_limit(const struct Context * ctx)433 bool ctx_has_limit(const struct Context *ctx)
434 {
435   return ctx && ctx->pattern;
436 }
437 
438 /**
439  * ctx_mailbox - Wrapper to get the mailbox in a Context, or NULL
440  * @param ctx Context
441  * @retval ptr The mailbox in the Context
442  * @retval NULL Context is NULL or doesn't have a mailbox
443  */
ctx_mailbox(struct Context * ctx)444 struct Mailbox *ctx_mailbox(struct Context *ctx)
445 {
446   return ctx ? ctx->mailbox : NULL;
447 }
448