edit.c 11.6 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 32 33 34 35 36 37 38 39 40

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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)
 */
41
static char* EditorHelp = N_("\
Thomas Roessler's avatar
Thomas Roessler committed
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
~~		insert a line begining with a single ~\n\
~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\
~p		print the message\n\
~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\
59
.		on a line by itself ends input\n");
Thomas Roessler's avatar
Thomas Roessler committed
60 61 62 63 64 65 66 67 68 69 70 71

static char **
be_snarf_data (FILE *f, char **buf, int *bufmax, int *buflen, int offset,
	       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
72
    strfcpy (tmp, NONULL(Prefix), sizeof (tmp));
73
    tmplen = mutt_strlen (tmp);
Thomas Roessler's avatar
Thomas Roessler committed
74 75 76 77 78 79 80 81
    p = tmp + tmplen;
    tmplen = sizeof (tmp) - tmplen;
  }

  fseek (f, offset, 0);
  while (bytes > 0)
  {
    if (fgets (p, tmplen - 1, f) == NULL) break;
82
    bytes -= mutt_strlen (p);
Thomas Roessler's avatar
Thomas Roessler committed
83
    if (*bufmax == *buflen)
84
      safe_realloc (&buf, sizeof (char *) * (*bufmax += 25));
Thomas Roessler's avatar
Thomas Roessler committed
85 86
    buf[(*buflen)++] = safe_strdup (tmp);
  }
87
  if (buf && *bufmax == *buflen) { /* Do not smash memory past buf */
88
    safe_realloc (&buf, sizeof (char *) * (++*bufmax));
89
  }
Thomas Roessler's avatar
Thomas Roessler committed
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
  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)
    {
107
      snprintf(tmp, sizeof(tmp), "\"%s\" %lu bytes\n", path, (unsigned long) sb.st_size);
Thomas Roessler's avatar
Thomas Roessler committed
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
      addstr(tmp);
    }
    fclose (f);
  }
  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;
  
125
  if ((f = fopen (path, "w")) == NULL)		/* __FOPEN_CHECKED__ */
Thomas Roessler's avatar
Thomas Roessler committed
126 127 128 129 130 131 132 133 134 135 136 137 138 139
  {
    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
140
    FREE (&buf[buflen]);
Thomas Roessler's avatar
Thomas Roessler committed
141
  if (buf)
Thomas Roessler's avatar
Thomas Roessler committed
142
    FREE (&buf);
Thomas Roessler's avatar
Thomas Roessler committed
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
}

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)
  {
    n = atoi (msg);
    if (n > 0 && n <= Context->msgcount)
    {
      n--;

      /* add the attribution */
      if (Attribution)
      {
Thomas Roessler's avatar
Thomas Roessler committed
162
	mutt_make_string (tmp, sizeof (tmp) - 1, Attribution, Context, Context->hdrs[n]);
163
	strcat (tmp, "\n");	/* __STRCAT_CHECKED__ */
Thomas Roessler's avatar
Thomas Roessler committed
164 165 166
      }

      if (*bufmax == *buflen)
167
	safe_realloc ( &buf, sizeof (char *) * (*bufmax += 25));
Thomas Roessler's avatar
Thomas Roessler committed
168 169 170 171 172 173 174 175 176 177 178 179 180 181
      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)
182
	safe_realloc (&buf, sizeof (char *) * (*bufmax += 25));
Thomas Roessler's avatar
Thomas Roessler committed
183 184 185
      buf[(*buflen)++] = safe_strdup ("\n");
    }
    else
186
      printw (_("%d: invalid message number.\n"), n);
