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