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