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