Removed support for Encrypted: headers, which were removed in RFC 2822.
[mmh] / uip / mhstoresbr.c
1 /*
2 ** mhstoresbr.c -- routines to save/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 <h/md5.h>
13 #include <errno.h>
14 #include <setjmp.h>
15 #include <signal.h>
16 #include <h/tws.h>
17 #include <h/mime.h>
18 #include <h/mhparse.h>
19 #include <h/utils.h>
20
21
22 /*
23 ** The list of top-level contents to display
24 */
25 extern CT *cts;
26
27 int autosw = 0;
28
29 /*
30 ** Cache of current directory.  This must be
31 ** set before these routines are called.
32 */
33 char *cwd;
34
35 /*
36 ** The directory in which to store the contents.
37 */
38 static char *dir;
39
40 /*
41 ** Type for a compare function for qsort.  This keeps
42 ** the compiler happy.
43 */
44 typedef int (*qsort_comp) (const void *, const void *);
45
46
47 /* mhmisc.c */
48 int part_ok(CT, int);
49 int type_ok(CT, int);
50 int make_intermediates(char *);
51 void flush_errors(void);
52
53 /* mhshowsbr.c */
54 int show_content_aux(CT, int, int, char *, char *);
55
56 /*
57 ** prototypes
58 */
59 void store_all_messages(CT *);
60
61 /*
62 ** static prototypes
63 */
64 static void store_single_message(CT);
65 static int store_switch(CT);
66 static int store_generic(CT);
67 static int store_application(CT);
68 static int store_multi(CT);
69 static int store_partial(CT);
70 static int store_external(CT);
71 static int ct_compar(CT *, CT *);
72 static int store_content(CT, CT);
73 static int output_content_file(CT, int);
74 static int output_content_folder(char *, char *);
75 static int parse_format_string(CT, char *, char *, int, char *);
76 static void get_storeproc(CT);
77 static int copy_some_headers(FILE *, CT);
78
79
80 /*
81 ** Main entry point to store content
82 ** from a collection of messages.
83 */
84
85 void
86 store_all_messages(CT *cts)
87 {
88         CT ct, *ctp;
89         char *cp;
90
91         /*
92         ** Check for the directory in which to
93         ** store any contents.
94         */
95         if (autosw)
96                 dir = getcpy(cwd);
97         else if ((cp = context_find(nmhstorage)) && *cp)
98                 dir = getcpy(cp);
99         else
100                 dir = getcpy(cwd);
101
102         for (ctp = cts; *ctp; ctp++) {
103                 ct = *ctp;
104                 store_single_message(ct);
105         }
106
107         flush_errors();
108 }
109
110
111 /*
112 ** Entry point to store the content
113 ** in a (single) message
114 */
115
116 static void
117 store_single_message(CT ct)
118 {
119         if (type_ok(ct, 1)) {
120                 umask(ct->c_umask);
121                 store_switch(ct);
122                 if (ct->c_fp) {
123                         fclose(ct->c_fp);
124                         ct->c_fp = NULL;
125                 }
126                 if (ct->c_ceclosefnx)
127                         (*ct->c_ceclosefnx) (ct);
128         }
129 }
130
131
132 /*
133 ** Switching routine to store different content types
134 */
135
136 static int
137 store_switch(CT ct)
138 {
139         switch (ct->c_type) {
140         case CT_MULTIPART:
141                 return store_multi(ct);
142                 break;
143
144         case CT_MESSAGE:
145                 switch (ct->c_subtype) {
146                 case MESSAGE_PARTIAL:
147                         return store_partial(ct);
148                         break;
149
150                 case MESSAGE_EXTERNAL:
151                         return store_external(ct);
152
153                 case MESSAGE_RFC822:
154                 default:
155                         return store_generic(ct);
156                         break;
157                 }
158                 break;
159
160         case CT_APPLICATION:
161                 return store_application(ct);
162                 break;
163
164         case CT_TEXT:
165         case CT_AUDIO:
166         case CT_IMAGE:
167         case CT_VIDEO:
168                 return store_generic(ct);
169                 break;
170
171         default:
172                 adios(NULL, "unknown content type %d", ct->c_type);
173                 break;
174         }
175
176         return OK;  /* NOT REACHED */
177 }
178
179
180 /*
181 ** Generic routine to store a MIME content.
182 ** (audio, video, image, text, message/rfc922)
183 */
184
185 static int
186 store_generic(CT ct)
187 {
188         /*
189         ** Check if the content specifies a filename.
190         ** Don't bother with this for type "message"
191         ** (only "message/rfc822" will use store_generic).
192         */
193         if (autosw && ct->c_type != CT_MESSAGE)
194                 get_storeproc(ct);
195
196         return store_content(ct, NULL);
197 }
198
199
200 /*
201 ** Store content of type "application"
202 */
203
204 static int
205 store_application(CT ct)
206 {
207         char **ap, **ep;
208         CI ci = &ct->c_ctinfo;
209
210         /* Check if the content specifies a filename */
211         if (autosw)
212                 get_storeproc(ct);
213
214         /*
215         ** If storeproc is not defined, and the content is type
216         ** "application/octet-stream", we also check for various
217         ** attribute/value pairs which specify if this a tar file.
218         */
219         if (!ct->c_storeproc && ct->c_subtype == APPLICATION_OCTETS) {
220                 int tarP = 0, zP = 0, gzP = 0;
221
222                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
223                         /* check for "type=tar" attribute */
224                         if (!mh_strcasecmp(*ap, "type")) {
225                                 if (mh_strcasecmp(*ep, "tar"))
226                                         break;
227
228                                 tarP = 1;
229                                 continue;
230                         }
231
232                         /* check for "conversions=compress" attribute */
233                         if ((!mh_strcasecmp(*ap, "conversions") ||
234                                         !mh_strcasecmp(*ap, "x-conversions"))
235                                         && (!mh_strcasecmp(*ep, "compress") ||
236                                         !mh_strcasecmp(*ep, "x-compress"))) {
237                                 zP = 1;
238                                 continue;
239                         }
240                         /* check for "conversions=gzip" attribute */
241                         if ((!mh_strcasecmp(*ap, "conversions") ||
242                                         !mh_strcasecmp(*ap, "x-conversions"))
243                                         && (!mh_strcasecmp(*ep, "gzip") ||
244                                         !mh_strcasecmp(*ep, "x-gzip"))) {
245                                 gzP = 1;
246                                 continue;
247                         }
248                 }
249
250                 if (tarP) {
251                         ct->c_showproc = getcpy(zP ? "%euncompress | tar tvf -" : (gzP ? "%egzip -dc | tar tvf -" : "%etar tvf -"));
252                         if (!ct->c_storeproc) {
253                                 if (autosw) {
254                                         ct->c_storeproc = getcpy(zP ? "| uncompress | tar xvpf -" : (gzP ? "| gzip -dc | tar xvpf -" : "| tar xvpf -"));
255                                         ct->c_umask = 0022;
256                                 } else {
257                                         ct->c_storeproc= getcpy(zP ? "%m%P.tar.Z" : (gzP ? "%m%P.tar.gz" : "%m%P.tar"));
258                                 }
259                         }
260                 }
261         }
262
263         return store_content(ct, NULL);
264 }
265
266
267 /*
268 ** Store the content of a multipart message
269 */
270
271 static int
272 store_multi(CT ct)
273 {
274         int result;
275         struct multipart *m = (struct multipart *) ct->c_ctparams;
276         struct part *part;
277
278         result = NOTOK;
279         for (part = m->mp_parts; part; part = part->mp_next) {
280                 CT  p = part->mp_part;
281
282                 if (part_ok(p, 1) && type_ok(p, 1)) {
283                         result = store_switch(p);
284                         if (result == OK && ct->c_subtype == MULTI_ALTERNATE)
285                                 break;
286                 }
287         }
288
289         return result;
290 }
291
292
293 /*
294 ** Reassemble and store the contents of a collection
295 ** of messages of type "message/partial".
296 */
297
298 static int
299 store_partial(CT ct)
300 {
301         int cur, hi, i;
302         CT p, *ctp, *ctq;
303         CT *base;
304         struct partial *pm, *qm;
305
306         qm = (struct partial *) ct->c_ctparams;
307         if (qm->pm_stored)
308                 return OK;
309
310         hi = i = 0;
311         for (ctp = cts; *ctp; ctp++) {
312                 p = *ctp;
313                 if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) {
314                         pm = (struct partial *) p->c_ctparams;
315                         if (!pm->pm_stored &&
316                                         strcmp(qm->pm_partid, pm->pm_partid)
317                                         == 0) {
318                                 pm->pm_marked = pm->pm_partno;
319                                 if (pm->pm_maxno)
320                                         hi = pm->pm_maxno;
321                                 pm->pm_stored = 1;
322                                 i++;
323                         } else
324                                 pm->pm_marked = 0;
325                 }
326         }
327
328         if (hi == 0) {
329                 advise(NULL, "missing (at least) last part of multipart message");
330                 return NOTOK;
331         }
332
333         if ((base = (CT *) calloc((size_t) (i + 1), sizeof(*base))) == NULL)
334                 adios(NULL, "out of memory");
335
336         ctq = base;
337         for (ctp = cts; *ctp; ctp++) {
338                 p = *ctp;
339                 if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) {
340                         pm = (struct partial *) p->c_ctparams;
341                         if (pm->pm_marked)
342                                 *ctq++ = p;
343                 }
344         }
345         *ctq = NULL;
346
347         if (i > 1)
348                 qsort((char *) base, i, sizeof(*base), (qsort_comp) ct_compar);
349
350         cur = 1;
351         for (ctq = base; *ctq; ctq++) {
352                 p = *ctq;
353                 pm = (struct partial *) p->c_ctparams;
354                 if (pm->pm_marked != cur) {
355                         if (pm->pm_marked == cur - 1) {
356                                 admonish(NULL, "duplicate part %d of %d part multipart message", pm->pm_marked, hi);
357                                 continue;
358                         }
359
360 missing_part:
361                         advise (NULL, "missing %spart %d of %d part multipart message", cur != hi ? "(at least) " : "", cur, hi);
362                         goto losing;
363                 } else
364                         cur++;
365         }
366         if (hi != --cur) {
367                 cur = hi;
368                 goto missing_part;
369         }
370
371         /*
372         ** Now cycle through the sorted list of messages of type
373         ** "message/partial" and save/append them to a file.
374         */
375
376         ctq = base;
377         ct = *ctq++;
378         if (store_content(ct, NULL) == NOTOK) {
379 losing:
380                 free((char *) base);
381                 return NOTOK;
382         }
383
384         for (; *ctq; ctq++) {
385                 p = *ctq;
386                 if (store_content(p, ct) == NOTOK)
387                         goto losing;
388         }
389
390         free((char *) base);
391         return OK;
392 }
393
394
395 /*
396 ** Store content from a message of type "message/external".
397 */
398
399 static int
400 store_external(CT ct)
401 {
402         int result = NOTOK;
403         struct exbody *e = (struct exbody *) ct->c_ctparams;
404         CT p = e->eb_content;
405
406         if (!type_ok(p, 1))
407                 return OK;
408
409         /*
410         ** Check if the parameters for the external body
411         ** specified a filename.
412         */
413         if (autosw) {
414                 char *cp;
415
416                 if ((cp = e->eb_name) && *cp != '/' && *cp != '.' &&
417                                 *cp != '|' && *cp != '!' && !strchr(cp, '%')) {
418                         if (!ct->c_storeproc)
419                                 ct->c_storeproc = getcpy(cp);
420                         if (!p->c_storeproc)
421                                 p->c_storeproc = getcpy(cp);
422                 }
423         }
424
425         /*
426         ** Since we will let the Content structure for the
427         ** external body substitute for the current content,
428         ** we temporarily change its partno (number inside
429         ** multipart), so everything looks right.
430         */
431         p->c_partno = ct->c_partno;
432
433         /* we probably need to check if content is really there */
434         result = store_switch(p);
435
436         p->c_partno = NULL;
437         return result;
438 }
439
440
441 /*
442 ** Compare the numbering from two different
443 ** message/partials (needed for sorting).
444 */
445
446 static int
447 ct_compar(CT *a, CT *b)
448 {
449         struct partial *am = (struct partial *) ((*a)->c_ctparams);
450         struct partial *bm = (struct partial *) ((*b)->c_ctparams);
451
452         return (am->pm_marked - bm->pm_marked);
453 }
454
455
456 /*
457 ** Store contents of a message or message part to
458 ** a folder, a file, the standard output, or pass
459 ** the contents to a command.
460 **
461 ** If the current content to be saved is a followup part
462 ** to a collection of messages of type "message/partial",
463 ** then field "p" is a pointer to the Content structure
464 ** to the first message/partial in the group.
465 */
466
467 static int
468 store_content(CT ct, CT p)
469 {
470         int appending = 0, msgnum = 0;
471         int is_partial = 0, first_partial = 0;
472         int last_partial = 0;
473         char *cp, buffer[BUFSIZ];
474
475         /*
476         ** Do special processing for messages of
477         ** type "message/partial".
478         **
479         ** We first check if this content is of type
480         ** "message/partial".  If it is, then we need to check
481         ** whether it is the first and/or last in the group.
482         **
483         ** Then if "p" is a valid pointer, it points to the Content
484         ** structure of the first partial in the group.  So we copy
485         ** the file name and/or folder name from that message.  In
486         ** this case, we also note that we will be appending.
487         */
488         if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) {
489                 struct partial *pm = (struct partial *) ct->c_ctparams;
490
491                 /* Yep, it's a message/partial */
492                 is_partial = 1;
493
494                 /* But is it the first and/or last in the collection? */
495                 if (pm->pm_partno == 1)
496                         first_partial = 1;
497                 if (pm->pm_maxno && pm->pm_partno == pm->pm_maxno)
498                         last_partial = 1;
499
500                 /*
501                 ** If "p" is a valid pointer, then it points to the
502                 ** Content structure for the first message in the group.
503                 ** So we just copy the filename or foldername information
504                 ** from the previous iteration of this function.
505                 */
506                 if (p) {
507                         appending = 1;
508                         ct->c_storage = getcpy(p->c_storage);
509
510                         /* record the folder name */
511                         if (p->c_folder) {
512                                 ct->c_folder = getcpy(p->c_folder);
513                         }
514                         goto got_filename;
515                 }
516         }
517
518         /*
519         ** Get storage formatting string.
520         **
521         ** 1) If we have storeproc defined, then use that
522         ** 2) Else check for a mhstore-store-<type>/<subtype> entry
523         ** 3) Else check for a mhstore-store-<type> entry
524         ** 4) Else if content is "message", use "+" (current folder)
525         ** 5) Else use string "%m%P.%s".
526         */
527         if ((cp = ct->c_storeproc) == NULL || *cp == '\0') {
528                 CI ci = &ct->c_ctinfo;
529
530                 snprintf(buffer, sizeof(buffer), "%s-store-%s/%s",
531                                 invo_name, ci->ci_type, ci->ci_subtype);
532                 if ((cp = context_find(buffer)) == NULL || *cp == '\0') {
533                         snprintf(buffer, sizeof(buffer), "%s-store-%s",
534                                         invo_name, ci->ci_type);
535                         if ((cp = context_find(buffer)) == NULL ||
536                                         *cp == '\0') {
537                                 cp = ct->c_type == CT_MESSAGE ?
538                                                 "+" : "%m%P.%s";
539                         }
540                 }
541         }
542
543         /*
544         ** Check the beginning of storage formatting string
545         ** to see if we are saving content to a folder.
546         */
547         if (*cp == '+' || *cp == '@') {
548                 char *tmpfilenam, *folder;
549
550                 /* Store content in temporary file for now */
551                 tmpfilenam = m_mktemp(invo_name, NULL, NULL);
552                 ct->c_storage = getcpy(tmpfilenam);
553
554                 /* Get the folder name */
555                 if (cp[1])
556                         folder = getcpy(expandfol(cp));
557                 else
558                         folder = getcurfol();
559
560                 /* Check if folder exists */
561                 create_folder(toabsdir(folder), 0, exit);
562
563                 /* Record the folder name */
564                 ct->c_folder = getcpy(folder);
565
566                 if (cp[1])
567                         free(folder);
568
569                 goto got_filename;
570         }
571
572         /*
573         ** Parse and expand the storage formatting string
574         ** in `cp' into `buffer'.
575         */
576         parse_format_string(ct, cp, buffer, sizeof(buffer), dir);
577
578         /*
579         ** If formatting begins with '|' or '!', then pass
580         ** content to standard input of a command and return.
581         */
582         if (buffer[0] == '|' || buffer[0] == '!')
583                 return show_content_aux(ct, 1, 0, buffer + 1, dir);
584
585         /* record the filename */
586         ct->c_storage = getcpy(buffer);
587
588 got_filename:
589         /* flush the output stream */
590         fflush(stdout);
591
592         /* Now save or append the content to a file */
593         if (output_content_file(ct, appending) == NOTOK)
594                 return NOTOK;
595
596         /*
597         ** If necessary, link the file into a folder and remove
598         ** the temporary file.  If this message is a partial,
599         ** then only do this if it is the last one in the group.
600         */
601         if (ct->c_folder && (!is_partial || last_partial)) {
602                 msgnum = output_content_folder(ct->c_folder, ct->c_storage);
603                 unlink(ct->c_storage);
604                 if (msgnum == NOTOK)
605                         return NOTOK;
606         }
607
608         /*
609         ** Now print out the name/number of the message
610         ** that we are storing.
611         */
612         if (is_partial) {
613                 if (first_partial)
614                         fprintf(stderr, "reassembling partials ");
615                 if (last_partial)
616                         fprintf(stderr, "%s", ct->c_file);
617                 else
618                         fprintf(stderr, "%s,", ct->c_file);
619         } else {
620                 fprintf(stderr, "storing message %s", ct->c_file);
621                 if (ct->c_partno)
622                         fprintf(stderr, " part %s", ct->c_partno);
623         }
624
625         /*
626         ** Unless we are in the "middle" of group of message/partials,
627         ** we now print the name of the file, folder, and/or message
628         ** to which we are storing the content.
629         */
630         if (!is_partial || last_partial) {
631                 if (ct->c_folder) {
632                         fprintf(stderr, " to folder %s as message %d\n",
633                                         ct->c_folder, msgnum);
634                 } else if (strcmp(ct->c_storage, "-")==0) {
635                         fprintf(stderr, " to stdout\n");
636                 } else {
637                         int cwdlen;
638
639                         cwdlen = strlen(cwd);
640                         fprintf(stderr, " as file %s\n",
641                                         strncmp(ct->c_storage, cwd,
642                                         cwdlen)!=0 ||
643                                         ct->c_storage[cwdlen] != '/' ?
644                                         ct->c_storage :
645                                         ct->c_storage + cwdlen + 1);
646                 }
647         }
648
649         return OK;
650 }
651
652
653 /*
654 ** Output content to a file
655 */
656
657 static int
658 output_content_file(CT ct, int appending)
659 {
660         int filterstate;
661         char *file, buffer[BUFSIZ];
662         long pos, last;
663         FILE *fp;
664
665         /*
666         ** If the pathname is absolute, make sure
667         ** all the relevant directories exist.
668         */
669         if (strchr(ct->c_storage, '/') && make_intermediates(ct->c_storage)
670                         == NOTOK)
671                 return NOTOK;
672
673         if (ct->c_encoding != CE_7BIT) {
674                 int cc, fd;
675
676                 if (!ct->c_ceopenfnx) {
677                         advise(NULL, "don't know how to decode part %s of message %s", ct->c_partno, ct->c_file);
678                         return NOTOK;
679                 }
680
681                 file = appending || strcmp(ct->c_storage, "-")==0 ?
682                                 NULL : ct->c_storage;
683                 if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
684                         return NOTOK;
685                 if (strcmp(file, ct->c_storage)==0) {
686                         (*ct->c_ceclosefnx) (ct);
687                         return OK;
688                 }
689
690                 /*
691                 ** Send to standard output
692                 */
693                 if (strcmp(ct->c_storage, "-")==0) {
694                         int gd;
695
696                         if ((gd = dup(fileno(stdout))) == NOTOK) {
697                                 advise("stdout", "unable to dup");
698 losing:
699                                 (*ct->c_ceclosefnx) (ct);
700                                 return NOTOK;
701                         }
702                         if ((fp = fdopen(gd, appending ? "a" : "w")) == NULL) {
703                                 advise("stdout", "unable to fdopen (%d, \"%s\") from", gd, appending ? "a" : "w");
704                                 close(gd);
705                                 goto losing;
706                         }
707                 } else {
708                         /*
709                         ** Open output file
710                         */
711                         if ((fp = fopen(ct->c_storage, appending ? "a" : "w"))
712                                         == NULL) {
713                                 advise(ct->c_storage, "unable to fopen for %s",
714                                                 appending ?
715                                                 "appending" : "writing");
716                                 goto losing;
717                         }
718                 }
719
720                 /*
721                 ** Filter the header fields of the initial enclosing
722                 ** message/partial into the file.
723                 */
724                 if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) {
725                         struct partial *pm = (struct partial *) ct->c_ctparams;
726
727                         if (pm->pm_partno == 1)
728                                 copy_some_headers(fp, ct);
729                 }
730
731                 for (;;) {
732                         switch (cc = read(fd, buffer, sizeof(buffer))) {
733                         case NOTOK:
734                                 advise(file, "error reading content from");
735                                 break;
736
737                         case OK:
738                                 break;
739
740                         default:
741                                 fwrite(buffer, sizeof(*buffer), cc, fp);
742                                 continue;
743                         }
744                         break;
745                 }
746
747                 (*ct->c_ceclosefnx) (ct);
748
749                 if (cc != NOTOK && fflush(fp))
750                         advise(ct->c_storage, "error writing to");
751
752                 fclose(fp);
753
754                 return (cc != NOTOK ? OK : NOTOK);
755         }
756
757         if (!ct->c_fp && (ct->c_fp = fopen(ct->c_file, "r")) == NULL) {
758                 advise(ct->c_file, "unable to open for reading");
759                 return NOTOK;
760         }
761
762         pos = ct->c_begin;
763         last = ct->c_end;
764         fseek(ct->c_fp, pos, SEEK_SET);
765
766         if (strcmp(ct->c_storage, "-")==0) {
767                 int gd;
768
769                 if ((gd = dup(fileno(stdout))) == NOTOK) {
770                         advise("stdout", "unable to dup");
771                         return NOTOK;
772                 }
773                 if ((fp = fdopen(gd, appending ? "a" : "w")) == NULL) {
774                         advise("stdout", "unable to fdopen (%d, \"%s\") from",
775                                         gd, appending ? "a" : "w");
776                         close(gd);
777                         return NOTOK;
778                 }
779         } else {
780                 if ((fp = fopen(ct->c_storage, appending ? "a" : "w"))
781                                 == NULL) {
782                         advise(ct->c_storage, "unable to fopen for %s",
783                                         appending ? "appending" : "writing");
784                         return NOTOK;
785                 }
786         }
787
788         /*
789         ** Copy a few of the header fields of the initial
790         ** enclosing message/partial into the file.
791         */
792         filterstate = 0;
793         if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) {
794                 struct partial *pm = (struct partial *) ct->c_ctparams;
795
796                 if (pm->pm_partno == 1) {
797                         copy_some_headers(fp, ct);
798                         filterstate = 1;
799                 }
800         }
801
802         while (fgets(buffer, sizeof(buffer) - 1, ct->c_fp)) {
803                 if ((pos += strlen(buffer)) > last) {
804                         int diff;
805
806                         diff = strlen(buffer) - (pos - last);
807                         if (diff >= 0)
808                                 buffer[diff] = '\0';
809                 }
810                 /*
811                 ** If this is the first content of a group of
812                 ** message/partial contents, then we only copy a few
813                 ** of the header fields of the enclosed message.
814                 */
815                 if (filterstate) {
816                         switch (buffer[0]) {
817                         case ' ':
818                         case '\t':
819                                 if (filterstate < 0)
820                                         buffer[0] = 0;
821                                 break;
822
823                         case '\n':
824                                 filterstate = 0;
825                                 break;
826
827                         default:
828                                 if (!uprf(buffer, XXX_FIELD_PRF) && !uprf(buffer, VRSN_FIELD) && !uprf(buffer, "Subject:") && !uprf(buffer, "Message-ID:")) {
829                                         filterstate = -1;
830                                         buffer[0] = 0;
831                                         break;
832                                 }
833                                 filterstate = 1;
834                                 break;
835                         }
836                 }
837                 fputs(buffer, fp);
838                 if (pos >= last)
839                         break;
840         }
841
842         if (fflush(fp))
843                 advise(ct->c_storage, "error writing to");
844
845         fclose(fp);
846         fclose(ct->c_fp);
847         ct->c_fp = NULL;
848         return OK;
849 }
850
851
852 /*
853 ** Add a file to a folder.
854 **
855 ** Return the new message number of the file
856 ** when added to the folder.  Return -1, if
857 ** there is an error.
858 */
859
860 static int
861 output_content_folder(char *folder, char *filename)
862 {
863         int msgnum;
864         struct msgs *mp;
865
866         /* Read the folder. */
867         if ((mp = folder_read(folder))) {
868                 /* Link file into folder */
869                 msgnum = folder_addmsg(&mp, filename, 0, 0, 0, 0, NULL);
870         } else {
871                 advise(NULL, "unable to read folder %s", folder);
872                 return NOTOK;
873         }
874
875         /* free folder structure */
876         folder_free(mp);
877
878         /*
879         ** Return msgnum.  We are relying on the fact that
880         ** msgnum will be -1, if folder_addmsg() had an error.
881         */
882         return msgnum;
883 }
884
885
886 /*
887 ** Parse and expand the storage formatting string
888 ** pointed to by "cp" into "buffer".
889 */
890
891 static int
892 parse_format_string(CT ct, char *cp, char *buffer, int buflen, char *dir)
893 {
894         int len;
895         char *bp;
896         CI ci = &ct->c_ctinfo;
897
898         /*
899         ** If storage string is "-", just copy it, and
900         ** return (send content to standard output).
901         */
902         if (cp[0] == '-' && cp[1] == '\0') {
903                 strncpy(buffer, cp, buflen);
904                 return 0;
905         }
906
907         bp = buffer;
908         bp[0] = '\0';
909
910         /*
911         ** If formatting string is a pathname that doesn't
912         ** begin with '/', then preface the path with the
913         ** appropriate directory.
914         */
915         if (*cp != '/' && *cp != '|' && *cp != '!') {
916                 snprintf(bp, buflen, "%s/", dir[1] ? dir : "");
917                 len = strlen(bp);
918                 bp += len;
919                 buflen -= len;
920         }
921
922         for (; *cp; cp++) {
923
924                 /* We are processing a storage escape */
925                 if (*cp == '%') {
926                         switch (*++cp) {
927                         case 'a':
928                                 /*
929                                 ** Insert parameters from Content-Type.
930                                 ** This is only valid for '|' commands.
931                                 */
932                                 if (buffer[0] != '|' && buffer[0] != '!') {
933                                         *bp++ = *--cp;
934                                         *bp = '\0';
935                                         buflen--;
936                                         continue;
937                                 } else {
938                                         char **ap, **ep;
939                                         char *s = "";
940
941                                         for (ap=ci->ci_attrs, ep=ci->ci_values;
942                                                          *ap; ap++, ep++) {
943                                                 snprintf(bp, buflen,
944                                                                 "%s%s=\"%s\"",
945                                                                 s, *ap, *ep);
946                                                 len = strlen(bp);
947                                                 bp += len;
948                                                 buflen -= len;
949                                                 s = " ";
950                                         }
951                                 }
952                                 break;
953
954                         case 'm':
955                                 /* insert message number */
956                                 snprintf(bp, buflen, "%s",
957                                                 mhbasename(ct->c_file));
958                                 break;
959
960                         case 'P':
961                                 /* insert part number with leading dot */
962                                 if (ct->c_partno)
963                                         snprintf(bp, buflen, ".%s",
964                                                         ct->c_partno);
965                                 break;
966
967                         case 'p':
968                                 /* insert part number withouth leading dot */
969                                 if (ct->c_partno)
970                                         strncpy(bp, ct->c_partno, buflen);
971                                 break;
972
973                         case 't':
974                                 /* insert content type */
975                                 strncpy(bp, ci->ci_type, buflen);
976                                 break;
977
978                         case 's':
979                                 /* insert content subtype */
980                                 strncpy(bp, ci->ci_subtype, buflen);
981                                 break;
982
983                         case '%':
984                                 /* insert the character % */
985                                 goto raw;
986
987                         default:
988                                 *bp++ = *--cp;
989                                 *bp = '\0';
990                                 buflen--;
991                                 continue;
992                         }
993
994                         /* Advance bp and decrement buflen */
995                         len = strlen(bp);
996                         bp += len;
997                         buflen -= len;
998
999                 } else {
1000 raw:
1001                         *bp++ = *cp;
1002                         *bp = '\0';
1003                         buflen--;
1004                 }
1005         }
1006
1007         return 0;
1008 }
1009
1010
1011 /*
1012 ** Check if the content specifies a filename
1013 ** in its MIME parameters.
1014 */
1015
1016 static void
1017 get_storeproc(CT ct)
1018 {
1019         char **ap, **ep, *cp;
1020         CI ci = &ct->c_ctinfo;
1021
1022         /*
1023         ** If the storeproc has already been defined,
1024         ** we just return (for instance, if this content
1025         ** is part of a "message/external".
1026         */
1027         if (ct->c_storeproc)
1028                 return;
1029
1030         /*
1031         ** Check the attribute/value pairs, for the attribute "name".
1032         ** If found, do a few sanity checks and copy the value into
1033         ** the storeproc.
1034         */
1035         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1036                 if (!mh_strcasecmp(*ap, "name") && *(cp = *ep) != '/' &&
1037                                 *cp != '.' && *cp != '|' && *cp != '!' &&
1038                                 !strchr(cp, '%')) {
1039                         ct->c_storeproc = getcpy(cp);
1040                         return;
1041                 }
1042         }
1043 }
1044
1045
1046 /*
1047 ** Copy some of the header fields of the initial message/partial
1048 ** message into the header of the reassembled message.
1049 */
1050
1051 static int
1052 copy_some_headers(FILE *out, CT ct)
1053 {
1054         HF hp;
1055
1056         hp = ct->c_first_hf;  /* start at first header field */
1057
1058         while (hp) {
1059                 /*
1060                 ** A few of the header fields of the enclosing
1061                 ** messages are not copied.
1062                 */
1063                 if (!uprf(hp->name, XXX_FIELD_PRF) &&
1064                                 mh_strcasecmp(hp->name, VRSN_FIELD) &&
1065                                 mh_strcasecmp(hp->name, "Subject") &&
1066                                 mh_strcasecmp(hp->name, "Message-ID"))
1067                         fprintf(out, "%s:%s", hp->name, hp->value);
1068                 hp = hp->next;  /* next header field */
1069         }
1070
1071         return OK;
1072 }