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