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