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