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