edit.c 12.1 KB
Newer Older
Thomas Roessler's avatar
Thomas Roessler committed
1
/*
2
 * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
Thomas Roessler's avatar
Thomas Roessler committed
3 4 5 6 7 8 9 10 11 12 13 14 15
 * 
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
 * 
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with this program; if not, write to the Free Software
16
 *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
Thomas Roessler's avatar
Thomas Roessler committed
17 18 19 20
 */ 

/* Close approximation of the mailx(1) builtin editor for sending mail. */

21 22 23 24
#if HAVE_CONFIG_H
# include "config.h"
#endif

Thomas Roessler's avatar
Thomas Roessler committed
25 26
#include "mutt.h"
#include "mutt_curses.h"
27
#include "mutt_idna.h"
Thomas Roessler's avatar
Thomas Roessler committed
28 29 30 31

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
32
#include <locale.h>
Thomas Roessler's avatar
Thomas Roessler committed
33 34 35 36 37 38 39 40 41
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

/*
 * SLcurses_waddnstr() can't take a "const char *", so this is only
 * declared "static" (sigh)
 */
42
static char* EditorHelp1 = N_("\
43
~~		insert a line beginning with a single ~\n\
Thomas Roessler's avatar
Thomas Roessler committed
44 45 46 47 48 49 50
~b users	add users to the Bcc: field\n\
~c users	add users to the Cc: field\n\
~f messages	include messages\n\
~F messages	same as ~f, except also include headers\n\
~h		edit the message header\n\
~m messages	include and quote messages\n\
~M messages	same as ~m, except include headers\n\
51 52 53
~p		print the message\n");

static char* EditorHelp2 = N_("\
Thomas Roessler's avatar
Thomas Roessler committed
54 55 56 57 58 59 60 61
~q		write file and quit editor\n\
~r file		read a file into the editor\n\
~t users	add users to the To: field\n\
~u		recall the previous line\n\
~v		edit message with the $visual editor\n\
~w file		write message to file\n\
~x		abort changes and quit editor\n\
~?		this message\n\
62
.		on a line by itself ends input\n");
Thomas Roessler's avatar
Thomas Roessler committed
63 64

static char **
65
be_snarf_data (FILE *f, char **buf, int *bufmax, int *buflen, LOFF_T offset,
Thomas Roessler's avatar
Thomas Roessler committed
66 67 68 69 70 71 72 73 74
	       int bytes, int prefix)
{
  char tmp[HUGE_STRING];
  char *p = tmp;
  int tmplen = sizeof (tmp);

  tmp[sizeof (tmp) - 1] = 0;
  if (prefix)
  {
Thomas Roessler's avatar
Thomas Roessler committed
75
    strfcpy (tmp, NONULL(Prefix), sizeof (tmp));
76
    tmplen = mutt_strlen (tmp);
Thomas Roessler's avatar
Thomas Roessler committed
77 78 79 80
    p = tmp + tmplen;
    tmplen = sizeof (tmp) - tmplen;
  }

81
  fseeko (f, offset, 0);
Thomas Roessler's avatar
Thomas Roessler committed
82 83 84
  while (bytes > 0)
  {
    if (fgets (p, tmplen - 1, f) == NULL) break;
85
    bytes -= mutt_strlen (p);
Thomas Roessler's avatar
Thomas Roessler committed
86
    if (*bufmax == *buflen)
87
      safe_realloc (&buf, sizeof (char *) * (*bufmax += 25));
Thomas Roessler's avatar
Thomas Roessler committed
88 89
    buf[(*buflen)++] = safe_strdup (tmp);
  }
90
  if (buf && *bufmax == *buflen) { /* Do not smash memory past buf */
91
    safe_realloc (&buf, sizeof (char *) * (++*bufmax));
92
  }
Thomas Roessler's avatar
Thomas Roessler committed
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
  if (buf) buf[*buflen] = NULL;
  return (buf);
}

static char **
be_snarf_file (const char *path, char **buf, int *max, int *len, int verbose)
{
  FILE *f;
  char tmp[LONG_STRING];
  struct stat sb;
  
  if ((f = fopen (path, "r")))
  {
    fstat (fileno (f), &sb);
    buf = be_snarf_data (f, buf, max, len, 0, sb.st_size, 0);
    if (verbose)
    {
110
      snprintf(tmp, sizeof(tmp), "\"%s\" %lu bytes\n", path, (unsigned long) sb.st_size);
Thomas Roessler's avatar
Thomas Roessler committed
111 112
      addstr(tmp);
    }
113
    safe_fclose (&f);
Thomas Roessler's avatar
Thomas Roessler committed
114 115 116 117 118 119 120 121 122 123 124 125 126 127
  }
  else
  {
    snprintf(tmp, sizeof(tmp), "%s: %s\n", path, strerror(errno));
    addstr(tmp);
  }
  return (buf);
}

static int be_barf_file (const char *path, char **buf, int buflen)
{
  FILE *f;
  int i;
  
128
  if ((f = fopen (path, "w")) == NULL)		/* __FOPEN_CHECKED__ */
Thomas Roessler's avatar
Thomas Roessler committed
129 130 131 132 133 134 135 136 137 138 139 140 141 142
  {
    addstr (strerror (errno));
    addch ('\n');
    return (-1);
  }
  for (i = 0; i < buflen; i++) fputs (buf[i], f);
  if (fclose (f) == 0) return 0;
  printw ("fclose: %s\n", strerror (errno));
  return (-1);
}

static void be_free_memory (char **buf, int buflen)
{
  while (buflen-- > 0)
Thomas Roessler's avatar
Thomas Roessler committed
143
    FREE (&buf[buflen]);
Thomas Roessler's avatar
Thomas Roessler committed
144
  if (buf)
Thomas Roessler's avatar
Thomas Roessler committed
145
    FREE (&buf);
Thomas Roessler's avatar
Thomas Roessler committed
146 147 148 149 150 151 152 153 154 155 156
}

static char **
be_include_messages (char *msg, char **buf, int *bufmax, int *buflen,
		     int pfx, int inc_hdrs)
{
  int offset, bytes, n;
  char tmp[LONG_STRING];

  while ((msg = strtok (msg, " ,")) != NULL)
  {
157
    if (mutt_atoi (msg, &n) == 0 && n > 0 && n <= Context->msgcount)
Thomas Roessler's avatar
Thomas Roessler committed
158 159 160 161 162 163
    {
      n--;

      /* add the attribution */
      if (Attribution)
      {
164
        setlocale (LC_TIME, NONULL (AttributionLocale));
Thomas Roessler's avatar
Thomas Roessler committed
165
	mutt_make_string (tmp, sizeof (tmp) - 1, Attribution, Context, Context->hdrs[n]);
166
        setlocale (LC_TIME, "");
167
	strcat (tmp, "\n");	/* __STRCAT_CHECKED__ */
Thomas Roessler's avatar
Thomas Roessler committed
168 169 170
      }

      if (*bufmax == *buflen)
171
	safe_realloc ( &buf, sizeof (char *) * (*bufmax += 25));
Thomas Roessler's avatar
Thomas Roessler committed
172 173 174 175 176 177 178 179 180 181 182 183 184 185
      buf[(*buflen)++] = safe_strdup (tmp);

      bytes = Context->hdrs[n]->content->length;
      if (inc_hdrs)
      {
	offset = Context->hdrs[n]->offset;
	bytes += Context->hdrs[n]->content->offset - offset;
      }
      else
	offset = Context->hdrs[n]->content->offset;
      buf = be_snarf_data (Context->fp, buf, bufmax, buflen, offset, bytes,
			   pfx);

      if (*bufmax == *buflen)
186
	safe_realloc (&buf, sizeof (char *) * (*bufmax += 25));
Thomas Roessler's avatar
Thomas Roessler committed
187 188 189
      buf[(*buflen)++] = safe_strdup ("\n");
    }
    else
190
      printw (_("%d: invalid message number.\n"), n);
Thomas Roessler's avatar
Thomas Roessler committed
191 192 193 194 195 196 197 198 199 200 201 202 203
    msg = NULL;
  }
  return (buf);
}

static void be_print_header (ENVELOPE *env)
{
  char tmp[HUGE_STRING];

  if (env->to)
  {
    addstr ("To: ");
    tmp[0] = 0;
204
    rfc822_write_address (tmp, sizeof (tmp), env->to, 1);
Thomas Roessler's avatar
Thomas Roessler committed
205 206 207 208 209 210 211
    addstr (tmp);
    addch ('\n');
  }
  if (env->cc)
  {
    addstr ("Cc: ");
    tmp[0] = 0;
212
    rfc822_write_address (tmp, sizeof (tmp), env->cc, 1);
Thomas Roessler's avatar
Thomas Roessler committed
213 214 215 216 217 218 219
    addstr (tmp);
    addch ('\n');
  }
  if (env->bcc)
  {
    addstr ("Bcc: ");
    tmp[0] = 0;
220
    rfc822_write_address (tmp, sizeof (tmp), env->bcc, 1);
Thomas Roessler's avatar
Thomas Roessler committed
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
    addstr (tmp);
    addch ('\n');
  }
  if (env->subject)
  {
    addstr ("Subject: ");
    addstr (env->subject);
    addch ('\n');
  }
  addch ('\n');
}

/* args:
 *	force	override the $ask* vars (used for the ~h command)
 */
static void be_edit_header (ENVELOPE *e, int force)
{
  char tmp[HUGE_STRING];

240
  mutt_window_move (MuttMessageWindow, 0, 0);
Thomas Roessler's avatar
Thomas Roessler committed
241 242 243

  addstr ("To: ");
  tmp[0] = 0;
244 245
  mutt_addrlist_to_local (e->to);
  rfc822_write_address (tmp, sizeof (tmp), e->to, 0);
Thomas Roessler's avatar
Thomas Roessler committed
246 247
  if (!e->to || force)
  {
248
    if (mutt_enter_string (tmp, sizeof (tmp), 4, 0) == 0)
Thomas Roessler's avatar
Thomas Roessler committed
249 250 251 252
    {
      rfc822_free_address (&e->to);
      e->to = mutt_parse_adrlist (e->to, tmp);
      e->to = mutt_expand_aliases (e->to);
253
      mutt_addrlist_to_intl (e->to, NULL);	/* XXX - IDNA error reporting? */
Thomas Roessler's avatar
Thomas Roessler committed
254
      tmp[0] = 0;
255
      rfc822_write_address (tmp, sizeof (tmp), e->to, 1);
256
      mutt_window_mvaddstr (MuttMessageWindow, 0, 4, tmp);
Thomas Roessler's avatar
Thomas Roessler committed
257 258 259 260
    }
  }
  else
  {
261
    mutt_addrlist_to_intl (e->to, NULL);	/* XXX - IDNA error reporting? */
Thomas Roessler's avatar
Thomas Roessler committed
262 263 264 265 266 267 268 269
    addstr (tmp);
  }
  addch ('\n');

  if (!e->subject || force)
  {
    addstr ("Subject: ");
    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);
Thomas Roessler's avatar
Thomas Roessler committed
272 273 274 275 276 277 278
    addch ('\n');
  }

  if ((!e->cc && option (OPTASKCC)) || force)
  {
    addstr ("Cc: ");
    tmp[0] = 0;
279 280
    mutt_addrlist_to_local (e->cc);
    rfc822_write_address (tmp, sizeof (tmp), e->cc, 0);
281
    if (mutt_enter_string (tmp, sizeof (tmp), 4, 0) == 0)
Thomas Roessler's avatar
Thomas Roessler committed
282 283 284 285 286
    {
      rfc822_free_address (&e->cc);
      e->cc = mutt_parse_adrlist (e->cc, tmp);
      e->cc = mutt_expand_aliases (e->cc);
      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);
Thomas Roessler's avatar
Thomas Roessler committed
290
    }
291
    else
292
      mutt_addrlist_to_intl (e->cc, NULL);
Thomas Roessler's avatar
Thomas Roessler committed
293 294 295 296 297 298 299
    addch ('\n');
  }

  if (option (OPTASKBCC) || force)
  {
    addstr ("Bcc: ");
    tmp[0] = 0;
300 301
    mutt_addrlist_to_local (e->bcc);
    rfc822_write_address (tmp, sizeof (tmp), e->bcc, 0);
302
    if (mutt_enter_string (tmp, sizeof (tmp), 5, 0) == 0)
Thomas Roessler's avatar
Thomas Roessler committed
303 304 305 306
    {
      rfc822_free_address (&e->bcc);
      e->bcc = mutt_parse_adrlist (e->bcc, tmp);
      e->bcc = mutt_expand_aliases (e->bcc);
307
      mutt_addrlist_to_intl (e->bcc, NULL);
Thomas Roessler's avatar
Thomas Roessler committed
308
      tmp[0] = 0;
309
      rfc822_write_address (tmp, sizeof (tmp), e->bcc, 1);
310
      mutt_window_mvaddstr (MuttMessageWindow, 0, 5, tmp);
Thomas Roessler's avatar
Thomas Roessler committed
311
    }
312
    else
313
      mutt_addrlist_to_intl (e->bcc, NULL);
Thomas Roessler's avatar
Thomas Roessler committed
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
    addch ('\n');
  }
}

