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