Thomas Roessler's avatar
Thomas Roessler committed
187 188 189 190 191 192 193 194 195 196 197 198 199
    msg = NULL;
  }
  return (buf);
}

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

  if (env->to)
  {
    addstr ("To: ");
    tmp[0] = 0;
200
    rfc822_write_address (tmp, sizeof (tmp), env->to, 1);
Thomas Roessler's avatar
Thomas Roessler committed
201 202 203 204 205 206 207
    addstr (tmp);
    addch ('\n');
  }
  if (env->cc)
  {
    addstr ("Cc: ");
    tmp[0] = 0;
208
    rfc822_write_address (tmp, sizeof (tmp), env->cc, 1);
Thomas Roessler's avatar
Thomas Roessler committed
209 210 211 212 213 214 215
    addstr (tmp);
    addch ('\n');
  }
  if (env->bcc)
  {
    addstr ("Bcc: ");
    tmp[0] = 0;
216
    rfc822_write_address (tmp, sizeof (tmp), env->bcc, 1);
Thomas Roessler's avatar
Thomas Roessler committed
217 218 219 220 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];

  move (LINES-1, 0);

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

  if (!e->subject || force)
  {
    addstr ("Subject: ");
    strfcpy (tmp, e->subject ? e->subject: "", sizeof (tmp));
266
    if (mutt_enter_string (tmp, sizeof (tmp), LINES-1, 9, 0) == 0)
267
      mutt_str_replace (&e->subject, tmp);
Thomas Roessler's avatar
Thomas Roessler committed
268 269 270 271 272 273 274
    addch ('\n');
  }

  if ((!e->cc && option (OPTASKCC)) || force)
  {
    addstr ("Cc: ");
    tmp[0] = 0;
275 276
    mutt_addrlist_to_local (e->cc);
    rfc822_write_address (tmp, sizeof (tmp), e->cc, 0);
277
    if (mutt_enter_string (tmp, sizeof (tmp), LINES-1, 4, 0) == 0)
Thomas Roessler's avatar
Thomas Roessler committed
278 279 280 281 282
    {
      rfc822_free_address (&e->cc);
      e->cc = mutt_parse_adrlist (e->cc, tmp);
      e->cc = mutt_expand_aliases (e->cc);
      tmp[0] = 0;
283 284
      mutt_addrlist_to_idna (e->cc, NULL);
      rfc822_write_address (tmp, sizeof (tmp), e->cc, 1);
Thomas Roessler's avatar
Thomas Roessler committed
285 286
      mvaddstr (LINES - 1, 4, tmp);
    }
287 288
    else
      mutt_addrlist_to_idna (e->cc, NULL);
Thomas Roessler's avatar
Thomas Roessler committed
289 290 291 292 293 294 295
    addch ('\n');
  }

  if (option (OPTASKBCC) || force)
  {
    addstr ("Bcc: ");
    tmp[0] = 0;
296 297
    mutt_addrlist_to_local (e->bcc);
    rfc822_write_address (tmp, sizeof (tmp), e->bcc, 0);
298
    if (mutt_enter_string (tmp, sizeof (tmp), LINES-1, 5, 0) == 0)
Thomas Roessler's avatar
Thomas Roessler committed
299 300 301 302
    {
      rfc822_free_address (&e->bcc);
      e->bcc = mutt_parse_adrlist (e->bcc, tmp);
      e->bcc = mutt_expand_aliases (e->bcc);
303
      mutt_addrlist_to_idna (e->bcc, NULL);
Thomas Roessler's avatar
Thomas Roessler committed
304
      tmp[0] = 0;
305
      rfc822_write_address (tmp, sizeof (tmp), e->bcc, 1);
Thomas Roessler's avatar
Thomas Roessler committed
306 307
      mvaddstr (LINES - 1, 5, tmp);
    }
308 309
    else
      mutt_addrlist_to_idna (e->bcc, NULL);
Thomas Roessler's avatar
Thomas Roessler committed
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
    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);

328
  addstr (_("(End message with a . on a line by itself)\n"));
Thomas Roessler's avatar
Thomas Roessler committed
329 330 331 332 333 334

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

  tmp[0] = 0;
  while (!done)
  {
335
    if (mutt_enter_string (tmp, sizeof (tmp), LINES-1, 0, 0) == -1)
Thomas Roessler's avatar
Thomas Roessler committed
336 337 338 339 340 341
    {
      tmp[0] = 0;
      continue;
    }
    addch ('\n');

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

      p = tmp + 2;
      SKIPWS (p);

      switch (tmp[1])
      {
	case '?':
355
	  addstr (_(EditorHelp));
Thomas Roessler's avatar
Thomas Roessler committed
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
	  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)
375
 	    {
Thomas Roessler's avatar
Thomas Roessler committed
376
	      /* include the current message */
377 378
	      p = tmp + mutt_strlen (tmp) + 1;
	      snprintf (tmp + mutt_strlen (tmp), sizeof (tmp) - mutt_strlen (tmp), " %d",
Thomas Roessler's avatar
Thomas Roessler committed
379 380 381
								cur->msgno + 1);
	    }
	    buf = be_include_messages (p, buf, &bufmax, &buflen,
382 383
				       (ascii_tolower (tmp[1]) == 'm'),
				       (ascii_isupper ((unsigned char) tmp[1])));
Thomas Roessler's avatar
Thomas Roessler committed
384 385
	  }
	  else
