NeoMutt  2021-02-05-666-ge300cd
Teaching an old dog new tricks
DOXYGEN
editmsg.c
Go to the documentation of this file.
1 
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 
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 sb;
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));
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 
90  if (rc == -1)
91  {
92  mutt_error(_("could not write temporary mail folder: %s"), strerror(oerrno));
93  goto bail;
94  }
95 
96  rc = stat(mutt_buffer_string(fname), &sb);
97  if (rc == -1)
98  {
99  mutt_error(_("Can't stat %s: %s"), mutt_buffer_string(fname), strerror(errno));
100  goto bail;
101  }
102 
103  /* The file the user is going to edit is not a real mbox, so we need to
104  * truncate the last newline in the temp file, which is logically part of
105  * the message separator, and not the body of the message. If we fail to
106  * remove it, the message will grow by one line each time the user edits
107  * the message. */
108  if ((sb.st_size != 0) && (truncate(mutt_buffer_string(fname), sb.st_size - 1) == -1))
109  {
110  rc = -1;
111  mutt_error(_("could not truncate temporary mail folder: %s"), strerror(errno));
112  goto bail;
113  }
114 
115  if (action == EVM_VIEW)
116  {
117  /* remove write permissions */
119  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  mutt_buffer_string(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  /* re-stat after the truncate, to avoid false "modified" bugs */
130  rc = stat(mutt_buffer_string(fname), &sb);
131  if (rc == -1)
132  {
133  mutt_error(_("Can't stat %s: %s"), mutt_buffer_string(fname), strerror(errno));
134  goto bail;
135  }
136 
137  /* Do not reuse the stat sb here as it is outdated. */
138  time_t mtime = mutt_file_decrease_mtime(mutt_buffer_string(fname), NULL);
139  if (mtime == (time_t) -1)
140  {
141  rc = -1;
143  goto bail;
144  }
145 
146  const char *const c_editor = cs_subset_string(NeoMutt->sub, "editor");
147  mutt_edit_file(NONULL(c_editor), mutt_buffer_string(fname));
148 
149  rc = stat(mutt_buffer_string(fname), &sb);
150  if (rc == -1)
151  {
152  mutt_error(_("Can't stat %s: %s"), mutt_buffer_string(fname), strerror(errno));
153  goto bail;
154  }
155 
156  if (sb.st_size == 0)
157  {
158  mutt_message(_("Message file is empty"));
159  rc = 1;
160  goto bail;
161  }
162 
163  if ((action == EVM_EDIT) && (sb.st_mtime == mtime))
164  {
165  mutt_message(_("Message not modified"));
166  rc = 1;
167  goto bail;
168  }
169 
170  if ((action == EVM_VIEW) && (sb.st_mtime != mtime))
171  {
172  mutt_message(_("Message of read-only mailbox modified! Ignoring changes."));
173  rc = 1;
174  goto bail;
175  }
176 
177  if (action == EVM_VIEW)
178  {
179  /* stop processing here and skip right to the end */
180  rc = 1;
181  goto bail;
182  }
183 
184  fp = fopen(mutt_buffer_string(fname), "r");
185  if (!fp)
186  {
187  rc = -1;
188  mutt_error(_("Can't open message file: %s"), strerror(errno));
189  goto bail;
190  }
191 
193  {
194  rc = -1;
195  /* L10N: %s is from strerror(errno) */
196  mutt_error(_("Can't append to folder: %s"), strerror(errno));
197  goto bail;
198  }
200  CopyHeaderFlags cf =
201  (((m->type == MUTT_MBOX) || (m->type == MUTT_MMDF)) ? CH_NO_FLAGS : CH_NOSTATUS);
202 
203  if (fgets(buf, sizeof(buf), fp) && is_from(buf, NULL, 0, NULL))
204  {
205  if ((m->type == MUTT_MBOX) || (m->type == MUTT_MMDF))
206  cf = CH_FROM | CH_FORCE_FROM;
207  }
208  else
209  of = MUTT_ADD_FROM;
210 
211  /* XXX - we have to play games with the message flags to avoid
212  * problematic behavior with maildir folders. */
213 
214  bool o_read = e->read;
215  bool o_old = e->old;
216  e->read = false;
217  e->old = false;
218  struct Message *msg = mx_msg_open_new(m, e, of);
219  e->read = o_read;
220  e->old = o_old;
221 
222  if (!msg)
223  {
224  rc = -1;
225  mutt_error(_("Can't append to folder: %s"), strerror(errno));
226  mx_mbox_close(m);
227  goto bail;
228  }
229 
230  rc = mutt_copy_hdr(fp, msg->fp, 0, sb.st_size, CH_NOLEN | cf, NULL, 0);
231  if (rc == 0)
232  {
233  fputc('\n', msg->fp);
234  mutt_file_copy_stream(fp, msg->fp);
235  }
236 
237  rc = mx_msg_commit(m, msg);
238  mx_msg_close(m, &msg);
239 
240  mx_mbox_close(m);
241 
242 bail:
243  mutt_file_fclose(&fp);
244 
245  if (rc >= 0)
246  unlink(mutt_buffer_string(fname));
247 
248  if (rc == 0)
249  {
250  mutt_set_flag(m, e, MUTT_DELETE, true);
251  mutt_set_flag(m, e, MUTT_PURGE, true);
252  mutt_set_flag(m, e, MUTT_READ, true);
253 
254  const bool c_delete_untag = cs_subset_bool(NeoMutt->sub, "delete_untag");
255  if (c_delete_untag)
256  mutt_set_flag(m, e, MUTT_TAG, false);
257  }
258  else if (rc == -1)
259  mutt_message(_("Error. Preserving temporary file: %s"), mutt_buffer_string(fname));
260 
261  m->append = old_append;
262 
263  mutt_buffer_pool_release(&fname);
264  return rc;
265 }
266 
276 int mutt_ev_message(struct Mailbox *m, struct EmailList *el, enum EvMessage action)
277 {
278  struct EmailNode *en = NULL;
279  STAILQ_FOREACH(en, el, entries)
280  {
281  if (ev_message(action, m, en->email) == -1)
282  return -1;
283  }
284 
285  return 0;
286 }
Convenience wrapper for the gui headers.
#define MUTT_MSG_NO_FLAGS
No flags are set.
Definition: mx.h:41
#define mutt_buffer_mktemp(buf)
Definition: muttlib.h:74
enum MailboxType type
Mailbox type.
Definition: mailbox.h:105
#define NONULL(x)
Definition: string2.h:37
#define mutt_set_flag(m, e, flag, bf)
Definition: protos.h:66
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
bool mx_mbox_open(struct Mailbox *m, OpenMailboxFlags flags)
Open a mailbox and parse it.
Definition: mx.c:304
The envelope/body of an email.
Definition: email.h:37
struct Buffer * mutt_buffer_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:101
Structs that make up an email.
#define mutt_error(...)
Definition: logging.h:88
static int ev_message(enum EvMessage action, struct Mailbox *m, struct Email *e)
Edit an email or view it in an external editor.
Definition: editmsg.c:57
int mutt_file_chmod_rm_stat(const char *path, mode_t mode, struct stat *st)
Remove permissions from a file.
Definition: file.c:1149
void mutt_buffer_pool_release(struct Buffer **pbuf)
Free a Buffer from the pool.
Definition: pool.c:112
int mutt_append_message(struct Mailbox *m_dst, struct Mailbox *m_src, struct Email *e, struct Message *msg, CopyMessageFlags cmflags, CopyHeaderFlags chflags)
Append a message.
Definition: copy.c:923
struct timespec mtime
Time Mailbox was last changed.
Definition: mailbox.h:107
String manipulation buffer.
Definition: buffer.h:33
#define CH_FROM
Retain the "From " message separator?
Definition: copy.h:56
#define _(a)
Definition: message.h:28
#define CH_NOSTATUS
Suppress the status and x-status fields.
Definition: copy.h:58
Messages to be purged (bypass trash)
Definition: mutt.h:96
#define MUTT_APPEND
Open mailbox for appending messages.
Definition: mxapi.h:62
enum MxStatus mx_mbox_close(struct Mailbox *m)
Save changes and close mailbox.
Definition: mx.c:610
#define MUTT_CM_NO_FLAGS
No flags are set.
Definition: copy.h:35
#define mutt_perror(...)
Definition: logging.h:89
Container for Accounts, Notifications.
Definition: neomutt.h:36
Convenience wrapper for the config headers.
static const char * mutt_buffer_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:77
Some miscellaneous functions.
int cs_subset_str_native_set(const struct ConfigSubset *sub, const char *name, intptr_t value, struct Buffer *err)
Natively set the value of a string config item.
Definition: subset.c:305
int mx_msg_close(struct Mailbox *m, struct Message **msg)
Close a message.
Definition: mx.c:1186
bool read
Email is read.
Definition: email.h:51
struct Message * mx_msg_open_new(struct Mailbox *m, const struct Email *e, MsgOpenFlags flags)
Open a new message.
Definition: mx.c:1054
Many unsorted constants and some structs.
API for mailboxes.
bool old
Email is seen, but unread.
Definition: email.h:50
void mailbox_free(struct Mailbox **ptr)
Free a Mailbox.
Definition: mailbox.c:87
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:153
Convenience wrapper for the core headers.
EvMessage
Edit or View a message.
Definition: protos.h:53
#define CH_FORCE_FROM
Give CH_FROM precedence over CH_WEED?
Definition: copy.h:66
View the message.
Definition: protos.h:55
Prototypes for many functions.
Edit the message.
Definition: protos.h:56
A local copy of an email.
Definition: mxapi.h:41
Messages to be deleted.
Definition: mutt.h:94
A mailbox.
Definition: mailbox.h:81
#define CH_NO_FLAGS
No flags are set.
Definition: copy.h:51
Tagged messages.
Definition: mutt.h:99
&#39;mmdf&#39; Mailbox type
Definition: mailbox.h:49
Messages that have been read.
Definition: mutt.h:92
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:317
bool append
Mailbox is opened in append mode.
Definition: mailbox.h:113
#define MUTT_NEWFOLDER
Create a new folder - same as MUTT_APPEND,.
Definition: mxapi.h:65
#define MUTT_QUIET
Do not print any messages.
Definition: mxapi.h:64
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:352
#define mutt_debug(LEVEL,...)
Definition: logging.h:85
#define CH_NOLEN
Don&#39;t write Content-Length: and Lines:
Definition: copy.h:64
uint8_t MsgOpenFlags
Flags for mx_msg_open_new(), e.g. MUTT_ADD_FROM.
Definition: mx.h:40
&#39;mbox&#39; Mailbox type
Definition: mailbox.h:48
Duplicate the structure of an entire email.
Log at debug level 1.
Definition: logging.h:40
int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
Copy the contents of one file into another.
Definition: file.c:271
struct Email * email
Email in the list.
Definition: email.h:131
uint32_t CopyHeaderFlags
Flags for mutt_copy_header(), e.g. CH_UPDATE.
Definition: copy.h:50
int mx_msg_commit(struct Mailbox *m, struct Message *msg)
Commit a message to a folder - Wrapper for MxOps::msg_commit()
Definition: mx.c:1165
int mutt_copy_hdr(FILE *fp_in, FILE *fp_out, LOFF_T off_start, LOFF_T off_end, CopyHeaderFlags chflags, const char *prefix, int wraplen)
Copy header from one file to another.
Definition: copy.c:104
FILE * fp
pointer to the message data
Definition: mxapi.h:43
time_t mutt_file_decrease_mtime(const char *fp, struct stat *st)
Decrease a file&#39;s modification time by 1 second.
Definition: file.c:966
#define mutt_message(...)
Definition: logging.h:87
unsigned char cs_subset_enum(const struct ConfigSubset *sub, const char *name)
Get a enumeration config item by name.
Definition: helpers.c:97
struct Mailbox * mx_path_resolve(const char *path)
Get a Mailbox for a path.
Definition: mx.c:1668
List of Emails.
Definition: email.h:129
bool is_from(const char *s, char *path, size_t pathlen, time_t *tp)
Is a string a &#39;From&#39; header line?
Definition: from.c:48
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
Convenience wrapper for the library headers.
#define MUTT_ADD_FROM
add a From_ line
Definition: mx.h:42
void mutt_edit_file(const char *editor, const char *file)
Let the user edit a file.
Definition: curs_lib.c:380
int mutt_ev_message(struct Mailbox *m, struct EmailList *el, enum EvMessage action)
Edit or view a message.
Definition: editmsg.c:276