1 /**
2  * @file
3  * Manipulate an email's header
4  *
5  * @authors
6  * Copyright (C) 1996-2009,2012 Michael R. Elkins <me@mutt.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_mutt_header Manipulate an email's header
25  *
26  * Manipulate an email's header
27  */
28 
29 #include "config.h"
30 #include <stddef.h>
31 #include <stdbool.h>
32 #include <stdint.h>
33 #include <stdio.h>
34 #include <sys/stat.h>
35 #include <time.h>
36 #include "mutt/lib.h"
37 #include "email/lib.h"
38 #include "core/lib.h"
39 #include "alias/lib.h"
40 #include "gui/lib.h"
41 #include "mutt.h"
42 #include "mutt_header.h"
43 #include "index/lib.h"
44 #include "ncrypt/lib.h"
45 #include "send/lib.h"
46 #include "muttlib.h"
47 #include "options.h"
48 #include "protos.h"
49 
50 /**
51  * label_ref_dec - Decrease the refcount of a label
52  * @param m     Mailbox
53  * @param label Label
54  */
label_ref_dec(struct Mailbox * m,char * label)55 static void label_ref_dec(struct Mailbox *m, char *label)
56 {
57   struct HashElem *elem = mutt_hash_find_elem(m->label_hash, label);
58   if (!elem)
59     return;
60 
61   uintptr_t count = (uintptr_t) elem->data;
62   if (count <= 1)
63   {
64     mutt_hash_delete(m->label_hash, label, NULL);
65     return;
66   }
67 
68   count--;
69   elem->data = (void *) count;
70 }
71 
72 /**
73  * label_ref_inc - Increase the refcount of a label
74  * @param m     Mailbox
75  * @param label Label
76  */
label_ref_inc(struct Mailbox * m,char * label)77 static void label_ref_inc(struct Mailbox *m, char *label)
78 {
79   uintptr_t count;
80 
81   struct HashElem *elem = mutt_hash_find_elem(m->label_hash, label);
82   if (!elem)
83   {
84     count = 1;
85     mutt_hash_insert(m->label_hash, label, (void *) count);
86     return;
87   }
88 
89   count = (uintptr_t) elem->data;
90   count++;
91   elem->data = (void *) count;
92 }
93 
94 /**
95  * label_message - Add an X-Label: field
96  * @param[in]  m         Mailbox
97  * @param[in]  e         Email
98  * @param[out] new_label Set to true if this is a new label
99  * @retval true The label was added
100  */
label_message(struct Mailbox * m,struct Email * e,char * new_label)101 static bool label_message(struct Mailbox *m, struct Email *e, char *new_label)
102 {
103   if (!e)
104     return false;
105   if (mutt_str_equal(e->env->x_label, new_label))
106     return false;
107 
108   if (e->env->x_label)
109     label_ref_dec(m, e->env->x_label);
110   if (mutt_str_replace(&e->env->x_label, new_label))
111     label_ref_inc(m, e->env->x_label);
112 
113   e->changed = true;
114   e->env->changed |= MUTT_ENV_CHANGED_XLABEL;
115   return true;
116 }
117 
118 /**
119  * mutt_label_message - Let the user label a message
120  * @param m  Mailbox
121  * @param el List of Emails to label
122  * @retval num Number of messages changed
123  */
mutt_label_message(struct Mailbox * m,struct EmailList * el)124 int mutt_label_message(struct Mailbox *m, struct EmailList *el)
125 {
126   if (!m || !el)
127     return 0;
128 
129   char buf[1024] = { 0 };
130 
131   struct EmailNode *en = STAILQ_FIRST(el);
132   if (!STAILQ_NEXT(en, entries))
133   {
134     // If there's only one email, use its label as a template
135     if (en->email->env->x_label)
136       mutt_str_copy(buf, en->email->env->x_label, sizeof(buf));
137   }
138 
139   if (mutt_get_field("Label: ", buf, sizeof(buf), MUTT_LABEL /* | MUTT_CLEAR */,
140                      false, NULL, NULL) != 0)
141   {
142     return 0;
143   }
144 
145   char *new_label = buf;
146   SKIPWS(new_label);
147   if (*new_label == '\0')
148     new_label = NULL;
149 
150   int changed = 0;
151   STAILQ_FOREACH(en, el, entries)
152   {
153     if (label_message(m, en->email, new_label))
154     {
155       changed++;
156       mutt_set_header_color(m, en->email);
157     }
158   }
159 
160   return changed;
161 }
162 
163 /**
164  * mutt_edit_headers - Let the user edit the message header and body
165  * @param editor Editor command
166  * @param body   File containing message body
167  * @param e      Email
168  * @param fcc    Buffer for the fcc field
169  */
mutt_edit_headers(const char * editor,const char * body,struct Email * e,struct Buffer * fcc)170 void mutt_edit_headers(const char *editor, const char *body, struct Email *e,
171                        struct Buffer *fcc)
172 {
173   char buf[1024];
174   const char *p = NULL;
175   int i;
176   struct Envelope *n = NULL;
177   time_t mtime;
178   struct stat st = { 0 };
179 
180   struct Buffer *path = mutt_buffer_pool_get();
181   mutt_buffer_mktemp(path);
182   FILE *fp_out = mutt_file_fopen(mutt_buffer_string(path), "w");
183   if (!fp_out)
184   {
185     mutt_perror(mutt_buffer_string(path));
186     goto cleanup;
187   }
188 
189   mutt_env_to_local(e->env);
190   mutt_rfc822_write_header(fp_out, e->env, NULL, MUTT_WRITE_HEADER_EDITHDRS,
191                            false, false, NeoMutt->sub);
192   fputc('\n', fp_out); /* tie off the header. */
193 
194   /* now copy the body of the message. */
195   FILE *fp_in = fopen(body, "r");
196   if (!fp_in)
197   {
198     mutt_perror(body);
199     mutt_file_fclose(&fp_out);
200     goto cleanup;
201   }
202 
203   mutt_file_copy_stream(fp_in, fp_out);
204 
205   mutt_file_fclose(&fp_in);
206   mutt_file_fclose(&fp_out);
207 
208   if (stat(mutt_buffer_string(path), &st) == -1)
209   {
210     mutt_perror(mutt_buffer_string(path));
211     goto cleanup;
212   }
213 
214   mtime = mutt_file_decrease_mtime(mutt_buffer_string(path), &st);
215   if (mtime == (time_t) -1)
216   {
217     mutt_perror(mutt_buffer_string(path));
218     goto cleanup;
219   }
220 
221   mutt_edit_file(editor, mutt_buffer_string(path));
222   if ((stat(mutt_buffer_string(path), &st) != 0) || (mtime == st.st_mtime))
223   {
224     mutt_debug(LL_DEBUG1, "temp file was not modified\n");
225     /* the file has not changed! */
226     mutt_file_unlink(mutt_buffer_string(path));
227     goto cleanup;
228   }
229 
230   mutt_file_unlink(body);
231   mutt_list_free(&e->env->userhdrs);
232 
233   /* Read the temp file back in */
234   fp_in = fopen(mutt_buffer_string(path), "r");
235   if (!fp_in)
236   {
237     mutt_perror(mutt_buffer_string(path));
238     goto cleanup;
239   }
240 
241   fp_out = mutt_file_fopen(body, "w");
242   if (!fp_out)
243   {
244     /* intentionally leak a possible temporary file here */
245     mutt_file_fclose(&fp_in);
246     mutt_perror(body);
247     goto cleanup;
248   }
249 
250   n = mutt_rfc822_read_header(fp_in, NULL, true, false);
251   while ((i = fread(buf, 1, sizeof(buf), fp_in)) > 0)
252     fwrite(buf, 1, i, fp_out);
253   mutt_file_fclose(&fp_out);
254   mutt_file_fclose(&fp_in);
255   mutt_file_unlink(mutt_buffer_string(path));
256 
257   /* in case the user modifies/removes the In-Reply-To header with
258    * $edit_headers set, we remove References: as they're likely invalid;
259    * we can simply compare strings as we don't generate References for
260    * multiple Message-Ids in IRT anyways */
261 #ifdef USE_NNTP
262   if (!OptNewsSend)
263 #endif
264   {
265     if (!STAILQ_EMPTY(&e->env->in_reply_to) &&
266         (STAILQ_EMPTY(&n->in_reply_to) ||
267          !mutt_str_equal(STAILQ_FIRST(&n->in_reply_to)->data,
268                          STAILQ_FIRST(&e->env->in_reply_to)->data)))
269     {
270       mutt_list_free(&e->env->references);
271     }
272   }
273 
274   /* restore old info. */
275   mutt_list_free(&n->references);
276   STAILQ_SWAP(&n->references, &e->env->references, ListNode);
277 
278   mutt_env_free(&e->env);
279   e->env = n;
280   n = NULL;
281 
282   mutt_expand_aliases_env(e->env);
283 
284   /* search through the user defined headers added to see if
285    * fcc: or attach: or pgp: was specified */
286 
287   struct ListNode *np = NULL, *tmp = NULL;
288   STAILQ_FOREACH_SAFE(np, &e->env->userhdrs, entries, tmp)
289   {
290     bool keep = true;
291     size_t plen;
292 
293     if (fcc && (plen = mutt_istr_startswith(np->data, "fcc:")))
294     {
295       p = mutt_str_skip_email_wsp(np->data + plen);
296       if (*p)
297       {
298         mutt_buffer_strcpy(fcc, p);
299         mutt_buffer_pretty_mailbox(fcc);
300       }
301       keep = false;
302     }
303     else if ((plen = mutt_istr_startswith(np->data, "attach:")))
304     {
305       struct Body *body2 = NULL;
306       struct Body *parts = NULL;
307 
308       p = mutt_str_skip_email_wsp(np->data + plen);
309       if (*p)
310       {
311         mutt_buffer_reset(path);
312         for (; (p[0] != '\0') && (p[0] != ' ') && (p[0] != '\t'); p++)
313         {
314           if (p[0] == '\\')
315           {
316             if (p[1] == '\0')
317               break;
318             p++;
319           }
320           mutt_buffer_addch(path, *p);
321         }
322         p = mutt_str_skip_email_wsp(p);
323 
324         mutt_buffer_expand_path(path);
325         body2 = mutt_make_file_attach(mutt_buffer_string(path), NeoMutt->sub);
326         if (body2)
327         {
328           body2->description = mutt_str_dup(p);
329           for (parts = e->body; parts->next; parts = parts->next)
330             ; // do nothing
331 
332           parts->next = body2;
333         }
334         else
335         {
336           mutt_buffer_pretty_mailbox(path);
337           mutt_error(_("%s: unable to attach file"), mutt_buffer_string(path));
338         }
339       }
340       keep = false;
341     }
342     else if (((WithCrypto & APPLICATION_PGP) != 0) &&
343              (plen = mutt_istr_startswith(np->data, "pgp:")))
344     {
345       e->security = mutt_parse_crypt_hdr(np->data + plen, false, APPLICATION_PGP);
346       if (e->security)
347         e->security |= APPLICATION_PGP;
348       keep = false;
349     }
350 
351     if (!keep)
352     {
353       STAILQ_REMOVE(&e->env->userhdrs, np, ListNode, entries);
354       FREE(&np->data);
355       FREE(&np);
356     }
357   }
358 
359 cleanup:
360   mutt_buffer_pool_release(&path);
361 }
362 
363 /**
364  * mutt_make_label_hash - Create a Hash Table to store the labels
365  * @param m Mailbox
366  */
mutt_make_label_hash(struct Mailbox * m)367 void mutt_make_label_hash(struct Mailbox *m)
368 {
369   /* 131 is just a rough prime estimate of how many distinct
370    * labels someone might have in a m.  */
371   m->label_hash = mutt_hash_new(131, MUTT_HASH_STRDUP_KEYS);
372 }
373 
374 /**
375  * mutt_label_hash_add - Add a message's labels to the Hash Table
376  * @param m Mailbox
377  * @param e Email
378  */
mutt_label_hash_add(struct Mailbox * m,struct Email * e)379 void mutt_label_hash_add(struct Mailbox *m, struct Email *e)
380 {
381   if (!m || !m->label_hash)
382     return;
383   if (e->env->x_label)
384     label_ref_inc(m, e->env->x_label);
385 }
386 
387 /**
388  * mutt_label_hash_remove - Remove a message's labels from the Hash Table
389  * @param m Mailbox
390  * @param e Email
391  */
mutt_label_hash_remove(struct Mailbox * m,struct Email * e)392 void mutt_label_hash_remove(struct Mailbox *m, struct Email *e)
393 {
394   if (!m || !m->label_hash)
395     return;
396   if (e->env->x_label)
397     label_ref_dec(m, e->env->x_label);
398 }
399