386
	    addstr (_("No mailbox.\n"));
Thomas Roessler's avatar
Thomas Roessler committed
387 388 389
	  break;
	case 'p':
	  addstr ("-----\n");
390
	  addstr (_("Message contains:\n"));
Thomas Roessler's avatar
Thomas Roessler committed
391 392 393
	  be_print_header (msg->env);
	  for (i = 0; i < buflen; i++)
	    addstr (buf[i]);
394
	  addstr (_("(continue)\n"));
Thomas Roessler's avatar
Thomas Roessler committed
395 396 397 398 399 400
	  break;
	case 'q':
	  done = 1;
	  break;
	case 'r':
	  if (*p)
Thomas Roessler's avatar
Thomas Roessler committed
401 402 403 404 405
          {
	    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
406
	  else
407
	    addstr (_("missing filename.\n"));
Thomas Roessler's avatar
Thomas Roessler committed
408 409
	  break;
	case 's':
410
	  mutt_str_replace (&msg->env->subject, p);
Thomas Roessler's avatar
Thomas Roessler committed
411 412 413 414 415 416 417 418 419 420
	  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));
421
	    tmp[mutt_strlen (tmp)-1] = 0;
Thomas Roessler's avatar
Thomas Roessler committed
422
	    FREE (&buf[buflen]);
Thomas Roessler's avatar
Thomas Roessler committed
423 424 425 426
	    buf[buflen] = NULL;
	    continue;
	  }
	  else
427
	    addstr (_("No lines in message.\n"));
Thomas Roessler's avatar
Thomas Roessler committed
428 429 430 431 432 433
	  break;

	case 'e':
	case 'v':
	  if (be_barf_file (path, buf, buflen) == 0)
	  {
434
	    char *tag, *err;
Thomas Roessler's avatar
Thomas Roessler committed
435 436 437 438 439
	    be_free_memory (buf, buflen);
	    buf = NULL;
	    bufmax = buflen = 0;

	    if (option (OPTEDITHDRS))
440 441
	    {
	      mutt_env_to_local (msg->env);
Thomas Roessler's avatar
Thomas Roessler committed
442
	      mutt_edit_headers (NONULL(Visual), path, msg, NULL, 0);
443 444 445
	      if (mutt_env_to_idna (msg->env, &tag, &err))
		printw (_("Bad IDN in %s: '%s'\n"), tag, err);
	    }
Thomas Roessler's avatar
Thomas Roessler committed
446
	    else
Thomas Roessler's avatar
Thomas Roessler committed
447
	      mutt_edit_file (NONULL(Visual), path);
Thomas Roessler's avatar
Thomas Roessler committed
448 449 450

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

451
	    addstr (_("(continue)\n"));
Thomas Roessler's avatar
Thomas Roessler committed
452 453 454 455 456 457 458 459 460 461
	  }
	  break;
	case 'w':
	  be_barf_file (*p ? p : path, buf, buflen);
	  break;
	case 'x':
	  abort = 1;
	  done = 1;
	  break;
	default:
462
	  printw (_("%s: unknown editor command (~? for help)\n"), tmp);
Thomas Roessler's avatar
Thomas Roessler committed
463 464 465
	  break;
      }
    }
466
    else if (mutt_strcmp (".", tmp) == 0)
Thomas Roessler's avatar
Thomas Roessler committed
467 468 469
      done = 1;
    else
    {
470
      safe_strcat (tmp, sizeof (tmp), "\n");
Thomas Roessler's avatar
Thomas Roessler committed
471
      if (buflen == bufmax)
472
	safe_realloc (&buf, sizeof (char *) * (bufmax += 25));
Thomas Roessler's avatar
Thomas Roessler committed
473 474 475 476 477 478 479 480 481 482 483
      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);
}