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