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