mutt stable branch with some hacks
1/*
2 * Copyright (C) 1996-2000,2007,2010,2013 Michael R. Elkins <me@mutt.org>
3 * Copyright (C) 1999-2008 Thomas Roessler <roessler@does-not-exist.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20#if HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "version.h"
25#include "mutt.h"
26#include "mutt_curses.h"
27#include "mime.h"
28#include "mailbox.h"
29#include "mx.h"
30#include "url.h"
31
32#ifdef USE_IMAP
33#include "imap.h"
34#endif
35
36#include "mutt_crypt.h"
37
38#include <string.h>
39#include <ctype.h>
40#include <unistd.h>
41#include <stdlib.h>
42#include <sys/wait.h>
43#include <errno.h>
44#include <sys/stat.h>
45#include <fcntl.h>
46#include <time.h>
47#include <sys/types.h>
48#include <utime.h>
49#include <dirent.h>
50
51BODY *mutt_new_body (void)
52{
53 BODY *p = (BODY *) safe_calloc (1, sizeof (BODY));
54
55 p->disposition = DISPATTACH;
56 p->use_disp = 1;
57 return (p);
58}
59
60/* Modified by blong to accept a "suggestion" for file name. If
61 * that file exists, then construct one with unique name but
62 * keep any extension. This might fail, I guess.
63 * Renamed to mutt_adv_mktemp so I only have to change where it's
64 * called, and not all possible cases.
65 */
66void mutt_adv_mktemp (BUFFER *buf)
67{
68 BUFFER *prefix = NULL;
69 char *suffix;
70 struct stat sb;
71
72 if (!(buf->data && buf->data[0]))
73 {
74 mutt_buffer_mktemp (buf);
75 }
76 else
77 {
78 prefix = mutt_buffer_pool_get ();
79 mutt_buffer_strcpy (prefix, buf->data);
80 mutt_sanitize_filename (prefix->data, 1);
81 mutt_buffer_printf (buf, "%s/%s", NONULL (Tempdir), mutt_b2s (prefix));
82 if (lstat (mutt_b2s (buf), &sb) == -1 && errno == ENOENT)
83 goto out;
84
85 if ((suffix = strrchr (prefix->data, '.')) != NULL)
86 {
87 *suffix = 0;
88 ++suffix;
89 }
90 mutt_buffer_mktemp_pfx_sfx (buf, mutt_b2s (prefix), suffix);
91
92 out:
93 mutt_buffer_pool_release (&prefix);
94 }
95}
96
97/* create a send-mode duplicate from a receive-mode body */
98
99int mutt_copy_body (FILE *fp, BODY **tgt, BODY *src)
100{
101 BUFFER *tmp = NULL;
102 BODY *b;
103 PARAMETER *par, **ppar;
104 short use_disp;
105
106 tmp = mutt_buffer_pool_get ();
107
108 if (src->filename)
109 {
110 use_disp = 1;
111 mutt_buffer_strcpy (tmp, src->filename);
112 }
113 else
114 {
115 use_disp = 0;
116 }
117
118 mutt_adv_mktemp (tmp);
119 if (mutt_save_attachment (fp, src, mutt_b2s (tmp), 0, NULL) == -1)
120 {
121 mutt_buffer_pool_release (&tmp);
122 return -1;
123 }
124
125 *tgt = mutt_new_body ();
126 b = *tgt;
127
128 memcpy (b, src, sizeof (BODY));
129 b->parts = NULL;
130 b->next = NULL;
131
132 b->filename = safe_strdup (mutt_b2s (tmp));
133 b->use_disp = use_disp;
134 b->unlink = 1;
135
136 if (mutt_is_text_part (b))
137 b->noconv = 1;
138
139 b->xtype = safe_strdup (b->xtype);
140 b->subtype = safe_strdup (b->subtype);
141 b->form_name = safe_strdup (b->form_name);
142 b->d_filename = safe_strdup (b->d_filename);
143 /* mutt_adv_mktemp() will mangle the filename in tmp,
144 * so preserve it in d_filename */
145 if (!b->d_filename && use_disp)
146 b->d_filename = safe_strdup (src->filename);
147 b->description = safe_strdup (b->description);
148
149 /*
150 * we don't seem to need the HEADER structure currently.
151 * XXX - this may change in the future
152 */
153
154 if (b->hdr) b->hdr = NULL;
155
156 /* copy parameters */
157 for (par = b->parameter, ppar = &b->parameter; par; ppar = &(*ppar)->next, par = par->next)
158 {
159 *ppar = mutt_new_parameter ();
160 (*ppar)->attribute = safe_strdup (par->attribute);
161 (*ppar)->value = safe_strdup (par->value);
162 }
163
164 mutt_stamp_attachment (b);
165
166 mutt_buffer_pool_release (&tmp);
167 return 0;
168}
169
170
171
172void mutt_free_body (BODY **p)
173{
174 BODY *a = *p, *b;
175
176 while (a)
177 {
178 b = a;
179 a = a->next;
180
181 if (b->parameter)
182 mutt_free_parameter (&b->parameter);
183 if (b->filename)
184 {
185 if (b->unlink)
186 unlink (b->filename);
187 dprint (1, (debugfile, "mutt_free_body: %sunlinking %s.\n",
188 b->unlink ? "" : "not ", b->filename));
189 }
190
191 FREE (&b->filename);
192 FREE (&b->d_filename);
193 FREE (&b->charset);
194 FREE (&b->content);
195 FREE (&b->xtype);
196 FREE (&b->subtype);
197 FREE (&b->description);
198 FREE (&b->form_name);
199
200 if (b->hdr)
201 {
202 /* Don't free twice (b->hdr->content = b->parts) */
203 b->hdr->content = NULL;
204 mutt_free_header(&b->hdr);
205 }
206
207 mutt_free_envelope (&b->mime_headers);
208
209 if (b->parts)
210 mutt_free_body (&b->parts);
211
212 FREE (&b);
213 }
214
215 *p = 0;
216}
217
218void mutt_free_parameter (PARAMETER **p)
219{
220 PARAMETER *t = *p;
221 PARAMETER *o;
222
223 while (t)
224 {
225 FREE (&t->attribute);
226 FREE (&t->value);
227 o = t;
228 t = t->next;
229 FREE (&o);
230 }
231 *p = 0;
232}
233
234LIST *mutt_add_list (LIST *head, const char *data)
235{
236 size_t len = mutt_strlen (data);
237
238 return mutt_add_list_n (head, data, len ? len + 1 : 0);
239}
240
241LIST *mutt_add_list_n (LIST *head, const void *data, size_t len)
242{
243 LIST *tmp;
244
245 for (tmp = head; tmp && tmp->next; tmp = tmp->next)
246 ;
247 if (tmp)
248 {
249 tmp->next = safe_malloc (sizeof (LIST));
250 tmp = tmp->next;
251 }
252 else
253 head = tmp = safe_malloc (sizeof (LIST));
254
255 tmp->data = safe_malloc (len);
256 if (len)
257 memcpy (tmp->data, data, len);
258 tmp->next = NULL;
259 return head;
260}
261
262LIST *mutt_find_list (LIST *l, const char *data)
263{
264 LIST *p = l;
265
266 while (p)
267 {
268 if (data == p->data)
269 return p;
270 if (data && p->data && mutt_strcmp (p->data, data) == 0)
271 return p;
272 p = p->next;
273 }
274 return NULL;
275}
276
277int mutt_remove_from_rx_list (RX_LIST **l, const char *str)
278{
279 RX_LIST *p, *last = NULL;
280 int rv = -1;
281
282 if (mutt_strcmp ("*", str) == 0)
283 {
284 mutt_free_rx_list (l); /* ``unCMD *'' means delete all current entries */
285 rv = 0;
286 }
287 else
288 {
289 p = *l;
290 last = NULL;
291 while (p)
292 {
293 if (ascii_strcasecmp (str, p->rx->pattern) == 0)
294 {
295 mutt_free_regexp (&p->rx);
296 if (last)
297 last->next = p->next;
298 else
299 (*l) = p->next;
300 FREE (&p);
301 rv = 0;
302 }
303 else
304 {
305 last = p;
306 p = p->next;
307 }
308 }
309 }
310 return (rv);
311}
312
313void mutt_free_list (LIST **list)
314{
315 LIST *p;
316
317 if (!list) return;
318 while (*list)
319 {
320 p = *list;
321 *list = (*list)->next;
322 FREE (&p->data);
323 FREE (&p);
324 }
325}
326
327void mutt_free_list_generic(LIST **list, void (*data_free)(char **))
328{
329 LIST *p;
330
331 /* wrap mutt_free_list if no data_free function was provided */
332 if (data_free == NULL)
333 {
334 mutt_free_list(list);
335 return;
336 }
337
338 if (!list) return;
339 while (*list)
340 {
341 p = *list;
342 *list = (*list)->next;
343 data_free(&p->data);
344 FREE (&p);
345 }
346}
347
348LIST *mutt_copy_list (LIST *p)
349{
350 LIST *t, *r=NULL, *l=NULL;
351
352 for (; p; p = p->next)
353 {
354 t = (LIST *) safe_malloc (sizeof (LIST));
355 t->data = safe_strdup (p->data);
356 t->next = NULL;
357 if (l)
358 {
359 r->next = t;
360 r = r->next;
361 }
362 else
363 l = r = t;
364 }
365 return (l);
366}
367
368HEADER *mutt_dup_header(HEADER *h)
369{
370 HEADER *hnew;
371
372 hnew = mutt_new_header();
373 memcpy(hnew, h, sizeof (HEADER));
374 return hnew;
375}
376
377void mutt_free_header (HEADER **h)
378{
379 if (!h || !*h) return;
380 mutt_free_envelope (&(*h)->env);
381 mutt_free_body (&(*h)->content);
382 FREE (&(*h)->maildir_flags);
383 FREE (&(*h)->tree);
384 FREE (&(*h)->path);
385#ifdef MIXMASTER
386 mutt_free_list (&(*h)->chain);
387#endif
388#if defined USE_POP || defined USE_IMAP
389 FREE (&(*h)->data);
390#endif
391 FREE (h); /* __FREE_CHECKED__ */
392}
393
394/* returns true if the header contained in "s" is in list "t" */
395int mutt_matches_ignore (const char *s, LIST *t)
396{
397 for (; t; t = t->next)
398 {
399 if (!ascii_strncasecmp (s, t->data, mutt_strlen (t->data)) || *t->data == '*')
400 return 1;
401 }
402 return 0;
403}
404
405char *mutt_expand_path (char *s, size_t slen)
406{
407 return _mutt_expand_path (s, slen, 0);
408}
409
410char *_mutt_expand_path (char *s, size_t slen, int rx)
411{
412 BUFFER *s_buf;
413
414 s_buf = mutt_buffer_pool_get ();
415
416 mutt_buffer_addstr (s_buf, NONULL (s));
417 _mutt_buffer_expand_path (s_buf, rx);
418 strfcpy (s, mutt_b2s (s_buf), slen);
419
420 mutt_buffer_pool_release (&s_buf);
421
422 return s;
423}
424
425void mutt_buffer_expand_path (BUFFER *src)
426{
427 _mutt_buffer_expand_path (src, 0);
428}
429
430void _mutt_buffer_expand_path (BUFFER *src, int rx)
431{
432 BUFFER *p, *q, *tmp;
433 const char *s, *tail = "";
434 char *t;
435 int recurse = 0;
436
437 p = mutt_buffer_pool_get ();
438 q = mutt_buffer_pool_get ();
439 tmp = mutt_buffer_pool_get ();
440
441 do
442 {
443 recurse = 0;
444 s = mutt_b2s (src);
445
446 switch (*s)
447 {
448 case '~':
449 {
450 if (*(s + 1) == '/' || *(s + 1) == 0)
451 {
452 mutt_buffer_strcpy (p, NONULL(Homedir));
453 tail = s + 1;
454 }
455 else
456 {
457 struct passwd *pw;
458 if ((t = strchr (s + 1, '/')))
459 *t = 0;
460
461 if ((pw = getpwnam (s + 1)))
462 {
463 mutt_buffer_strcpy (p, pw->pw_dir);
464 if (t)
465 {
466 *t = '/';
467 tail = t;
468 }
469 else
470 tail = "";
471 }
472 else
473 {
474 /* user not found! */
475 if (t)
476 *t = '/';
477 mutt_buffer_clear (p);
478 tail = s;
479 }
480 }
481 }
482 break;
483
484 case '=':
485 case '+':
486 {
487#ifdef USE_IMAP
488 /* if folder = {host} or imap[s]://host/: don't append slash */
489 if (mx_is_imap (NONULL (Maildir)) &&
490 (Maildir[strlen (Maildir) - 1] == '}' ||
491 Maildir[strlen (Maildir) - 1] == '/'))
492 mutt_buffer_strcpy (p, NONULL (Maildir));
493 else
494#endif
495 if (Maildir && Maildir[strlen (Maildir) - 1] == '/')
496 mutt_buffer_strcpy (p, NONULL (Maildir));
497 else
498 mutt_buffer_printf (p, "%s/", NONULL (Maildir));
499
500 tail = s + 1;
501 }
502 break;
503
504 /* elm compatibility, @ expands alias to user name */
505
506 case '@':
507 {
508 HEADER *h;
509 ADDRESS *alias;
510
511 if ((alias = mutt_lookup_alias (s + 1)))
512 {
513 h = mutt_new_header();
514 h->env = mutt_new_envelope();
515 h->env->from = h->env->to = alias;
516
517 /* TODO: fix mutt_default_save() to use BUFFER */
518 mutt_buffer_increase_size (p, _POSIX_PATH_MAX);
519 mutt_default_save (p->data, p->dsize, h);
520 mutt_buffer_fix_dptr (p);
521
522 h->env->from = h->env->to = NULL;
523 mutt_free_header (&h);
524 /* Avoid infinite recursion if the resulting folder starts with '@' */
525 if (*(mutt_b2s (p)) != '@')
526 recurse = 1;
527
528 tail = "";
529 }
530 }
531 break;
532
533 case '>':
534 {
535 mutt_buffer_strcpy (p, NONULL(Inbox));
536 tail = s + 1;
537 }
538 break;
539
540 case '<':
541 {
542 mutt_buffer_strcpy (p, NONULL(Outbox));
543 tail = s + 1;
544 }
545 break;
546
547 case '!':
548 {
549 if (*(s+1) == '!')
550 {
551 mutt_buffer_strcpy (p, NONULL(LastFolder));
552 tail = s + 2;
553 }
554 else
555 {
556 mutt_buffer_strcpy (p, NONULL(Spoolfile));
557 tail = s + 1;
558 }
559 }
560 break;
561
562 case '-':
563 {
564 mutt_buffer_strcpy (p, NONULL(LastFolder));
565 tail = s + 1;
566 }
567 break;
568
569 case '^':
570 {
571 mutt_buffer_strcpy (p, NONULL(CurrentFolder));
572 tail = s + 1;
573 }
574 break;
575
576 default:
577 {
578 mutt_buffer_clear (p);
579 tail = s;
580 }
581 }
582
583 if (rx && *(mutt_b2s (p)) && !recurse)
584 {
585 mutt_rx_sanitize_string (q, mutt_b2s (p));
586 mutt_buffer_printf (tmp, "%s%s", mutt_b2s (q), tail);
587 }
588 else
589 mutt_buffer_printf (tmp, "%s%s", mutt_b2s (p), tail);
590
591 mutt_buffer_strcpy (src, mutt_b2s (tmp));
592 }
593 while (recurse);
594
595 mutt_buffer_pool_release (&p);
596 mutt_buffer_pool_release (&q);
597 mutt_buffer_pool_release (&tmp);
598
599#ifdef USE_IMAP
600 /* Rewrite IMAP path in canonical form - aids in string comparisons of
601 * folders. May possibly fail, in which case s should be the same. */
602 if (mx_is_imap (mutt_b2s (src)))
603 imap_expand_path (src);
604#endif
605}
606
607/* Extract the real name from /etc/passwd's GECOS field.
608 * When set, honor the regular expression in GecosMask,
609 * otherwise assume that the GECOS field is a
610 * comma-separated list.
611 * Replace "&" by a capitalized version of the user's login
612 * name.
613 */
614
615char *mutt_gecos_name (char *dest, size_t destlen, struct passwd *pw)
616{
617 regmatch_t pat_match[1];
618 size_t pwnl;
619 int idx;
620 char *p;
621
622 if (!pw || !pw->pw_gecos)
623 return NULL;
624
625 memset (dest, 0, destlen);
626
627 if (GecosMask.rx)
628 {
629 if (regexec (GecosMask.rx, pw->pw_gecos, 1, pat_match, 0) == 0)
630 strfcpy (dest, pw->pw_gecos + pat_match[0].rm_so,
631 MIN (pat_match[0].rm_eo - pat_match[0].rm_so + 1, destlen));
632 }
633 else if ((p = strchr (pw->pw_gecos, ',')))
634 strfcpy (dest, pw->pw_gecos, MIN (destlen, p - pw->pw_gecos + 1));
635 else
636 strfcpy (dest, pw->pw_gecos, destlen);
637
638 pwnl = strlen (pw->pw_name);
639
640 for (idx = 0; dest[idx]; idx++)
641 {
642 if (dest[idx] == '&')
643 {
644 memmove (&dest[idx + pwnl], &dest[idx + 1],
645 MAX((ssize_t)(destlen - idx - pwnl - 1), 0));
646 memcpy (&dest[idx], pw->pw_name, MIN(destlen - idx - 1, pwnl));
647 dest[idx] = toupper ((unsigned char) dest[idx]);
648 }
649 }
650
651 return dest;
652}
653
654
655char *mutt_get_parameter (const char *s, PARAMETER *p)
656{
657 for (; p; p = p->next)
658 if (ascii_strcasecmp (s, p->attribute) == 0)
659 return (p->value);
660
661 return NULL;
662}
663
664void mutt_set_parameter (const char *attribute, const char *value, PARAMETER **p)
665{
666 PARAMETER *q;
667
668 if (!value)
669 {
670 mutt_delete_parameter (attribute, p);
671 return;
672 }
673
674 for (q = *p; q; q = q->next)
675 {
676 if (ascii_strcasecmp (attribute, q->attribute) == 0)
677 {
678 mutt_str_replace (&q->value, value);
679 return;
680 }
681 }
682
683 q = mutt_new_parameter();
684 q->attribute = safe_strdup(attribute);
685 q->value = safe_strdup(value);
686 q->next = *p;
687 *p = q;
688}
689
690void mutt_delete_parameter (const char *attribute, PARAMETER **p)
691{
692 PARAMETER *q;
693
694 for (q = *p; q; p = &q->next, q = q->next)
695 {
696 if (ascii_strcasecmp (attribute, q->attribute) == 0)
697 {
698 *p = q->next;
699 q->next = NULL;
700 mutt_free_parameter (&q);
701 return;
702 }
703 }
704}
705
706/* returns 1 if Mutt can't display this type of data, 0 otherwise */
707int mutt_needs_mailcap (BODY *m)
708{
709 switch (m->type)
710 {
711 case TYPETEXT:
712 /* we can display any text, overridable by auto_view */
713 return 0;
714 break;
715
716 case TYPEAPPLICATION:
717 if ((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp(m))
718 return 0;
719 if ((WithCrypto & APPLICATION_SMIME) && mutt_is_application_smime(m))
720 return 0;
721 break;
722
723 case TYPEMULTIPART:
724 case TYPEMESSAGE:
725 return 0;
726 }
727
728 return 1;
729}
730
731int mutt_is_text_part (BODY *b)
732{
733 int t = b->type;
734 char *s = b->subtype;
735
736 if ((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp (b))
737 return 0;
738
739 if (t == TYPETEXT)
740 return 1;
741
742 if (t == TYPEMESSAGE)
743 {
744 if (!ascii_strcasecmp ("delivery-status", s))
745 return 1;
746 }
747
748 if ((WithCrypto & APPLICATION_PGP) && t == TYPEAPPLICATION)
749 {
750 if (!ascii_strcasecmp ("pgp-keys", s))
751 return 1;
752 }
753
754 return 0;
755}
756
757#ifdef USE_AUTOCRYPT
758void mutt_free_autocrypthdr (AUTOCRYPTHDR **p)
759{
760 AUTOCRYPTHDR *cur;
761
762 if (!p)
763 return;
764
765 while (*p)
766 {
767 cur = *p;
768 *p = (*p)->next;
769 FREE (&cur->addr);
770 FREE (&cur->keydata);
771 FREE (&cur);
772 }
773}
774#endif
775
776void mutt_free_envelope (ENVELOPE **p)
777{
778 if (!*p) return;
779 rfc822_free_address (&(*p)->return_path);
780 rfc822_free_address (&(*p)->from);
781 rfc822_free_address (&(*p)->to);
782 rfc822_free_address (&(*p)->cc);
783 rfc822_free_address (&(*p)->bcc);
784 rfc822_free_address (&(*p)->sender);
785 rfc822_free_address (&(*p)->reply_to);
786 rfc822_free_address (&(*p)->mail_followup_to);
787
788 FREE (&(*p)->list_post);
789 FREE (&(*p)->subject);
790 /* real_subj is just an offset to subject and shouldn't be freed */
791 FREE (&(*p)->disp_subj);
792 FREE (&(*p)->message_id);
793 FREE (&(*p)->supersedes);
794 FREE (&(*p)->date);
795 FREE (&(*p)->x_label);
796
797 mutt_buffer_free (&(*p)->spam);
798
799 mutt_free_list (&(*p)->references);
800 mutt_free_list (&(*p)->in_reply_to);
801 mutt_free_list (&(*p)->userhdrs);
802
803#ifdef USE_AUTOCRYPT
804 mutt_free_autocrypthdr (&(*p)->autocrypt);
805 mutt_free_autocrypthdr (&(*p)->autocrypt_gossip);
806#endif
807
808 FREE (p); /* __FREE_CHECKED__ */
809}
810
811/* move all the headers from extra not present in base into base */
812void mutt_merge_envelopes(ENVELOPE* base, ENVELOPE** extra)
813{
814 /* copies each existing element if necessary, and sets the element
815 * to NULL in the source so that mutt_free_envelope doesn't leave us
816 * with dangling pointers. */
817#define MOVE_ELEM(h) if (!base->h) { base->h = (*extra)->h; (*extra)->h = NULL; }
818 MOVE_ELEM(return_path);
819 MOVE_ELEM(from);
820 MOVE_ELEM(to);
821 MOVE_ELEM(cc);
822 MOVE_ELEM(bcc);
823 MOVE_ELEM(sender);
824 MOVE_ELEM(reply_to);
825 MOVE_ELEM(mail_followup_to);
826 MOVE_ELEM(list_post);
827 MOVE_ELEM(message_id);
828 MOVE_ELEM(supersedes);
829 MOVE_ELEM(date);
830 if (!(base->changed & MUTT_ENV_CHANGED_XLABEL))
831 {
832 MOVE_ELEM(x_label);
833 }
834 if (!(base->changed & MUTT_ENV_CHANGED_REFS))
835 {
836 MOVE_ELEM(references);
837 }
838 if (!(base->changed & MUTT_ENV_CHANGED_IRT))
839 {
840 MOVE_ELEM(in_reply_to);
841 }
842
843 /* real_subj is subordinate to subject */
844 if (!base->subject)
845 {
846 base->subject = (*extra)->subject;
847 base->real_subj = (*extra)->real_subj;
848 base->disp_subj = (*extra)->disp_subj;
849 (*extra)->subject = NULL;
850 (*extra)->real_subj = NULL;
851 (*extra)->disp_subj = NULL;
852 }
853 /* spam and user headers should never be hashed, and the new envelope may
854 * have better values. Use new versions regardless. */
855 mutt_buffer_free (&base->spam);
856 mutt_free_list (&base->userhdrs);
857 MOVE_ELEM(spam);
858 MOVE_ELEM(userhdrs);
859#undef MOVE_ELEM
860
861 mutt_free_envelope(extra);
862}
863
864void _mutt_buffer_mktemp (BUFFER *buf, const char *prefix, const char *suffix,
865 const char *src, int line)
866{
867 mutt_buffer_printf (buf, "%s/%s-%s-%d-%d-%ld%ld%s%s",
868 NONULL (Tempdir), NONULL (prefix), NONULL (Hostname),
869 (int) getuid (), (int) getpid (), random (), random (),
870 suffix ? "." : "", NONULL (suffix));
871 dprint (3, (debugfile, "%s:%d: mutt_mktemp returns \"%s\".\n", src, line, mutt_b2s (buf)));
872 if (unlink (mutt_b2s (buf)) && errno != ENOENT)
873 dprint (1, (debugfile, "%s:%d: ERROR: unlink(\"%s\"): %s (errno %d)\n",
874 src, line, mutt_b2s (buf), strerror (errno), errno));
875}
876
877void _mutt_mktemp (char *s, size_t slen, const char *prefix, const char *suffix,
878 const char *src, int line)
879{
880 size_t n = snprintf (s, slen, "%s/%s-%s-%d-%d-%ld%ld%s%s",
881 NONULL (Tempdir), NONULL (prefix), NONULL (Hostname),
882 (int) getuid (), (int) getpid (), random (), random (),
883 suffix ? "." : "", NONULL (suffix));
884 if (n >= slen)
885 dprint (1, (debugfile, "%s:%d: ERROR: insufficient buffer space to hold temporary filename! slen=%zu but need %zu\n",
886 src, line, slen, n));
887 dprint (3, (debugfile, "%s:%d: mutt_mktemp returns \"%s\".\n", src, line, s));
888 if (unlink (s) && errno != ENOENT)
889 dprint (1, (debugfile, "%s:%d: ERROR: unlink(\"%s\"): %s (errno %d)\n", src, line, s, strerror (errno), errno));
890}
891
892/* these characters must be escaped in regular expressions */
893
894static const char rx_special_chars[] = "^.[$()|*+?{\\";
895
896int mutt_rx_sanitize_string (BUFFER *dest, const char *src)
897{
898 mutt_buffer_clear (dest);
899 while (*src)
900 {
901 if (strchr (rx_special_chars, *src))
902 mutt_buffer_addch (dest, '\\');
903 mutt_buffer_addch (dest, *src++);
904 }
905 return 0;
906}
907
908void mutt_free_alias (ALIAS **p)
909{
910 ALIAS *t;
911
912 while (*p)
913 {
914 t = *p;
915 *p = (*p)->next;
916 mutt_alias_delete_reverse (t);
917 FREE (&t->name);
918 rfc822_free_address (&t->addr);
919 FREE (&t);
920 }
921}
922
923void mutt_buffer_pretty_mailbox (BUFFER *s)
924{
925 /* This reduces the size of the BUFFER, so we can pass it through.
926 * We adjust the size just to make sure s->data is not NULL though */
927 mutt_buffer_increase_size (s, _POSIX_PATH_MAX);
928 mutt_pretty_mailbox (s->data, s->dsize);
929 mutt_buffer_fix_dptr (s);
930}
931
932/* collapse the pathname using ~ or = when possible */
933void mutt_pretty_mailbox (char *s, size_t buflen)
934{
935 char *p = s, *q = s;
936 size_t len;
937 url_scheme_t scheme;
938 char tmp[PATH_MAX];
939
940 scheme = url_check_scheme (s);
941
942#ifdef USE_IMAP
943 if (scheme == U_IMAP || scheme == U_IMAPS)
944 {
945 imap_pretty_mailbox (s, buflen);
946 return;
947 }
948#endif
949
950 /* if s is an url, only collapse path component */
951 if (scheme != U_UNKNOWN)
952 {
953 p = strchr(s, ':')+1;
954 if (!strncmp (p, "//", 2))
955 q = strchr (p+2, '/');
956 if (!q)
957 q = strchr (p, '\0');
958 p = q;
959 }
960
961 /* cleanup path */
962 if (strstr (p, "//") || strstr (p, "/./"))
963 {
964 /* first attempt to collapse the pathname, this is more
965 * lightweight than realpath() and doesn't resolve links
966 */
967 while (*p)
968 {
969 if (*p == '/' && p[1] == '/')
970 {
971 *q++ = '/';
972 p += 2;
973 }
974 else if (p[0] == '/' && p[1] == '.' && p[2] == '/')
975 {
976 *q++ = '/';
977 p += 3;
978 }
979 else
980 *q++ = *p++;
981 }
982 *q = 0;
983 }
984 else if (strstr (p, "..") &&
985 (scheme == U_UNKNOWN || scheme == U_FILE) &&
986 realpath (p, tmp))
987 strfcpy (p, tmp, buflen - (p - s));
988
989 if (mutt_strncmp (s, Maildir, (len = mutt_strlen (Maildir))) == 0 &&
990 s[len] == '/')
991 {
992 *s++ = '=';
993 memmove (s, s + len, mutt_strlen (s + len) + 1);
994 }
995 else if (mutt_strncmp (s, Homedir, (len = mutt_strlen (Homedir))) == 0 &&
996 s[len] == '/')
997 {
998 *s++ = '~';
999 memmove (s, s + len - 1, mutt_strlen (s + len - 1) + 1);
1000 }
1001}
1002
1003void mutt_pretty_size (char *s, size_t len, LOFF_T n)
1004{
1005 if (option (OPTSIZESHOWBYTES) && (n < 1024))
1006 snprintf (s, len, "%d", (int)n);
1007 else if (n == 0)
1008 strfcpy (s,
1009 option (OPTSIZEUNITSONLEFT) ? "K0" : "0K",
1010 len);
1011 else if (option (OPTSIZESHOWFRACTIONS) && (n < 10189)) /* 0.1K - 9.9K */
1012 {
1013 snprintf (s, len,
1014 option (OPTSIZEUNITSONLEFT) ? "K%3.1f" : "%3.1fK",
1015 (n < 103) ? 0.1 : n / 1024.0);
1016 }
1017 else if (!option (OPTSIZESHOWMB) || (n < 1023949)) /* 10K - 999K */
1018 {
1019 /* 51 is magic which causes 10189/10240 to be rounded up to 10 */
1020 snprintf (s, len,
1021 option (OPTSIZEUNITSONLEFT) ? ("K" OFF_T_FMT) : (OFF_T_FMT "K"),
1022 (n + 51) / 1024);
1023 }
1024 else if (option (OPTSIZESHOWFRACTIONS) && (n < 10433332)) /* 1.0M - 9.9M */
1025 {
1026 snprintf (s, len,
1027 option (OPTSIZEUNITSONLEFT) ? "M%3.1f" : "%3.1fM",
1028 n / 1048576.0);
1029 }
1030 else /* 10M+ */
1031 {
1032 /* (10433332 + 52428) / 1048576 = 10 */
1033 snprintf (s, len,
1034 option (OPTSIZEUNITSONLEFT) ? ("M" OFF_T_FMT) : (OFF_T_FMT "M"),
1035 (n + 52428) / 1048576);
1036 }
1037}
1038
1039void _mutt_buffer_quote_filename (BUFFER *d, const char *f, int add_outer)
1040{
1041 mutt_buffer_clear (d);
1042
1043 if (!f)
1044 return;
1045
1046 if (add_outer)
1047 mutt_buffer_addch (d, '\'');
1048
1049 for (; *f; f++)
1050 {
1051 if (*f == '\'' || *f == '`')
1052 {
1053 mutt_buffer_addch (d, '\'');
1054 mutt_buffer_addch (d, '\\');
1055 mutt_buffer_addch (d, *f);
1056 mutt_buffer_addch (d, '\'');
1057 }
1058 else
1059 mutt_buffer_addch (d, *f);
1060 }
1061
1062 if (add_outer)
1063 mutt_buffer_addch (d, '\'');
1064}
1065
1066static const char safe_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
1067
1068void mutt_buffer_sanitize_filename (BUFFER *d, const char *f, short slash)
1069{
1070 mutt_buffer_clear (d);
1071
1072 if (!f)
1073 return;
1074
1075 for (; *f; f++)
1076 {
1077 if ((slash && *f == '/') || !strchr (safe_chars, *f))
1078 mutt_buffer_addch (d, '_');
1079 else
1080 mutt_buffer_addch (d, *f);
1081 }
1082}
1083
1084void mutt_expand_file_fmt (BUFFER *dest, const char *fmt, const char *src)
1085{
1086 BUFFER *tmp;
1087
1088 tmp = mutt_buffer_pool_get ();
1089 mutt_buffer_quote_filename (tmp, src);
1090 mutt_expand_fmt (dest, fmt, mutt_b2s (tmp));
1091 mutt_buffer_pool_release (&tmp);
1092}
1093
1094void mutt_expand_fmt (BUFFER *dest, const char *fmt, const char *src)
1095{
1096 const char *p;
1097 int found = 0;
1098
1099 mutt_buffer_clear (dest);
1100
1101 for (p = fmt; *p; p++)
1102 {
1103 if (*p == '%')
1104 {
1105 switch (p[1])
1106 {
1107 case '%':
1108 mutt_buffer_addch (dest, *p++);
1109 break;
1110 case 's':
1111 found = 1;
1112 mutt_buffer_addstr (dest, src);
1113 p++;
1114 break;
1115 default:
1116 mutt_buffer_addch (dest, *p);
1117 break;
1118 }
1119 }
1120 else
1121 {
1122 mutt_buffer_addch (dest, *p);
1123 }
1124 }
1125
1126 if (!found)
1127 {
1128 mutt_buffer_addch (dest, ' ');
1129 mutt_buffer_addstr (dest, src);
1130 }
1131}
1132
1133/* return 0 on success, -1 on abort, 1 on error */
1134int mutt_check_overwrite (const char *attname, const char *path,
1135 BUFFER *fname, int *append, char **directory)
1136{
1137 int rc = 0;
1138 BUFFER *tmp = NULL;
1139 struct stat st;
1140
1141 mutt_buffer_strcpy (fname, path);
1142 if (access (mutt_b2s (fname), F_OK) != 0)
1143 return 0;
1144 if (stat (mutt_b2s (fname), &st) != 0)
1145 return -1;
1146 if (S_ISDIR (st.st_mode))
1147 {
1148 if (directory)
1149 {
1150 switch (mutt_multi_choice
1151 /* L10N:
1152 Means "The path you specified as the destination file is a directory."
1153 See the msgid "Save to file: " (alias.c, recvattach.c) */
1154 (_("File is a directory, save under it? [(y)es, (n)o, (a)ll]"), _("yna")))
1155 {
1156 case 3: /* all */
1157 mutt_str_replace (directory, mutt_b2s (fname));
1158 break;
1159 case 1: /* yes */
1160 FREE (directory); /* __FREE_CHECKED__ */
1161 break;
1162 case -1: /* abort */
1163 FREE (directory); /* __FREE_CHECKED__ */
1164 return -1;
1165 case 2: /* no */
1166 FREE (directory); /* __FREE_CHECKED__ */
1167 return 1;
1168 }
1169 }
1170 /* L10N:
1171 Means "The path you specified as the destination file is a directory."
1172 See the msgid "Save to file: " (alias.c, recvattach.c) */
1173 else if ((rc = mutt_yesorno (_("File is a directory, save under it?"), MUTT_YES)) != MUTT_YES)
1174 return (rc == MUTT_NO) ? 1 : -1;
1175
1176 tmp = mutt_buffer_pool_get ();
1177 mutt_buffer_strcpy (tmp, mutt_basename (NONULL (attname)));
1178 if ((mutt_buffer_get_field (_("File under directory: "), tmp,
1179 MUTT_FILE | MUTT_CLEAR) != 0) ||
1180 !mutt_buffer_len (tmp))
1181 {
1182 mutt_buffer_pool_release (&tmp);
1183 return (-1);
1184 }
1185 mutt_buffer_concat_path (fname, path, mutt_b2s (tmp));
1186 mutt_buffer_pool_release (&tmp);
1187 }
1188
1189 if (*append == 0 && access (mutt_b2s (fname), F_OK) == 0)
1190 {
1191 switch (mutt_multi_choice
1192 (_("File exists, (o)verwrite, (a)ppend, or (c)ancel?"), _("oac")))
1193 {
1194 case -1: /* abort */
1195 return -1;
1196 case 3: /* cancel */
1197 return 1;
1198
1199 case 2: /* append */
1200 *append = MUTT_SAVE_APPEND;
1201 break;
1202 case 1: /* overwrite */
1203 *append = MUTT_SAVE_OVERWRITE;
1204 break;
1205 }
1206 }
1207 return 0;
1208}
1209
1210void mutt_save_path (char *d, size_t dsize, ADDRESS *a)
1211{
1212 if (a && a->mailbox)
1213 {
1214 strfcpy (d, a->mailbox, dsize);
1215 if (!option (OPTSAVEADDRESS))
1216 {
1217 char *p;
1218
1219 if ((p = strpbrk (d, "%@")))
1220 *p = 0;
1221 }
1222 mutt_strlower (d);
1223 }
1224 else
1225 *d = 0;
1226}
1227
1228void mutt_buffer_save_path (BUFFER *dest, ADDRESS *a)
1229{
1230 if (a && a->mailbox)
1231 {
1232 mutt_buffer_strcpy (dest, a->mailbox);
1233 if (!option (OPTSAVEADDRESS))
1234 {
1235 char *p;
1236
1237 if ((p = strpbrk (dest->data, "%@")))
1238 {
1239 *p = 0;
1240 mutt_buffer_fix_dptr (dest);
1241 }
1242 }
1243 mutt_strlower (dest->data);
1244 }
1245 else
1246 mutt_buffer_clear (dest);
1247}
1248
1249void mutt_safe_path (BUFFER *dest, ADDRESS *a)
1250{
1251 char *p;
1252
1253 mutt_buffer_save_path (dest, a);
1254 for (p = dest->data; *p; p++)
1255 if (*p == '/' || ISSPACE (*p) || !IsPrint ((unsigned char) *p))
1256 *p = '_';
1257}
1258
1259void mutt_buffer_concat_path (BUFFER *d, const char *dir, const char *fname)
1260{
1261 const char *fmt = "%s/%s";
1262
1263 if (!*fname || (*dir && dir[strlen(dir)-1] == '/'))
1264 fmt = "%s%s";
1265
1266 mutt_buffer_printf (d, fmt, dir, fname);
1267}
1268
1269/*
1270 * Write the concatened pathname (dir + "/" + fname) into dst.
1271 * The slash is omitted when dir or fname is of 0 length.
1272 */
1273void mutt_buffer_concatn_path (BUFFER *dst, const char *dir, size_t dirlen,
1274 const char *fname, size_t fnamelen)
1275{
1276 mutt_buffer_clear (dst);
1277 if (dirlen)
1278 mutt_buffer_addstr_n (dst, dir, dirlen);
1279 if (dirlen && fnamelen)
1280 mutt_buffer_addch (dst, '/');
1281 if (fnamelen)
1282 mutt_buffer_addstr_n (dst, fname, fnamelen);
1283}
1284
1285const char *mutt_getcwd (BUFFER *cwd)
1286{
1287 char *retval;
1288
1289 mutt_buffer_increase_size (cwd, _POSIX_PATH_MAX);
1290 retval = getcwd (cwd->data, cwd->dsize);
1291 while (!retval && errno == ERANGE)
1292 {
1293 mutt_buffer_increase_size (cwd, cwd->dsize + STRING);
1294 retval = getcwd (cwd->data, cwd->dsize);
1295 }
1296 if (retval)
1297 mutt_buffer_fix_dptr (cwd);
1298 else
1299 mutt_buffer_clear (cwd);
1300
1301 return retval;
1302}
1303
1304/* Note this function uses a fixed size buffer of LONG_STRING and so
1305 * should only be used for visual modifications, such as disp_subj. */
1306char *mutt_apply_replace (char *dbuf, size_t dlen, char *sbuf, REPLACE_LIST *rlist)
1307{
1308 REPLACE_LIST *l;
1309 static regmatch_t *pmatch = NULL;
1310 static int nmatch = 0;
1311 static char twinbuf[2][LONG_STRING];
1312 int switcher = 0;
1313 char *p;
1314 int i, n;
1315 size_t cpysize, tlen;
1316 char *src, *dst;
1317
1318 if (dbuf && dlen)
1319 dbuf[0] = '\0';
1320
1321 if (sbuf == NULL || *sbuf == '\0' || (dbuf && !dlen))
1322 return dbuf;
1323
1324 twinbuf[0][0] = '\0';
1325 twinbuf[1][0] = '\0';
1326 src = twinbuf[switcher];
1327 dst = src;
1328
1329 strfcpy(src, sbuf, LONG_STRING);
1330
1331 for (l = rlist; l; l = l->next)
1332 {
1333 /* If this pattern needs more matches, expand pmatch. */
1334 if (l->nmatch > nmatch)
1335 {
1336 safe_realloc (&pmatch, l->nmatch * sizeof(regmatch_t));
1337 nmatch = l->nmatch;
1338 }
1339
1340 if (regexec (l->rx->rx, src, l->nmatch, pmatch, 0) == 0)
1341 {
1342 tlen = 0;
1343 switcher ^= 1;
1344 dst = twinbuf[switcher];
1345
1346 dprint (5, (debugfile, "mutt_apply_replace: %s matches %s\n", src, l->rx->pattern));
1347
1348 /* Copy into other twinbuf with substitutions */
1349 if (l->template)
1350 {
1351 for (p = l->template; *p && (tlen < LONG_STRING - 1); )
1352 {
1353 if (*p == '%')
1354 {
1355 p++;
1356 if (*p == 'L')
1357 {
1358 p++;
1359 cpysize = MIN (pmatch[0].rm_so, LONG_STRING - tlen - 1);
1360 strncpy(&dst[tlen], src, cpysize);
1361 tlen += cpysize;
1362 }
1363 else if (*p == 'R')
1364 {
1365 p++;
1366 cpysize = MIN (strlen (src) - pmatch[0].rm_eo, LONG_STRING - tlen - 1);
1367 strncpy(&dst[tlen], &src[pmatch[0].rm_eo], cpysize);
1368 tlen += cpysize;
1369 }
1370 else
1371 {
1372 n = strtoul(p, &p, 10); /* get subst number */
1373 while (isdigit((unsigned char)*p)) /* skip subst token */
1374 ++p;
1375 for (i = pmatch[n].rm_so; (i < pmatch[n].rm_eo) && (tlen < LONG_STRING-1); i++)
1376 dst[tlen++] = src[i];
1377 }
1378 }
1379 else
1380 dst[tlen++] = *p++;
1381 }
1382 }
1383 dst[tlen] = '\0';
1384 dprint (5, (debugfile, "mutt_apply_replace: subst %s\n", dst));
1385 }
1386 src = dst;
1387 }
1388
1389 if (dbuf)
1390 strfcpy(dbuf, dst, dlen);
1391 else
1392 dbuf = safe_strdup(dst);
1393 return dbuf;
1394}
1395
1396
1397void mutt_FormatString (char *dest, /* output buffer */
1398 size_t destlen, /* output buffer len */
1399 size_t col, /* starting column (nonzero when called recursively) */
1400 int cols, /* maximum columns */
1401 const char *src, /* template string */
1402 format_t *callback, /* callback for processing */
1403 unsigned long data, /* callback data */
1404 format_flag flags) /* callback flags */
1405{
1406 char prefix[SHORT_STRING], buf[LONG_STRING], *cp, *wptr = dest, ch;
1407 char ifstring[SHORT_STRING], elsestring[SHORT_STRING];
1408 size_t wlen, count, len, wid;
1409 pid_t pid;
1410 FILE *filter;
1411 int n;
1412 char *recycler;
1413
1414 prefix[0] = '\0';
1415 destlen--; /* save room for the terminal \0 */
1416 wlen = ((flags & MUTT_FORMAT_ARROWCURSOR) && option (OPTARROWCURSOR)) ? 3 : 0;
1417 col += wlen;
1418
1419 if ((flags & MUTT_FORMAT_NOFILTER) == 0)
1420 {
1421 int off = -1;
1422
1423 /* Do not consider filters if no pipe at end */
1424 n = mutt_strlen(src);
1425 if (n > 1 && src[n-1] == '|')
1426 {
1427 /* Scan backwards for backslashes */
1428 off = n;
1429 while (off > 0 && src[off-2] == '\\')
1430 off--;
1431 }
1432
1433 /* If number of backslashes is even, the pipe is real. */
1434 /* n-off is the number of backslashes. */
1435 if (off > 0 && ((n-off) % 2) == 0)
1436 {
1437 BUFFER *srcbuf, *word, *command;
1438 char srccopy[LONG_STRING];
1439#ifdef DEBUG
1440 int i = 0;
1441#endif
1442
1443 dprint(3, (debugfile, "fmtpipe = %s\n", src));
1444
1445 strncpy(srccopy, src, n);
1446 srccopy[n-1] = '\0';
1447
1448 /* prepare BUFFERs */
1449 srcbuf = mutt_buffer_from (srccopy);
1450 /* note: we are resetting dptr and *reading* from the buffer, so we don't
1451 * want to use mutt_buffer_clear(). */
1452 srcbuf->dptr = srcbuf->data;
1453 word = mutt_buffer_new ();
1454 command = mutt_buffer_new ();
1455
1456 /* Iterate expansions across successive arguments */
1457 do
1458 {
1459 char *p;
1460
1461 /* Extract the command name and copy to command line */
1462 dprint(3, (debugfile, "fmtpipe +++: %s\n", srcbuf->dptr));
1463 if (word->data)
1464 *word->data = '\0';
1465 mutt_extract_token(word, srcbuf, 0);
1466 dprint(3, (debugfile, "fmtpipe %2d: %s\n", i++, word->data));
1467 mutt_buffer_addch(command, '\'');
1468 mutt_FormatString(buf, sizeof(buf), 0, cols, word->data, callback, data,
1469 flags | MUTT_FORMAT_NOFILTER);
1470 for (p = buf; p && *p; p++)
1471 {
1472 if (*p == '\'')
1473 /* shell quoting doesn't permit escaping a single quote within
1474 * single-quoted material. double-quoting instead will lead
1475 * shell variable expansions, so break out of the single-quoted
1476 * span, insert a double-quoted single quote, and resume. */
1477 mutt_buffer_addstr(command, "'\"'\"'");
1478 else
1479 mutt_buffer_addch(command, *p);
1480 }
1481 mutt_buffer_addch(command, '\'');
1482 mutt_buffer_addch(command, ' ');
1483 } while (MoreArgs(srcbuf));
1484
1485 dprint(3, (debugfile, "fmtpipe > %s\n", command->data));
1486
1487 col -= wlen; /* reset to passed in value */
1488 wptr = dest; /* reset write ptr */
1489 wlen = ((flags & MUTT_FORMAT_ARROWCURSOR) && option (OPTARROWCURSOR)) ? 3 : 0;
1490 if ((pid = mutt_create_filter(command->data, NULL, &filter, NULL)) != -1)
1491 {
1492 int rc;
1493
1494 n = fread(dest, 1, destlen /* already decremented */, filter);
1495 safe_fclose (&filter);
1496 rc = mutt_wait_filter(pid);
1497 if (rc != 0)
1498 dprint(1, (debugfile, "format pipe command exited code %d\n", rc));
1499 if (n > 0)
1500 {
1501 dest[n] = 0;
1502 while ((n > 0) && (dest[n-1] == '\n' || dest[n-1] == '\r'))
1503 dest[--n] = '\0';
1504 dprint(3, (debugfile, "fmtpipe < %s\n", dest));
1505
1506 /* If the result ends with '%', this indicates that the filter
1507 * generated %-tokens that mutt can expand. Eliminate the '%'
1508 * marker and recycle the string through mutt_FormatString().
1509 * To literally end with "%", use "%%". */
1510 if ((n > 0) && dest[n-1] == '%')
1511 {
1512 --n;
1513 dest[n] = '\0'; /* remove '%' */
1514 if ((n > 0) && dest[n-1] != '%')
1515 {
1516 recycler = safe_strdup(dest);
1517 if (recycler)
1518 {
1519 /* destlen is decremented at the start of this function
1520 * to save space for the terminal nul char. We can add
1521 * it back for the recursive call since the expansion of
1522 * format pipes does not try to append a nul itself.
1523 */
1524 mutt_FormatString(dest, destlen+1, col, cols, recycler, callback, data, flags);
1525 FREE(&recycler);
1526 }
1527 }
1528 }
1529 }
1530 else
1531 {
1532 /* read error */
1533 dprint(1, (debugfile, "error reading from fmtpipe: %s (errno=%d)\n", strerror(errno), errno));
1534 *wptr = 0;
1535 }
1536 }
1537 else
1538 {
1539 /* Filter failed; erase write buffer */
1540 *wptr = '\0';
1541 }
1542
1543 mutt_buffer_free(&command);
1544 mutt_buffer_free(&srcbuf);
1545 mutt_buffer_free(&word);
1546 return;
1547 }
1548 }
1549
1550 while (*src && wlen < destlen)
1551 {
1552 if (*src == '%')
1553 {
1554 if (*++src == '%')
1555 {
1556 *wptr++ = '%';
1557 wlen++;
1558 col++;
1559 src++;
1560 continue;
1561 }
1562
1563 if (*src == '?')
1564 {
1565 flags |= MUTT_FORMAT_OPTIONAL;
1566 src++;
1567 }
1568 else
1569 {
1570 flags &= ~MUTT_FORMAT_OPTIONAL;
1571
1572 /* eat the format string */
1573 cp = prefix;
1574 count = 0;
1575 while (count < sizeof (prefix) &&
1576 (isdigit ((unsigned char) *src) || *src == '.' || *src == '-' || *src == '='))
1577 {
1578 *cp++ = *src++;
1579 count++;
1580 }
1581 *cp = 0;
1582 }
1583
1584 if (!*src)
1585 break; /* bad format */
1586
1587 ch = *src++; /* save the character to switch on */
1588
1589 if (flags & MUTT_FORMAT_OPTIONAL)
1590 {
1591 if (*src != '?')
1592 break; /* bad format */
1593 src++;
1594
1595 /* eat the `if' part of the string */
1596 cp = ifstring;
1597 count = 0;
1598 while (count < sizeof (ifstring) && *src && *src != '?' && *src != '&')
1599 {
1600 *cp++ = *src++;
1601 count++;
1602 }
1603 *cp = 0;
1604
1605 /* eat the `else' part of the string (optional) */
1606 if (*src == '&')
1607 src++; /* skip the & */
1608 cp = elsestring;
1609 count = 0;
1610 while (count < sizeof (elsestring) && *src && *src != '?')
1611 {
1612 *cp++ = *src++;
1613 count++;
1614 }
1615 *cp = 0;
1616
1617 if (!*src)
1618 break; /* bad format */
1619
1620 src++; /* move past the trailing `?' */
1621 }
1622
1623 /* handle generic cases first */
1624 if (ch == '>' || ch == '*')
1625 {
1626 /* %>X: right justify to EOL, left takes precedence
1627 * %*X: right justify to EOL, right takes precedence */
1628 int soft = ch == '*';
1629 int pl, pw;
1630 if ((pl = mutt_charlen (src, &pw)) <= 0)
1631 pl = pw = 1;
1632
1633 /* see if there's room to add content, else ignore */
1634 if ((col < cols && wlen < destlen) || soft)
1635 {
1636 int pad;
1637
1638 /* get contents after padding */
1639 mutt_FormatString (buf, sizeof (buf), 0, cols, src + pl, callback, data, flags);
1640 len = mutt_strlen (buf);
1641 wid = mutt_strwidth (buf);
1642
1643 pad = (cols - col - wid) / pw;
1644 if (pad >= 0)
1645 {
1646 /* try to consume as many columns as we can, if we don't have
1647 * memory for that, use as much memory as possible */
1648 if (wlen + (pad * pl) + len > destlen)
1649 pad = (destlen > wlen + len) ? ((destlen - wlen - len) / pl) : 0;
1650 else
1651 {
1652 /* Add pre-spacing to make multi-column pad characters and
1653 * the contents after padding line up */
1654 while ((col + (pad * pw) + wid < cols) &&
1655 (wlen + (pad * pl) + len < destlen))
1656 {
1657 *wptr++ = ' ';
1658 wlen++;
1659 col++;
1660 }
1661 }
1662 while (pad-- > 0)
1663 {
1664 memcpy (wptr, src, pl);
1665 wptr += pl;
1666 wlen += pl;
1667 col += pw;
1668 }
1669 }
1670 else if (soft && pad < 0)
1671 {
1672 int offset = ((flags & MUTT_FORMAT_ARROWCURSOR) && option (OPTARROWCURSOR)) ? 3 : 0;
1673 int avail_cols = (cols > offset) ? (cols - offset) : 0;
1674 /* \0-terminate dest for length computation in mutt_wstr_trunc() */
1675 *wptr = 0;
1676 /* make sure right part is at most as wide as display */
1677 len = mutt_wstr_trunc (buf, destlen, avail_cols, &wid);
1678 /* truncate left so that right part fits completely in */
1679 wlen = mutt_wstr_trunc (dest, destlen - len, avail_cols - wid, &col);
1680 wptr = dest + wlen;
1681 /* Multi-column characters may be truncated in the middle.
1682 * Add spacing so the right hand side lines up. */
1683 while ((col + wid < avail_cols) && (wlen + len < destlen))
1684 {
1685 *wptr++ = ' ';
1686 wlen++;
1687 col++;
1688 }
1689 }
1690 if (len + wlen > destlen)
1691 len = mutt_wstr_trunc (buf, destlen - wlen, cols - col, NULL);
1692 memcpy (wptr, buf, len);
1693 wptr += len;
1694 wlen += len;
1695 col += wid;
1696 src += pl;
1697 }
1698 break; /* skip rest of input */
1699 }
1700 else if (ch == '|')
1701 {
1702 /* pad to EOL */
1703 int pl, pw, c;
1704 if ((pl = mutt_charlen (src, &pw)) <= 0)
1705 pl = pw = 1;
1706
1707 /* see if there's room to add content, else ignore */
1708 if (col < cols && wlen < destlen)
1709 {
1710 c = (cols - col) / pw;
1711 if (c > 0 && wlen + (c * pl) > destlen)
1712 c = ((signed)(destlen - wlen)) / pl;
1713 while (c > 0)
1714 {
1715 memcpy (wptr, src, pl);
1716 wptr += pl;
1717 wlen += pl;
1718 col += pw;
1719 c--;
1720 }
1721 src += pl;
1722 }
1723 break; /* skip rest of input */
1724 }
1725 else
1726 {
1727 short tolower = 0;
1728 short nodots = 0;
1729
1730 while (ch == '_' || ch == ':')
1731 {
1732 if (ch == '_')
1733 tolower = 1;
1734 else if (ch == ':')
1735 nodots = 1;
1736
1737 ch = *src++;
1738 }
1739
1740 /* use callback function to handle this case */
1741 src = callback (buf, sizeof (buf), col, cols, ch, src, prefix, ifstring, elsestring, data, flags);
1742
1743 if (tolower)
1744 mutt_strlower (buf);
1745 if (nodots)
1746 {
1747 char *p = buf;
1748 for (; *p; p++)
1749 if (*p == '.')
1750 *p = '_';
1751 }
1752
1753 if ((len = mutt_strlen (buf)) + wlen > destlen)
1754 len = mutt_wstr_trunc (buf, destlen - wlen, cols - col, NULL);
1755
1756 memcpy (wptr, buf, len);
1757 wptr += len;
1758 wlen += len;
1759 col += mutt_strwidth (buf);
1760 }
1761 }
1762 else if (*src == '\\')
1763 {
1764 if (!*++src)
1765 break;
1766 switch (*src)
1767 {
1768 case 'n':
1769 *wptr = '\n';
1770 break;
1771 case 't':
1772 *wptr = '\t';
1773 break;
1774 case 'r':
1775 *wptr = '\r';
1776 break;
1777 case 'f':
1778 *wptr = '\f';
1779 break;
1780 case 'v':
1781 *wptr = '\v';
1782 break;
1783 default:
1784 *wptr = *src;
1785 break;
1786 }
1787 src++;
1788 wptr++;
1789 wlen++;
1790 col++;
1791 }
1792 else
1793 {
1794 int tmp, w;
1795 /* in case of error, simply copy byte */
1796 if ((tmp = mutt_charlen (src, &w)) < 0)
1797 tmp = w = 1;
1798 if (tmp > 0 && wlen + tmp < destlen)
1799 {
1800 memcpy (wptr, src, tmp);
1801 wptr += tmp;
1802 src += tmp;
1803 wlen += tmp;
1804 col += w;
1805 }
1806 else
1807 {
1808 src += destlen - wlen;
1809 wlen = destlen;
1810 }
1811 }
1812 }
1813 *wptr = 0;
1814}
1815
1816/* This function allows the user to specify a command to read stdout from in
1817 place of a normal file. If the last character in the string is a pipe (|),
1818 then we assume it is a command to run instead of a normal file. */
1819FILE *mutt_open_read (const char *path, pid_t *thepid)
1820{
1821 FILE *f;
1822 struct stat s;
1823
1824 int len = mutt_strlen (path);
1825
1826 if (path[len - 1] == '|')
1827 {
1828 /* read from a pipe */
1829
1830 char *s = safe_strdup (path);
1831
1832 s[len - 1] = 0;
1833 mutt_endwin (NULL);
1834 *thepid = mutt_create_filter (s, NULL, &f, NULL);
1835 FREE (&s);
1836 }
1837 else
1838 {
1839 if (stat (path, &s) < 0)
1840 return (NULL);
1841 if (S_ISDIR (s.st_mode))
1842 {
1843 errno = EINVAL;
1844 return (NULL);
1845 }
1846 f = fopen (path, "r");
1847 *thepid = -1;
1848 }
1849 return (f);
1850}
1851
1852/* returns 0 if OK to proceed, -1 to abort, 1 to retry */
1853int mutt_save_confirm (const char *s, struct stat *st)
1854{
1855 BUFFER *tmp = NULL;
1856 int ret = 0;
1857 int rc;
1858 int magic = 0;
1859
1860 magic = mx_get_magic (s);
1861
1862#ifdef USE_POP
1863 if (magic == MUTT_POP)
1864 {
1865 mutt_error _("Can't save message to POP mailbox.");
1866 return 1;
1867 }
1868#endif
1869
1870 if (magic > 0 && !mx_access (s, W_OK))
1871 {
1872 if (option (OPTCONFIRMAPPEND))
1873 {
1874 tmp = mutt_buffer_pool_get ();
1875 mutt_buffer_printf (tmp, _("Append messages to %s?"), s);
1876 if ((rc = mutt_yesorno (mutt_b2s (tmp), MUTT_YES)) == MUTT_NO)
1877 ret = 1;
1878 else if (rc == -1)
1879 ret = -1;
1880 mutt_buffer_pool_release (&tmp);
1881 }
1882 }
1883
1884 if (stat (s, st) != -1)
1885 {
1886 if (magic == -1)
1887 {
1888 mutt_error (_("%s is not a mailbox!"), s);
1889 return 1;
1890 }
1891 }
1892 else if (magic != MUTT_IMAP)
1893 {
1894 st->st_mtime = 0;
1895 st->st_atime = 0;
1896
1897 if (errno == ENOENT)
1898 {
1899 if (option (OPTCONFIRMCREATE))
1900 {
1901 tmp = mutt_buffer_pool_get ();
1902 mutt_buffer_printf (tmp, _("Create %s?"), s);
1903 if ((rc = mutt_yesorno (mutt_b2s (tmp), MUTT_YES)) == MUTT_NO)
1904 ret = 1;
1905 else if (rc == -1)
1906 ret = -1;
1907 mutt_buffer_pool_release (&tmp);
1908 }
1909 }
1910 else
1911 {
1912 mutt_perror (s);
1913 return 1;
1914 }
1915 }
1916
1917 mutt_window_clearline (MuttMessageWindow, 0);
1918 return (ret);
1919}
1920
1921void state_prefix_putc (char c, STATE *s)
1922{
1923 if (s->flags & MUTT_PENDINGPREFIX)
1924 {
1925 state_reset_prefix (s);
1926 if (s->prefix)
1927 state_puts (s->prefix, s);
1928 }
1929
1930 state_putc (c, s);
1931
1932 if (c == '\n')
1933 state_set_prefix (s);
1934}
1935
1936int state_printf (STATE *s, const char *fmt, ...)
1937{
1938 int rv;
1939 va_list ap;
1940
1941 va_start (ap, fmt);
1942 rv = vfprintf (s->fpout, fmt, ap);
1943 va_end (ap);
1944
1945 return rv;
1946}
1947
1948void state_mark_attach (STATE *s)
1949{
1950 if ((s->flags & MUTT_DISPLAY) &&
1951 (!Pager || !mutt_strcmp (Pager, "builtin")))
1952 state_puts (AttachmentMarker, s);
1953}
1954
1955void state_mark_protected_header (STATE *s)
1956{
1957 if ((s->flags & MUTT_DISPLAY) &&
1958 (!Pager || !mutt_strcmp (Pager, "builtin")))
1959 state_puts (ProtectedHeaderMarker, s);
1960}
1961
1962void state_attach_puts (const char *t, STATE *s)
1963{
1964 if (*t != '\n') state_mark_attach (s);
1965 while (*t)
1966 {
1967 state_putc (*t, s);
1968 if (*t++ == '\n' && *t)
1969 if (*t != '\n') state_mark_attach (s);
1970 }
1971}
1972
1973int state_putwc (wchar_t wc, STATE *s)
1974{
1975 char mb[MB_LEN_MAX] = "";
1976 int rc;
1977
1978 if ((rc = wcrtomb (mb, wc, NULL)) < 0)
1979 return rc;
1980 if (fputs (mb, s->fpout) == EOF)
1981 return -1;
1982 return 0;
1983}
1984
1985int state_putws (const wchar_t *ws, STATE *s)
1986{
1987 const wchar_t *p = ws;
1988
1989 while (p && *p != L'\0')
1990 {
1991 if (state_putwc (*p, s) < 0)
1992 return -1;
1993 p++;
1994 }
1995 return 0;
1996}
1997
1998void mutt_display_sanitize (char *s)
1999{
2000 for (; *s; s++)
2001 {
2002 if (!IsPrint (*s))
2003 *s = '?';
2004 }
2005}
2006
2007void mutt_sleep (short s)
2008{
2009 if (SleepTime > s)
2010 sleep (SleepTime);
2011 else if (s)
2012 sleep(s);
2013}
2014
2015/* Decrease a file's modification time by 1 second */
2016
2017time_t mutt_decrease_mtime (const char *f, struct stat *st)
2018{
2019 struct utimbuf utim;
2020 struct stat _st;
2021 time_t mtime;
2022
2023 if (!st)
2024 {
2025 if (stat (f, &_st) == -1)
2026 return -1;
2027 st = &_st;
2028 }
2029
2030 if ((mtime = st->st_mtime) == time (NULL))
2031 {
2032 mtime -= 1;
2033 utim.actime = mtime;
2034 utim.modtime = mtime;
2035 utime (f, &utim);
2036 }
2037
2038 return mtime;
2039}
2040
2041/* sets mtime of 'to' to mtime of 'from' */
2042void mutt_set_mtime (const char* from, const char* to)
2043{
2044 struct utimbuf utim;
2045 struct stat st;
2046
2047 if (stat (from, &st) != -1)
2048 {
2049 utim.actime = st.st_mtime;
2050 utim.modtime = st.st_mtime;
2051 utime (to, &utim);
2052 }
2053}
2054
2055/* set atime to current time, just as read() would do on !noatime.
2056 * Silently ignored if unsupported. */
2057void mutt_touch_atime (int f)
2058{
2059#ifdef HAVE_FUTIMENS
2060 struct timespec times[2]={{0,UTIME_NOW},{0,UTIME_OMIT}};
2061 futimens(f, times);
2062#endif
2063}
2064
2065int mutt_timespec_compare (struct timespec *a, struct timespec *b)
2066{
2067 if (a->tv_sec < b->tv_sec)
2068 return -1;
2069 if (a->tv_sec > b->tv_sec)
2070 return 1;
2071
2072 if (a->tv_nsec < b->tv_nsec)
2073 return -1;
2074 if (a->tv_nsec > b->tv_nsec)
2075 return 1;
2076 return 0;
2077}
2078
2079void mutt_get_stat_timespec (struct timespec *dest, struct stat *sb, mutt_stat_type type)
2080{
2081 dest->tv_sec = 0;
2082 dest->tv_nsec = 0;
2083
2084 switch (type)
2085 {
2086 case MUTT_STAT_ATIME:
2087 dest->tv_sec = sb->st_atime;
2088#ifdef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC
2089 dest->tv_nsec = sb->st_atim.tv_nsec;
2090#endif
2091 break;
2092 case MUTT_STAT_MTIME:
2093 dest->tv_sec = sb->st_mtime;
2094#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
2095 dest->tv_nsec = sb->st_mtim.tv_nsec;
2096#endif
2097 break;
2098 case MUTT_STAT_CTIME:
2099 dest->tv_sec = sb->st_ctime;
2100#ifdef HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC
2101 dest->tv_nsec = sb->st_ctim.tv_nsec;
2102#endif
2103 break;
2104 }
2105}
2106
2107int mutt_stat_timespec_compare (struct stat *sba, mutt_stat_type type, struct timespec *b)
2108{
2109 struct timespec a;
2110
2111 mutt_get_stat_timespec (&a, sba, type);
2112 return mutt_timespec_compare (&a, b);
2113}
2114
2115int mutt_stat_compare (struct stat *sba, mutt_stat_type sba_type,
2116 struct stat *sbb, mutt_stat_type sbb_type)
2117{
2118 struct timespec a, b;
2119
2120 mutt_get_stat_timespec (&a, sba, sba_type);
2121 mutt_get_stat_timespec (&b, sbb, sbb_type);
2122 return mutt_timespec_compare (&a, &b);
2123}
2124
2125const char *mutt_make_version (void)
2126{
2127 static char vstring[STRING];
2128 snprintf (vstring, sizeof (vstring), "Mutt %s (%s)",
2129 MUTT_VERSION, ReleaseDate);
2130 return vstring;
2131}
2132
2133REGEXP *mutt_compile_regexp (const char *s, int flags)
2134{
2135 REGEXP *pp = safe_calloc (sizeof (REGEXP), 1);
2136 pp->pattern = safe_strdup (s);
2137 pp->rx = safe_calloc (sizeof (regex_t), 1);
2138 if (REGCOMP (pp->rx, NONULL(s), flags) != 0)
2139 mutt_free_regexp (&pp);
2140
2141 return pp;
2142}
2143
2144void mutt_free_regexp (REGEXP **pp)
2145{
2146 FREE (&(*pp)->pattern);
2147 regfree ((*pp)->rx);
2148 FREE (&(*pp)->rx);
2149 FREE (pp); /* __FREE_CHECKED__ */
2150}
2151
2152void mutt_free_rx_list (RX_LIST **list)
2153{
2154 RX_LIST *p;
2155
2156 if (!list) return;
2157 while (*list)
2158 {
2159 p = *list;
2160 *list = (*list)->next;
2161 mutt_free_regexp (&p->rx);
2162 FREE (&p);
2163 }
2164}
2165
2166void mutt_free_replace_list (REPLACE_LIST **list)
2167{
2168 REPLACE_LIST *p;
2169
2170 if (!list) return;
2171 while (*list)
2172 {
2173 p = *list;
2174 *list = (*list)->next;
2175 mutt_free_regexp (&p->rx);
2176 FREE (&p->template);
2177 FREE (&p);
2178 }
2179}
2180
2181int mutt_match_rx_list (const char *s, RX_LIST *l)
2182{
2183 if (!s) return 0;
2184
2185 for (; l; l = l->next)
2186 {
2187 if (regexec (l->rx->rx, s, (size_t) 0, (regmatch_t *) 0, (int) 0) == 0)
2188 {
2189 dprint (5, (debugfile, "mutt_match_rx_list: %s matches %s\n", s, l->rx->pattern));
2190 return 1;
2191 }
2192 }
2193
2194 return 0;
2195}
2196
2197/* Match a string against the patterns defined by the 'spam' command and output
2198 * the expanded format into `text` when there is a match. If textsize<=0, the
2199 * match is performed but the format is not expanded and no assumptions are made
2200 * about the value of `text` so it may be NULL.
2201 *
2202 * Returns 1 if the argument `s` matches a pattern in the spam list, otherwise
2203 * 0. */
2204int mutt_match_spam_list (const char *s, REPLACE_LIST *l, char *text, int textsize)
2205{
2206 static regmatch_t *pmatch = NULL;
2207 static int nmatch = 0;
2208 int tlen = 0;
2209 char *p;
2210
2211 if (!s) return 0;
2212
2213 for (; l; l = l->next)
2214 {
2215 /* If this pattern needs more matches, expand pmatch. */
2216 if (l->nmatch > nmatch)
2217 {
2218 safe_realloc (&pmatch, l->nmatch * sizeof(regmatch_t));
2219 nmatch = l->nmatch;
2220 }
2221
2222 /* Does this pattern match? */
2223 if (regexec (l->rx->rx, s, (size_t) l->nmatch, (regmatch_t *) pmatch, (int) 0) == 0)
2224 {
2225 dprint (5, (debugfile, "mutt_match_spam_list: %s matches %s\n", s, l->rx->pattern));
2226 dprint (5, (debugfile, "mutt_match_spam_list: %d subs\n", (int)l->rx->rx->re_nsub));
2227
2228 /* Copy template into text, with substitutions. */
2229 for (p = l->template; *p && tlen < textsize - 1;)
2230 {
2231 /* backreference to pattern match substring, eg. %1, %2, etc) */
2232 if (*p == '%')
2233 {
2234 char *e; /* used as pointer to end of integer backreference in strtol() call */
2235 int n;
2236
2237 ++p; /* skip over % char */
2238 n = strtol(p, &e, 10);
2239 /* Ensure that the integer conversion succeeded (e!=p) and bounds check. The upper bound check
2240 * should not strictly be necessary since add_to_spam_list() finds the largest value, and
2241 * the static array above is always large enough based on that value. */
2242 if (e != p && n >= 0 && n <= l->nmatch && pmatch[n].rm_so != -1)
2243 {
2244 /* copy as much of the substring match as will fit in the output buffer, saving space for
2245 * the terminating nul char */
2246 int idx;
2247 for (idx = pmatch[n].rm_so; (idx < pmatch[n].rm_eo) && (tlen < textsize - 1); ++idx)
2248 text[tlen++] = s[idx];
2249 }
2250 p = e; /* skip over the parsed integer */
2251 }
2252 else
2253 {
2254 text[tlen++] = *p++;
2255 }
2256 }
2257 /* tlen should always be less than textsize except when textsize<=0
2258 * because the bounds checks in the above code leave room for the
2259 * terminal nul char. This should avoid returning an unterminated
2260 * string to the caller. When textsize<=0 we make no assumption about
2261 * the validity of the text pointer. */
2262 if (tlen < textsize)
2263 {
2264 text[tlen] = '\0';
2265 dprint (5, (debugfile, "mutt_match_spam_list: \"%s\"\n", text));
2266 }
2267 return 1;
2268 }
2269 }
2270
2271 return 0;
2272}
2273
2274void mutt_encode_path (BUFFER *dest, const char *src)
2275{
2276 char *p;
2277 int rc;
2278
2279 p = safe_strdup (src);
2280 rc = mutt_convert_string (&p, Charset, "utf-8", 0);
2281 /* `src' may be NULL, such as when called from the pop3 driver. */
2282 mutt_buffer_strcpy (dest, (rc == 0) ? NONULL(p) : NONULL(src));
2283 FREE (&p);
2284}
2285
2286
2287/************************************************************************
2288 * These functions are transplanted from lib.c, in order to modify them *
2289 * to use BUFFERs. *
2290 ************************************************************************/
2291
2292/* remove a directory and everything under it */
2293int mutt_rmtree (const char* path)
2294{
2295 DIR* dirp;
2296 struct dirent* de;
2297 BUFFER *cur = NULL;
2298 struct stat statbuf;
2299 int rc = 0;
2300
2301 if (!(dirp = opendir (path)))
2302 {
2303 dprint (1, (debugfile, "mutt_rmtree: error opening directory %s\n", path));
2304 return -1;
2305 }
2306
2307 /* We avoid using the buffer pool for this function, because it
2308 * invokes recursively to an unknown depth. */
2309 cur = mutt_buffer_new ();
2310 mutt_buffer_increase_size (cur, _POSIX_PATH_MAX);
2311
2312 while ((de = readdir (dirp)))
2313 {
2314 if (!strcmp (".", de->d_name) || !strcmp ("..", de->d_name))
2315 continue;
2316
2317 mutt_buffer_printf (cur, "%s/%s", path, de->d_name);
2318 /* XXX make nonrecursive version */
2319
2320 if (stat(mutt_b2s (cur), &statbuf) == -1)
2321 {
2322 rc = 1;
2323 continue;
2324 }
2325
2326 if (S_ISDIR (statbuf.st_mode))
2327 rc |= mutt_rmtree (mutt_b2s (cur));
2328 else
2329 rc |= unlink (mutt_b2s (cur));
2330 }
2331 closedir (dirp);
2332
2333 rc |= rmdir (path);
2334
2335 mutt_buffer_free (&cur);
2336 return rc;
2337}
2338
2339/* Create a temporary directory next to a file name */
2340
2341static int mutt_mkwrapdir (const char *path, BUFFER *newfile, BUFFER *newdir)
2342{
2343 const char *basename;
2344 BUFFER *parent = NULL;
2345 char *p;
2346 int rc = 0;
2347
2348 parent = mutt_buffer_pool_get ();
2349 mutt_buffer_strcpy (parent, NONULL (path));
2350
2351 if ((p = strrchr (parent->data, '/')))
2352 {
2353 *p = '\0';
2354 basename = p + 1;
2355 }
2356 else
2357 {
2358 mutt_buffer_strcpy (parent, ".");
2359 basename = path;
2360 }
2361
2362 mutt_buffer_printf (newdir, "%s/%s", mutt_b2s (parent), ".muttXXXXXX");
2363 if (mkdtemp(newdir->data) == NULL)
2364 {
2365 dprint(1, (debugfile, "mutt_mkwrapdir: mkdtemp() failed\n"));
2366 rc = -1;
2367 goto cleanup;
2368 }
2369
2370 mutt_buffer_printf (newfile, "%s/%s", mutt_b2s (newdir), NONULL(basename));
2371
2372cleanup:
2373 mutt_buffer_pool_release (&parent);
2374 return rc;
2375}
2376
2377static int mutt_put_file_in_place (const char *path, const char *safe_file, const char *safe_dir)
2378{
2379 int rv;
2380
2381 rv = safe_rename (safe_file, path);
2382 unlink (safe_file);
2383 rmdir (safe_dir);
2384 return rv;
2385}
2386
2387int safe_open (const char *path, int flags)
2388{
2389 struct stat osb, nsb;
2390 int fd;
2391 BUFFER *safe_file = NULL;
2392 BUFFER *safe_dir = NULL;
2393
2394 if (flags & O_EXCL)
2395 {
2396 safe_file = mutt_buffer_pool_get ();
2397 safe_dir = mutt_buffer_pool_get ();
2398
2399 if (mutt_mkwrapdir (path, safe_file, safe_dir) == -1)
2400 {
2401 fd = -1;
2402 goto cleanup;
2403 }
2404
2405 if ((fd = open (mutt_b2s (safe_file), flags, 0600)) < 0)
2406 {
2407 rmdir (mutt_b2s (safe_dir));
2408 goto cleanup;
2409 }
2410
2411 /* NFS and I believe cygwin do not handle movement of open files well */
2412 close (fd);
2413 if (mutt_put_file_in_place (path, mutt_b2s (safe_file), mutt_b2s (safe_dir)) == -1)
2414 {
2415 fd = -1;
2416 goto cleanup;
2417 }
2418 }
2419
2420 if ((fd = open (path, flags & ~O_EXCL, 0600)) < 0)
2421 goto cleanup;
2422
2423 /* make sure the file is not symlink */
2424 if (lstat (path, &osb) < 0 || fstat (fd, &nsb) < 0 ||
2425 compare_stat(&osb, &nsb) == -1)
2426 {
2427/* dprint (1, (debugfile, "safe_open(): %s is a symlink!\n", path)); */
2428 close (fd);
2429 fd = -1;
2430 goto cleanup;
2431 }
2432
2433cleanup:
2434 mutt_buffer_pool_release (&safe_file);
2435 mutt_buffer_pool_release (&safe_dir);
2436
2437 return (fd);
2438}
2439
2440/* when opening files for writing, make sure the file doesn't already exist
2441 * to avoid race conditions.
2442 */
2443FILE *safe_fopen (const char *path, const char *mode)
2444{
2445 if (mode[0] == 'w')
2446 {
2447 int fd;
2448 int flags = O_CREAT | O_EXCL;
2449
2450#ifdef O_NOFOLLOW
2451 flags |= O_NOFOLLOW;
2452#endif
2453
2454 if (mode[1] == '+')
2455 flags |= O_RDWR;
2456 else
2457 flags |= O_WRONLY;
2458
2459 if ((fd = safe_open (path, flags)) < 0)
2460 return (NULL);
2461
2462 return (fdopen (fd, mode));
2463 }
2464 else
2465 return (fopen (path, mode));
2466}
2467
2468int safe_symlink(const char *oldpath, const char *newpath)
2469{
2470 struct stat osb, nsb;
2471
2472 if (!oldpath || !newpath)
2473 return -1;
2474
2475 if (unlink(newpath) == -1 && errno != ENOENT)
2476 return -1;
2477
2478 if (oldpath[0] == '/')
2479 {
2480 if (symlink (oldpath, newpath) == -1)
2481 return -1;
2482 }
2483 else
2484 {
2485 BUFFER *abs_oldpath = NULL;
2486
2487 abs_oldpath = mutt_buffer_pool_get ();
2488
2489 if (mutt_getcwd (abs_oldpath) == NULL)
2490 {
2491 mutt_buffer_pool_release (&abs_oldpath);
2492 return -1;
2493 }
2494
2495 mutt_buffer_addch (abs_oldpath, '/');
2496 mutt_buffer_addstr (abs_oldpath, oldpath);
2497 if (symlink (mutt_b2s (abs_oldpath), newpath) == -1)
2498 {
2499 mutt_buffer_pool_release (&abs_oldpath);
2500 return -1;
2501 }
2502
2503 mutt_buffer_pool_release (&abs_oldpath);
2504 }
2505
2506 if (stat(oldpath, &osb) == -1 || stat(newpath, &nsb) == -1
2507 || compare_stat(&osb, &nsb) == -1)
2508 {
2509 unlink(newpath);
2510 return -1;
2511 }
2512
2513 return 0;
2514}
2515
2516/* END lib.c transplant functions */