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