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