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