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