NeoMutt  2018-07-16 +2481-68dcde
Teaching an old dog new tricks
DOXYGEN
edit.c
Go to the documentation of this file.
1 
30 /* Close approximation of the mailx(1) builtin editor for sending mail. */
31 
32 #include "config.h"
33 #include <ctype.h>
34 #include <errno.h>
35 #include <locale.h>
36 #include <stdbool.h>
37 #include <stdio.h>
38 #include <string.h>
39 #include <sys/stat.h>
40 #include "mutt/mutt.h"
41 #include "address/lib.h"
42 #include "email/lib.h"
43 #include "core/lib.h"
44 #include "mutt.h"
45 #include "alias.h"
46 #include "context.h"
47 #include "curs_lib.h"
48 #include "globals.h"
49 #include "hdrline.h"
50 #include "mutt_curses.h"
51 #include "mutt_header.h"
52 #include "mutt_window.h"
53 #include "muttlib.h"
54 #include "protos.h"
55 
56 /* These Config Variables are only used in edit.c */
57 char *C_Escape;
58 
59 /* SLcurses_waddnstr() can't take a "const char *", so this is only
60  * declared "static" (sigh)
61  */
62 static char *EditorHelp1 =
63  N_("~~ insert a line beginning with a single ~\n"
64  "~b users add users to the Bcc: field\n"
65  "~c users add users to the Cc: field\n"
66  "~f messages include messages\n"
67  "~F messages same as ~f, except also include headers\n"
68  "~h edit the message header\n"
69  "~m messages include and quote messages\n"
70  "~M messages same as ~m, except include headers\n"
71  "~p print the message\n");
72 
73 static char *EditorHelp2 =
74  N_("~q write file and quit editor\n"
75  "~r file read a file into the editor\n"
76  "~t users add users to the To: field\n"
77  "~u recall the previous line\n"
78  "~v edit message with the $visual editor\n"
79  "~w file write message to file\n"
80  "~x abort changes and quit editor\n"
81  "~? this message\n"
82  ". on a line by itself ends input\n");
83 
95 static char **be_snarf_data(FILE *fp, char **buf, int *bufmax, int *buflen,
96  LOFF_T offset, int bytes, int prefix)
97 {
98  char tmp[8192];
99  char *p = tmp;
100  int tmplen = sizeof(tmp);
101 
102  tmp[sizeof(tmp) - 1] = '\0';
103  if (prefix)
104  {
105  mutt_str_strfcpy(tmp, C_IndentString, sizeof(tmp));
106  tmplen = mutt_str_strlen(tmp);
107  p = tmp + tmplen;
108  tmplen = sizeof(tmp) - tmplen;
109  }
110 
111  fseeko(fp, offset, SEEK_SET);
112  while (bytes > 0)
113  {
114  if (!fgets(p, tmplen - 1, fp))
115  break;
116  bytes -= mutt_str_strlen(p);
117  if (*bufmax == *buflen)
118  mutt_mem_realloc(&buf, sizeof(char *) * (*bufmax += 25));
119  buf[(*buflen)++] = mutt_str_strdup(tmp);
120  }
121  if (buf && (*bufmax == *buflen))
122  { /* Do not smash memory past buf */
123  mutt_mem_realloc(&buf, sizeof(char *) * (++*bufmax));
124  }
125  if (buf)
126  buf[*buflen] = NULL;
127  return buf;
128 }
129 
139 static char **be_snarf_file(const char *path, char **buf, int *max, int *len, bool verbose)
140 {
141  char tmp[1024];
142  struct stat sb;
143 
144  FILE *fp = fopen(path, "r");
145  if (fp)
146  {
147  fstat(fileno(fp), &sb);
148  buf = be_snarf_data(fp, buf, max, len, 0, sb.st_size, 0);
149  if (verbose)
150  {
151  snprintf(tmp, sizeof(tmp), "\"%s\" %lu bytes\n", path, (unsigned long) sb.st_size);
152  addstr(tmp);
153  }
154  mutt_file_fclose(&fp);
155  }
156  else
157  {
158  snprintf(tmp, sizeof(tmp), "%s: %s\n", path, strerror(errno));
159  addstr(tmp);
160  }
161  return buf;
162 }
163 
172 static int be_barf_file(const char *path, char **buf, int buflen)
173 {
174  FILE *fp = fopen(path, "w");
175  if (!fp)
176  {
177  addstr(strerror(errno));
178  addch('\n');
179  return -1;
180  }
181  for (int i = 0; i < buflen; i++)
182  fputs(buf[i], fp);
183  if (fclose(fp) == 0)
184  return 0;
185  printw("fclose: %s\n", strerror(errno));
186  return -1;
187 }
188 
194 static void be_free_memory(char **buf, int buflen)
195 {
196  while (buflen-- > 0)
197  FREE(&buf[buflen]);
198  FREE(&buf);
199 }
200 
211 static char **be_include_messages(char *msg, char **buf, int *bufmax,
212  int *buflen, int pfx, int inc_hdrs)
213 {
214  int n;
215  // int offset, bytes;
216  char tmp[1024];
217 
218  if (!msg || !buf || !bufmax || !buflen)
219  return buf;
220 
221  while ((msg = strtok(msg, " ,")))
222  {
223  if ((mutt_str_atoi(msg, &n) == 0) && (n > 0) && (n <= Context->mailbox->msg_count))
224  {
225  n--;
226 
227  /* add the attribution */
228  if (C_Attribution)
229  {
230  setlocale(LC_TIME, NONULL(C_AttributionLocale));
231  mutt_make_string(tmp, sizeof(tmp) - 1, C_Attribution, Context,
233  setlocale(LC_TIME, "");
234  strcat(tmp, "\n");
235  }
236 
237  if (*bufmax == *buflen)
238  mutt_mem_realloc(&buf, sizeof(char *) * (*bufmax += 25));
239  buf[(*buflen)++] = mutt_str_strdup(tmp);
240 
241 #if 0
242  /* This only worked for mbox Mailboxes because they had Context->fp set.
243  * As that no longer exists, the code is now completely broken. */
244  bytes = Context->mailbox->emails[n]->content->length;
245  if (inc_hdrs)
246  {
247  offset = Context->mailbox->emails[n]->offset;
248  bytes += Context->mailbox->emails[n]->content->offset - offset;
249  }
250  else
251  offset = Context->mailbox->emails[n]->content->offset;
252  buf = be_snarf_data(Context->fp, buf, bufmax, buflen, offset, bytes, pfx);
253 #endif
254 
255  if (*bufmax == *buflen)
256  mutt_mem_realloc(&buf, sizeof(char *) * (*bufmax += 25));
257  buf[(*buflen)++] = mutt_str_strdup("\n");
258  }
259  else
260  printw(_("%d: invalid message number.\n"), n);
261  msg = NULL;
262  }
263  return buf;
264 }
265 
270 static void be_print_header(struct Envelope *env)
271 {
272  char tmp[8192];
273 
274  if (!TAILQ_EMPTY(&env->to))
275  {
276  addstr("To: ");
277  tmp[0] = '\0';
278  mutt_addrlist_write(tmp, sizeof(tmp), &env->to, true);
279  addstr(tmp);
280  addch('\n');
281  }
282  if (!TAILQ_EMPTY(&env->cc))
283  {
284  addstr("Cc: ");
285  tmp[0] = '\0';
286  mutt_addrlist_write(tmp, sizeof(tmp), &env->cc, true);
287  addstr(tmp);
288  addch('\n');
289  }
290  if (!TAILQ_EMPTY(&env->bcc))
291  {
292  addstr("Bcc: ");
293  tmp[0] = '\0';
294  mutt_addrlist_write(tmp, sizeof(tmp), &env->bcc, true);
295  addstr(tmp);
296  addch('\n');
297  }
298  if (env->subject)
299  {
300  addstr("Subject: ");
301  addstr(env->subject);
302  addch('\n');
303  }
304  addch('\n');
305 }
306 
312 static void be_edit_header(struct Envelope *e, bool force)
313 {
314  char tmp[8192];
315 
317 
318  addstr("To: ");
319  tmp[0] = '\0';
321  mutt_addrlist_write(tmp, sizeof(tmp), &e->to, false);
322  if (TAILQ_EMPTY(&e->to) || force)
323  {
324  if (mutt_enter_string(tmp, sizeof(tmp), 4, MUTT_COMP_NO_FLAGS) == 0)
325  {
326  mutt_addrlist_clear(&e->to);
327  mutt_addrlist_parse2(&e->to, tmp);
328  mutt_expand_aliases(&e->to);
329  mutt_addrlist_to_intl(&e->to, NULL); /* XXX - IDNA error reporting? */
330  tmp[0] = '\0';
331  mutt_addrlist_write(tmp, sizeof(tmp), &e->to, true);
333  }
334  }
335  else
336  {
337  mutt_addrlist_to_intl(&e->to, NULL); /* XXX - IDNA error reporting? */
338  addstr(tmp);
339  }
340  addch('\n');
341 
342  if (!e->subject || force)
343  {
344  addstr("Subject: ");
345  mutt_str_strfcpy(tmp, e->subject ? e->subject : "", sizeof(tmp));
346  if (mutt_enter_string(tmp, sizeof(tmp), 9, MUTT_COMP_NO_FLAGS) == 0)
347  mutt_str_replace(&e->subject, tmp);
348  addch('\n');
349  }
350 
351  if ((TAILQ_EMPTY(&e->cc) && C_Askcc) || force)
352  {
353  addstr("Cc: ");
354  tmp[0] = '\0';
356  mutt_addrlist_write(tmp, sizeof(tmp), &e->cc, false);
357  if (mutt_enter_string(tmp, sizeof(tmp), 4, MUTT_COMP_NO_FLAGS) == 0)
358  {
359  mutt_addrlist_clear(&e->cc);
360  mutt_addrlist_parse2(&e->cc, tmp);
361  mutt_expand_aliases(&e->cc);
362  tmp[0] = '\0';
363  mutt_addrlist_to_intl(&e->cc, NULL);
364  mutt_addrlist_write(tmp, sizeof(tmp), &e->cc, true);
366  }
367  else
368  mutt_addrlist_to_intl(&e->cc, NULL);
369  addch('\n');
370  }
371 
372  if (C_Askbcc || force)
373  {
374  addstr("Bcc: ");
375  tmp[0] = '\0';
377  mutt_addrlist_write(tmp, sizeof(tmp), &e->bcc, false);
378  if (mutt_enter_string(tmp, sizeof(tmp), 5, MUTT_COMP_NO_FLAGS) == 0)
379  {
381  mutt_addrlist_parse2(&e->bcc, tmp);
383  mutt_addrlist_to_intl(&e->bcc, NULL);
384  tmp[0] = '\0';
385  mutt_addrlist_write(tmp, sizeof(tmp), &e->bcc, true);
387  }
388  else
389  mutt_addrlist_to_intl(&e->bcc, NULL);
390  addch('\n');
391  }
392 }
393 
402 int mutt_builtin_editor(const char *path, struct Email *e_new, struct Email *e_cur)
403 {
404  char **buf = NULL;
405  int bufmax = 0, buflen = 0;
406  char tmp[1024];
407  bool abort = false;
408  bool done = false;
409  char *p = NULL;
410 
411  scrollok(stdscr, true);
412 
413  be_edit_header(e_new->env, false);
414 
415  addstr(_("(End message with a . on a line by itself)\n"));
416 
417  buf = be_snarf_file(path, buf, &bufmax, &buflen, false);
418 
419  tmp[0] = '\0';
420  while (!done)
421  {
422  if (mutt_enter_string(tmp, sizeof(tmp), 0, MUTT_COMP_NO_FLAGS) == -1)
423  {
424  tmp[0] = '\0';
425  continue;
426  }
427  addch('\n');
428 
429  if (C_Escape && (tmp[0] == C_Escape[0]) && (tmp[1] != C_Escape[0]))
430  {
431  /* remove trailing whitespace from the line */
432  p = tmp + mutt_str_strlen(tmp) - 1;
433  while ((p >= tmp) && IS_SPACE(*p))
434  *p-- = '\0';
435 
436  p = tmp + 2;
437  SKIPWS(p);
438 
439  switch (tmp[1])
440  {
441  case '?':
442  addstr(_(EditorHelp1));
443  addstr(_(EditorHelp2));
444  break;
445  case 'b':
446  mutt_addrlist_parse2(&e_new->env->bcc, p);
447  mutt_expand_aliases(&e_new->env->bcc);
448  break;
449  case 'c':
450  mutt_addrlist_parse2(&e_new->env->cc, p);
451  mutt_expand_aliases(&e_new->env->cc);
452  break;
453  case 'h':
454  be_edit_header(e_new->env, true);
455  break;
456  case 'F':
457  case 'f':
458  case 'm':
459  case 'M':
460  if (Context)
461  {
462  if (!*p && e_cur)
463  {
464  /* include the current message */
465  p = tmp + mutt_str_strlen(tmp) + 1;
466  snprintf(tmp + mutt_str_strlen(tmp),
467  sizeof(tmp) - mutt_str_strlen(tmp), " %d", e_cur->msgno + 1);
468  }
469  buf = be_include_messages(p, buf, &bufmax, &buflen, (tolower(tmp[1]) == 'm'),
470  (isupper((unsigned char) tmp[1])));
471  }
472  else
473  addstr(_("No mailbox.\n"));
474  break;
475  case 'p':
476  addstr("-----\n");
477  addstr(_("Message contains:\n"));
478  be_print_header(e_new->env);
479  for (int i = 0; i < buflen; i++)
480  addstr(buf[i]);
481  /* L10N: This entry is shown AFTER the message content,
482  not IN the middle of the content.
483  So it doesn't mean "(message will continue)"
484  but means "(press any key to continue using neomutt)". */
485  addstr(_("(continue)\n"));
486  break;
487  case 'q':
488  done = true;
489  break;
490  case 'r':
491  if (*p)
492  {
493  mutt_str_strfcpy(tmp, p, sizeof(tmp));
494  mutt_expand_path(tmp, sizeof(tmp));
495  buf = be_snarf_file(tmp, buf, &bufmax, &buflen, true);
496  }
497  else
498  addstr(_("missing filename.\n"));
499  break;
500  case 's':
501  mutt_str_replace(&e_new->env->subject, p);
502  break;
503  case 't':
504  mutt_addrlist_parse(&e_new->env->to, p);
505  mutt_expand_aliases(&e_new->env->to);
506  break;
507  case 'u':
508  if (buflen)
509  {
510  buflen--;
511  mutt_str_strfcpy(tmp, buf[buflen], sizeof(tmp));
512  tmp[mutt_str_strlen(tmp) - 1] = '\0';
513  FREE(&buf[buflen]);
514  buf[buflen] = NULL;
515  continue;
516  }
517  else
518  addstr(_("No lines in message.\n"));
519  break;
520 
521  case 'e':
522  case 'v':
523  if (be_barf_file(path, buf, buflen) == 0)
524  {
525  const char *tag = NULL;
526  char *err = NULL;
527  be_free_memory(buf, buflen);
528  buf = NULL;
529  bufmax = 0;
530  buflen = 0;
531 
532  if (C_EditHeaders)
533  {
534  mutt_env_to_local(e_new->env);
535  mutt_edit_headers(NONULL(C_Visual), path, e_new, NULL, 0);
536  if (mutt_env_to_intl(e_new->env, &tag, &err))
537  printw(_("Bad IDN in '%s': '%s'"), tag, err);
538  /* tag is a statically allocated string and should not be freed */
539  FREE(&err);
540  }
541  else
543 
544  buf = be_snarf_file(path, buf, &bufmax, &buflen, false);
545 
546  addstr(_("(continue)\n"));
547  }
548  break;
549  case 'w':
550  be_barf_file((p[0] != '\0') ? p : path, buf, buflen);
551  break;
552  case 'x':
553  abort = true;
554  done = true;
555  break;
556  default:
557  printw(_("%s: unknown editor command (~? for help)\n"), tmp);
558  break;
559  }
560  }
561  else if (mutt_str_strcmp(".", tmp) == 0)
562  done = true;
563  else
564  {
565  mutt_str_strcat(tmp, sizeof(tmp), "\n");
566  if (buflen == bufmax)
567  mutt_mem_realloc(&buf, sizeof(char *) * (bufmax += 25));
568  buf[buflen++] = mutt_str_strdup((tmp[1] == '~') ? tmp + 1 : tmp);
569  }
570 
571  tmp[0] = '\0';
572  }
573 
574  if (!abort)
575  be_barf_file(path, buf, buflen);
576  be_free_memory(buf, buflen);
577 
578  return abort ? -1 : 0;
579 }
struct Email ** emails
Array of Emails.
Definition: mailbox.h:110
The "current" mailbox.
Definition: context.h:36
#define NONULL(x)
Definition: string2.h:37
Define wrapper functions around Curses/Slang.
Representation of the email&#39;s header.
void mutt_expand_aliases(struct AddressList *al)
Expand aliases in a List of Addresses.
Definition: alias.c:304
int mutt_str_atoi(const char *str, int *dst)
Convert ASCII string to an integer.
Definition: string.c:262
The envelope/body of an email.
Definition: email.h:39
WHERE char * C_AttributionLocale
Config: Locale for dates in the attribution message.
Definition: globals.h:103
Window management.
GUI miscellaneous curses (window drawing) routines.
WHERE char * C_IndentString
Config: String used to indent &#39;reply&#39; text.
Definition: globals.h:140
Structs that make up an email.
String processing routines to generate the mail index.
The "currently-open" mailbox.
int mutt_addrlist_parse(struct AddressList *al, const char *s)
Parse a list of email addresses.
Definition: address.c:457
int mutt_addrlist_to_local(struct AddressList *al)
Convert an Address list from Punycode.
Definition: address.c:1298
struct AddressList bcc
Email&#39;s &#39;Bcc&#39; list.
Definition: envelope.h:60
void mutt_addrlist_clear(struct AddressList *al)
Unlink and free all Address in an AddressList.
Definition: address.c:1381
struct Body * content
List of MIME parts.
Definition: email.h:92
LOFF_T offset
offset where the actual data begins
Definition: body.h:44
#define _(a)
Definition: message.h:28
size_t mutt_str_strlen(const char *a)
Calculate the length of a string, safely.
Definition: string.c:666
size_t mutt_addrlist_write(char *buf, size_t buflen, const struct AddressList *al, bool display)
Write an Address to a buffer.
Definition: address.c:1137
int mutt_addrlist_parse2(struct AddressList *al, const char *s)
Parse a list of email addresses.
Definition: address.c:606
int mutt_env_to_intl(struct Envelope *env, const char **tag, char **err)
Convert an Envelope&#39;s Address fields to Punycode format.
Definition: envelope.c:312
static char ** be_snarf_data(FILE *fp, char **buf, int *bufmax, int *buflen, LOFF_T offset, int bytes, int prefix)
Read data from a file into a buffer.
Definition: edit.c:95
Representation of a single alias to an email address.
Hundreds of global variables to back the user variables.
Email Address Handling.
Some miscellaneous functions.
char * mutt_expand_path(char *buf, size_t buflen)
Create the canonical path.
Definition: muttlib.c:128
static void be_edit_header(struct Envelope *e, bool force)
Edit the message headers.
Definition: edit.c:312
WHERE bool C_EditHeaders
Config: Let the user edit the email headers whilst editing an email.
Definition: globals.h:217
struct Mailbox * mailbox
Definition: context.h:50
#define MUTT_COMP_NO_FLAGS
No flags are set.
Definition: mutt.h:64
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:150
struct Envelope * env
Envelope information.
Definition: email.h:91
Convenience wrapper for the core headers.
#define SKIPWS(ch)
Definition: string2.h:47
struct AddressList cc
Email&#39;s &#39;Cc&#39; list.
Definition: envelope.h:59
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
Prototypes for many functions.
LOFF_T length
length (in bytes) of attachment
Definition: body.h:45
static char ** be_snarf_file(const char *path, char **buf, int *max, int *len, bool verbose)
Read a file into a buffer.
Definition: edit.c:139
size_t mutt_str_strfcpy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:750
void mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:453
int mutt_window_mvaddstr(struct MuttWindow *win, int row, int col, const char *str)
Move the cursor and write a fixed string to a Window.
Definition: mutt_window.c:202
int mutt_window_move(struct MuttWindow *win, int row, int col)
Move the cursor in a Window.
Definition: mutt_window.c:188
LOFF_T offset
Where in the stream does this message begin?
Definition: email.h:85
static char ** be_include_messages(char *msg, char **buf, int *bufmax, int *buflen, int pfx, int inc_hdrs)
Gather the contents of some messages.
Definition: edit.c:211
#define IS_SPACE(ch)
Definition: string2.h:38
char * mutt_str_strcat(char *buf, size_t buflen, const char *s)
Concatenate two strings.
Definition: string.c:395
void mutt_env_to_local(struct Envelope *env)
Convert an Envelope&#39;s Address fields to local format.
Definition: envelope.c:274
static char * EditorHelp2
Definition: edit.c:73
char * C_Escape
Config: Escape character to use for functions in the built-in editor.
Definition: edit.c:57
char * subject
Email&#39;s subject.
Definition: envelope.h:66
int mutt_addrlist_to_intl(struct AddressList *al, char **err)
Convert an Address list to Punycode.
Definition: address.c:1216
static void be_print_header(struct Envelope *env)
Print a message Header.
Definition: edit.c:270
char * mutt_str_strdup(const char *str)
Copy a string, safely.
Definition: string.c:380
int mutt_builtin_editor(const char *path, struct Email *e_new, struct Email *e_cur)
Show the user the built-in editor.
Definition: edit.c:402
#define mutt_make_string(BUF, BUFLEN, S, CTX, M, E)
Definition: hdrline.h:61
void mutt_edit_headers(const char *editor, const char *body, struct Email *e, char *fcc, size_t fcclen)
Let the user edit the message header and body.
Definition: mutt_header.c:168
int mutt_enter_string(char *buf, size_t buflen, int col, CompletionFlags flags)
Ask the user for a string.
Definition: enter.c:146
#define FREE(x)
Definition: memory.h:40
struct AddressList to
Email&#39;s &#39;To&#39; list.
Definition: envelope.h:58
struct MuttWindow * MuttMessageWindow
Message Window.
Definition: mutt_window.c:42
#define TAILQ_EMPTY(head)
Definition: queue.h:715
WHERE char * C_Attribution
Config: Message to start a reply, "On DATE, PERSON wrote:".
Definition: globals.h:102
#define N_(a)
Definition: message.h:32
WHERE bool C_Askbcc
Config: Ask the user for the blind-carbon-copy recipients.
Definition: globals.h:202
void mutt_edit_file(const char *editor, const char *file)
Let the user edit a file.
Definition: curs_lib.c:308
Convenience wrapper for the library headers.
int mutt_str_strcmp(const char *a, const char *b)
Compare two strings, safely.
Definition: string.c:615
static int be_barf_file(const char *path, char **buf, int buflen)
Write a buffer to a file.
Definition: edit.c:172
WHERE bool C_Askcc
Config: Ask the user for the carbon-copy recipients.
Definition: globals.h:203
The header of an Email.
Definition: envelope.h:54
int msgno
Number displayed to the user.
Definition: email.h:88
WHERE char * C_Visual
Config: Editor to use when &#39;~v&#39; is given in the built-in editor.
Definition: globals.h:153
static char * EditorHelp1
Definition: edit.c:62
static void be_free_memory(char **buf, int buflen)
Free an array of buffers.
Definition: edit.c:194