edit.c 12.1 KB
Newer Older
1
/*
2
 * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
 * 
 *     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
 *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */ 
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

25 26 27
#include "mutt.h"
#include "mutt_curses.h"
#include "mutt_idna.h"
28 29 30 31 32 33 34 35 36

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <locale.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
37 38 39 40 41

/*
 * SLcurses_waddnstr() can't take a "const char *", so this is only
 * declared "static" (sigh)
 */
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
static char* EditorHelp1 = N_("\
~~		insert a line beginning 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");

static char* EditorHelp2 = 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\
.		on a line by itself ends input\n");

static char **
be_snarf_data (FILE *f, char **buf, int *bufmax, int *buflen, LOFF_T offset,
	       int bytes, int prefix)
67 68 69
{
  char tmp[HUGE_STRING];
  char *p = tmp;
70
  int tmplen = sizeof (tmp);
71

72
  tmp[sizeof (tmp) - 1] = 0;
73 74
  if (prefix)
  {
75 76
    strfcpy (tmp, NONULL(Prefix), sizeof (tmp));
    tmplen = mutt_strlen (tmp);
77
    p = tmp + tmplen;
78
    tmplen = sizeof (tmp) - tmplen;
79 80
  }

81
  fseeko (f, offset, 0);
82 83
  while (bytes > 0)
  {
84 85
    if (fgets (p, tmplen - 1, f) == NULL) break;
    bytes -= mutt_strlen (p);
86
    if (*bufmax == *buflen)
87 88
      safe_realloc (&buf, sizeof (char *) * (*bufmax += 25));
    buf[(*buflen)++] = safe_strdup (tmp);
89
  }
90 91
  if (buf && *bufmax == *buflen) { /* Do not smash memory past buf */
    safe_realloc (&buf, sizeof (char *) * (++*bufmax));
92
  }
93 94
  if (buf) buf[*buflen] = NULL;
  return (buf);
95 96
}

97 98
static char **
be_snarf_file (const char *path, char **buf, int *max, int *len, int verbose)
99
{
100
  FILE *f;
101 102
  char tmp[LONG_STRING];
  struct stat sb;
103 104
  
  if ((f = fopen (path, "r")))
105
  {
106 107
    fstat (fileno (f), &sb);
    buf = be_snarf_data (f, buf, max, len, 0, sb.st_size, 0);
108 109 110 111 112
    if (verbose)
    {
      snprintf(tmp, sizeof(tmp), "\"%s\" %lu bytes\n", path, (unsigned long) sb.st_size);
      addstr(tmp);
    }
113
    safe_fclose (&f);
114 115 116 117 118 119
  }
  else
  {
    snprintf(tmp, sizeof(tmp), "%s: %s\n", path, strerror(errno));
    addstr(tmp);
  }
120
  return (buf);
121 122
}

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

140
static 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
}

148 149 150
static char **
be_include_messages (char *msg, char **buf, int *bufmax, int *buflen,
		     int pfx, int inc_hdrs)
151 152 153 154
{
  int offset, bytes, n;
  char tmp[LONG_STRING];

155
  while ((msg = strtok (msg, " ,")) != NULL)
156
  {
157
    if (mutt_atoi (msg, &n) == 0 && n > 0 && n <= Context->msgcount)
158 159 160 161 162 163
    {
      n--;

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

      if (*bufmax == *buflen)
171 172
	safe_realloc ( &buf, sizeof (char *) * (*bufmax += 25));
      buf[(*buflen)++] = safe_strdup (tmp);
173 174 175 176

      bytes = Context->hdrs[n]->content->length;
      if (inc_hdrs)
      {
177 178
	offset = Context->hdrs[n]->offset;
	bytes += Context->hdrs[n]->content->offset - offset;
179 180
      }
      else
181 182 183
	offset = Context->hdrs[n]->content->offset;
      buf = be_snarf_data (Context->fp, buf, bufmax, buflen, offset, bytes,
			   pfx);
184 185

      if (*bufmax == *buflen)
186 187
	safe_realloc (&buf, sizeof (char *) * (*bufmax += 25));
      buf[(*buflen)++] = safe_strdup ("\n");
188 189
    }
    else
190
      printw (_("%d: invalid message number.\n"), n);
191 192
    msg = NULL;
  }
193
  return (buf);
194 195
}

