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