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