1 /**
2  * @file
3  * Prepare an email to be edited
4  *
5  * @authors
6  * Copyright (C) 1999-2002 Thomas Roessler <roessler@does-not-exist.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_editmsg Prepare an email to be edited
25  *
26  * Prepare an email to be edited
27  */
28 
29 #include "config.h"
30 #include <errno.h>
31 #include <stdbool.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <sys/stat.h>
35 #include <time.h>
36 #include <unistd.h>
37 #include "mutt/lib.h"
38 #include "config/lib.h"
39 #include "email/lib.h"
40 #include "core/lib.h"
41 #include "gui/lib.h"
42 #include "mutt.h"
43 #include "copy.h"
44 #include "muttlib.h"
45 #include "mx.h"
46 #include "protos.h"
47 
48 /**
49  * ev_message - Edit an email or view it in an external editor
50  * @param action Action to perform, e.g. #EVM_EDIT
51  * @param m      Mailbox
52  * @param e      Email
53  * @retval  1 Message not modified
54  * @retval  0 Message edited successfully
55  * @retval -1 Error
56  */
ev_message(enum EvMessage action,struct Mailbox * m,struct Email * e)57 static int ev_message(enum EvMessage action, struct Mailbox *m, struct Email *e)
58 {
59   char buf[256];
60   int rc;
61   FILE *fp = NULL;
62   struct stat st = { 0 };
63   bool old_append = m->append;
64 
65   struct Buffer *fname = mutt_buffer_pool_get();
66   mutt_buffer_mktemp(fname);
67 
68   // Temporarily force $mbox_type to be MUTT_MBOX
69   const unsigned char c_mbox_type = cs_subset_enum(NeoMutt->sub, "mbox_type");
70   cs_subset_str_native_set(NeoMutt->sub, "mbox_type", MUTT_MBOX, NULL);
71 
72   struct Mailbox *m_fname = mx_path_resolve(mutt_buffer_string(fname));
73   if (!mx_mbox_open(m_fname, MUTT_NEWFOLDER))
74   {
75     mutt_error(_("could not create temporary folder: %s"), strerror(errno));
76     mutt_buffer_pool_release(&fname);
77     mailbox_free(&m_fname);
78     return -1;
79   }
80 
81   cs_subset_str_native_set(NeoMutt->sub, "mbox_type", c_mbox_type, NULL);
82 
83   const CopyHeaderFlags chflags =
84       CH_NOLEN | (((m->type == MUTT_MBOX) || (m->type == MUTT_MMDF)) ? CH_NO_FLAGS : CH_NOSTATUS);
85   rc = mutt_append_message(m_fname, m, e, NULL, MUTT_CM_NO_FLAGS, chflags);
86   int oerrno = errno;
87 
88   mx_mbox_close(m_fname);
89   if (m_fname->flags == MB_HIDDEN)
90     mailbox_free(&m_fname);
91 
92   if (rc == -1)
93   {
94     mutt_error(_("could not write temporary mail folder: %s"), strerror(oerrno));
95     goto bail;
96   }
97 
98   rc = stat(mutt_buffer_string(fname), &st);
99   if (rc == -1)
100   {
101     mutt_error(_("Can't stat %s: %s"), mutt_buffer_string(fname), strerror(errno));
102     goto bail;
103   }
104 
105   /* The file the user is going to edit is not a real mbox, so we need to
106    * truncate the last newline in the temp file, which is logically part of
107    * the message separator, and not the body of the message.  If we fail to
108    * remove it, the message will grow by one line each time the user edits
109    * the message.  */
110   if ((st.st_size != 0) && (truncate(mutt_buffer_string(fname), st.st_size - 1) == -1))
111   {
112     rc = -1;
113     mutt_error(_("could not truncate temporary mail folder: %s"), strerror(errno));
114     goto bail;
115   }
116 
117   if (action == EVM_VIEW)
118   {
119     /* remove write permissions */
120     rc = mutt_file_chmod_rm_stat(mutt_buffer_string(fname),
121                                  S_IWUSR | S_IWGRP | S_IWOTH, &st);
122     if (rc == -1)
123     {
124       mutt_debug(LL_DEBUG1, "Could not remove write permissions of %s: %s",
125                  mutt_buffer_string(fname), strerror(errno));
126       /* Do not bail out here as we are checking afterwards if we should adopt
127        * changes of the temporary file. */
128     }
129   }
130 
131   /* re-stat after the truncate, to avoid false "modified" bugs */
132   rc = stat(mutt_buffer_string(fname), &st);
133   if (rc == -1)
134   {
135     mutt_error(_("Can't stat %s: %s"), mutt_buffer_string(fname), strerror(errno));
136     goto bail;
137   }
138 
139   /* Do not reuse the stat st here as it is outdated. */
140   time_t mtime = mutt_file_decrease_mtime(mutt_buffer_string(fname), NULL);
141   if (mtime == (time_t) -1)
142   {
143     rc = -1;
144     mutt_perror(mutt_buffer_string(fname));
145     goto bail;
146   }
147 
148   const char *const c_editor = cs_subset_string(NeoMutt->sub, "editor");
149   mutt_edit_file(NONULL(c_editor), mutt_buffer_string(fname));
150 
151   rc = stat(mutt_buffer_string(fname), &st);
152   if (rc == -1)
153   {
154     mutt_error(_("Can't stat %s: %s"), mutt_buffer_string(fname), strerror(errno));
155     goto bail;
156   }
157 
158   if (st.st_size == 0)
159   {
160     mutt_message(_("Message file is empty"));
161     rc = 1;
162     goto bail;
163   }
164 
165   if ((action == EVM_EDIT) && (st.st_mtime == mtime))
166   {
167     mutt_message(_("Message not modified"));
168     rc = 1;
169     goto bail;
170   }
171 
172   if ((action == EVM_VIEW) && (st.st_mtime != mtime))
173   {
174     mutt_message(_("Message of read-only mailbox modified! Ignoring changes."));
175     rc = 1;
176     goto bail;
177   }
178 
179   if (action == EVM_VIEW)
180   {
181     /* stop processing here and skip right to the end */
182     rc = 1;
183     goto bail;
184   }
185 
186   fp = fopen(mutt_buffer_string(fname), "r");
187   if (!fp)
188   {
189     rc = -1;
190     mutt_error(_("Can't open message file: %s"), strerror(errno));
191     goto bail;
192   }
193 
194   if (!mx_mbox_open(m, MUTT_APPEND | MUTT_QUIET))
195   {
196     rc = -1;
197     /* L10N: %s is from strerror(errno) */
198     mutt_error(_("Can't append to folder: %s"), strerror(errno));
199     goto bail;
200   }
201   MsgOpenFlags of = MUTT_MSG_NO_FLAGS;
202   CopyHeaderFlags cf =
203       (((m->type == MUTT_MBOX) || (m->type == MUTT_MMDF)) ? CH_NO_FLAGS : CH_NOSTATUS);
204 
205   if (fgets(buf, sizeof(buf), fp) && is_from(buf, NULL, 0, NULL))
206   {
207     if ((m->type == MUTT_MBOX) || (m->type == MUTT_MMDF))
208       cf = CH_FROM | CH_FORCE_FROM;
209   }
210   else
211     of = MUTT_ADD_FROM;
212 
213   /* XXX - we have to play games with the message flags to avoid
214    * problematic behavior with maildir folders.  */
215 
216   bool o_read = e->read;
217   bool o_old = e->old;
218   e->read = false;
219   e->old = false;
220   struct Message *msg = mx_msg_open_new(m, e, of);
221   e->read = o_read;
222   e->old = o_old;
223 
224   if (!msg)
225   {
226     rc = -1;
227     mutt_error(_("Can't append to folder: %s"), strerror(errno));
228     mx_mbox_close(m);
229     goto bail;
230   }
231 
232   rc = mutt_copy_hdr(fp, msg->fp, 0, st.st_size, CH_NOLEN | cf, NULL, 0);
233   if (rc == 0)
234   {
235     fputc('\n', msg->fp);
236     mutt_file_copy_stream(fp, msg->fp);
237   }
238 
239   rc = mx_msg_commit(m, msg);
240   mx_msg_close(m, &msg);
241 
242   mx_mbox_close(m);
243 
244 bail:
245   mutt_file_fclose(&fp);
246 
247   if (rc >= 0)
248     unlink(mutt_buffer_string(fname));
249 
250   if (rc == 0)
251   {
252     mutt_set_flag(m, e, MUTT_DELETE, true);
253     mutt_set_flag(m, e, MUTT_PURGE, true);
254     mutt_set_flag(m, e, MUTT_READ, true);
255 
256     const bool c_delete_untag = cs_subset_bool(NeoMutt->sub, "delete_untag");
257     if (c_delete_untag)
258       mutt_set_flag(m, e, MUTT_TAG, false);
259   }
260   else if (rc == -1)
261     mutt_message(_("Error. Preserving temporary file: %s"), mutt_buffer_string(fname));
262 
263   m->append = old_append;
264 
265   mutt_buffer_pool_release(&fname);
266   return rc;
267 }
268 
269 /**
270  * mutt_ev_message - Edit or view a message
271  * @param m      Mailbox
272  * @param el     List of Emails
273  * @param action Action to perform, e.g. #EVM_EDIT
274  * @retval 1  Message not modified
275  * @retval 0  Message edited successfully
276  * @retval -1 Error
277  */
mutt_ev_message(struct Mailbox * m,struct EmailList * el,enum EvMessage action)278 int mutt_ev_message(struct Mailbox *m, struct EmailList *el, enum EvMessage action)
279 {
280   struct EmailNode *en = NULL;
281   STAILQ_FOREACH(en, el, entries)
282   {
283     if (ev_message(action, m, en->email) == -1)
284       return -1;
285   }
286 
287   return 0;
288 }
289