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