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