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