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