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