Nico Golde:
[apps/madmutt.git] / edit.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 /* Close approximation of the mailx(1) builtin editor for sending mail. */
11
12 #if HAVE_CONFIG_H
13 # include "config.h"
14 #endif
15
16 #include "mutt.h"
17 #include "ascii.h"
18 #include "enter.h"
19 #include "mutt_curses.h"
20 #include "mutt_idna.h"
21
22 #include "lib/mem.h"
23 #include "lib/intl.h"
24 #include "lib/str.h"
25
26 #include <stdio.h>
27 #include <string.h>
28 #include <stdlib.h>
29 #include <ctype.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32 #include <errno.h>
33
34 /*
35  * SLcurses_waddnstr() can't take a "const char *", so this is only
36  * declared "static" (sigh)
37  */
38 static char *EditorHelp = N_("\
39 ~~              insert a line begining with a single ~\n\
40 ~b users        add users to the Bcc: field\n\
41 ~c users        add users to the Cc: field\n\
42 ~f messages     include messages\n\
43 ~F messages     same as ~f, except also include headers\n\
44 ~h              edit the message header\n\
45 ~m messages     include and quote messages\n\
46 ~M messages     same as ~m, except include headers\n\
47 ~p              print the message\n\
48 ~q              write file and quit editor\n\
49 ~r file         read a file into the editor\n\
50 ~t users        add users to the To: field\n\
51 ~u              recall the previous line\n\
52 ~v              edit message with the $visual editor\n\
53 ~w file         write message to file\n\
54 ~x              abort changes and quit editor\n\
55 ~?              this message\n\
56 .               on a line by itself ends input\n");
57
58 static char **be_snarf_data (FILE * f, char **buf, int *bufmax, int *buflen,
59                              int offset, int bytes, int prefix)
60 {
61   char tmp[HUGE_STRING];
62   char *p = tmp;
63   int tmplen = sizeof (tmp);
64
65   tmp[sizeof (tmp) - 1] = 0;
66   if (prefix) {
67     strfcpy (tmp, NONULL (Prefix), sizeof (tmp));
68     tmplen = str_len (tmp);
69     p = tmp + tmplen;
70     tmplen = sizeof (tmp) - tmplen;
71   }
72
73   fseek (f, offset, 0);
74   while (bytes > 0) {
75     if (fgets (p, tmplen - 1, f) == NULL)
76       break;
77     bytes -= str_len (p);
78     if (*bufmax == *buflen)
79       mem_realloc (&buf, sizeof (char *) * (*bufmax += 25));
80     buf[(*buflen)++] = str_dup (tmp);
81   }
82   if (buf && *bufmax == *buflen) {      /* Do not smash memory past buf */
83     mem_realloc (&buf, sizeof (char *) * (++*bufmax));
84   }
85   if (buf)
86     buf[*buflen] = NULL;
87   return (buf);
88 }
89
90 static char **be_snarf_file (const char *path, char **buf, int *max, int *len,
91                              int verbose)
92 {
93   FILE *f;
94   char tmp[LONG_STRING];
95   struct stat sb;
96
97   if ((f = fopen (path, "r"))) {
98     fstat (fileno (f), &sb);
99     buf = be_snarf_data (f, buf, max, len, 0, sb.st_size, 0);
100     if (verbose) {
101       snprintf (tmp, sizeof (tmp), "\"%s\" %lu bytes\n", path,
102                 (unsigned long) sb.st_size);
103       addstr (tmp);
104     }
105     fclose (f);
106   }
107   else {
108     snprintf (tmp, sizeof (tmp), "%s: %s\n", path, strerror (errno));
109     addstr (tmp);
110   }
111   return (buf);
112 }
113
114 static int be_barf_file (const char *path, char **buf, int buflen)
115 {
116   FILE *f;
117   int i;
118
119   if ((f = safe_fopen (path, "w")) == NULL) {   /* __FOPEN_CHECKED__ */
120     addstr (strerror (errno));
121     addch ('\n');
122     return (-1);
123   }
124   for (i = 0; i < buflen; i++)
125     fputs (buf[i], f);
126   if (fclose (f) == 0)
127     return 0;
128   printw ("fclose: %s\n", strerror (errno));
129   return (-1);
130 }
131
132 static void be_free_memory (char **buf, int buflen)
133 {
134   while (buflen-- > 0)
135     mem_free (&buf[buflen]);
136   if (buf)
137     mem_free (&buf);
138 }
139
140 static char **be_include_messages (char *msg, char **buf, int *bufmax,
141                                    int *buflen, int pfx, int inc_hdrs)
142 {
143   int offset, bytes, n;
144   char tmp[LONG_STRING];
145
146   while ((msg = strtok (msg, " ,")) != NULL) {
147     n = atoi (msg);
148     if (n > 0 && n <= Context->msgcount) {
149       n--;
150
151       /* add the attribution */
152       if (Attribution) {
153         mutt_make_string (tmp, sizeof (tmp) - 1, Attribution, Context,
154                           Context->hdrs[n]);
155         strcat (tmp, "\n");     /* __STRCAT_CHECKED__ */
156       }
157
158       if (*bufmax == *buflen)
159         mem_realloc (&buf, sizeof (char *) * (*bufmax += 25));
160       buf[(*buflen)++] = str_dup (tmp);
161
162       bytes = Context->hdrs[n]->content->length;
163       if (inc_hdrs) {
164         offset = Context->hdrs[n]->offset;
165         bytes += Context->hdrs[n]->content->offset - offset;
166       }
167       else
168         offset = Context->hdrs[n]->content->offset;
169       buf = be_snarf_data (Context->fp, buf, bufmax, buflen, offset, bytes,
170                            pfx);
171
172       if (*bufmax == *buflen)
173         mem_realloc (&buf, sizeof (char *) * (*bufmax += 25));
174       buf[(*buflen)++] = str_dup ("\n");
175     }
176     else
177       printw (_("%d: invalid message number.\n"), n);
178     msg = NULL;
179   }
180   return (buf);
181 }
182
183 static void be_print_header (ENVELOPE * env)
184 {
185   char tmp[HUGE_STRING];
186
187   if (env->to) {
188     addstr ("To: ");
189     tmp[0] = 0;
190     rfc822_write_address (tmp, sizeof (tmp), env->to, 1);
191     addstr (tmp);
192     addch ('\n');
193   }
194   if (env->cc) {
195     addstr ("Cc: ");
196     tmp[0] = 0;
197     rfc822_write_address (tmp, sizeof (tmp), env->cc, 1);
198     addstr (tmp);
199     addch ('\n');
200   }
201   if (env->bcc) {
202     addstr ("Bcc: ");
203     tmp[0] = 0;
204     rfc822_write_address (tmp, sizeof (tmp), env->bcc, 1);
205     addstr (tmp);
206     addch ('\n');
207   }
208   if (env->subject) {
209     addstr ("Subject: ");
210     addstr (env->subject);
211     addch ('\n');
212   }
213   addch ('\n');
214 }
215
216 /* args:
217  *      force   override the $ask* vars (used for the ~h command)
218  */
219 static void be_edit_header (ENVELOPE * e, int force)
220 {
221   char tmp[HUGE_STRING];
222
223   move (LINES - 1, 0);
224
225   addstr ("To: ");
226   tmp[0] = 0;
227   mutt_addrlist_to_local (e->to);
228   rfc822_write_address (tmp, sizeof (tmp), e->to, 0);
229   if (!e->to || force) {
230     if (mutt_enter_string (tmp, sizeof (tmp), LINES - 1, 4, 0) == 0) {
231       rfc822_free_address (&e->to);
232       e->to = mutt_parse_adrlist (e->to, tmp);
233       e->to = mutt_expand_aliases (e->to);
234       mutt_addrlist_to_idna (e->to, NULL);      /* XXX - IDNA error reporting? */
235       tmp[0] = 0;
236       rfc822_write_address (tmp, sizeof (tmp), e->to, 1);
237       mvaddstr (LINES - 1, 4, tmp);
238     }
239   }
240   else {
241     mutt_addrlist_to_idna (e->to, NULL);        /* XXX - IDNA error reporting? */
242     addstr (tmp);
243   }
244   addch ('\n');
245
246   if (!e->subject || force) {
247     addstr ("Subject: ");
248     strfcpy (tmp, e->subject ? e->subject : "", sizeof (tmp));
249     if (mutt_enter_string (tmp, sizeof (tmp), LINES - 1, 9, 0) == 0)
250       str_replace (&e->subject, tmp);
251     addch ('\n');
252   }
253
254   if ((!e->cc && option (OPTASKCC)) || force) {
255     addstr ("Cc: ");
256     tmp[0] = 0;
257     mutt_addrlist_to_local (e->cc);
258     rfc822_write_address (tmp, sizeof (tmp), e->cc, 0);
259     if (mutt_enter_string (tmp, sizeof (tmp), LINES - 1, 4, 0) == 0) {
260       rfc822_free_address (&e->cc);
261       e->cc = mutt_parse_adrlist (e->cc, tmp);
262       e->cc = mutt_expand_aliases (e->cc);
263       tmp[0] = 0;
264       mutt_addrlist_to_idna (e->cc, NULL);
265       rfc822_write_address (tmp, sizeof (tmp), e->cc, 1);
266       mvaddstr (LINES - 1, 4, tmp);
267     }
268     else
269       mutt_addrlist_to_idna (e->cc, NULL);
270     addch ('\n');
271   }
272
273   if (option (OPTASKBCC) || force) {
274     addstr ("Bcc: ");
275     tmp[0] = 0;
276     mutt_addrlist_to_local (e->bcc);
277     rfc822_write_address (tmp, sizeof (tmp), e->bcc, 0);
278     if (mutt_enter_string (tmp, sizeof (tmp), LINES - 1, 5, 0) == 0) {
279       rfc822_free_address (&e->bcc);
280       e->bcc = mutt_parse_adrlist (e->bcc, tmp);
281       e->bcc = mutt_expand_aliases (e->bcc);
282       mutt_addrlist_to_idna (e->bcc, NULL);
283       tmp[0] = 0;
284       rfc822_write_address (tmp, sizeof (tmp), e->bcc, 1);
285       mvaddstr (LINES - 1, 5, tmp);
286     }
287     else
288       mutt_addrlist_to_idna (e->bcc, NULL);
289     addch ('\n');
290   }
291 }
292
293 int mutt_builtin_editor (const char *path, HEADER * msg, HEADER * cur)
294 {
295   char **buf = NULL;
296   int bufmax = 0, buflen = 0;
297   char tmp[LONG_STRING];
298   int abort = 0;
299   int done = 0;
300   int i;
301   char *p;
302
303   scrollok (stdscr, TRUE);
304
305   be_edit_header (msg->env, 0);
306
307   addstr (_("(End message with a . on a line by itself)\n"));
308
309   buf = be_snarf_file (path, buf, &bufmax, &buflen, 0);
310
311   tmp[0] = 0;
312   while (!done) {
313     if (mutt_enter_string (tmp, sizeof (tmp), LINES - 1, 0, 0) == -1) {
314       tmp[0] = 0;
315       continue;
316     }
317     addch ('\n');
318
319     if (EscChar && tmp[0] == EscChar[0] && tmp[1] != EscChar[0]) {
320       /* remove trailing whitespace from the line */
321       p = tmp + str_len (tmp) - 1;
322       while (p >= tmp && ISSPACE (*p))
323         *p-- = 0;
324
325       p = tmp + 2;
326       SKIPWS (p);
327
328       switch (tmp[1]) {
329       case '?':
330         addstr (_(EditorHelp));
331         break;
332       case 'b':
333         msg->env->bcc = mutt_parse_adrlist (msg->env->bcc, p);
334         msg->env->bcc = mutt_expand_aliases (msg->env->bcc);
335         break;
336       case 'c':
337         msg->env->cc = mutt_parse_adrlist (msg->env->cc, p);
338         msg->env->cc = mutt_expand_aliases (msg->env->cc);
339         break;
340       case 'h':
341         be_edit_header (msg->env, 1);
342         break;
343       case 'F':
344       case 'f':
345       case 'm':
346       case 'M':
347         if (Context) {
348           if (!*p && cur) {
349             /* include the current message */
350             p = tmp + str_len (tmp) + 1;
351             snprintf (tmp + str_len (tmp),
352                       sizeof (tmp) - str_len (tmp), " %d",
353                       cur->msgno + 1);
354           }
355           buf = be_include_messages (p, buf, &bufmax, &buflen,
356                                      (ascii_tolower (tmp[1]) == 'm'),
357                                      (ascii_isupper
358                                       ((unsigned char) tmp[1])));
359         }
360         else
361           addstr (_("No mailbox.\n"));
362         break;
363       case 'p':
364         addstr ("-----\n");
365         addstr (_("Message contains:\n"));
366         be_print_header (msg->env);
367         for (i = 0; i < buflen; i++)
368           addstr (buf[i]);
369         addstr (_("(continue)\n"));
370         break;
371       case 'q':
372         done = 1;
373         break;
374       case 'r':
375         if (*p) {
376           strncpy (tmp, p, sizeof (tmp));
377           mutt_expand_path (tmp, sizeof (tmp));
378           buf = be_snarf_file (tmp, buf, &bufmax, &buflen, 1);
379         }
380         else
381           addstr (_("missing filename.\n"));
382         break;
383       case 's':
384         str_replace (&msg->env->subject, p);
385         break;
386       case 't':
387         msg->env->to = rfc822_parse_adrlist (msg->env->to, p);
388         msg->env->to = mutt_expand_aliases (msg->env->to);
389         break;
390       case 'u':
391         if (buflen) {
392           buflen--;
393           strfcpy (tmp, buf[buflen], sizeof (tmp));
394           tmp[str_len (tmp) - 1] = 0;
395           mem_free (&buf[buflen]);
396           buf[buflen] = NULL;
397           continue;
398         }
399         else
400           addstr (_("No lines in message.\n"));
401         break;
402
403       case 'e':
404       case 'v':
405         if (be_barf_file (path, buf, buflen) == 0) {
406           char *tag, *err;
407
408           be_free_memory (buf, buflen);
409           buf = NULL;
410           bufmax = buflen = 0;
411
412           if (option (OPTEDITHDRS)) {
413             mutt_env_to_local (msg->env);
414             mutt_edit_headers (NONULL (Visual), path, msg, NULL, 0);
415             if (mutt_env_to_idna (msg->env, &tag, &err))
416               printw (_("Bad IDN in %s: '%s'\n"), tag, err);
417           }
418           else
419             mutt_edit_file (NONULL (Visual), path);
420
421           buf = be_snarf_file (path, buf, &bufmax, &buflen, 0);
422
423           addstr (_("(continue)\n"));
424         }
425         break;
426       case 'w':
427         be_barf_file (*p ? p : path, buf, buflen);
428         break;
429       case 'x':
430         abort = 1;
431         done = 1;
432         break;
433       default:
434         printw (_("%s: unknown editor command (~? for help)\n"), tmp);
435         break;
436       }
437     }
438     else if (str_cmp (".", tmp) == 0)
439       done = 1;
440     else {
441       str_cat (tmp, sizeof (tmp), "\n");
442       if (buflen == bufmax)
443         mem_realloc (&buf, sizeof (char *) * (bufmax += 25));
444       buf[buflen++] = str_dup (tmp[1] == '~' ? tmp + 1 : tmp);
445     }
446
447     tmp[0] = 0;
448   }
449
450   if (!abort)
451     be_barf_file (path, buf, buflen);
452   be_free_memory (buf, buflen);
453
454   return (abort ? -1 : 0);
455 }