ed36c9603075083f7c61ea7cf326649d907c5183
[mmh] / uip / mhstore.c
1 /*
2 ** mhstore.c -- store the contents of MIME messages
3 **
4 ** This code is Copyright (c) 2002, by the authors of nmh.  See the
5 ** COPYRIGHT file in the root directory of the nmh distribution for
6 ** complete copyright information.
7 */
8
9 #include <h/mh.h>
10 #include <fcntl.h>
11 #include <h/signals.h>
12 #include <errno.h>
13 #include <setjmp.h>
14 #include <signal.h>
15 #include <h/tws.h>
16 #include <h/mime.h>
17 #include <h/mhparse.h>
18 #include <h/utils.h>
19
20 static struct swit switches[] = {
21 #define AUTOSW  0
22         { "auto", 0 },
23 #define NAUTOSW  1
24         { "noauto", 0 },
25 #define FILESW  2  /* interface from show */
26         { "file file", 0 },
27 #define PARTSW  3
28         { "part number", 0 },
29 #define TYPESW  4
30         { "type content", 0 },
31 #define VERSIONSW  5
32         { "version", 0 },
33 #define HELPSW  6
34         { "help", 0 },
35 #define DEBUGSW  7
36         { "debug", -5 },
37         { NULL, 0 }
38 };
39
40
41 /* mhparse.c */
42 extern char *tmp;  /* directory to place temp files */
43
44 /* mhmisc.c */
45 extern int npart;
46 extern int ntype;
47 extern char *parts[NPARTS + 1];
48 extern char *types[NTYPES + 1];
49 extern int userrs;
50
51 int debugsw = 0;
52
53 #define quitser pipeser
54
55 /* mhparse.c */
56 CT parse_mime(char *);
57
58 /* mhmisc.c */
59 int part_ok(CT, int);
60 int type_ok(CT, int);
61 void set_endian(void);
62 void flush_errors(void);
63
64 /* mhfree.c */
65 void free_content(CT);
66 extern CT *cts;  /* The list of top-level contents to display */
67 void freects_done(int) NORETURN;
68
69 /*
70 ** static prototypes
71 */
72 static void pipeser(int);
73
74 int autosw = 0;
75
76 /*
77 ** Cache of current directory.  This must be
78 ** set before these routines are called.
79 */
80 char *cwd;
81
82 /*
83 ** The directory in which to store the contents.
84 */
85 static char *dir;
86
87 /*
88 ** Type for a compare function for qsort.  This keeps
89 ** the compiler happy.
90 */
91 typedef int (*qsort_comp) (const void *, const void *);
92
93
94 /* mhmisc.c */
95 int part_ok(CT, int);
96 int type_ok(CT, int);
97 int make_intermediates(char *);
98 void flush_errors(void);
99
100 /* mhshowsbr.c */
101 int show_content_aux(CT, int, char *, char *);
102
103 /*
104 ** static prototypes
105 */
106 static void store_single_message(CT);
107 static int store_switch(CT);
108 static int store_generic(CT);
109 static int store_application(CT);
110 static int store_multi(CT);
111 static int store_partial(CT);
112 static int store_external(CT);
113 static int ct_compar(CT *, CT *);
114 static int store_content(CT, CT);
115 static int output_content_file(CT, int);
116 static int output_content_folder(char *, char *);
117 static int parse_format_string(CT, char *, char *, int, char *);
118 static void get_storeproc(CT);
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         done=freects_done;
136
137 #ifdef LOCALE
138         setlocale(LC_ALL, "");
139 #endif
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                                 done(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                                 done(1);
164                         case VERSIONSW:
165                                 print_version(invo_name);
166                                 done(1);
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                                 done(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                 done(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         done(0);
355         return 1;
356 }
357
358
359 static void
360 pipeser(int i)
361 {
362         if (i == SIGQUIT) {
363                 unlink("core");
364                 fflush(stdout);
365                 fprintf(stderr, "\n");
366                 fflush(stderr);
367         }
368
369         done(1);
370         /* NOTREACHED */
371 }
372
373
374 /*
375 ** Main entry point to store content from a collection of messages.
376 */
377 static void
378 store_all_messages(CT *cts)
379 {
380         CT ct, *ctp;
381         char *cp;
382
383         /*
384         ** Check for the directory in which to
385         ** store any contents.
386         */
387         if (autosw)
388                 dir = getcpy(cwd);
389         if ((cp = context_find(nmhstorage)) && *cp)
390                 dir = getcpy(cp);
391         else
392                 dir = getcpy(cwd);
393
394         for (ctp = cts; *ctp; ctp++) {
395                 ct = *ctp;
396                 store_single_message(ct);
397         }
398
399         flush_errors();
400 }
401
402
403 /*
404 ** Entry point to store the content
405 ** in a (single) message
406 */
407
408 static void
409 store_single_message(CT ct)
410 {
411         if (type_ok(ct, 1)) {
412                 umask(ct->c_umask);
413                 store_switch(ct);
414                 if (ct->c_fp) {
415                         fclose(ct->c_fp);
416                         ct->c_fp = NULL;
417                 }
418                 if (ct->c_ceclosefnx)
419                         (*ct->c_ceclosefnx) (ct);
420         }
421 }
422
423
424 /*
425 ** Switching routine to store different content types
426 */
427
428 static int
429 store_switch(CT ct)
430 {
431         switch (ct->c_type) {
432         case CT_MULTIPART:
433                 return store_multi(ct);
434                 break;
435
436         case CT_MESSAGE:
437                 switch (ct->c_subtype) {
438                 case MESSAGE_PARTIAL:
439                         return store_partial(ct);
440                         break;
441
442                 case MESSAGE_EXTERNAL:
443                         return store_external(ct);
444
445                 case MESSAGE_RFC822:
446                 default:
447                         return store_generic(ct);
448                         break;
449                 }
450                 break;
451
452         case CT_APPLICATION:
453                 return store_application(ct);
454                 break;
455
456         case CT_TEXT:
457         case CT_AUDIO:
458         case CT_IMAGE:
459         case CT_VIDEO:
460                 return store_generic(ct);
461                 break;
462
463         default:
464                 adios(NULL, "unknown content type %d", ct->c_type);
465                 break;
466         }
467
468         return OK;  /* NOT REACHED */
469 }
470
471
472 /*
473 ** Generic routine to store a MIME content.
474 ** (audio, video, image, text, message/rfc922)
475 */
476
477 static int
478 store_generic(CT ct)
479 {
480         /*
481         ** Check if the content specifies a filename.
482         ** Don't bother with this for type "message"
483         ** (only "message/rfc822" will use store_generic).
484         */
485         if (autosw && ct->c_type != CT_MESSAGE)
486                 get_storeproc(ct);
487
488         return store_content(ct, NULL);
489 }
490
491
492 /*
493 ** Store content of type "application"
494 */
495
496 static int
497 store_application(CT ct)
498 {
499         char **ap, **ep;
500         CI ci = &ct->c_ctinfo;
501
502         /* Check if the content specifies a filename */
503         if (autosw)
504                 get_storeproc(ct);
505
506         /*
507         ** If storeproc is not defined, and the content is type
508         ** "application/octet-stream", we also check for various
509         ** attribute/value pairs which specify if this a tar file.
510         */
511         if (!ct->c_storeproc && ct->c_subtype == APPLICATION_OCTETS) {
512                 int tarP = 0, zP = 0, gzP = 0;
513
514                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
515                         /* check for "type=tar" attribute */
516                         if (!mh_strcasecmp(*ap, "type")) {
517                                 if (mh_strcasecmp(*ep, "tar"))
518                                         break;
519
520                                 tarP = 1;
521                                 continue;
522                         }
523
524                         /* check for "conversions=compress" attribute */
525                         if ((!mh_strcasecmp(*ap, "conversions") ||
526                                         !mh_strcasecmp(*ap, "x-conversions"))
527                                         && (!mh_strcasecmp(*ep, "compress") ||
528                                         !mh_strcasecmp(*ep, "x-compress"))) {
529                                 zP = 1;
530                                 continue;
531                         }
532                         /* check for "conversions=gzip" attribute */
533                         if ((!mh_strcasecmp(*ap, "conversions") ||
534                                         !mh_strcasecmp(*ap, "x-conversions"))
535                                         && (!mh_strcasecmp(*ep, "gzip") ||
536                                         !mh_strcasecmp(*ep, "x-gzip"))) {
537                                 gzP = 1;
538                                 continue;
539                         }
540                 }
541
542                 if (tarP) {
543                         ct->c_showproc = getcpy(zP ? "%euncompress | tar tvf -" : (gzP ? "%egzip -dc | tar tvf -" : "%etar tvf -"));
544                         if (!ct->c_storeproc) {
545                                 if (autosw) {
546                                         ct->c_storeproc = getcpy(zP ? "| uncompress | tar xvpf -" : (gzP ? "| gzip -dc | tar xvpf -" : "| tar xvpf -"));
547                                         ct->c_umask = 0022;
548                                 } else {
549                                         ct->c_storeproc= getcpy(zP ? "%m%P.tar.Z" : (gzP ? "%m%P.tar.gz" : "%m%P.tar"));
550                                 }
551                         }
552                 }
553         }
554
555         return store_content(ct, NULL);
556 }
557
558
559 /*
560 ** Store the content of a multipart message
561 */
562
563 static int
564 store_multi(CT ct)
565 {
566         int result;
567         struct multipart *m = (struct multipart *) ct->c_ctparams;
568         struct part *part;
569
570         result = NOTOK;
571         for (part = m->mp_parts; part; part = part->mp_next) {
572                 CT  p = part->mp_part;
573
574                 if (part_ok(p, 1) && type_ok(p, 1)) {
575                         result = store_switch(p);
576                         if (result == OK && ct->c_subtype == MULTI_ALTERNATE)
577                                 break;
578                 }
579         }
580
581         return result;
582 }
583
584
585 /*
586 ** Reassemble and store the contents of a collection
587 ** of messages of type "message/partial".
588 */
589
590 static int
591 store_partial(CT ct)
592 {
593         int cur, hi, i;
594         CT p, *ctp, *ctq;
595         CT *base;
596         struct partial *pm, *qm;
597
598         qm = (struct partial *) ct->c_ctparams;
599         if (qm->pm_stored)
600                 return OK;
601
602         hi = i = 0;
603         for (ctp = cts; *ctp; ctp++) {
604                 p = *ctp;
605                 if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) {
606                         pm = (struct partial *) p->c_ctparams;
607                         if (!pm->pm_stored &&
608                                         strcmp(qm->pm_partid, pm->pm_partid)
609                                         == 0) {
610                                 pm->pm_marked = pm->pm_partno;
611                                 if (pm->pm_maxno)
612                                         hi = pm->pm_maxno;
613                                 pm->pm_stored = 1;
614                                 i++;
615                         } else
616                                 pm->pm_marked = 0;
617                 }
618         }
619
620         if (hi == 0) {
621                 advise(NULL, "missing (at least) last part of multipart message");
622                 return NOTOK;
623         }
624
625         if ((base = (CT *) calloc((size_t) (i + 1), sizeof(*base))) == NULL)
626                 adios(NULL, "out of memory");
627
628         ctq = base;
629         for (ctp = cts; *ctp; ctp++) {
630                 p = *ctp;
631                 if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) {
632                         pm = (struct partial *) p->c_ctparams;
633                         if (pm->pm_marked)
634                                 *ctq++ = p;
635                 }
636         }
637         *ctq = NULL;
638
639         if (i > 1)
640                 qsort((char *) base, i, sizeof(*base), (qsort_comp) ct_compar);
641
642         cur = 1;
643         for (ctq = base; *ctq; ctq++) {
644                 p = *ctq;
645                 pm = (struct partial *) p->c_ctparams;
646                 if (pm->pm_marked != cur) {
647                         if (pm->pm_marked == cur - 1) {
648                                 admonish(NULL, "duplicate part %d of %d part multipart message", pm->pm_marked, hi);
649                                 continue;
650                         }
651
652 missing_part:
653                         advise (NULL, "missing %spart %d of %d part multipart message", cur != hi ? "(at least) " : "", cur, hi);
654                         goto losing;
655                 } else
656                         cur++;
657         }
658         if (hi != --cur) {
659                 cur = hi;
660                 goto missing_part;
661         }
662
663         /*
664         ** Now cycle through the sorted list of messages of type
665         ** "message/partial" and save/append them to a file.
666         */
667
668         ctq = base;
669         ct = *ctq++;
670         if (store_content(ct, NULL) == NOTOK) {
671 losing:
672                 free((char *) base);
673                 return NOTOK;
674         }
675
676         for (; *ctq; ctq++) {
677                 p = *ctq;
678                 if (store_content(p, ct) == NOTOK)
679                         goto losing;
680         }
681
682         free((char *) base);
683         return OK;
684 }
685
686
687 /*
688 ** Store content from a message of type "message/external".
689 */
690
691 static int
692 store_external(CT ct)
693 {
694         int result = NOTOK;
695         struct exbody *e = (struct exbody *) ct->c_ctparams;
696         CT p = e->eb_content;
697
698         if (!type_ok(p, 1))
699                 return OK;
700
701         /*
702         ** Check if the parameters for the external body
703         ** specified a filename.
704         */
705         if (autosw) {
706                 char *cp;
707
708                 if ((cp = e->eb_name) && *cp != '/' && *cp != '.' &&
709                                 *cp != '|' && *cp != '!' && !strchr(cp, '%')) {
710                         if (!ct->c_storeproc)
711                                 ct->c_storeproc = getcpy(cp);
712                         if (!p->c_storeproc)
713                                 p->c_storeproc = getcpy(cp);
714                 }
715         }
716
717         /*
718         ** Since we will let the Content structure for the
719         ** external body substitute for the current content,
720         ** we temporarily change its partno (number inside
721         ** multipart), so everything looks right.
722         */
723         p->c_partno = ct->c_partno;
724
725         /* we probably need to check if content is really there */
726         result = store_switch(p);
727
728         p->c_partno = NULL;
729         return result;
730 }
731
732
733 /*
734 ** Compare the numbering from two different
735 ** message/partials (needed for sorting).
736 */
737
738 static int
739 ct_compar(CT *a, CT *b)
740 {
741         struct partial *am = (struct partial *) ((*a)->c_ctparams);
742         struct partial *bm = (struct partial *) ((*b)->c_ctparams);
743
744         return (am->pm_marked - bm->pm_marked);
745 }
746
747
748 /*
749 ** Store contents of a message or message part to
750 ** a folder, a file, the standard output, or pass
751 ** the contents to a command.
752 **
753 ** If the current content to be saved is a followup part
754 ** to a collection of messages of type "message/partial",
755 ** then field "p" is a pointer to the Content structure
756 ** to the first message/partial in the group.
757 */
758
759 static int
760 store_content(CT ct, CT p)
761 {
762         int appending = 0, msgnum = 0;
763         int is_partial = 0, first_partial = 0;
764         int last_partial = 0;
765         char *cp, buffer[BUFSIZ];
766
767         /*
768         ** Do special processing for messages of
769         ** type "message/partial".
770         **
771         ** We first check if this content is of type
772         ** "message/partial".  If it is, then we need to check
773         ** whether it is the first and/or last in the group.
774         **
775         ** Then if "p" is a valid pointer, it points to the Content
776         ** structure of the first partial in the group.  So we copy
777         ** the file name and/or folder name from that message.  In
778         ** this case, we also note that we will be appending.
779         */
780         if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) {
781                 struct partial *pm = (struct partial *) ct->c_ctparams;
782
783                 /* Yep, it's a message/partial */
784                 is_partial = 1;
785
786                 /* But is it the first and/or last in the collection? */
787                 if (pm->pm_partno == 1)
788                         first_partial = 1;
789                 if (pm->pm_maxno && pm->pm_partno == pm->pm_maxno)
790                         last_partial = 1;
791
792                 /*
793                 ** If "p" is a valid pointer, then it points to the
794                 ** Content structure for the first message in the group.
795                 ** So we just copy the filename or foldername information
796                 ** from the previous iteration of this function.
797                 */
798                 if (p) {
799                         appending = 1;
800                         ct->c_storage = getcpy(p->c_storage);
801
802                         /* record the folder name */
803                         if (p->c_folder) {
804                                 ct->c_folder = getcpy(p->c_folder);
805                         }
806                         goto got_filename;
807                 }
808         }
809
810         /*
811         ** Get storage formatting string.
812         **
813         ** 1) If we have storeproc defined, then use that
814         ** 2) Else check for a mhstore-store-<type>/<subtype> entry
815         ** 3) Else check for a mhstore-store-<type> entry
816         ** 4) Else if content is "message", use "+" (current folder)
817         ** 5) Else use string "%m%P.%s".
818         */
819         if ((cp = ct->c_storeproc) == NULL || *cp == '\0') {
820                 CI ci = &ct->c_ctinfo;
821
822                 snprintf(buffer, sizeof(buffer), "%s-store-%s/%s",
823                                 invo_name, ci->ci_type, ci->ci_subtype);
824                 if ((cp = context_find(buffer)) == NULL || *cp == '\0') {
825                         snprintf(buffer, sizeof(buffer), "%s-store-%s",
826                                         invo_name, ci->ci_type);
827                         if ((cp = context_find(buffer)) == NULL ||
828                                         *cp == '\0') {
829                                 cp = ct->c_type == CT_MESSAGE ?
830                                                 "+" : "%m%P.%s";
831                         }
832                 }
833         }
834
835         /*
836         ** Check the beginning of storage formatting string
837         ** to see if we are saving content to a folder.
838         */
839         if (*cp == '+' || *cp == '@') {
840                 char *tmpfilenam, *folder;
841
842                 /* Store content in temporary file for now */
843                 tmpfilenam = m_mktemp(invo_name, NULL, NULL);
844                 ct->c_storage = getcpy(tmpfilenam);
845
846                 /* Get the folder name */
847                 if (cp[1])
848                         folder = getcpy(expandfol(cp));
849                 else
850                         folder = getcurfol();
851
852                 /* Check if folder exists */
853                 create_folder(toabsdir(folder), 0, exit);
854
855                 /* Record the folder name */
856                 ct->c_folder = getcpy(folder);
857
858                 if (cp[1])
859                         free(folder);
860
861                 goto got_filename;
862         }
863
864         /*
865         ** Parse and expand the storage formatting string
866         ** in `cp' into `buffer'.
867         */
868         parse_format_string(ct, cp, buffer, sizeof(buffer), dir);
869
870         /*
871         ** If formatting begins with '|' or '!', then pass
872         ** content to standard input of a command and return.
873         */
874         if (buffer[0] == '|' || buffer[0] == '!')
875                 return show_content_aux(ct, 0, buffer + 1, dir);
876
877         /* record the filename */
878         ct->c_storage = getcpy(buffer);
879
880 got_filename:
881         /* flush the output stream */
882         fflush(stdout);
883
884         /* Now save or append the content to a file */
885         if (output_content_file(ct, appending) == NOTOK)
886                 return NOTOK;
887
888         /*
889         ** If necessary, link the file into a folder and remove
890         ** the temporary file.  If this message is a partial,
891         ** then only do this if it is the last one in the group.
892         */
893         if (ct->c_folder && (!is_partial || last_partial)) {
894                 msgnum = output_content_folder(ct->c_folder, ct->c_storage);
895                 unlink(ct->c_storage);
896                 if (msgnum == NOTOK)
897                         return NOTOK;
898         }
899
900         /*
901         ** Now print out the name/number of the message
902         ** that we are storing.
903         */
904         if (is_partial) {
905                 if (first_partial)
906                         fprintf(stderr, "reassembling partials ");
907                 if (last_partial)
908                         fprintf(stderr, "%s", ct->c_file);
909                 else
910                         fprintf(stderr, "%s,", ct->c_file);
911         } else {
912                 fprintf(stderr, "storing message %s", ct->c_file);
913                 if (ct->c_partno)
914                         fprintf(stderr, " part %s", ct->c_partno);
915         }
916
917         /*
918         ** Unless we are in the "middle" of group of message/partials,
919         ** we now print the name of the file, folder, and/or message
920         ** to which we are storing the content.
921         */
922         if (!is_partial || last_partial) {
923                 if (ct->c_folder) {
924                         fprintf(stderr, " to folder %s as message %d\n",
925                                         ct->c_folder, msgnum);
926                 } else if (strcmp(ct->c_storage, "-")==0) {
927                         fprintf(stderr, " to stdout\n");
928                 } else {
929                         int cwdlen;
930
931                         cwdlen = strlen(cwd);
932                         fprintf(stderr, " as file %s\n",
933                                         strncmp(ct->c_storage, cwd,
934                                         cwdlen)!=0 ||
935                                         ct->c_storage[cwdlen] != '/' ?
936                                         ct->c_storage :
937                                         ct->c_storage + cwdlen + 1);
938                 }
939         }
940
941         return OK;
942 }
943
944
945 /*
946 ** Output content to a file
947 */
948
949 static int
950 output_content_file(CT ct, int appending)
951 {
952         int filterstate;
953         char *file, buffer[BUFSIZ];
954         long pos, last;
955         FILE *fp;
956
957         /*
958         ** If the pathname is absolute, make sure
959         ** all the relevant directories exist.
960         */
961         if (strchr(ct->c_storage, '/') && make_intermediates(ct->c_storage)
962                         == NOTOK)
963                 return NOTOK;
964
965         if (ct->c_encoding != CE_7BIT) {
966                 int cc, fd;
967
968                 if (!ct->c_ceopenfnx) {
969                         advise(NULL, "don't know how to decode part %s of message %s", ct->c_partno, ct->c_file);
970                         return NOTOK;
971                 }
972
973                 file = appending || strcmp(ct->c_storage, "-")==0 ?
974                                 NULL : ct->c_storage;
975                 if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
976                         return NOTOK;
977                 if (strcmp(file, ct->c_storage)==0) {
978                         (*ct->c_ceclosefnx) (ct);
979                         return OK;
980                 }
981
982                 /*
983                 ** Send to standard output
984                 */
985                 if (strcmp(ct->c_storage, "-")==0) {
986                         int gd;
987
988                         if ((gd = dup(fileno(stdout))) == NOTOK) {
989                                 advise("stdout", "unable to dup");
990 losing:
991                                 (*ct->c_ceclosefnx) (ct);
992                                 return NOTOK;
993                         }
994                         if ((fp = fdopen(gd, appending ? "a" : "w")) == NULL) {
995                                 advise("stdout", "unable to fdopen (%d, \"%s\") from", gd, appending ? "a" : "w");
996                                 close(gd);
997                                 goto losing;
998                         }
999                 } else {
1000                         /*
1001                         ** Open output file
1002                         */
1003                         if ((fp = fopen(ct->c_storage, appending ? "a" : "w"))
1004                                         == NULL) {
1005                                 advise(ct->c_storage, "unable to fopen for %s",
1006                                                 appending ?
1007                                                 "appending" : "writing");
1008                                 goto losing;
1009                         }
1010                 }
1011
1012                 /*
1013                 ** Filter the header fields of the initial enclosing
1014                 ** message/partial into the file.
1015                 */
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                 }
1022
1023                 for (;;) {
1024                         switch (cc = read(fd, buffer, sizeof(buffer))) {
1025                         case NOTOK:
1026                                 advise(file, "error reading content from");
1027                                 break;
1028
1029                         case OK:
1030                                 break;
1031
1032                         default:
1033                                 fwrite(buffer, sizeof(*buffer), cc, fp);
1034                                 continue;
1035                         }
1036                         break;
1037                 }
1038
1039                 (*ct->c_ceclosefnx) (ct);
1040
1041                 if (cc != NOTOK && fflush(fp))
1042                         advise(ct->c_storage, "error writing to");
1043
1044                 fclose(fp);
1045
1046                 return (cc != NOTOK ? OK : NOTOK);
1047         }
1048
1049         if (!ct->c_fp && (ct->c_fp = fopen(ct->c_file, "r")) == NULL) {
1050                 advise(ct->c_file, "unable to open for reading");
1051                 return NOTOK;
1052         }
1053
1054         pos = ct->c_begin;
1055         last = ct->c_end;
1056         fseek(ct->c_fp, pos, SEEK_SET);
1057
1058         if (strcmp(ct->c_storage, "-")==0) {
1059                 int gd;
1060
1061                 if ((gd = dup(fileno(stdout))) == NOTOK) {
1062                         advise("stdout", "unable to dup");
1063                         return NOTOK;
1064                 }
1065                 if ((fp = fdopen(gd, appending ? "a" : "w")) == NULL) {
1066                         advise("stdout", "unable to fdopen (%d, \"%s\") from",
1067                                         gd, appending ? "a" : "w");
1068                         close(gd);
1069                         return NOTOK;
1070                 }
1071         } else {
1072                 if ((fp = fopen(ct->c_storage, appending ? "a" : "w"))
1073                                 == NULL) {
1074                         advise(ct->c_storage, "unable to fopen for %s",
1075                                         appending ? "appending" : "writing");
1076                         return NOTOK;
1077                 }
1078         }
1079
1080         /*
1081         ** Copy a few of the header fields of the initial
1082         ** enclosing message/partial into the file.
1083         */
1084         filterstate = 0;
1085         if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) {
1086                 struct partial *pm = (struct partial *) ct->c_ctparams;
1087
1088                 if (pm->pm_partno == 1) {
1089                         copy_some_headers(fp, ct);
1090                         filterstate = 1;
1091                 }
1092         }
1093
1094         while (fgets(buffer, sizeof(buffer) - 1, ct->c_fp)) {
1095                 if ((pos += strlen(buffer)) > last) {
1096                         int diff;
1097
1098                         diff = strlen(buffer) - (pos - last);
1099                         if (diff >= 0)
1100                                 buffer[diff] = '\0';
1101                 }
1102                 /*
1103                 ** If this is the first content of a group of
1104                 ** message/partial contents, then we only copy a few
1105                 ** of the header fields of the enclosed message.
1106                 */
1107                 if (filterstate) {
1108                         switch (buffer[0]) {
1109                         case ' ':
1110                         case '\t':
1111                                 if (filterstate < 0)
1112                                         buffer[0] = 0;
1113                                 break;
1114
1115                         case '\n':
1116                                 filterstate = 0;
1117                                 break;
1118
1119                         default:
1120                                 if (!uprf(buffer, XXX_FIELD_PRF) && !uprf(buffer, VRSN_FIELD) && !uprf(buffer, "Subject:") && !uprf(buffer, "Message-ID:")) {
1121                                         filterstate = -1;
1122                                         buffer[0] = 0;
1123                                         break;
1124                                 }
1125                                 filterstate = 1;
1126                                 break;
1127                         }
1128                 }
1129                 fputs(buffer, fp);
1130                 if (pos >= last)
1131                         break;
1132         }
1133
1134         if (fflush(fp))
1135                 advise(ct->c_storage, "error writing to");
1136
1137         fclose(fp);
1138         fclose(ct->c_fp);
1139         ct->c_fp = NULL;
1140         return OK;
1141 }
1142
1143
1144 /*
1145 ** Add a file to a folder.
1146 **
1147 ** Return the new message number of the file
1148 ** when added to the folder.  Return -1, if
1149 ** there is an error.
1150 */
1151
1152 static int
1153 output_content_folder(char *folder, char *filename)
1154 {
1155         int msgnum;
1156         struct msgs *mp;
1157
1158         /* Read the folder. */
1159         if ((mp = folder_read(folder))) {
1160                 /* Link file into folder */
1161                 msgnum = folder_addmsg(&mp, filename, 0, 0, 0, 0, NULL);
1162         } else {
1163                 advise(NULL, "unable to read folder %s", folder);
1164                 return NOTOK;
1165         }
1166
1167         /* free folder structure */
1168         folder_free(mp);
1169
1170         /*
1171         ** Return msgnum.  We are relying on the fact that
1172         ** msgnum will be -1, if folder_addmsg() had an error.
1173         */
1174         return msgnum;
1175 }
1176
1177
1178 /*
1179 ** Parse and expand the storage formatting string
1180 ** pointed to by "cp" into "buffer".
1181 */
1182
1183 static int
1184 parse_format_string(CT ct, char *cp, char *buffer, int buflen, char *dir)
1185 {
1186         int len;
1187         char *bp;
1188         CI ci = &ct->c_ctinfo;
1189
1190         /*
1191         ** If storage string is "-", just copy it, and
1192         ** return (send content to standard output).
1193         */
1194         if (cp[0] == '-' && cp[1] == '\0') {
1195                 strncpy(buffer, cp, buflen);
1196                 return 0;
1197         }
1198
1199         bp = buffer;
1200         bp[0] = '\0';
1201
1202         /*
1203         ** If formatting string is a pathname that doesn't
1204         ** begin with '/', then preface the path with the
1205         ** appropriate directory.
1206         */
1207         if (*cp != '/' && *cp != '|' && *cp != '!') {
1208                 snprintf(bp, buflen, "%s/", dir[1] ? dir : "");
1209                 len = strlen(bp);
1210                 bp += len;
1211                 buflen -= len;
1212         }
1213
1214         for (; *cp; cp++) {
1215
1216                 /* We are processing a storage escape */
1217                 if (*cp == '%') {
1218                         switch (*++cp) {
1219                         case 'a':
1220                                 /*
1221                                 ** Insert parameters from Content-Type.
1222                                 ** This is only valid for '|' commands.
1223                                 */
1224                                 if (buffer[0] != '|' && buffer[0] != '!') {
1225                                         *bp++ = *--cp;
1226                                         *bp = '\0';
1227                                         buflen--;
1228                                         continue;
1229                                 } else {
1230                                         char **ap, **ep;
1231                                         char *s = "";
1232
1233                                         for (ap=ci->ci_attrs, ep=ci->ci_values;
1234                                                          *ap; ap++, ep++) {
1235                                                 snprintf(bp, buflen,
1236                                                                 "%s%s=\"%s\"",
1237                                                                 s, *ap, *ep);
1238                                                 len = strlen(bp);
1239                                                 bp += len;
1240                                                 buflen -= len;
1241                                                 s = " ";
1242                                         }
1243                                 }
1244                                 break;
1245
1246                         case 'm':
1247                                 /* insert message number */
1248                                 snprintf(bp, buflen, "%s",
1249                                                 mhbasename(ct->c_file));
1250                                 break;
1251
1252                         case 'P':
1253                                 /* insert part number with leading dot */
1254                                 if (ct->c_partno)
1255                                         snprintf(bp, buflen, ".%s",
1256                                                         ct->c_partno);
1257                                 break;
1258
1259                         case 'p':
1260                                 /* insert part number withouth leading dot */
1261                                 if (ct->c_partno)
1262                                         strncpy(bp, ct->c_partno, buflen);
1263                                 break;
1264
1265                         case 't':
1266                                 /* insert content type */
1267                                 strncpy(bp, ci->ci_type, buflen);
1268                                 break;
1269
1270                         case 's':
1271                                 /* insert content subtype */
1272                                 strncpy(bp, ci->ci_subtype, buflen);
1273                                 break;
1274
1275                         case '%':
1276                                 /* insert the character % */
1277                                 goto raw;
1278
1279                         default:
1280                                 *bp++ = *--cp;
1281                                 *bp = '\0';
1282                                 buflen--;
1283                                 continue;
1284                         }
1285
1286                         /* Advance bp and decrement buflen */
1287                         len = strlen(bp);
1288                         bp += len;
1289                         buflen -= len;
1290
1291                 } else {
1292 raw:
1293                         *bp++ = *cp;
1294                         *bp = '\0';
1295                         buflen--;
1296                 }
1297         }
1298
1299         return 0;
1300 }
1301
1302
1303 /*
1304 ** Check if the content specifies a filename
1305 ** in its MIME parameters.
1306 */
1307
1308 static void
1309 get_storeproc(CT ct)
1310 {
1311         char **ap, **ep, *cp;
1312         CI ci = &ct->c_ctinfo;
1313
1314         /*
1315         ** If the storeproc has already been defined,
1316         ** we just return (for instance, if this content
1317         ** is part of a "message/external".
1318         */
1319         if (ct->c_storeproc)
1320                 return;
1321
1322         /*
1323         ** Check the attribute/value pairs, for the attribute "name".
1324         ** If found, do a few sanity checks and copy the value into
1325         ** the storeproc.
1326         */
1327         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1328                 if (!mh_strcasecmp(*ap, "name") && *(cp = *ep) != '/' &&
1329                                 *cp != '.' && *cp != '|' && *cp != '!' &&
1330                                 !strchr(cp, '%')) {
1331                         ct->c_storeproc = getcpy(cp);
1332                         return;
1333                 }
1334         }
1335 }
1336
1337
1338 /*
1339 ** Copy some of the header fields of the initial message/partial
1340 ** message into the header of the reassembled message.
1341 */
1342
1343 static int
1344 copy_some_headers(FILE *out, CT ct)
1345 {
1346         HF hp;
1347
1348         hp = ct->c_first_hf;  /* start at first header field */
1349
1350         while (hp) {
1351                 /*
1352                 ** A few of the header fields of the enclosing
1353                 ** messages are not copied.
1354                 */
1355                 if (!uprf(hp->name, XXX_FIELD_PRF) &&
1356                                 mh_strcasecmp(hp->name, VRSN_FIELD) &&
1357                                 mh_strcasecmp(hp->name, "Subject") &&
1358                                 mh_strcasecmp(hp->name, "Message-ID"))
1359                         fprintf(out, "%s:%s", hp->name, hp->value);
1360                 hp = hp->next;  /* next header field */
1361         }
1362
1363         return OK;
1364 }