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