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