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