196
static void be_print_header (ENVELOPE *env)
197 198 199 200 201
{
  char tmp[HUGE_STRING];

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

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

240
  mutt_window_move (MuttMessageWindow, 0, 0);
241

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

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

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

296
  if (option (OPTASKBCC) || force)
297
  {
298
    addstr ("Bcc: ");
299
    tmp[0] = 0;
300 301 302
    mutt_addrlist_to_local (e->bcc);
    rfc822_write_address (tmp, sizeof (tmp), e->bcc, 0);
    if (mutt_enter_string (tmp, sizeof (tmp), 5, 0) == 0)
303
    {
304 305 306 307
      rfc822_free_address (&e->bcc);
      e->bcc = mutt_parse_adrlist (e->bcc, tmp);
      e->bcc = mutt_expand_aliases (e->bcc);
      mutt_addrlist_to_intl (e->bcc, NULL);
308
      tmp[0] = 0;
309 310
      rfc822_write_address (tmp, sizeof (tmp), e->bcc, 1);
      mutt_window_mvaddstr (MuttMessageWindow, 0, 5, tmp);
311 312
    }
    else
313 314
      mutt_addrlist_to_intl (e->bcc, NULL);
    addch ('\n');
315 316 317
  }
}

318
int mutt_builtin_editor (const char *path, HEADER *msg, HEADER *cur)
319 320 321 322 323 324 325
{
  char **buf = NULL;
  int bufmax = 0, buflen = 0;
  char tmp[LONG_STRING];
  int abort = 0;
  int done = 0;
  int i;
326 327 328
  char *p;
  
  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 337 338

  tmp[0] = 0;
  while (!done)
  {
339
    if (mutt_enter_string (tmp, sizeof (tmp), 0, 0) == -1)
340 341 342 343
    {
      tmp[0] = 0;
      continue;
    }
344
    addch ('\n');
345 346 347 348

    if (EscChar && tmp[0] == EscChar[0] && tmp[1] != EscChar[0])
    {
      /* remove trailing whitespace from the line */
349 350 351
      p = tmp + mutt_strlen (tmp) - 1;
      while (p >= tmp && ISSPACE (*p))
	*p-- = 0;
352 353

      p = tmp + 2;
354
      SKIPWS (p);
355 356 357

      switch (tmp[1])
      {
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
	case '?':
	  addstr (_(EditorHelp1));
          addstr (_(EditorHelp2));
	  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)
 	    {
	      /* include the current message */
	      p = tmp + mutt_strlen (tmp) + 1;
	      snprintf (tmp + mutt_strlen (tmp), sizeof (tmp) - mutt_strlen (tmp), " %d",
								cur->msgno + 1);
	    }
	    buf = be_include_messages (p, buf, &bufmax, &buflen,
				       (ascii_tolower (tmp[1]) == 'm'),
				       (ascii_isupper ((unsigned char) tmp[1])));
	  }
	  else
	    addstr (_("No mailbox.\n"));
	  break;
	case 'p':
	  addstr ("-----\n");
	  addstr (_("Message contains:\n"));
	  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 405 406 407 408 409 410
	  addstr (_("(continue)\n"));
	  break;
	case 'q':
	  done = 1;
	  break;
	case 'r':
	  if (*p)
411
          {
412 413 414
	    strncpy(tmp, p, sizeof(tmp));
	    mutt_expand_path(tmp, sizeof(tmp));
	    buf = be_snarf_file (tmp, buf, &bufmax, &buflen, 1);
415
          }
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
	  else
	    addstr (_("missing filename.\n"));
	  break;
	case 's':
	  mutt_str_replace (&msg->env->subject, p);
	  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));
	    tmp[mutt_strlen (tmp)-1] = 0;
	    FREE (&buf[buflen]);
	    buf[buflen] = NULL;
	    continue;
	  }
	  else
	    addstr (_("No lines in message.\n"));
	  break;

	case 'e':
	case 'v':
	  if (be_barf_file (path, buf, buflen) == 0)
	  {
	    char *tag, *err;
	    be_free_memory (buf, buflen);
	    buf = NULL;
	    bufmax = buflen = 0;

	    if (option (OPTEDITHDRS))
	    {
	      mutt_env_to_local (msg->env);
	      mutt_edit_headers (NONULL(Visual), path, msg, NULL, 0);
	      if (mutt_env_to_intl (msg->env, &tag, &err))
		printw (_("Bad IDN in %s: '%s'\n"), tag, err);
	    }
	    else
	      mutt_edit_file (NONULL(Visual), path);

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

	    addstr (_("(continue)\n"));
	  }
	  break;
	case 'w':
	  be_barf_file (*p ? p : path, buf, buflen);
	  break;
	case 'x':
	  abort = 1;
	  done = 1;
	  break;
	default:
	  printw (_("%s: unknown editor command (~? for help)\n"), tmp);
	  break;
474 475
      }
    }
476
    else if (mutt_strcmp (".", tmp) == 0)
477 478 479
      done = 1;
    else
    {
480
      safe_strcat (tmp, sizeof (tmp), "\n");
481
      if (buflen == bufmax)
482 483
	safe_realloc (&buf, sizeof (char *) * (bufmax += 25));
      buf[buflen++] = safe_strdup (tmp[1] == '~' ? tmp + 1 : tmp);
484
    }
485
    
486 487 488
    tmp[0] = 0;
  }

489 490
  if (!abort) be_barf_file (path, buf, buflen);
  be_free_memory (buf, buflen);
491 492 493

  return (abort ? -1 : 0);
}