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