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