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