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