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