int mutt_builtin_editor (const char *path, HEADER *msg, HEADER *cur)
{
  char **buf = NULL;
  int bufmax = 0, buflen = 0;
  char tmp[LONG_STRING];
  int abort = 0;
  int done = 0;
  int i;
  char *p;
  
  scrollok (stdscr, TRUE);

  be_edit_header (msg->env, 0);

332
  addstr (_("(End message with a . on a line by itself)\n"));
Thomas Roessler's avatar
Thomas Roessler committed
333 334 335 336 337 338

  buf = be_snarf_file (path, buf, &bufmax, &buflen, 0);

  tmp[0] = 0;
  while (!done)
  {
339
    if (mutt_enter_string (tmp, sizeof (tmp), 0, 0) == -1)
Thomas Roessler's avatar
Thomas Roessler committed
340 341 342 343 344 345
    {
      tmp[0] = 0;
      continue;
    }
    addch ('\n');

Thomas Roessler's avatar
Thomas Roessler committed
346
    if (EscChar && tmp[0] == EscChar[0] && tmp[1] != EscChar[0])
Thomas Roessler's avatar
Thomas Roessler committed
347 348
    {
      /* remove trailing whitespace from the line */
349
      p = tmp + mutt_strlen (tmp) - 1;
Thomas Roessler's avatar
Thomas Roessler committed
350 351 352 353 354 355 356 357 358
      while (p >= tmp && ISSPACE (*p))
	*p-- = 0;

      p = tmp + 2;
      SKIPWS (p);

      switch (tmp[1])
      {
	case '?':
359 360
	  addstr (_(EditorHelp1));
          addstr (_(EditorHelp2));
Thomas Roessler's avatar
Thomas Roessler committed
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
	  break;
	case 'b':
	  msg->env->bcc = mutt_parse_adrlist (msg->env->bcc, p);
	  msg->env->bcc = mutt_expand_aliases (msg->env->bcc);
	  break;
	case 'c':
	  msg->env->cc = mutt_parse_adrlist (msg->env->cc, p);
	  msg->env->cc = mutt_expand_aliases (msg->env->cc);
	  break;
	case 'h':
	  be_edit_header (msg->env, 1);
	  break;
	case 'F':
	case 'f':
	case 'm':
	case 'M':
	  if (Context)
	  {
	    if (!*p && cur)
380
 	    {
Thomas Roessler's avatar
Thomas Roessler committed
381
	      /* include the current message */
382 383
	      p = tmp + mutt_strlen (tmp) + 1;
	      snprintf (tmp + mutt_strlen (tmp), sizeof (tmp) - mutt_strlen (tmp), " %d",
Thomas Roessler's avatar
Thomas Roessler committed
384 385 386
								cur->msgno + 1);
	    }
	    buf = be_include_messages (p, buf, &bufmax, &buflen,
387 388
				       (ascii_tolower (tmp[1]) == 'm'),
				       (ascii_isupper ((unsigned char) tmp[1])));
Thomas Roessler's avatar
Thomas Roessler committed
389 390
	  }
	  else
391
	    addstr (_("No mailbox.\n"));
Thomas Roessler's avatar
Thomas Roessler committed
392 393 394
	  break;
	case 'p':
	  addstr ("-----\n");
395
	  addstr (_("Message contains:\n"));
Thomas Roessler's avatar
Thomas Roessler committed
396 397 398
	  be_print_header (msg->env);
	  for (i = 0; i < buflen; i++)
	    addstr (buf[i]);
399 400 401 402 403
          /* L10N:
             This entry is shown AFTER the message content,
             not IN the middle of the content.
             So it doesn't mean "(message will continue)"
             but means "(press any key to continue using mutt)". */
404
	  addstr (_("(continue)\n"));
Thomas Roessler's avatar
Thomas Roessler committed
405 406 407 408 409 410
	  break;
	case 'q':
	  done = 1;
	  break;
	case 'r':
	  if (*p)
Thomas Roessler's avatar
Thomas Roessler committed
411 412 413 414 415
          {
	    strncpy(tmp, p, sizeof(tmp));
	    mutt_expand_path(tmp, sizeof(tmp));
	    buf = be_snarf_file (tmp, buf, &bufmax, &buflen, 1);
          }
Thomas Roessler's avatar
Thomas Roessler committed
416
	  else
417
	    addstr (_("missing filename.\n"));
Thomas Roessler's avatar
Thomas Roessler committed
418 419
	  break;
	case 's':
420
	  mutt_str_replace (&msg->env->subject, p);
Thomas Roessler's avatar
Thomas Roessler committed
421 422 423 424 425 426 427 428 429 430
	  break;
	case 't':
	  msg->env->to = rfc822_parse_adrlist (msg->env->to, p);
	  msg->env->to = mutt_expand_aliases (msg->env->to);
	  break;
	case 'u':
	  if (buflen)
	  {
	    buflen--;
	    strfcpy (tmp, buf[buflen], sizeof (tmp));
431
	    tmp[mutt_strlen (tmp)-1] = 0;
Thomas Roessler's avatar
Thomas Roessler committed
432
	    FREE (&buf[buflen]);
Thomas Roessler's avatar
Thomas Roessler committed
433 434 435 436
	    buf[buflen] = NULL;
	    continue;
	  }
	  else
437
	    addstr (_("No lines in message.\n"));
Thomas Roessler's avatar
Thomas Roessler committed
438 439 440 441 442 443
	  break;

	case 'e':
	case 'v':
	  if (be_barf_file (path, buf, buflen) == 0)
	  {
444
	    char *tag, *err;
Thomas Roessler's avatar
Thomas Roessler committed
445 446 447 448 449
	    be_free_memory (buf, buflen);
	    buf = NULL;
	    bufmax = buflen = 0;

	    if (option (OPTEDITHDRS))
450 451
	    {
	      mutt_env_to_local (msg->env);
Thomas Roessler's avatar
Thomas Roessler committed
452
	      mutt_edit_headers (NONULL(Visual), path, msg, NULL, 0);
453
	      if (mutt_env_to_intl (msg->env, &tag, &err))
454 455
		printw (_("Bad IDN in %s: '%s'\n"), tag, err);
	    }
Thomas Roessler's avatar
Thomas Roessler committed
456
	    else
Thomas Roessler's avatar
Thomas Roessler committed
457
	      mutt_edit_file (NONULL(Visual), path);
Thomas Roessler's avatar
Thomas Roessler committed
458 459 460

	    buf = be_snarf_file (path, buf, &bufmax, &buflen, 0);

461
	    addstr (_("(continue)\n"));
Thomas Roessler's avatar
Thomas Roessler committed
462 463 464 465 466 467 468 469 470 471
	  }
	  break;
	case 'w':
	  be_barf_file (*p ? p : path, buf, buflen);
	  break;
	case 'x':
	  abort = 1;
	  done = 1;
	  break;
	default:
472
	  printw (_("%s: unknown editor command (~? for help)\n"), tmp);
Thomas Roessler's avatar
Thomas Roessler committed
473 474 475
	  break;
      }
    }
476
    else if (mutt_strcmp (".", tmp) == 0)
Thomas Roessler's avatar
Thomas Roessler committed
477 478 479
      done = 1;
    else
    {
480
      safe_strcat (tmp, sizeof (tmp), "\n");
Thomas Roessler's avatar
Thomas Roessler committed
481
      if (buflen == bufmax)
482
	safe_realloc (&buf, sizeof (char *) * (bufmax += 25));
Thomas Roessler's avatar
Thomas Roessler committed
483 484 485 486 487 488 489 490 491 492 493
      buf[buflen++] = safe_strdup (tmp[1] == '~' ? tmp + 1 : tmp);
    }
    
    tmp[0] = 0;
  }

  if (!abort) be_barf_file (path, buf, buflen);
  be_free_memory (buf, buflen);

  return (abort ? -1 : 0);
}