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