Use sysexits.h for better exit-codes
[mmh] / uip / mhstore.c
1 /*
2 ** mhstore.c -- store the contents of MIME 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/signals.h>
12 #include <errno.h>
13 #include <signal.h>
14 #include <h/tws.h>
15 #include <h/mime.h>
16 #include <h/mhparse.h>
17 #include <h/utils.h>
18 #include <unistd.h>
19 #include <sys/stat.h>
20 #include <locale.h>
21 #include <sysexits.h>
22
23 static struct swit switches[] = {
24 #define AUTOSW  0
25         { "auto", 0 },
26 #define NAUTOSW  1
27         { "noauto", 2 },
28 #define FILESW  2  /* interface from show */
29         { "file file", 0 },
30 #define PARTSW  3
31         { "part number", 0 },
32 #define TYPESW  4
33         { "type content", 0 },
34 #define VERSIONSW  5
35         { "Version", 0 },
36 #define HELPSW  6
37         { "help", 0 },
38 #define DEBUGSW  7
39         { "debug", -5 },
40         { NULL, 0 }
41 };
42
43
44 /* mhparse.c */
45 extern char *tmp;  /* directory to place temp files */
46
47 /* mhmisc.c */
48 extern int npart;
49 extern int ntype;
50 extern char *parts[NPARTS + 1];
51 extern char *types[NTYPES + 1];
52 extern int userrs;
53
54 int debugsw = 0;
55
56 #define quitser pipeser
57
58 /* mhparse.c */
59 CT parse_mime(char *);
60
61 /* mhmisc.c */
62 int part_ok(CT, int);
63 int type_ok(CT, int);
64 void set_endian(void);
65 void flush_errors(void);
66
67 /* mhfree.c */
68 void free_content(CT);
69 extern CT *cts;  /* The list of top-level contents to display */
70 void freects_done();
71
72 /*
73 ** static prototypes
74 */
75 static void pipeser(int);
76
77 int autosw = 1;
78
79 /*
80 ** Cache of current directory.  This must be
81 ** set before these routines are called.
82 */
83 char *cwd;
84
85 /*
86 ** The directory in which to store the contents.
87 */
88 static char *dir;
89
90 /*
91 ** Type for a compare function for qsort.  This keeps
92 ** the compiler happy.
93 */
94 typedef int (*qsort_comp) (const void *, const void *);
95
96
97 /* mhmisc.c */
98 int part_ok(CT, int);
99 int type_ok(CT, int);
100 int make_intermediates(char *);
101 void flush_errors(void);
102
103 /* mhshowsbr.c */
104 int show_content_aux(CT, int, char *, char *);
105
106 /*
107 ** static prototypes
108 */
109 static void store_single_message(CT);
110 static int store_switch(CT);
111 static int store_generic(CT);
112 static int store_multi(CT);
113 static int store_partial(CT);
114 static int store_external(CT);
115 static int ct_compar(CT *, CT *);
116 static int store_content(CT, CT);
117 static int output_content_file(CT, int);
118 static int output_content_folder(char *, char *);
119 static int parse_format_string(CT, char *, char *, int, char *);
120 static int copy_some_headers(FILE *, CT);
121 static void store_all_messages(CT *);
122
123
124 int
125 main(int argc, char **argv)
126 {
127         int msgnum;
128         char *cp, *file = NULL, *folder = NULL;
129         char *maildir, buf[100], **argp;
130         char **arguments;
131         struct msgs_array msgs = { 0, 0, NULL };
132         struct msgs *mp = NULL;
133         CT ct, *ctp;
134         FILE *fp;
135
136         if (atexit(freects_done) != 0) {
137                 adios(EX_OSERR, NULL, "atexit failed");
138         }
139
140         setlocale(LC_ALL, "");
141         invo_name = mhbasename(argv[0]);
142
143         /* read user profile/context */
144         context_read();
145
146         arguments = getarguments(invo_name, argc, argv, 1);
147         argp = arguments;
148
149         /*
150         ** Parse arguments
151         */
152         while ((cp = *argp++)) {
153                 if (*cp == '-') {
154                         switch (smatch(++cp, switches)) {
155                         case AMBIGSW:
156                                 ambigsw(cp, switches);
157                                 exit(EX_USAGE);
158                         case UNKWNSW:
159                                 adios(EX_USAGE, NULL, "-%s unknown", cp);
160
161                         case HELPSW:
162                                 snprintf(buf, sizeof(buf), "%s [+folder] [msgs] [switches]", invo_name);
163                                 print_help(buf, switches, 1);
164                                 exit(argc == 2 ? EX_OK : EX_USAGE);
165                         case VERSIONSW:
166                                 print_version(invo_name);
167                                 exit(argc == 2 ? EX_OK : EX_USAGE);
168
169                         case AUTOSW:
170                                 autosw++;
171                                 continue;
172                         case NAUTOSW:
173                                 autosw = 0;
174                                 continue;
175
176                         case PARTSW:
177                                 if (!(cp = *argp++) || *cp == '-')
178                                         adios(EX_USAGE, NULL, "missing argument to %s",
179                                                         argp[-2]);
180                                 if (npart >= NPARTS)
181                                         adios(EX_USAGE, NULL, "too many parts (starting with %s), %d max", cp, NPARTS);
182                                 parts[npart++] = cp;
183                                 continue;
184
185                         case TYPESW:
186                                 if (!(cp = *argp++) || *cp == '-')
187                                         adios(EX_USAGE, NULL, "missing argument to %s",
188                                                         argp[-2]);
189                                 if (ntype >= NTYPES)
190                                         adios(EX_USAGE, NULL, "too many types (starting with %s), %d max", cp, NTYPES);
191                                 types[ntype++] = cp;
192                                 continue;
193
194                         case FILESW:
195                                 if (!(cp = *argp++) || (*cp == '-' && cp[1]))
196                                         adios(EX_USAGE, NULL, "missing argument to %s",
197                                                         argp[-2]);
198                                 file = *cp == '-' ? cp : getcpy(expanddir(cp));
199                                 continue;
200
201                         case DEBUGSW:
202                                 debugsw = 1;
203                                 continue;
204                         }
205                 }
206                 if (*cp == '+' || *cp == '@') {
207                         if (folder)
208                                 adios(EX_USAGE, NULL, "only one folder at a time!");
209                         else
210                                 folder = getcpy(expandfol(cp));
211                 } else
212                         app_msgarg(&msgs, cp);
213         }
214
215         /* null terminate the list of acceptable parts/types */
216         parts[npart] = NULL;
217         types[ntype] = NULL;
218
219         set_endian();
220
221         /*
222         ** Check if we've specified an additional profile
223         */
224         if ((cp = getenv("MHSTORE"))) {
225                 if ((fp = fopen(cp, "r"))) {
226                         readconfig((struct node **) 0, fp, cp, 0);
227                         fclose(fp);
228                 } else {
229                         admonish("", "unable to read $MHSTORE profile (%s)",
230                                         cp);
231                 }
232         }
233
234         /*
235         ** Read the standard profile setup
236         */
237         if ((fp = fopen(cp = etcpath("mhn.defaults"), "r"))) {
238                 readconfig((struct node **) 0, fp, cp, 0);
239                 fclose(fp);
240         }
241
242         /*
243         ** Cache the current directory before we do any chdirs()'s.
244         */
245         cwd = getcpy(pwd());
246
247         /*
248         ** Check for storage directory.  If specified,
249         ** then store temporary files there.  Else we
250         ** store them in standard nmh directory.
251         */
252         if ((cp = context_find(nmhstorage)) && *cp)
253                 tmp = concat(cp, "/", invo_name, NULL);
254         else
255                 tmp = getcpy(toabsdir(invo_name));
256
257         if (file && msgs.size)
258                 adios(EX_USAGE, NULL, "cannot specify msg and file at same time!");
259
260         /*
261         ** check if message is coming from file
262         */
263         if (file) {
264                 if (!(cts = (CT *) calloc((size_t) 2, sizeof(*cts))))
265                         adios(EX_OSERR, NULL, "out of memory");
266                 ctp = cts;
267
268                 if ((ct = parse_mime(file)))
269                         *ctp++ = ct;
270         } else {
271                 /*
272                 ** message(s) are coming from a folder
273                 */
274                 if (!msgs.size)
275                         app_msgarg(&msgs, seq_cur);
276                 if (!folder)
277                         folder = getcurfol();
278                 maildir = toabsdir(folder);
279
280                 if (chdir(maildir) == NOTOK)
281                         adios(EX_OSERR, maildir, "unable to change directory to");
282
283                 /* read folder and create message structure */
284                 if (!(mp = folder_read(folder)))
285                         adios(EX_IOERR, NULL, "unable to read folder %s", folder);
286
287                 /* check for empty folder */
288                 if (mp->nummsg == 0)
289                         adios(EX_DATAERR, NULL, "no messages in %s", folder);
290
291                 /* parse all the message ranges/sequences and set SELECTED */
292                 for (msgnum = 0; msgnum < msgs.size; msgnum++)
293                         if (!m_convert(mp, msgs.msgs[msgnum]))
294                                 exit(EX_USAGE);
295                 seq_setprev(mp);  /* set the previous-sequence */
296
297                 if (!(cts = (CT *) calloc((size_t) (mp->numsel + 1),
298                                 sizeof(*cts))))
299                         adios(EX_OSERR, NULL, "out of memory");
300                 ctp = cts;
301
302                 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
303                         if (is_selected(mp, msgnum)) {
304                                 char *msgnam;
305
306                                 msgnam = m_name(msgnum);
307                                 if ((ct = parse_mime(msgnam)))
308                                         *ctp++ = ct;
309                         }
310                 }
311         }
312
313         if (!*cts)
314                 exit(EX_SOFTWARE);
315
316         userrs = 1;
317         SIGNAL(SIGQUIT, quitser);
318         SIGNAL(SIGPIPE, pipeser);
319
320         /*
321         ** Get the associated umask for the relevant contents.
322         */
323         for (ctp = cts; *ctp; ctp++) {
324                 struct stat st;
325
326                 ct = *ctp;
327                 if (type_ok(ct, 1) && !ct->c_umask) {
328                         if (stat(ct->c_file, &st) != NOTOK)
329                                 ct->c_umask = ~(st.st_mode & 0777);
330                         else
331                                 ct->c_umask = ~m_gmprot();
332                 }
333         }
334
335         /*
336         ** Store the message content
337         */
338         store_all_messages(cts);
339
340         /* Now free all the structures for the content */
341         for (ctp = cts; *ctp; ctp++)
342                 free_content(*ctp);
343
344         free((char *) cts);
345         cts = NULL;
346
347         /* If reading from a folder, do some updating */
348         if (mp) {
349                 context_replace(curfolder, folder); /* update current folder */
350                 seq_setcur(mp, mp->hghsel);  /* update current message */
351                 seq_save(mp);  /* synchronize sequences  */
352                 context_save();  /* save the context file  */
353         }
354
355         return 0;
356 }
357
358
359 static void
360 pipeser(int i)
361 {
362         if (i == SIGQUIT) {
363                 unlink("core");
364                 fflush(stdout);
365                 fprintf(stderr, "\n");
366                 fflush(stderr);
367                 exit(EX_TEMPFAIL);
368         }
369
370         exit(EX_IOERR);
371         /* NOTREACHED */
372 }
373
374
375 /*
376 ** Main entry point to store content from a collection of messages.
377 */
378 static void
379 store_all_messages(CT *cts)
380 {
381         CT ct, *ctp;
382         char *cp;
383
384         /*
385         ** Check for the directory in which to
386         ** store any contents.
387         */
388         if ((cp = context_find(nmhstorage)) && *cp)
389                 dir = getcpy(cp);
390         else
391                 dir = getcpy(cwd);
392
393         for (ctp = cts; *ctp; ctp++) {
394                 ct = *ctp;
395                 store_single_message(ct);
396         }
397
398         flush_errors();
399 }
400
401
402 /*
403 ** Entry point to store the content
404 ** in a (single) message
405 */
406
407 static void
408 store_single_message(CT ct)
409 {
410         if (type_ok(ct, 1)) {
411                 umask(ct->c_umask);
412                 store_switch(ct);
413                 if (ct->c_fp) {
414                         fclose(ct->c_fp);
415                         ct->c_fp = NULL;
416                 }
417                 if (ct->c_ceclosefnx)
418                         (*ct->c_ceclosefnx) (ct);
419         }
420 }
421
422
423 /*
424 ** Switching routine to store different content types
425 */
426
427 static int
428 store_switch(CT ct)
429 {
430         switch (ct->c_type) {
431         case CT_MULTIPART:
432                 return store_multi(ct);
433                 break;
434
435         case CT_MESSAGE:
436                 switch (ct->c_subtype) {
437                 case MESSAGE_PARTIAL:
438                         return store_partial(ct);
439                         break;
440
441                 case MESSAGE_EXTERNAL:
442                         return store_external(ct);
443
444                 case MESSAGE_RFC822:
445                 default:
446                         return store_generic(ct);
447                         break;
448                 }
449                 break;
450
451         case CT_APPLICATION:
452         case CT_TEXT:
453         case CT_AUDIO:
454         case CT_IMAGE:
455         case CT_VIDEO:
456                 return store_generic(ct);
457                 break;
458
459         default:
460                 adios(EX_DATAERR, NULL, "unknown content type %d", ct->c_type);
461                 break;
462         }
463
464         return OK;  /* NOT REACHED */
465 }
466
467
468 /*
469 ** Generic routine to store a MIME content.
470 ** (application, audio, video, image, text, message/rfc922)
471 */
472 static int
473 store_generic(CT ct)
474 {
475         char **ap, **vp, *cp;
476         CI ci = &ct->c_ctinfo;
477
478         /*
479         ** Check if the content specifies a filename in its MIME parameters.
480         ** Don't bother with this for type "message"
481         ** (only the "message" subtype "rfc822" will use store_generic).
482         */
483         if (autosw && ct->c_type != CT_MESSAGE) {
484                 /*
485                 ** Check the attribute/value pairs, for the attribute "name".
486                 ** If found, take the basename, do a few sanity checks and
487                 ** copy the value into c_storeproc.
488                 */
489                 for (ap = ci->ci_attrs, vp = ci->ci_values; *ap; ap++,vp++) {
490                         if (mh_strcasecmp(*ap, "name")!=0) {
491                                 continue;
492                         }
493                         cp = mhbasename(*vp);
494                         if (*cp && *cp!='.' && *cp!='|' && *cp!='!' &&
495                                         !strchr(cp, '%')) {
496                                 /* filename looks good: use it */
497                                 ct->c_storeproc = getcpy(cp);
498                         }
499                         break;
500                 }
501         }
502
503         return store_content(ct, NULL);
504 }
505
506
507 /*
508 ** Store the content of a multipart message
509 */
510
511 static int
512 store_multi(CT ct)
513 {
514         int result;
515         struct multipart *m = (struct multipart *) ct->c_ctparams;
516         struct part *part;
517
518         result = NOTOK;
519         for (part = m->mp_parts; part; part = part->mp_next) {
520                 CT  p = part->mp_part;
521
522                 if (part_ok(p, 1) && type_ok(p, 1)) {
523                         result = store_switch(p);
524                         if (result == OK && ct->c_subtype == MULTI_ALTERNATE)
525                                 break;
526                 }
527         }
528
529         return result;
530 }
531
532
533 /*
534 ** Reassemble and store the contents of a collection
535 ** of messages of type "message/partial".
536 */
537
538 static int
539 store_partial(CT ct)
540 {
541         int cur, hi, i;
542         CT p, *ctp, *ctq;
543         CT *base;
544         struct partial *pm, *qm;
545
546         qm = (struct partial *) ct->c_ctparams;
547         if (qm->pm_stored)
548                 return OK;
549
550         hi = i = 0;
551         for (ctp = cts; *ctp; ctp++) {
552                 p = *ctp;
553                 if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) {
554                         pm = (struct partial *) p->c_ctparams;
555                         if (!pm->pm_stored &&
556                                         strcmp(qm->pm_partid, pm->pm_partid)
557                                         == 0) {
558                                 pm->pm_marked = pm->pm_partno;
559                                 if (pm->pm_maxno)
560                                         hi = pm->pm_maxno;
561                                 pm->pm_stored = 1;
562                                 i++;
563                         } else
564                                 pm->pm_marked = 0;
565                 }
566         }
567
568         if (hi == 0) {
569                 advise(NULL, "missing (at least) last part of multipart message");
570                 return NOTOK;
571         }
572
573         if ((base = (CT *) calloc((size_t) (i + 1), sizeof(*base))) == NULL)
574                 adios(EX_OSERR, NULL, "out of memory");
575
576         ctq = base;
577         for (ctp = cts; *ctp; ctp++) {
578                 p = *ctp;
579                 if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) {
580                         pm = (struct partial *) p->c_ctparams;
581                         if (pm->pm_marked)
582                                 *ctq++ = p;
583                 }
584         }
585         *ctq = NULL;
586
587         if (i > 1)
588                 qsort((char *) base, i, sizeof(*base), (qsort_comp) ct_compar);
589
590         cur = 1;
591         for (ctq = base; *ctq; ctq++) {
592                 p = *ctq;
593                 pm = (struct partial *) p->c_ctparams;
594                 if (pm->pm_marked != cur) {
595                         if (pm->pm_marked == cur - 1) {
596                                 admonish(NULL, "duplicate part %d of %d part multipart message", pm->pm_marked, hi);
597                                 continue;
598                         }
599
600 missing_part:
601                         advise (NULL, "missing %spart %d of %d part multipart message", cur != hi ? "(at least) " : "", cur, hi);
602                         goto losing;
603                 } else
604                         cur++;
605         }
606         if (hi != --cur) {
607                 cur = hi;
608                 goto missing_part;
609         }
610
611         /*
612         ** Now cycle through the sorted list of messages of type
613         ** "message/partial" and save/append them to a file.
614         */
615
616         ctq = base;
617         ct = *ctq++;
618         if (store_content(ct, NULL) == NOTOK) {
619 losing:
620                 free((char *) base);
621                 return NOTOK;
622         }
623
624         for (; *ctq; ctq++) {
625                 p = *ctq;
626                 if (store_content(p, ct) == NOTOK)
627                         goto losing;
628         }
629
630         free((char *) base);
631         return OK;
632 }
633
634
635 /*
636 ** Show how to retrieve content of type "message/external".
637 */
638 static int
639 store_external(CT ct)
640 {
641         char **ap, **ep;
642         char *msg;
643         FILE *fp;
644         char buf[BUFSIZ];
645
646         msg = add("You need to fetch the contents yourself:", NULL);
647         ap = ct->c_ctinfo.ci_attrs;
648         ep = ct->c_ctinfo.ci_values;
649         for (; *ap; ap++, ep++) {
650                 msg = add(concat("\n\t", *ap, ": ", *ep, NULL), msg);
651         }
652         if (!(fp = fopen(ct->c_file, "r"))) {
653                 adios(EX_IOERR, ct->c_file, "unable to open");
654         }
655         fseek(fp, ct->c_begin, SEEK_SET);
656         while (!feof(fp) && ftell(fp) < ct->c_end) {
657                 if (!fgets(buf, sizeof buf, fp)) {
658                         adios(EX_IOERR, ct->c_file, "unable to read");
659                 }
660                 *strchr(buf, '\n') = '\0';
661                 msg = add(concat("\n\t", buf, NULL), msg);
662         }
663         fclose(fp);
664         advise(NULL, msg);
665         return OK;
666 }
667
668
669 /*
670 ** Compare the numbering from two different
671 ** message/partials (needed for sorting).
672 */
673
674 static int
675 ct_compar(CT *a, CT *b)
676 {
677         struct partial *am = (struct partial *) ((*a)->c_ctparams);
678         struct partial *bm = (struct partial *) ((*b)->c_ctparams);
679
680         return (am->pm_marked - bm->pm_marked);
681 }
682
683
684 /*
685 ** Store contents of a message or message part to
686 ** a folder, a file, the standard output, or pass
687 ** the contents to a command.
688 **
689 ** If the current content to be saved is a followup part
690 ** to a collection of messages of type "message/partial",
691 ** then field "p" is a pointer to the Content structure
692 ** to the first message/partial in the group.
693 */
694
695 static int
696 store_content(CT ct, CT p)
697 {
698         int appending = 0, msgnum = 0;
699         int is_partial = 0, first_partial = 0;
700         int last_partial = 0;
701         char *cp, buffer[BUFSIZ];
702
703         /*
704         ** Do special processing for messages of
705         ** type "message/partial".
706         **
707         ** We first check if this content is of type
708         ** "message/partial".  If it is, then we need to check
709         ** whether it is the first and/or last in the group.
710         **
711         ** Then if "p" is a valid pointer, it points to the Content
712         ** structure of the first partial in the group.  So we copy
713         ** the file name and/or folder name from that message.  In
714         ** this case, we also note that we will be appending.
715         */
716         if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) {
717                 struct partial *pm = (struct partial *) ct->c_ctparams;
718
719                 /* Yep, it's a message/partial */
720                 is_partial = 1;
721
722                 /* But is it the first and/or last in the collection? */
723                 if (pm->pm_partno == 1)
724                         first_partial = 1;
725                 if (pm->pm_maxno && pm->pm_partno == pm->pm_maxno)
726                         last_partial = 1;
727
728                 /*
729                 ** If "p" is a valid pointer, then it points to the
730                 ** Content structure for the first message in the group.
731                 ** So we just copy the filename or foldername information
732                 ** from the previous iteration of this function.
733                 */
734                 if (p) {
735                         appending = 1;
736                         ct->c_storage = getcpy(p->c_storage);
737
738                         /* record the folder name */
739                         if (p->c_folder) {
740                                 ct->c_folder = getcpy(p->c_folder);
741                         }
742                         goto got_filename;
743                 }
744         }
745
746         /*
747         ** Get storage formatting string.
748         **
749         ** 1) If we have storeproc defined, then use that
750         ** 2) Else check for a mhstore-store-<type>/<subtype> entry
751         ** 3) Else check for a mhstore-store-<type> entry
752         ** 4) Else if content is "message", use "+" (current folder)
753         ** 5) Else use string "%m%P.%s".
754         */
755         if (!(cp = ct->c_storeproc) || !*cp) {
756                 CI ci = &ct->c_ctinfo;
757
758                 snprintf(buffer, sizeof(buffer), "%s-store-%s/%s",
759                                 invo_name, ci->ci_type, ci->ci_subtype);
760                 if ((cp = context_find(buffer)) == NULL || *cp == '\0') {
761                         snprintf(buffer, sizeof(buffer), "%s-store-%s",
762                                         invo_name, ci->ci_type);
763                         if ((cp = context_find(buffer)) == NULL ||
764                                         *cp == '\0') {
765                                 cp = ct->c_type == CT_MESSAGE ?
766                                                 "+" : "%m%P.%s";
767                         }
768                 }
769         }
770
771         /*
772         ** Check the beginning of storage formatting string
773         ** to see if we are saving content to a folder.
774         */
775         if (*cp == '+' || *cp == '@') {
776                 char *tmpfilenam, *folder;
777
778                 /* Store content in temporary file for now */
779                 tmpfilenam = m_mktemp(invo_name, NULL, NULL);
780                 ct->c_storage = getcpy(tmpfilenam);
781
782                 /* Get the folder name */
783                 if (cp[1])
784                         folder = getcpy(expandfol(cp));
785                 else
786                         folder = getcurfol();
787
788                 /* Check if folder exists */
789                 create_folder(toabsdir(folder), 0, exit);
790
791                 /* Record the folder name */
792                 ct->c_folder = getcpy(folder);
793
794                 if (cp[1])
795                         free(folder);
796
797                 goto got_filename;
798         }
799
800         /*
801         ** Parse and expand the storage formatting string
802         ** in `cp' into `buffer'.
803         */
804         parse_format_string(ct, cp, buffer, sizeof(buffer), dir);
805
806         /*
807         ** If formatting begins with '|' or '!', then pass
808         ** content to standard input of a command and return.
809         */
810         if (buffer[0] == '|' || buffer[0] == '!')
811                 return show_content_aux(ct, 0, buffer + 1, dir);
812
813         /* record the filename */
814         ct->c_storage = getcpy(buffer);
815
816 got_filename:
817         /* flush the output stream */
818         fflush(stdout);
819
820         /* Now save or append the content to a file */
821         if (output_content_file(ct, appending) == NOTOK)
822                 return NOTOK;
823
824         /*
825         ** If necessary, link the file into a folder and remove
826         ** the temporary file.  If this message is a partial,
827         ** then only do this if it is the last one in the group.
828         */
829         if (ct->c_folder && (!is_partial || last_partial)) {
830                 msgnum = output_content_folder(ct->c_folder, ct->c_storage);
831                 unlink(ct->c_storage);
832                 if (msgnum == NOTOK)
833                         return NOTOK;
834         }
835
836         /*
837         ** Now print out the name/number of the message
838         ** that we are storing.
839         */
840         if (is_partial) {
841                 if (first_partial)
842                         fprintf(stderr, "reassembling partials ");
843                 if (last_partial)
844                         fprintf(stderr, "%s", ct->c_file);
845                 else
846                         fprintf(stderr, "%s,", ct->c_file);
847         } else {
848                 fprintf(stderr, "storing message %s", ct->c_file);
849                 if (ct->c_partno)
850                         fprintf(stderr, " part %s", ct->c_partno);
851         }
852
853         /*
854         ** Unless we are in the "middle" of group of message/partials,
855         ** we now print the name of the file, folder, and/or message
856         ** to which we are storing the content.
857         */
858         if (!is_partial || last_partial) {
859                 if (ct->c_folder) {
860                         fprintf(stderr, " to folder %s as message %d\n",
861                                         ct->c_folder, msgnum);
862                 } else if (strcmp(ct->c_storage, "-")==0) {
863                         fprintf(stderr, " to stdout\n");
864                 } else {
865                         int cwdlen;
866
867                         cwdlen = strlen(cwd);
868                         fprintf(stderr, " as file %s\n",
869                                         strncmp(ct->c_storage, cwd,
870                                         cwdlen)!=0 ||
871                                         ct->c_storage[cwdlen] != '/' ?
872                                         ct->c_storage :
873                                         ct->c_storage + cwdlen + 1);
874                 }
875         }
876
877         return OK;
878 }
879
880
881 /*
882 ** Output content to a file
883 */
884
885 static int
886 output_content_file(CT ct, int appending)
887 {
888         int filterstate;
889         char *file, buffer[BUFSIZ];
890         long pos, last;
891         FILE *fp;
892
893         /*
894         ** If the pathname contains directories, make sure
895         ** all of them exist.
896         */
897         if (strchr(ct->c_storage, '/') && make_intermediates(ct->c_storage)
898                         == NOTOK)
899                 return NOTOK;
900
901         if (ct->c_encoding != CE_7BIT) {
902                 int cc, fd;
903
904                 if (!ct->c_ceopenfnx) {
905                         advise(NULL, "don't know how to decode part %s of message %s", ct->c_partno, ct->c_file);
906                         return NOTOK;
907                 }
908
909                 file = appending || strcmp(ct->c_storage, "-")==0 ?
910                                 NULL : ct->c_storage;
911                 if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
912                         return NOTOK;
913                 if (strcmp(file, ct->c_storage)==0) {
914                         (*ct->c_ceclosefnx) (ct);
915                         return OK;
916                 }
917
918                 /*
919                 ** Send to standard output
920                 */
921                 if (strcmp(ct->c_storage, "-")==0) {
922                         int gd;
923
924                         if ((gd = dup(fileno(stdout))) == NOTOK) {
925                                 advise("stdout", "unable to dup");
926 losing:
927                                 (*ct->c_ceclosefnx) (ct);
928                                 return NOTOK;
929                         }
930                         if ((fp = fdopen(gd, appending ? "a" : "w")) == NULL) {
931                                 advise("stdout", "unable to fdopen (%d, \"%s\") from", gd, appending ? "a" : "w");
932                                 close(gd);
933                                 goto losing;
934                         }
935                 } else {
936                         /*
937                         ** Open output file
938                         */
939                         if ((fp = fopen(ct->c_storage, appending ? "a" : "w"))
940                                         == NULL) {
941                                 advise(ct->c_storage, "unable to fopen for %s",
942                                                 appending ?
943                                                 "appending" : "writing");
944                                 goto losing;
945                         }
946                 }
947
948                 /*
949                 ** Filter the header fields of the initial enclosing
950                 ** message/partial into the file.
951                 */
952                 if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) {
953                         struct partial *pm = (struct partial *) ct->c_ctparams;
954
955                         if (pm->pm_partno == 1)
956                                 copy_some_headers(fp, ct);
957                 }
958
959                 for (;;) {
960                         switch (cc = read(fd, buffer, sizeof(buffer))) {
961                         case NOTOK:
962                                 advise(file, "error reading content from");
963                                 break;
964
965                         case OK:
966                                 break;
967
968                         default:
969                                 fwrite(buffer, sizeof(*buffer), cc, fp);
970                                 continue;
971                         }
972                         break;
973                 }
974
975                 (*ct->c_ceclosefnx) (ct);
976
977                 if (cc != NOTOK && fflush(fp))
978                         advise(ct->c_storage, "error writing to");
979
980                 fclose(fp);
981
982                 return (cc != NOTOK ? OK : NOTOK);
983         }
984
985         if (!ct->c_fp && (ct->c_fp = fopen(ct->c_file, "r")) == NULL) {
986                 advise(ct->c_file, "unable to open for reading");
987                 return NOTOK;
988         }
989
990         pos = ct->c_begin;
991         last = ct->c_end;
992         fseek(ct->c_fp, pos, SEEK_SET);
993
994         if (strcmp(ct->c_storage, "-")==0) {
995                 int gd;
996
997                 if ((gd = dup(fileno(stdout))) == NOTOK) {
998                         advise("stdout", "unable to dup");
999                         return NOTOK;
1000                 }
1001                 if ((fp = fdopen(gd, appending ? "a" : "w")) == NULL) {
1002                         advise("stdout", "unable to fdopen (%d, \"%s\") from",
1003                                         gd, appending ? "a" : "w");
1004                         close(gd);
1005                         return NOTOK;
1006                 }
1007         } else {
1008                 if ((fp = fopen(ct->c_storage, appending ? "a" : "w"))
1009                                 == NULL) {
1010                         advise(ct->c_storage, "unable to fopen for %s",
1011                                         appending ? "appending" : "writing");
1012                         return NOTOK;
1013                 }
1014         }
1015
1016         /*
1017         ** Copy a few of the header fields of the initial
1018         ** enclosing message/partial into the file.
1019         */
1020         filterstate = 0;
1021         if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) {
1022                 struct partial *pm = (struct partial *) ct->c_ctparams;
1023
1024                 if (pm->pm_partno == 1) {
1025                         copy_some_headers(fp, ct);
1026                         filterstate = 1;
1027                 }
1028         }
1029
1030         while (fgets(buffer, sizeof(buffer) - 1, ct->c_fp)) {
1031                 if ((pos += strlen(buffer)) > last) {
1032                         int diff;
1033
1034                         diff = strlen(buffer) - (pos - last);
1035                         if (diff >= 0)
1036                                 buffer[diff] = '\0';
1037                 }
1038                 /*
1039                 ** If this is the first content of a group of
1040                 ** message/partial contents, then we only copy a few
1041                 ** of the header fields of the enclosed message.
1042                 */
1043                 if (filterstate) {
1044                         switch (buffer[0]) {
1045                         case ' ':
1046                         case '\t':
1047                                 if (filterstate < 0)
1048                                         buffer[0] = 0;
1049                                 break;
1050
1051                         case '\n':
1052                                 filterstate = 0;
1053                                 break;
1054
1055                         default:
1056                                 if (!uprf(buffer, XXX_FIELD_PRF) && !uprf(buffer, VRSN_FIELD) && !uprf(buffer, "Subject:") && !uprf(buffer, "Message-ID:")) {
1057                                         filterstate = -1;
1058                                         buffer[0] = 0;
1059                                         break;
1060                                 }
1061                                 filterstate = 1;
1062                                 break;
1063                         }
1064                 }
1065                 fputs(buffer, fp);
1066                 if (pos >= last)
1067                         break;
1068         }
1069
1070         if (fflush(fp))
1071                 advise(ct->c_storage, "error writing to");
1072
1073         fclose(fp);
1074         fclose(ct->c_fp);
1075         ct->c_fp = NULL;
1076         return OK;
1077 }
1078
1079
1080 /*
1081 ** Add a file to a folder.
1082 **
1083 ** Return the new message number of the file
1084 ** when added to the folder.  Return -1, if
1085 ** there is an error.
1086 */
1087
1088 static int
1089 output_content_folder(char *folder, char *filename)
1090 {
1091         int msgnum;
1092         struct msgs *mp;
1093
1094         /* Read the folder. */
1095         if ((mp = folder_read(folder))) {
1096                 /* Link file into folder */
1097                 msgnum = folder_addmsg(&mp, filename, 0, 0, 0, 0, NULL);
1098         } else {
1099                 advise(NULL, "unable to read folder %s", folder);
1100                 return NOTOK;
1101         }
1102
1103         /* free folder structure */
1104         folder_free(mp);
1105
1106         /*
1107         ** Return msgnum.  We are relying on the fact that
1108         ** msgnum will be -1, if folder_addmsg() had an error.
1109         */
1110         return msgnum;
1111 }
1112
1113
1114 /*
1115 ** Parse and expand the storage formatting string
1116 ** pointed to by "cp" into "buffer".
1117 */
1118
1119 static int
1120 parse_format_string(CT ct, char *cp, char *buffer, int buflen, char *dir)
1121 {
1122         int len;
1123         char *bp;
1124         CI ci = &ct->c_ctinfo;
1125
1126         /*
1127         ** If storage string is "-", just copy it, and
1128         ** return (send content to standard output).
1129         */
1130         if (cp[0] == '-' && cp[1] == '\0') {
1131                 strncpy(buffer, cp, buflen);
1132                 return 0;
1133         }
1134
1135         bp = buffer;
1136         bp[0] = '\0';
1137
1138         /*
1139         ** If formatting string is a pathname that doesn't
1140         ** begin with '/', then preface the path with the
1141         ** appropriate directory.
1142         */
1143         if (*cp != '/' && *cp != '|' && *cp != '!') {
1144                 snprintf(bp, buflen, "%s/", dir[1] ? dir : "");
1145                 len = strlen(bp);
1146                 bp += len;
1147                 buflen -= len;
1148         }
1149
1150         for (; *cp; cp++) {
1151
1152                 /* We are processing a storage escape */
1153                 if (*cp == '%') {
1154                         switch (*++cp) {
1155                         case 'a':
1156                                 /*
1157                                 ** Insert parameters from Content-Type.
1158                                 ** This is only valid for '|' commands.
1159                                 */
1160                                 if (buffer[0] != '|' && buffer[0] != '!') {
1161                                         *bp++ = *--cp;
1162                                         *bp = '\0';
1163                                         buflen--;
1164                                         continue;
1165                                 } else {
1166                                         char **ap, **ep;
1167                                         char *s = "";
1168
1169                                         for (ap=ci->ci_attrs, ep=ci->ci_values;
1170                                                          *ap; ap++, ep++) {
1171                                                 snprintf(bp, buflen,
1172                                                                 "%s%s=\"%s\"",
1173                                                                 s, *ap, *ep);
1174                                                 len = strlen(bp);
1175                                                 bp += len;
1176                                                 buflen -= len;
1177                                                 s = " ";
1178                                         }
1179                                 }
1180                                 break;
1181
1182                         case 'm':
1183                                 /* insert message number */
1184                                 snprintf(bp, buflen, "%s",
1185                                                 mhbasename(ct->c_file));
1186                                 break;
1187
1188                         case 'P':
1189                                 /* insert part number with leading dot */
1190                                 if (ct->c_partno)
1191                                         snprintf(bp, buflen, ".%s",
1192                                                         ct->c_partno);
1193                                 break;
1194
1195                         case 'p':
1196                                 /* insert part number withouth leading dot */
1197                                 if (ct->c_partno)
1198                                         strncpy(bp, ct->c_partno, buflen);
1199                                 break;
1200
1201                         case 't':
1202                                 /* insert content type */
1203                                 strncpy(bp, ci->ci_type, buflen);
1204                                 break;
1205
1206                         case 's':
1207                                 /* insert content subtype */
1208                                 strncpy(bp, ci->ci_subtype, buflen);
1209                                 break;
1210
1211                         case '%':
1212                                 /* insert the character % */
1213                                 goto raw;
1214
1215                         default:
1216                                 *bp++ = *--cp;
1217                                 *bp = '\0';
1218                                 buflen--;
1219                                 continue;
1220                         }
1221
1222                         /* Advance bp and decrement buflen */
1223                         len = strlen(bp);
1224                         bp += len;
1225                         buflen -= len;
1226
1227                 } else {
1228 raw:
1229                         *bp++ = *cp;
1230                         *bp = '\0';
1231                         buflen--;
1232                 }
1233         }
1234
1235         return 0;
1236 }
1237
1238
1239 /*
1240 ** Copy some of the header fields of the initial message/partial
1241 ** message into the header of the reassembled message.
1242 */
1243
1244 static int
1245 copy_some_headers(FILE *out, CT ct)
1246 {
1247         HF hp;
1248
1249         hp = ct->c_first_hf;  /* start at first header field */
1250
1251         while (hp) {
1252                 /*
1253                 ** A few of the header fields of the enclosing
1254                 ** messages are not copied.
1255                 */
1256                 if (!uprf(hp->name, XXX_FIELD_PRF) &&
1257                                 mh_strcasecmp(hp->name, VRSN_FIELD) &&
1258                                 mh_strcasecmp(hp->name, "Subject") &&
1259                                 mh_strcasecmp(hp->name, "Message-ID"))
1260                         fprintf(out, "%s:%s", hp->name, hp->value);
1261                 hp = hp->next;  /* next header field */
1262         }
1263
1264         return OK;
1265 }