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