Blame view

mh/forw.c 12.9 KB
1
/* GNU Mailutils -- a suite of utilities for electronic mail
2 3
   Copyright (C) 2003, 2005, 2006, 2007, 2008, 2009, 2010 Free Software
   Foundation, Inc.
4 5 6

   GNU Mailutils is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
7
   the Free Software Foundation; either version 3, or (at your option)
8 9 10 11 12 13 14 15
   any later version.

   GNU Mailutils 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
16
   along with GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */
17 18 19 20 21

/* MH forw command */

#include <mh.h>

22 23
static char doc[] = N_("GNU MH forw")"\v"
N_("Options marked with `*' are not yet implemented.\n\
24
Use -help to obtain the list of traditional MH options.");
25
static char args_doc[] = N_("[MSGLIST]");
26 27 28 29

/* GNU options */
static struct argp_option options[] = {
  {"annotate",      ARG_ANNOTATE,      N_("BOOL"), OPTION_ARG_OPTIONAL,
Sergey Poznyakoff authored
30
   N_("add Forwarded: header to each forwarded message")},
31
  {"build",         ARG_BUILD,         0, 0,
Sergey Poznyakoff authored
32
   N_("build the draft and quit immediately")},
33
  {"draftfolder",   ARG_DRAFTFOLDER,   N_("FOLDER"), 0,
Sergey Poznyakoff authored
34
   N_("specify the folder for message drafts")},
35
  {"nodraftfolder", ARG_NODRAFTFOLDER, 0, 0,
Sergey Poznyakoff authored
36
   N_("undo the effect of the last --draftfolder option")},
37
  {"draftmessage" , ARG_DRAFTMESSAGE,  N_("MSG"), 0,
Sergey Poznyakoff authored
38
   N_("invoke the draftmessage facility")},
39
  {"folder",        ARG_FOLDER,        N_("FOLDER"), 0,
Sergey Poznyakoff authored
40
   N_("specify folder to operate upon")},
41
  {"editor",        ARG_EDITOR,        N_("PROG"), 0,
Sergey Poznyakoff authored
42
   N_("set the editor program to use")},
43
  {"noedit",        ARG_NOEDIT,        0, 0,
Sergey Poznyakoff authored
44
   N_("suppress the initial edit")},
45 46
  {"file",          ARG_FILE, N_("FILE"), 0,
   N_("read message from FILE")},
47
  {"format",        ARG_FORMAT,        N_("BOOL"), 0, 
Sergey Poznyakoff authored
48
   N_("format messages")},
49
  {"noformat",      ARG_NOFORMAT,      NULL, 0,
Sergey Poznyakoff authored
50
   N_("undo the effect of the last --format option") },
51
  {"form",          ARG_FORM,          N_("FILE"), 0,
Sergey Poznyakoff authored
52
   N_("read format from given file")},
53
  {"filter",        ARG_FILTER,        N_("FILE"), 0,
Sergey Poznyakoff authored
54
  N_("use filter FILE to preprocess the body of the message") },
55
  {"nofilter",      ARG_NOFILTER,      NULL, 0,
Sergey Poznyakoff authored
56
   N_("undo the effect of the last --filter option") },
57
  {"inplace",       ARG_INPLACE,       N_("BOOL"), OPTION_ARG_OPTIONAL,
Sergey Poznyakoff authored
58
   N_("* annotate the message in place")},
59 60
  {"noinplace",     ARG_NOINPLACE,     0,          OPTION_HIDDEN, "" },
  {"mime",          ARG_MIME,          N_("BOOL"), OPTION_ARG_OPTIONAL,
Sergey Poznyakoff authored
61
   N_("use MIME encapsulation") },
62 63 64
  {"nomime",        ARG_NOMIME,        NULL, OPTION_HIDDEN, "" },
  {"width", ARG_WIDTH, N_("NUMBER"), 0, N_("Set output width")},
  {"whatnowproc",   ARG_WHATNOWPROC,   N_("PROG"), 0,
65
   N_("set the replacement for whatnow program")},
66
  {"nowhatnowproc", ARG_NOWHATNOWPROC, NULL, 0,
67
   N_("ignore whatnowproc variable, use standard `whatnow' shell instead")},
68
  {"use",           ARG_USE,           N_("BOOL"), OPTION_ARG_OPTIONAL,
Sergey Poznyakoff authored
69
   N_("use draft file preserved after the last session") },
70 71
  {"nouse",         ARG_NOUSE,         N_("BOOL"), OPTION_HIDDEN, "" },

72 73 74 75 76
  {NULL},
};

/* Traditional MH options */
struct mh_option mh_option[] = {
77 78
  { "annotate",      MH_OPT_BOOL },
  { "build" },
79 80
  { "file",          MH_OPT_ARG, "msgfile" },
  { "form",          MH_OPT_ARG, "formatfile" },
81
  { "format",        MH_OPT_BOOL },
82
  { "draftfolder",   MH_OPT_ARG, "folder" },
83 84
  { "nodraftfolder" },
  { "draftmessage" },
85
  { "editor",        MH_OPT_ARG, "program" },
86
  { "noedit" },
87
  { "filter",        MH_OPT_ARG, "program" },
88
  { "inplace",       MH_OPT_BOOL },
89
  { "whatnowproc",   MH_OPT_ARG, "program" },
90 91 92
  { "nowhatnowproc" },
  { "mime",          MH_OPT_BOOL },
  { NULL }
93 94
};

95 96 97 98 99 100
enum encap_type
  {
    encap_clear,
    encap_mhl,
    encap_mime
  };
101

102
char *formfile;
103 104
struct mh_whatnow_env wh_env = { 0 };
static int initial_edit = 1;
105
static const char *whatnowproc;
106 107 108

static char *mhl_filter_file = NULL; /* --filter flag */

109
static int build_only = 0;      /* --build flag */
110
static int annotate = 0;        /* --annotate flag */
111 112
static enum encap_type encap = encap_clear; /* controlled by --format, --form
					       and --mime flags */
113 114
static int use_draft = 0;       /* --use flag */
static int width = 80;          /* --width flag */
115
static char *draftmessage = "new";
116
static const char *draftfolder = NULL;
117
static char *input_file;        /* input file name (--file option) */
118 119

static mh_msgset_t msgset;
120
static mu_mailbox_t mbox;
121 122

static int
123
opt_handler (int key, char *arg, struct argp_state *state)
124 125 126
{
  switch (key)
    {
127
    case ARGP_KEY_INIT:
128
      draftfolder = mh_global_profile_get ("Draft-Folder", NULL);
129
      whatnowproc = mh_global_profile_get ("whatnowproc", NULL);
130 131
      break;

132 133 134 135
    case ARG_ANNOTATE:
      annotate = is_true (arg);
      break;
      
136 137 138 139
    case ARG_BUILD:
      build_only = 1;
      break;

140 141 142 143
    case ARG_FILE:
      input_file = arg;
      break;
      
144
    case ARG_DRAFTFOLDER:
145
      draftfolder = arg;
146
      break;
147 148

    case ARG_NODRAFTFOLDER:
149
      draftfolder = NULL;
150
      break;
151 152
      
    case ARG_DRAFTMESSAGE:
153
      draftmessage = arg;
154 155 156 157 158 159
      break;

    case ARG_USE:
      use_draft = is_true (arg);
      break;

160 161 162 163
    case ARG_NOUSE:
      use_draft = 0;
      break;

164 165 166 167
    case ARG_WIDTH:
      width = strtoul (arg, NULL, 0);
      if (!width)
	{
168
	  argp_error (state, _("invalid width"));
169 170 171 172 173 174 175 176 177
	  exit (1);
	}
      break;

    case ARG_EDITOR:
      wh_env.editor = arg;
      break;
      
    case ARG_FOLDER: 
178
      mh_set_current_folder (arg);
179 180 181
      break;

    case ARG_FORM:
182
      mh_find_file (arg, &formfile);
183 184 185
      break;

    case ARG_FORMAT:
186 187 188
      if (is_true (arg))
	{
	  encap = encap_mhl;
189
	  mh_find_file ("mhl.forward", &mhl_filter_file);
190
	}
191
      else
192
	encap = encap_clear;
193
      break;
194 195 196 197
      
    case ARG_NOFORMAT:
      encap = encap_clear;
      break;
198 199

    case ARG_FILTER:
200
      mh_find_file (arg, &mhl_filter_file);
201
      encap = encap_mhl;
202 203 204
      break;
	
    case ARG_MIME:
205 206 207 208 209 210
      if (is_true (arg))
	{
	  encap = encap_mime;
	  break;
	}
      /*FALLTHRU*/
211
    case ARG_NOMIME:
212 213
      if (encap == encap_mime)
	encap = encap_clear;
214 215 216
      break;
      
    case ARG_INPLACE:
217 218 219
      mh_opt_notimpl_warning ("-inplace");
      break;

220
    case ARG_WHATNOWPROC:
221 222 223
      whatnowproc = arg;
      break;

224
    case ARG_NOWHATNOWPROC:
225
      whatnowproc = NULL;
226
      break;
227

228 229 230 231 232
    case ARGP_KEY_FINI:
      if (!formfile)
	mh_find_file ("forwcomps", &formfile);
      break;
      
233
    default:
234
      return ARGP_ERR_UNKNOWN;
235 236 237 238
    }
  return 0;
}

239 240
struct format_data
{
241
  int num;
242 243
  mu_stream_t stream;
  mu_list_t format;
244 245
};

246 247 248 249 250 251 252 253 254 255 256 257
/* State machine according to RFC 934:
   
      S1 ::   CRLF {CRLF} S1
            | "-" {"- -"} S2
            | c {c} S2

      S2 ::   CRLF {CRLF} S1
            | c {c} S2
*/

enum rfc934_state { S1, S2 };

258
static int
259
msg_copy (mu_message_t msg, mu_stream_t ostream)
260
{
261
  mu_stream_t istream;
262 263 264
  int rc;
  size_t n;
  char buf[512];
265
  enum rfc934_state state = S1;
266
  
267
  rc = mu_message_get_streamref (msg, &istream);
268 269 270
  if (rc)
    return rc;
  while (rc == 0
271
	 && mu_stream_read (istream, buf, sizeof buf, &n) == 0
272
	 && n > 0)
273 274 275 276 277 278 279 280 281
    {
      size_t start, i;
	
      for (i = start = 0; i < n; i++)
	switch (state)
	  {
	  case S1:
	    if (buf[i] == '-')
	      {
282 283
		rc = mu_stream_write (ostream, buf + start,
				      i - start + 1, NULL);
284 285
		if (rc)
		  return rc;
286
		rc = mu_stream_write (ostream, " -", 2, NULL);
287 288 289 290 291 292 293 294 295 296 297 298 299 300
		if (rc)
		  return rc;
		start = i + 1;
		state = S2;
	      }
	    else if (buf[i] != '\n')
	      state = S2;
	    break;
	      
	  case S2:
	    if (buf[i] == '\n')
	      state = S1;
	  }
      if (i > start)
301
	rc = mu_stream_write (ostream, buf + start, i  - start, NULL);
302
    }
303
  mu_stream_destroy (&istream);
304 305 306 307
  return rc;
}

void
308 309
format_message (mu_stream_t outstr, mu_message_t msg, int num,
		mu_list_t format)
310
{
311
  int rc = 0;
312 313 314 315
  
  if (annotate)
    mu_list_append (wh_env.anno_list, msg);
  
316 317 318 319 320 321 322 323 324 325 326 327
  if (num)
    rc = mu_stream_printf (outstr, "\n------- Message %d\n", num);

  if (rc == 0)
    {
      if (format)
	rc = mhl_format_run (format, width, 0, 0, msg, outstr);
      else
	rc = msg_copy (msg, outstr);
    }
  
  if (rc)
328
    {
329 330
      mu_error (_("cannot copy message: %s"), mu_strerror (rc));
      exit (1);
331
    }
332
}
333

334 335 336 337 338 339 340 341 342
void
format_message_itr (mu_mailbox_t mbox MU_ARG_UNUSED,
		    mu_message_t msg, size_t num, void *data)
{
  struct format_data *fp = data;

  format_message (fp->stream, msg, fp->num, fp->format);
  if (fp->num)
    fp->num++;
343 344 345 346 347 348
}

void
finish_draft ()
{
  int rc;
349 350
  mu_stream_t stream;
  mu_list_t format = NULL;
351 352 353
  struct format_data fd;
  char *str;
  
354
  if ((rc = mu_file_stream_create (&stream,
355
				   wh_env.file,
356
				   MU_STREAM_WRITE|MU_STREAM_CREAT)))
357
    {
358
      mu_error (_("cannot open output file `%s': %s"),
359 360 361 362
		wh_env.file, mu_strerror (rc));
      exit (1);
    }

363
  mu_stream_seek (stream, 0, SEEK_END, NULL);
364

365
  if (input_file)
366
    {
367 368
      mu_stream_t instr;
      int rc;
369
  
370 371 372 373 374 375 376 377 378 379
      if ((rc = mu_file_stream_create (&stream,
				       wh_env.file,
				       MU_STREAM_WRITE|MU_STREAM_CREAT)))
	{
	  mu_error (_("cannot open output file `%s': %s"),
		    wh_env.file, mu_strerror (rc));
	  exit (1);
	}

      mu_stream_seek (stream, 0, SEEK_END, NULL);
380
      
381 382
      rc = mu_file_stream_create (&instr, input_file, MU_STREAM_READ);
      if (rc)
383
	{
384 385 386
	  mu_diag_funcall (MU_DIAG_ERROR, "mu_file_stream_create",
			   input_file, rc);
	  exit (1);
387
	}
388 389 390
      
      rc = mu_stream_copy (stream, instr, 0, NULL);
      mu_stream_unref (instr);
391 392 393
    }
  else
    {
394
      if (encap == encap_mhl)
395
	{
396 397 398 399 400 401
	  if (mhl_filter_file)
	    {
	      format = mhl_format_compile (mhl_filter_file);
	      if (!format)
		exit (1);
	    }
402
	}
403 404 405 406 407 408
      
      if (annotate)
	{
	  wh_env.anno_field = "Forwarded";
	  mu_list_create (&wh_env.anno_list);
	}
409
      
410 411 412 413 414 415
      if (encap == encap_mime)
	{
	  mu_url_t url;
	  const char *mbox_path;
	  const char *p;
	  size_t i;
416
      
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
	  mu_mailbox_get_url (mbox, &url);
	  mu_url_sget_path (url, &mbox_path);
	  mu_asprintf (&str, "#forw [] +%s", mbox_path);
	  rc = mu_stream_write (stream, str, strlen (str), NULL);
	  free (str);
	  for (i = 0; rc == 0 && i < msgset.count; i++)
	    {
	      mu_message_t msg;
	      size_t num;
	      
	      mu_mailbox_get_message (mbox, msgset.list[i], &msg);
	      if (annotate)
		mu_list_append (wh_env.anno_list, msg);
	      mh_message_number (msg, &num);
	      p = mu_umaxtostr (0, num);
	      rc = mu_stream_write (stream, " ", 1, NULL);
	      if (rc)
		break;
	      rc = mu_stream_write (stream, p, strlen (p), NULL);
	    }
	}
438
      else
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
	{
	  str = "\n------- ";
	  rc = mu_stream_write (stream, str, strlen (str), NULL);
	  
	  if (msgset.count == 1)
	    {
	      fd.num = 0;
	      str = (char*) _("Forwarded message\n");
	    }
	  else
	    {
	      fd.num = 1;
	      str = (char*) _("Forwarded messages\n");
	    }
	  
	  rc = mu_stream_write (stream, str, strlen (str), NULL);
	  fd.stream = stream;
	  fd.format = format;
	  rc = mh_iterate (mbox, &msgset, format_message_itr, &fd);
      
	  str = "\n------- ";
	  rc = mu_stream_write (stream, str, strlen (str), NULL);
	  
	  if (msgset.count == 1)
	    str = (char*) _("End of Forwarded message");
	  else
	    str = (char*) _("End of Forwarded messages");
	  
	  rc = mu_stream_write (stream, str, strlen (str), NULL);
	}
469
      
470
      rc = mu_stream_write (stream, "\n\n", 2, NULL);
471
    }
472
  mu_stream_close (stream);
473
  mu_stream_destroy (&stream);
474 475 476 477 478
}

int
main (int argc, char **argv)
{
479
  int index, rc;
480 481

  /* Native Language Support */
Sergey Poznyakoff authored
482
  MU_APP_INIT_NLS ();
483

484
  mh_argp_init ();
485
  mh_argp_parse (&argc, &argv, 0, options, mh_option, args_doc, doc,
486 487 488 489 490
		 opt_handler, NULL, &index);

  argc -= index;
  argv += index;

491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
  if (input_file)
    {
      if (encap == encap_mime)
	{
	  mu_error (_("--build disables --mime"));
	  encap = encap_clear;
	}
      if (argc)
	{
	  mu_error (_("can't mix files and folders/msgs"));
	  exit (1);
	}
    }
  else
    {
506
      mbox = mh_open_folder (mh_current_folder (), MU_STREAM_RDWR);
507 508
      mh_msgset_parse (mbox, &msgset, argc, argv, "cur");
    }
509
  
510
  if (build_only || !draftfolder)
511
    wh_env.file = mh_expand_name (NULL, "draft", 0);
512
  else if (draftfolder)
513
    {
514
      if (mh_draft_message (draftfolder, draftmessage, &wh_env.file))
515 516 517
	return 1;
    }
  wh_env.draftfile = wh_env.file;
518 519

  switch (build_only ?
520
	  DISP_REPLACE : check_draft_disposition (&wh_env, use_draft))
521 522 523
    {
    case DISP_QUIT:
      exit (0);
524
      
525 526
    case DISP_USE:
      break;
527
      
528 529
    case DISP_REPLACE:
      unlink (wh_env.draftfile);
530
      mh_comp_draft (formfile, wh_env.file);
531 532 533 534 535 536
      finish_draft ();
    }
  
  /* Exit immediately if --build is given */
  if (build_only)
    {
537 538
      if (strcmp (wh_env.file, wh_env.draftfile))
	rename (wh_env.file, wh_env.draftfile);
539 540 541
      return 0;
    }
  
542
  rc = mh_whatnowproc (&wh_env, initial_edit, whatnowproc);
543

544
  mu_mailbox_sync (mbox);
545 546 547
  mu_mailbox_close (mbox);
  mu_mailbox_destroy (&mbox);
  return rc;
548
}