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