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