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