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