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