Fixed -mime for forw and repl.
[mmh] / uip / forw.c
1 /*
2 ** forw.c -- forward a message, or group of messages.
3 **
4 ** This code is Copyright (c) 2002, by the authors of nmh.  See the
5 ** COPYRIGHT file in the root directory of the nmh distribution for
6 ** complete copyright information.
7 */
8
9 #include <h/mh.h>
10 #include <fcntl.h>
11 #include <h/fmt_scan.h>
12 #include <h/tws.h>
13 #include <h/utils.h>
14
15
16 #define IFORMAT  "digest-issue-%s"
17 #define VFORMAT  "digest-volume-%s"
18
19 static struct swit switches[] = {
20 #define ANNOSW  0
21         { "annotate", 0 },
22 #define NANNOSW  1
23         { "noannotate", 0 },
24 #define EDITRSW  2
25         { "editor editor", 0 },
26 #define NEDITSW  3
27         { "noedit", 0 },
28 #define FILTSW  4
29         { "filter filterfile", 0 },
30 #define FORMSW  5
31         { "form formfile", 0 },
32 #define FRMTSW  6
33         { "format", 5 },
34 #define NFRMTSW  7
35         { "noformat", 7 },
36 #define INPLSW  8
37         { "inplace", 0 },
38 #define NINPLSW  9
39         { "noinplace", 0 },
40 #define MIMESW  10
41         { "mime", 0 },
42 #define NMIMESW  11
43         { "nomime", 0 },
44 #define DGSTSW  12
45         { "digest list", 0 },
46 #define ISSUESW  13
47         { "issue number", 0 },
48 #define VOLUMSW  14
49         { "volume number", 0 },
50 #define WHATSW  15
51         { "whatnowproc program", 0 },
52 #define NWHATSW  16
53         { "nowhatnowproc", 0 },
54 #define BITSTUFFSW  17
55         { "dashstuffing", 0 },  /* interface to mhl */
56 #define NBITSTUFFSW  18
57         { "nodashstuffing", 0 },
58 #define VERSIONSW  19
59         { "version", 0 },
60 #define HELPSW  20
61         { "help", 0 },
62 #define FILESW  21
63         { "file file", 4 },  /* interface from msh */
64
65 #ifdef MHE
66 #define BILDSW  22
67         { "build", 5 },  /* interface from mhe */
68 #endif /* MHE */
69
70         { NULL, 0 }
71 };
72
73 static char drft[BUFSIZ];
74
75 static char delim3[] =
76         "\n------------------------------------------------------------\n\n";
77 static char delim4[] = "\n------------------------------\n\n";
78
79
80 static struct msgs *mp = NULL;  /* used a lot */
81
82
83 /*
84 ** static prototypes
85 */
86 static void mhl_draft(int, char *, int, int, char *, char *, int);
87 static void copy_draft(int, char *, char *, int, int, int);
88 static void add_forw_hdr(char *);
89 static int build_form(char *, char *, int, int);
90
91
92 int
93 main(int argc, char **argv)
94 {
95         int msgp = 0, anot = 0, inplace = 1, mime = 0;
96         int issue = 0, volume = 0, dashstuff = 0;
97         int nedit = 0, nwhat = 0, in;
98         int out, msgnum;
99         char *cp, *cwd, *maildir;
100         char *digest = NULL, *ed = NULL;
101         char *file = NULL, *filter = NULL, *folder = NULL;
102         char *form = NULL, buf[BUFSIZ], value[10];
103         char **argp, **arguments, *msgs[MAXARGS];
104
105 #ifdef MHE
106         int buildsw = 0;
107 #endif /* MHE */
108
109 #ifdef LOCALE
110         setlocale(LC_ALL, "");
111 #endif
112         invo_name = mhbasename(argv[0]);
113
114         /* read user profile/context */
115         context_read();
116
117         arguments = getarguments(invo_name, argc, argv, 1);
118         argp = arguments;
119
120         while ((cp = *argp++)) {
121                 if (*cp == '-') {
122                         switch (smatch(++cp, switches)) {
123                         case AMBIGSW:
124                                 ambigsw(cp, switches);
125                                 done(1);
126                         case UNKWNSW:
127                                 adios(NULL, "-%s unknown", cp);
128
129                         case HELPSW:
130                                 snprintf(buf, sizeof(buf), "%s [+folder] [msgs] [switches]", invo_name);
131                                 print_help(buf, switches, 1);
132                                 done(1);
133                         case VERSIONSW:
134                                 print_version(invo_name);
135                                 done(1);
136
137                         case ANNOSW:
138                                 anot++;
139                                 continue;
140                         case NANNOSW:
141                                 anot = 0;
142                                 continue;
143
144                         case EDITRSW:
145                                 if (!(ed = *argp++) || *ed == '-')
146                                         adios(NULL, "missing argument to %s",
147                                                         argp[-2]);
148                                 nedit = 0;
149                                 continue;
150                         case NEDITSW:
151                                 nedit++;
152                                 continue;
153
154                         case WHATSW:
155                                 if (!(whatnowproc = *argp++) ||
156                                                 *whatnowproc == '-')
157                                         adios(NULL, "missing argument to %s",
158                                                         argp[-2]);
159                                 nwhat = 0;
160                                 continue;
161 #ifdef MHE
162                         case BILDSW:
163                                 buildsw++;  /* fall... */
164 #endif /* MHE */
165                         case NWHATSW:
166                                 nwhat++;
167                                 continue;
168
169                         case FILESW:
170                                 if (file)
171                                         adios(NULL, "only one file at a time!");
172                                 if (!(cp = *argp++) || *cp == '-')
173                                         adios(NULL, "missing argument to %s",
174                                                         argp[-2]);
175                                 file = getcpy(expanddir(cp));
176                                 continue;
177                         case FILTSW:
178                                 if (!(cp = *argp++) || *cp == '-')
179                                         adios(NULL, "missing argument to %s",
180                                                         argp[-2]);
181                                 filter = getcpy(etcpath(cp));
182                                 mime = 0;
183                                 continue;
184                         case FORMSW:
185                                 if (!(form = *argp++) || *form == '-')
186                                         adios(NULL, "missing argument to %s",
187                                                         argp[-2]);
188                                 continue;
189
190                         case FRMTSW:
191                                 filter = getcpy(etcpath(mhlforward));
192                                 continue;
193                         case NFRMTSW:
194                                 filter = NULL;
195                                 continue;
196
197                         case INPLSW:
198                                 inplace++;
199                                 continue;
200                         case NINPLSW:
201                                 inplace = 0;
202                                 continue;
203
204                         case MIMESW:
205                                 mime++;
206                                 filter = NULL;
207                                 continue;
208                         case NMIMESW:
209                                 mime = 0;
210                                 continue;
211
212                         case DGSTSW:
213                                 if (!(digest = *argp++) || *digest == '-')
214                                         adios(NULL, "missing argument to %s",
215                                                         argp[-2]);
216                                 mime = 0;
217                                 continue;
218                         case ISSUESW:
219                                 if (!(cp = *argp++) || *cp == '-')
220                                         adios(NULL, "missing argument to %s",
221                                                         argp[-2]);
222                                 if ((issue = atoi(cp)) < 1)
223                                         adios(NULL, "bad argument %s %s",
224                                                         argp[-2], cp);
225                                 continue;
226                         case VOLUMSW:
227                                 if (!(cp = *argp++) || *cp == '-')
228                                         adios(NULL, "missing argument to %s",
229                                                         argp[-2]);
230                                 if ((volume = atoi(cp)) < 1)
231                                         adios(NULL, "bad argument %s %s",
232                                                         argp[-2], cp);
233                                 continue;
234
235                         case BITSTUFFSW:
236                                 dashstuff = 1;  /* trinary logic */
237                                 continue;
238                         case NBITSTUFFSW:
239                                 dashstuff = -1;  /* trinary logic */
240                                 continue;
241                         }
242                 }
243                 if (*cp == '+' || *cp == '@') {
244                         if (folder)
245                                 adios(NULL, "only one folder at a time!");
246                         else
247                                 folder = getcpy(expandfol(cp));
248                 } else {
249                         msgs[msgp++] = cp;
250                 }
251         }
252
253         cwd = getcpy(pwd());
254
255         if (file && (msgp || folder))
256                 adios(NULL, "can't mix files and folders/msgs");
257
258 #ifdef MHE
259         strncpy(drft, buildsw ? toabsdir("draft")
260                 : m_draft(seq_beyond), sizeof(drft));
261 #else
262         strncpy(drft, m_draft(seq_beyond), sizeof(drft));
263 #endif /* MHE */
264
265         if (file) {
266                 /*
267                 ** Forwarding a file.
268                 */
269                 anot = 0;  /* don't want to annotate a file */
270         } else {
271                 /*
272                 ** Forwarding a message.
273                 */
274                 if (!msgp)
275                         msgs[msgp++] = seq_cur;
276                 if (!folder)
277                         folder = getcurfol();
278                 maildir = toabsdir(folder);
279
280                 if (chdir(maildir) == NOTOK)
281                         adios(maildir, "unable to change directory to");
282
283                 /* read folder and create message structure */
284                 if (!(mp = folder_read(folder)))
285                         adios(NULL, "unable to read folder %s", folder);
286
287                 /* check for empty folder */
288                 if (mp->nummsg == 0)
289                         adios(NULL, "no messages in %s", folder);
290
291                 /* parse all the message ranges/sequences and set SELECTED */
292                 for (msgnum = 0; msgnum < msgp; msgnum++)
293                         if (!m_convert(mp, msgs[msgnum]))
294                                 done(1);
295                 seq_setprev(mp);  /* set the previous sequence */
296         }
297
298         if (filter && access(filter, R_OK) == NOTOK)
299                 adios(filter, "unable to read");
300
301         /*
302         ** Open form (component) file.
303         */
304         if (digest) {
305                 if (issue == 0) {
306                         snprintf(buf, sizeof(buf), IFORMAT, digest);
307                         if (volume == 0 && (cp = context_find(buf))
308                                         && ((issue = atoi(cp)) < 0))
309                                 issue = 0;
310                         issue++;
311                 }
312                 if (volume == 0)
313                         snprintf(buf, sizeof(buf), VFORMAT, digest);
314                 if ((cp = context_find(buf)) == NULL ||
315                                 (volume = atoi(cp)) <= 0)
316                         volume = 1;
317                 if (!form)
318                         form = digestcomps;
319                 in = build_form(form, digest, volume, issue);
320         } else
321                 in = open_form(&form, forwcomps);
322
323         if ((out = creat(drft, m_gmprot())) == NOTOK)
324                 adios(drft, "unable to create");
325
326         /*
327         ** copy the components into the draft
328         */
329         cpydata(in, out, form, drft);
330         close(in);
331
332         if (file) {
333                 /* just copy the file into the draft */
334                 if ((in = open(file, O_RDONLY)) == NOTOK)
335                         adios(file, "unable to open");
336                 cpydata(in, out, file, drft);
337                 close(in);
338                 close(out);
339         } else {
340                 /*
341                 ** If filter file is defined, then format the
342                 ** messages into the draft using mhlproc.
343                 */
344                 if (filter) {
345                         mhl_draft(out, digest, volume, issue, drft, filter,
346                                         dashstuff);
347                         close(out);
348                 } else if (mime) {
349                         close(out);
350                         add_forw_hdr(drft);
351                 } else {
352                         copy_draft(out, digest, drft, volume, issue,
353                                         dashstuff);
354                         close(out);
355                 }
356
357                 if (digest) {
358                         snprintf(buf, sizeof(buf), IFORMAT, digest);
359                         snprintf(value, sizeof(value), "%d", issue);
360                         context_replace(buf, getcpy(value));
361                         snprintf(buf, sizeof(buf), VFORMAT, digest);
362                         snprintf(value, sizeof(value), "%d", volume);
363                         context_replace(buf, getcpy(value));
364                 }
365
366                 context_replace(curfolder, folder); /* update current folder */
367                 seq_setcur(mp, mp->lowsel);  /* update current message */
368                 seq_save(mp);  /* synchronize sequences */
369                 context_save();  /* save the context file */
370         }
371
372         if (nwhat)
373                 done(0);
374         what_now(ed, nedit, NOUSE, drft, NULL, 0, mp,
375                 anot ? "Forwarded" : NULL, inplace, cwd);
376         done(1);
377         return 1;
378 }
379
380
381 /*
382 ** Filter the messages you are forwarding, into the
383 ** draft calling the mhlproc, and reading its output
384 ** from a pipe.
385 */
386 static void
387 mhl_draft(int out, char *digest, int volume, int issue,
388         char *file, char *filter, int dashstuff)
389 {
390         pid_t child_id;
391         int i, msgnum, pd[2];
392         char *vec[MAXARGS];
393         char buf1[BUFSIZ];
394         char buf2[BUFSIZ];
395
396         if (pipe(pd) == NOTOK)
397                 adios("pipe", "unable to create");
398
399         vec[0] = mhbasename(mhlproc);
400
401         for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
402                 sleep(5);
403         switch (child_id) {
404         case NOTOK:
405                 adios("fork", "unable to");
406
407         case OK:
408                 close(pd[0]);
409                 dup2(pd[1], 1);
410                 close(pd[1]);
411
412                 i = 1;
413                 vec[i++] = "-forwall";
414                 vec[i++] = "-form";
415                 vec[i++] = filter;
416
417                 if (digest) {
418                         vec[i++] = "-digest";
419                         vec[i++] = digest;
420                         vec[i++] = "-issue";
421                         snprintf(buf1, sizeof(buf1), "%d", issue);
422                         vec[i++] = buf1;
423                         vec[i++] = "-volume";
424                         snprintf(buf2, sizeof(buf2), "%d", volume);
425                         vec[i++] = buf2;
426                 }
427
428                 /*
429                 ** Are we dashstuffing (quoting) the lines that begin
430                 ** with `-'.  We use the mhl default (don't add any
431                 ** flag) unless the user has specified a specific flag.
432                 */
433                 if (dashstuff > 0)
434                         vec[i++] = "-dashstuffing";
435                 else if (dashstuff < 0)
436                         vec[i++] = "-nodashstuffing";
437
438                 if (mp->numsel >= MAXARGS - i)
439                         adios(NULL, "more than %d messages for %s exec",
440                                         MAXARGS - i, vec[0]);
441
442                 /*
443                 ** Now add the message names to filter.  We can only
444                 ** handle about 995 messages (because vec is fixed
445                 ** size), but that should be plenty.
446                 */
447                 for (msgnum = mp->lowsel;
448                                 msgnum <= mp->hghsel && i < sizeof(vec) - 1;
449                                 msgnum++)
450                         if (is_selected(mp, msgnum))
451                                 vec[i++] = getcpy(m_name(msgnum));
452                 vec[i] = NULL;
453
454                 execvp(mhlproc, vec);
455                 fprintf(stderr, "unable to exec ");
456                 perror(mhlproc);
457                 _exit(-1);
458
459         default:
460                 close(pd[1]);
461                 cpydata(pd[0], out, vec[0], file);
462                 close(pd[0]);
463                 pidXwait(child_id, mhlproc);
464                 break;
465         }
466 }
467
468
469 /*
470 ** Copy the messages into the draft.  The messages are
471 ** not filtered through the mhlproc.  Do dashstuffing if
472 ** necessary.
473 */
474 static void
475 copy_draft(int out, char *digest, char *file, int volume, int issue,
476                 int dashstuff)
477 {
478         int fd,i, msgcnt, msgnum;
479         int len, buflen;
480         register char *bp, *msgnam;
481         char buffer[BUFSIZ];
482
483         msgcnt = 1;
484         for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
485                 if (is_selected(mp, msgnum)) {
486                         if (digest) {
487                                 strncpy(buffer, msgnum == mp->lowsel ?
488                                         delim3 : delim4, sizeof(buffer));
489                         } else {
490                                 /* Get buffer ready to go */
491                                 bp = buffer;
492                                 buflen = sizeof(buffer);
493
494                                 strncpy(bp, "\n-------", buflen);
495                                 len = strlen(bp);
496                                 bp += len;
497                                 buflen -= len;
498
499                                 if (msgnum == mp->lowsel) {
500                                         snprintf(bp, buflen, " Forwarded Message%s", mp->numsel > 1 ? "s" : "");
501                                 } else {
502                                         snprintf(bp, buflen, " Message %d", msgcnt);
503                                 }
504                                 len = strlen(bp);
505                                 bp += len;
506                                 buflen -= len;
507
508                                 strncpy(bp, "\n\n", buflen);
509                         }
510                         write(out, buffer, strlen(buffer));
511
512                         if ((fd = open(msgnam = m_name (msgnum), O_RDONLY))
513                                         == NOTOK) {
514                                 admonish(msgnam, "unable to read message");
515                                 continue;
516                         }
517
518                         /*
519                         ** Copy the message.  Add RFC934 quoting (dashstuffing)
520                         ** unless given the -nodashstuffing flag.
521                         */
522                         if (dashstuff >= 0)
523                                 cpydgst(fd, out, msgnam, file);
524                         else
525                                 cpydata(fd, out, msgnam, file);
526
527                         close(fd);
528                         msgcnt++;
529                 }
530         }
531
532         if (digest) {
533                 strncpy(buffer, delim4, sizeof(buffer));
534         } else {
535                 snprintf(buffer, sizeof(buffer),
536                                 "\n------- End of Forwarded Message%s\n\n",
537                                 mp->numsel > 1 ? "s" : "");
538         }
539         write(out, buffer, strlen(buffer));
540
541         if (digest) {
542                 snprintf(buffer, sizeof(buffer),
543                                 "End of %s Digest [Volume %d Issue %d]\n",
544                                 digest, volume, issue);
545                 i = strlen(buffer);
546                 for (bp = buffer + i; i > 1; i--)
547                         *bp++ = '*';
548                 *bp++ = '\n';
549                 *bp = 0;
550                 write(out, buffer, strlen(buffer));
551         }
552 }
553
554
555 /*
556 ** Create an attachment header for the to be forward messages.
557 */
558 static void
559 add_forw_hdr(char *draft)
560 {
561         int msgnum;
562         char buffer[BUFSIZ];
563
564         snprintf(buffer, sizeof(buffer), "+%s", mp->foldpath);
565         for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
566                 if (!is_selected(mp, msgnum)) {
567                         continue;
568                 }
569                 /* TODO: improve the code */
570                 strncat(buffer, " ", sizeof(buffer)-strlen(buffer)-1);
571                 strncat(buffer, m_name(msgnum),
572                                 sizeof(buffer)-strlen(buffer)-1);
573         }
574         annotate(draft, attach_hdr, buffer, 1, 0, -2, 1);
575 }
576
577
578 static int
579 build_form(char *form, char *digest, int volume, int issue)
580 {
581         int in;
582         int fmtsize;
583         register char *nfs;
584         char *line, tmpfil[BUFSIZ];
585         FILE *tmp;
586         register struct comp *cptr;
587         struct format *fmt;
588         int dat[5];
589         char *cp = NULL;
590
591         /* Get new format string */
592         nfs = new_fs(form, NULL, NULL);
593         fmtsize = strlen(nfs) + 256;
594
595         /* Compile format string */
596         fmt_compile(nfs, &fmt);
597
598         FINDCOMP(cptr, "digest");
599         if (cptr)
600                 cptr->c_text = digest;
601         FINDCOMP(cptr, "date");
602         if (cptr)
603                 cptr->c_text = getcpy(dtimenow(0));
604
605         dat[0] = issue;
606         dat[1] = volume;
607         dat[2] = 0;
608         dat[3] = fmtsize;
609         dat[4] = 0;
610
611         cp = m_mktemp2(NULL, invo_name, NULL, &tmp);
612         if (cp == NULL) adios("forw", "unable to create temporary file");
613         strncpy(tmpfil, cp, sizeof(tmpfil));
614         unlink(tmpfil);
615         if ((in = dup(fileno(tmp))) == NOTOK)
616                 adios("dup", "unable to");
617
618         line = mh_xmalloc((unsigned) fmtsize);
619         fmt_scan(fmt, line, fmtsize, dat);
620         fputs(line, tmp);
621         free(line);
622         if (fclose(tmp))
623                 adios(tmpfil, "error writing");
624
625         lseek(in, (off_t) 0, SEEK_SET);
626         return in;
627 }