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