]> git.llucax.com Git - software/mutt-debian.git/blob - muttlib.c
new patch from upstream to fix the atime issue
[software/mutt-debian.git] / muttlib.c
1 /*
2  * Copyright (C) 1996-2000,2007 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 "mutt.h"
25 #include "mutt_curses.h"
26 #include "mime.h"
27 #include "mailbox.h"
28 #include "mx.h"
29 #include "url.h"
30
31 #ifdef USE_IMAP
32 #include "imap.h"
33 #endif
34
35 #include "mutt_crypt.h"
36
37 #include <string.h>
38 #include <ctype.h>
39 #include <unistd.h>
40 #include <stdlib.h>
41 #include <sys/wait.h>
42 #include <errno.h>
43 #include <sys/stat.h>
44 #include <fcntl.h>
45 #include <time.h>
46 #include <sys/types.h>
47 #include <utime.h>
48
49 BODY *mutt_new_body (void)
50 {
51   BODY *p = (BODY *) safe_calloc (1, sizeof (BODY));
52     
53   p->disposition = DISPATTACH;
54   p->use_disp = 1;
55   return (p);
56 }
57
58
59 /* Modified by blong to accept a "suggestion" for file name.  If
60  * that file exists, then construct one with unique name but 
61  * keep any extension.  This might fail, I guess.
62  * Renamed to mutt_adv_mktemp so I only have to change where it's
63  * called, and not all possible cases.
64  */
65 void mutt_adv_mktemp (char *s, size_t l)
66 {
67   char buf[_POSIX_PATH_MAX];
68   char tmp[_POSIX_PATH_MAX];
69   char *period;
70   size_t sl;
71   struct stat sb;
72   
73   strfcpy (buf, NONULL (Tempdir), sizeof (buf));
74   mutt_expand_path (buf, sizeof (buf));
75   if (s[0] == '\0')
76   {
77     snprintf (s, l, "%s/muttXXXXXX", buf);
78     mktemp (s);
79   }
80   else
81   {
82     strfcpy (tmp, s, sizeof (tmp));
83     mutt_sanitize_filename (tmp, 1);
84     snprintf (s, l, "%s/%s", buf, tmp);
85     if (lstat (s, &sb) == -1 && errno == ENOENT)
86       return;
87     if ((period = strrchr (tmp, '.')) != NULL)
88       *period = 0;
89     snprintf (s, l, "%s/%s.XXXXXX", buf, tmp);
90     mktemp (s);
91     if (period != NULL)
92     {
93       *period = '.';
94       sl = mutt_strlen(s);
95       strfcpy(s + sl, period, l - sl);
96     }
97   }
98 }
99
100 /* create a send-mode duplicate from a receive-mode body */
101
102 int mutt_copy_body (FILE *fp, BODY **tgt, BODY *src)
103 {
104   char tmp[_POSIX_PATH_MAX];
105   BODY *b;
106
107   PARAMETER *par, **ppar;
108   
109   short use_disp;
110
111   if (src->filename)
112   {
113     use_disp = 1;
114     strfcpy (tmp, src->filename, sizeof (tmp));
115   }
116   else
117   {
118     use_disp = 0;
119     tmp[0] = '\0';
120   }
121
122   mutt_adv_mktemp (tmp, sizeof (tmp));
123   if (mutt_save_attachment (fp, src, tmp, 0, NULL) == -1)
124     return -1;
125       
126   *tgt = mutt_new_body ();
127   b = *tgt;
128
129   memcpy (b, src, sizeof (BODY));
130   b->parts = NULL;
131   b->next  = NULL;
132
133   b->filename = safe_strdup (tmp);
134   b->use_disp = use_disp;
135   b->unlink = 1;
136
137   if (mutt_is_text_part (b))
138     b->noconv = 1;
139
140   b->xtype = safe_strdup (b->xtype);
141   b->subtype = safe_strdup (b->subtype);
142   b->form_name = safe_strdup (b->form_name);
143   b->filename = safe_strdup (b->filename);
144   b->d_filename = safe_strdup (b->d_filename);
145   b->description = safe_strdup (b->description);
146
147   /* 
148    * we don't seem to need the HEADER structure currently.
149    * XXX - this may change in the future
150    */
151
152   if (b->hdr) b->hdr = NULL;
153   
154   /* copy parameters */
155   for (par = b->parameter, ppar = &b->parameter; par; ppar = &(*ppar)->next, par = par->next)
156   {
157     *ppar = mutt_new_parameter ();
158     (*ppar)->attribute = safe_strdup (par->attribute);
159     (*ppar)->value = safe_strdup (par->value);
160   }
161
162   mutt_stamp_attachment (b);
163   
164   return 0;
165 }
166
167
168
169 void mutt_free_body (BODY **p)
170 {
171   BODY *a = *p, *b;
172
173   while (a)
174   {
175     b = a;
176     a = a->next; 
177
178     if (b->parameter)
179       mutt_free_parameter (&b->parameter);
180     if (b->unlink && b->filename)
181     {
182       dprint (1, (debugfile, "mutt_free_body: Unlinking %s.\n", b->filename));
183       unlink (b->filename);
184     }
185     else if (b->filename) 
186       dprint (1, (debugfile, "mutt_free_body: Not unlinking %s.\n", b->filename));
187
188     FREE (&b->filename);
189     FREE (&b->content);
190     FREE (&b->xtype);
191     FREE (&b->subtype);
192     FREE (&b->description);
193     FREE (&b->form_name);
194
195     if (b->hdr)
196     {
197       /* Don't free twice (b->hdr->content = b->parts) */
198       b->hdr->content = NULL;
199       mutt_free_header(&b->hdr);
200     }
201
202     if (b->parts)
203       mutt_free_body (&b->parts);
204
205     FREE (&b);
206   }
207
208   *p = 0;
209 }
210
211 void mutt_free_parameter (PARAMETER **p)
212 {
213   PARAMETER *t = *p;
214   PARAMETER *o;
215
216   while (t)
217   {
218     FREE (&t->attribute);
219     FREE (&t->value);
220     o = t;
221     t = t->next;
222     FREE (&o);
223   }
224   *p = 0;
225 }
226
227 LIST *mutt_add_list (LIST *head, const char *data)
228 {
229   size_t len = mutt_strlen (data);
230
231   return mutt_add_list_n (head, data, len ? len + 1 : 0);
232 }
233
234 LIST *mutt_add_list_n (LIST *head, const void *data, size_t len)
235 {
236   LIST *tmp;
237   
238   for (tmp = head; tmp && tmp->next; tmp = tmp->next)
239     ;
240   if (tmp)
241   {
242     tmp->next = safe_malloc (sizeof (LIST));
243     tmp = tmp->next;
244   }
245   else
246     head = tmp = safe_malloc (sizeof (LIST));
247   
248   tmp->data = safe_malloc (len);
249   if (len)
250     memcpy (tmp->data, data, len);
251   tmp->next = NULL;
252   return head;
253 }
254
255 LIST *mutt_find_list (LIST *l, const char *data)
256 {
257   LIST *p = l;
258
259   while (p)
260   {
261     if (data == p->data)
262       return p;
263     if (data && p->data && mutt_strcmp (p->data, data) == 0)
264       return p;
265     p = p->next;
266   }
267   return NULL;
268 }
269
270 void mutt_free_list (LIST **list)
271 {
272   LIST *p;
273   
274   if (!list) return;
275   while (*list)
276   {
277     p = *list;
278     *list = (*list)->next;
279     FREE (&p->data);
280     FREE (&p);
281   }
282 }
283
284 HEADER *mutt_dup_header(HEADER *h)
285 {
286   HEADER *hnew;
287
288   hnew = mutt_new_header();
289   memcpy(hnew, h, sizeof (HEADER));
290   return hnew;
291 }
292
293 void mutt_free_header (HEADER **h)
294 {
295   if(!h || !*h) return;
296   mutt_free_envelope (&(*h)->env);
297   mutt_free_body (&(*h)->content);
298   FREE (&(*h)->maildir_flags);
299   FREE (&(*h)->tree);
300   FREE (&(*h)->path);
301 #ifdef MIXMASTER
302   mutt_free_list (&(*h)->chain);
303 #endif
304 #if defined USE_POP || defined USE_IMAP
305   FREE (&(*h)->data);
306 #endif
307   FREE (h);             /* __FREE_CHECKED__ */
308 }
309
310 /* returns true if the header contained in "s" is in list "t" */
311 int mutt_matches_ignore (const char *s, LIST *t)
312 {
313   for (; t; t = t->next)
314   {
315     if (!ascii_strncasecmp (s, t->data, mutt_strlen (t->data)) || *t->data == '*')
316       return 1;
317   }
318   return 0;
319 }
320
321 /* prepend the path part of *path to *link */
322 void mutt_expand_link (char *newpath, const char *path, const char *link)
323 {
324   const char *lb = NULL;
325   size_t len;
326
327   /* link is full path */
328   if (*link == '/')
329   {
330     strfcpy (newpath, link, _POSIX_PATH_MAX);
331     return;
332   }
333
334   if ((lb = strrchr (path, '/')) == NULL)
335   {
336     /* no path in link */
337     strfcpy (newpath, link, _POSIX_PATH_MAX);
338     return;
339   }
340
341   len = lb - path + 1;
342   memcpy (newpath, path, len);
343   strfcpy (newpath + len, link, _POSIX_PATH_MAX - len);
344 }
345
346 char *mutt_expand_path (char *s, size_t slen)
347 {
348   return _mutt_expand_path (s, slen, 0);
349 }
350
351 char *_mutt_expand_path (char *s, size_t slen, int rx)
352 {
353   char p[_POSIX_PATH_MAX] = "";
354   char q[_POSIX_PATH_MAX] = "";
355   char tmp[_POSIX_PATH_MAX];
356   char *t;
357
358   char *tail = ""; 
359
360   int recurse = 0;
361   
362   do 
363   {
364     recurse = 0;
365
366     switch (*s)
367     {
368       case '~':
369       {
370         if (*(s + 1) == '/' || *(s + 1) == 0)
371         {
372           strfcpy (p, NONULL(Homedir), sizeof (p));
373           tail = s + 1;
374         }
375         else
376         {
377           struct passwd *pw;
378           if ((t = strchr (s + 1, '/'))) 
379             *t = 0;
380
381           if ((pw = getpwnam (s + 1)))
382           {
383             strfcpy (p, pw->pw_dir, sizeof (p));
384             if (t)
385             {
386               *t = '/';
387               tail = t;
388             }
389             else
390               tail = "";
391           }
392           else
393           {
394             /* user not found! */
395             if (t)
396               *t = '/';
397             *p = '\0';
398             tail = s;
399           }
400         }
401       }
402       break;
403       
404       case '=':
405       case '+':    
406       {
407 #ifdef USE_IMAP
408         /* if folder = {host} or imap[s]://host/: don't append slash */
409         if (mx_is_imap (NONULL (Maildir)) &&
410             (Maildir[strlen (Maildir) - 1] == '}' ||
411              Maildir[strlen (Maildir) - 1] == '/'))
412           strfcpy (p, NONULL (Maildir), sizeof (p));
413         else
414 #endif
415         if (Maildir && *Maildir && Maildir[strlen (Maildir) - 1] == '/')
416           strfcpy (p, NONULL (Maildir), sizeof (p));
417         else
418           snprintf (p, sizeof (p), "%s/", NONULL (Maildir));
419         
420         tail = s + 1;
421       }
422       break;
423       
424       /* elm compatibility, @ expands alias to user name */
425     
426       case '@':
427       {
428         HEADER *h;
429         ADDRESS *alias;
430         
431         if ((alias = mutt_lookup_alias (s + 1)))
432         {
433           h = mutt_new_header();
434           h->env = mutt_new_envelope();
435           h->env->from = h->env->to = alias;
436           mutt_default_save (p, sizeof (p), h);
437           h->env->from = h->env->to = NULL;
438           mutt_free_header (&h);
439           /* Avoid infinite recursion if the resulting folder starts with '@' */
440           if (*p != '@')
441             recurse = 1;
442           
443           tail = "";
444         }
445       }
446       break;
447       
448       case '>':
449       {
450         strfcpy (p, NONULL(Inbox), sizeof (p));
451         tail = s + 1;
452       }
453       break;
454       
455       case '<':
456       {
457         strfcpy (p, NONULL(Outbox), sizeof (p));
458         tail = s + 1;
459       }
460       break;
461       
462       case '!':
463       {
464         if (*(s+1) == '!')
465         {
466           strfcpy (p, NONULL(LastFolder), sizeof (p));
467           tail = s + 2;
468         }
469         else 
470         {
471           strfcpy (p, NONULL(Spoolfile), sizeof (p));
472           tail = s + 1;
473         }
474       }
475       break;
476       
477       case '-':
478       {
479         strfcpy (p, NONULL(LastFolder), sizeof (p));
480         tail = s + 1;
481       }
482       break;
483       
484       case '^':        
485       {
486         strfcpy (p, NONULL(CurrentFolder), sizeof (p));
487         tail = s + 1;
488       }
489       break;
490
491       default:
492       {
493         *p = '\0';
494         tail = s;
495       }
496     }
497
498     if (rx && *p && !recurse)
499     {
500       mutt_rx_sanitize_string (q, sizeof (q), p);
501       snprintf (tmp, sizeof (tmp), "%s%s", q, tail);
502     }
503     else
504       snprintf (tmp, sizeof (tmp), "%s%s", p, tail);
505     
506     strfcpy (s, tmp, slen);
507   }
508   while (recurse);
509
510 #ifdef USE_IMAP
511   /* Rewrite IMAP path in canonical form - aids in string comparisons of
512    * folders. May possibly fail, in which case s should be the same. */
513   if (mx_is_imap (s))
514     imap_expand_path (s, slen);
515 #endif
516
517   return (s);
518 }
519
520 /* Extract the real name from /etc/passwd's GECOS field.
521  * When set, honor the regular expression in GecosMask,
522  * otherwise assume that the GECOS field is a 
523  * comma-separated list.
524  * Replace "&" by a capitalized version of the user's login
525  * name.
526  */
527
528 char *mutt_gecos_name (char *dest, size_t destlen, struct passwd *pw)
529 {
530   regmatch_t pat_match[1];
531   size_t pwnl;
532   int idx;
533   char *p;
534   
535   if (!pw || !pw->pw_gecos) 
536     return NULL;
537
538   memset (dest, 0, destlen);
539   
540   if (GecosMask.rx)
541   {
542     if (regexec (GecosMask.rx, pw->pw_gecos, 1, pat_match, 0) == 0)
543       strfcpy (dest, pw->pw_gecos + pat_match[0].rm_so, 
544                MIN (pat_match[0].rm_eo - pat_match[0].rm_so + 1, destlen));
545   }
546   else if ((p = strchr (pw->pw_gecos, ',')))
547     strfcpy (dest, pw->pw_gecos, MIN (destlen, p - pw->pw_gecos + 1));
548   else
549     strfcpy (dest, pw->pw_gecos, destlen);
550
551   pwnl = strlen (pw->pw_name);
552
553   for (idx = 0; dest[idx]; idx++)
554   {
555     if (dest[idx] == '&')
556     {
557       memmove (&dest[idx + pwnl], &dest[idx + 1],
558                MAX((ssize_t)(destlen - idx - pwnl - 1), 0));
559       memcpy (&dest[idx], pw->pw_name, MIN(destlen - idx - 1, pwnl));
560       dest[idx] = toupper ((unsigned char) dest[idx]);
561     }
562   }
563       
564   return dest;
565 }
566   
567
568 char *mutt_get_parameter (const char *s, PARAMETER *p)
569 {
570   for (; p; p = p->next)
571     if (ascii_strcasecmp (s, p->attribute) == 0)
572       return (p->value);
573
574   return NULL;
575 }
576
577 void mutt_set_parameter (const char *attribute, const char *value, PARAMETER **p)
578 {
579   PARAMETER *q;
580
581   if (!value)
582   {
583     mutt_delete_parameter (attribute, p);
584     return;
585   }
586   
587   for(q = *p; q; q = q->next)
588   {
589     if (ascii_strcasecmp (attribute, q->attribute) == 0)
590     {
591       mutt_str_replace (&q->value, value);
592       return;
593     }
594   }
595   
596   q = mutt_new_parameter();
597   q->attribute = safe_strdup(attribute);
598   q->value = safe_strdup(value);
599   q->next = *p;
600   *p = q;
601 }
602
603 void mutt_delete_parameter (const char *attribute, PARAMETER **p)
604 {
605   PARAMETER *q;
606   
607   for (q = *p; q; p = &q->next, q = q->next)
608   {
609     if (ascii_strcasecmp (attribute, q->attribute) == 0)
610     {
611       *p = q->next;
612       q->next = NULL;
613       mutt_free_parameter (&q);
614       return;
615     }
616   }
617 }
618
619 /* returns 1 if Mutt can't display this type of data, 0 otherwise */
620 int mutt_needs_mailcap (BODY *m)
621 {
622   switch (m->type)
623   {
624     case TYPETEXT:
625
626       if (!ascii_strcasecmp ("plain", m->subtype) ||
627           !ascii_strcasecmp ("rfc822-headers", m->subtype) ||
628           !ascii_strcasecmp ("enriched", m->subtype))
629         return 0;
630       break;
631
632     case TYPEAPPLICATION:
633       if((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp(m))
634         return 0;
635       if((WithCrypto & APPLICATION_SMIME) && mutt_is_application_smime(m))
636         return 0;
637       break;
638
639     case TYPEMULTIPART:
640     case TYPEMESSAGE:
641       return 0;
642   }
643
644   return 1;
645 }
646
647 int mutt_is_text_part (BODY *b)
648 {
649   int t = b->type;
650   char *s = b->subtype;
651   
652   if ((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp (b))
653     return 0;
654
655   if (t == TYPETEXT)
656     return 1;
657
658   if (t == TYPEMESSAGE)
659   {
660     if (!ascii_strcasecmp ("delivery-status", s))
661       return 1;
662   }
663
664   if ((WithCrypto & APPLICATION_PGP) && t == TYPEAPPLICATION)
665   {
666     if (!ascii_strcasecmp ("pgp-keys", s))
667       return 1;
668   }
669
670   return 0;
671 }
672
673 void mutt_free_envelope (ENVELOPE **p)
674 {
675   if (!*p) return;
676   rfc822_free_address (&(*p)->return_path);
677   rfc822_free_address (&(*p)->from);
678   rfc822_free_address (&(*p)->to);
679   rfc822_free_address (&(*p)->cc);
680   rfc822_free_address (&(*p)->bcc);
681   rfc822_free_address (&(*p)->sender);
682   rfc822_free_address (&(*p)->reply_to);
683   rfc822_free_address (&(*p)->mail_followup_to);
684
685   FREE (&(*p)->list_post);
686   FREE (&(*p)->subject);
687   /* real_subj is just an offset to subject and shouldn't be freed */
688   FREE (&(*p)->message_id);
689   FREE (&(*p)->supersedes);
690   FREE (&(*p)->date);
691   FREE (&(*p)->x_label);
692
693   mutt_buffer_free (&(*p)->spam);
694
695   mutt_free_list (&(*p)->references);
696   mutt_free_list (&(*p)->in_reply_to);
697   mutt_free_list (&(*p)->userhdrs);
698   FREE (p);             /* __FREE_CHECKED__ */
699 }
700
701 /* move all the headers from extra not present in base into base */
702 void mutt_merge_envelopes(ENVELOPE* base, ENVELOPE** extra)
703 {
704   /* copies each existing element if necessary, and sets the element
705   * to NULL in the source so that mutt_free_envelope doesn't leave us
706   * with dangling pointers. */
707 #define MOVE_ELEM(h) if (!base->h) { base->h = (*extra)->h; (*extra)->h = NULL; }
708   MOVE_ELEM(return_path);
709   MOVE_ELEM(from);
710   MOVE_ELEM(to);
711   MOVE_ELEM(cc);
712   MOVE_ELEM(bcc);
713   MOVE_ELEM(sender);
714   MOVE_ELEM(reply_to);
715   MOVE_ELEM(mail_followup_to);
716   MOVE_ELEM(list_post);
717   MOVE_ELEM(message_id);
718   MOVE_ELEM(supersedes);
719   MOVE_ELEM(date);
720   MOVE_ELEM(x_label);
721   if (!base->refs_changed)
722   {
723     MOVE_ELEM(references);
724   }
725   if (!base->irt_changed)
726   {
727     MOVE_ELEM(in_reply_to);
728   }
729   
730   /* real_subj is subordinate to subject */
731   if (!base->subject)
732   {
733     base->subject = (*extra)->subject;
734     base->real_subj = (*extra)->real_subj;
735     (*extra)->subject = NULL;
736     (*extra)->real_subj = NULL;
737   }
738   /* spam and user headers should never be hashed, and the new envelope may
739     * have better values. Use new versions regardless. */
740   mutt_buffer_free (&base->spam);
741   mutt_free_list (&base->userhdrs);
742   MOVE_ELEM(spam);
743   MOVE_ELEM(userhdrs);
744 #undef MOVE_ELEM
745   
746   mutt_free_envelope(extra);
747 }
748
749 void _mutt_mktemp (char *s, const char *src, int line)
750 {
751   snprintf (s, _POSIX_PATH_MAX, "%s/mutt-%s-%d-%d-%d", NONULL (Tempdir), NONULL(Hostname), (int) getuid(), (int) getpid (), Counter++);
752   dprint (3, (debugfile, "%s:%d: mutt_mktemp returns \"%s\".\n", src, line, s));
753   unlink (s);
754 }
755
756 void mutt_free_alias (ALIAS **p)
757 {
758   ALIAS *t;
759
760   while (*p)
761   {
762     t = *p;
763     *p = (*p)->next;
764     mutt_alias_delete_reverse (t);
765     FREE (&t->name);
766     rfc822_free_address (&t->addr);
767     FREE (&t);
768   }
769 }
770
771 /* collapse the pathname using ~ or = when possible */
772 void mutt_pretty_mailbox (char *s, size_t buflen)
773 {
774   char *p = s, *q = s;
775   size_t len;
776   url_scheme_t scheme;
777   char tmp[PATH_MAX];
778
779   scheme = url_check_scheme (s);
780
781 #ifdef USE_IMAP
782   if (scheme == U_IMAP || scheme == U_IMAPS)
783   {
784     imap_pretty_mailbox (s);
785     return;
786   }
787 #endif
788
789   /* if s is an url, only collapse path component */
790   if (scheme != U_UNKNOWN)
791   {
792     p = strchr(s, ':')+1;
793     if (!strncmp (p, "//", 2))
794       q = strchr (p+2, '/');
795     if (!q)
796       q = strchr (p, '\0');
797     p = q;
798   }
799
800   /* cleanup path */
801   if (strstr (p, "//") || strstr (p, "/./"))
802   {
803     /* first attempt to collapse the pathname, this is more
804      * lightweight than realpath() and doesn't resolve links
805      */
806     while (*p)
807     {
808       if (*p == '/' && p[1] == '/')
809       {
810         *q++ = '/';
811         p += 2;
812       }
813       else if (p[0] == '/' && p[1] == '.' && p[2] == '/')
814       {
815         *q++ = '/';
816         p += 3;
817       }
818       else
819         *q++ = *p++;
820     }
821     *q = 0;
822   }
823   else if (strstr (p, "..") && 
824            (scheme == U_UNKNOWN || scheme == U_FILE) &&
825            realpath (p, tmp))
826     strfcpy (p, tmp, buflen - (p - s));
827
828   if (mutt_strncmp (s, Maildir, (len = mutt_strlen (Maildir))) == 0 &&
829       s[len] == '/')
830   {
831     *s++ = '=';
832     memmove (s, s + len, mutt_strlen (s + len) + 1);
833   }
834   else if (mutt_strncmp (s, Homedir, (len = mutt_strlen (Homedir))) == 0 &&
835            s[len] == '/')
836   {
837     *s++ = '~';
838     memmove (s, s + len - 1, mutt_strlen (s + len - 1) + 1);
839   }
840 }
841
842 void mutt_pretty_size (char *s, size_t len, LOFF_T n)
843 {
844   if (n == 0)
845     strfcpy (s, "0K", len);
846   else if (n < 10189) /* 0.1K - 9.9K */
847     snprintf (s, len, "%3.1fK", (n < 103) ? 0.1 : n / 1024.0);
848   else if (n < 1023949) /* 10K - 999K */
849   {
850     /* 51 is magic which causes 10189/10240 to be rounded up to 10 */
851     snprintf (s, len, OFF_T_FMT "K", (n + 51) / 1024);
852   }
853   else if (n < 10433332) /* 1.0M - 9.9M */
854     snprintf (s, len, "%3.1fM", n / 1048576.0);
855   else /* 10M+ */
856   {
857     /* (10433332 + 52428) / 1048576 = 10 */
858     snprintf (s, len, OFF_T_FMT "M", (n + 52428) / 1048576);
859   }
860 }
861
862 void mutt_expand_file_fmt (char *dest, size_t destlen, const char *fmt, const char *src)
863 {
864   char tmp[LONG_STRING];
865   
866   mutt_quote_filename (tmp, sizeof (tmp), src);
867   mutt_expand_fmt (dest, destlen, fmt, tmp);
868 }
869
870 void mutt_expand_fmt (char *dest, size_t destlen, const char *fmt, const char *src)
871 {
872   const char *p;
873   char *d;
874   size_t slen;
875   int found = 0;
876
877   slen = mutt_strlen (src);
878   destlen--;
879   
880   for (p = fmt, d = dest; destlen && *p; p++)
881   {
882     if (*p == '%') 
883     {
884       switch (p[1])
885       {
886         case '%':
887           *d++ = *p++;
888           destlen--;
889           break;
890         case 's':
891           found = 1;
892           strfcpy (d, src, destlen + 1);
893           d       += destlen > slen ? slen : destlen;
894           destlen -= destlen > slen ? slen : destlen;
895           p++;
896           break;
897         default:
898           *d++ = *p; 
899           destlen--;
900           break;
901       }
902     }
903     else
904     {
905       *d++ = *p;
906       destlen--;
907     }
908   }
909   
910   *d = '\0';
911   
912   if (!found && destlen > 0)
913   {
914     safe_strcat (dest, destlen, " ");
915     safe_strcat (dest, destlen, src);
916   }
917   
918 }
919
920 /* return 0 on success, -1 on abort, 1 on error */
921 int mutt_check_overwrite (const char *attname, const char *path,
922                                 char *fname, size_t flen, int *append, char **directory) 
923 {
924   int rc = 0;
925   char tmp[_POSIX_PATH_MAX];
926   struct stat st;
927
928   strfcpy (fname, path, flen);
929   if (access (fname, F_OK) != 0)
930     return 0;
931   if (stat (fname, &st) != 0)
932     return -1;
933   if (S_ISDIR (st.st_mode))
934   {
935     if (directory)
936     {
937       switch (mutt_multi_choice
938               (_("File is a directory, save under it? [(y)es, (n)o, (a)ll]"), _("yna")))
939       {
940         case 3:         /* all */
941           mutt_str_replace (directory, fname);
942           break;
943         case 1:         /* yes */
944           FREE (directory);             /* __FREE_CHECKED__ */
945           break;
946         case -1:        /* abort */
947           FREE (directory);             /* __FREE_CHECKED__ */
948           return -1;
949         case  2:        /* no */
950           FREE (directory);             /* __FREE_CHECKED__ */
951           return 1;
952       }
953     }
954     else if ((rc = mutt_yesorno (_("File is a directory, save under it?"), M_YES)) != M_YES)
955       return (rc == M_NO) ? 1 : -1;
956
957     if (!attname || !attname[0])
958     {
959       tmp[0] = 0;
960       if (mutt_get_field (_("File under directory: "), tmp, sizeof (tmp),
961                                       M_FILE | M_CLEAR) != 0 || !tmp[0])
962         return (-1);
963       mutt_concat_path (fname, path, tmp, flen);
964     }
965     else
966       mutt_concat_path (fname, path, mutt_basename (attname), flen);
967   }
968   
969   if (*append == 0 && access (fname, F_OK) == 0)
970   {
971     switch (mutt_multi_choice
972             (_("File exists, (o)verwrite, (a)ppend, or (c)ancel?"), _("oac")))
973     {
974       case -1: /* abort */
975         return -1;
976       case 3:  /* cancel */
977         return 1;
978
979       case 2: /* append */
980         *append = M_SAVE_APPEND;
981         break;
982       case 1: /* overwrite */
983         *append = M_SAVE_OVERWRITE;
984         break;
985     }
986   }
987   return 0;
988 }
989
990 void mutt_save_path (char *d, size_t dsize, ADDRESS *a)
991 {
992   if (a && a->mailbox)
993   {
994     strfcpy (d, a->mailbox, dsize);
995     if (!option (OPTSAVEADDRESS))
996     {
997       char *p;
998
999       if ((p = strpbrk (d, "%@")))
1000         *p = 0;
1001     }
1002     mutt_strlower (d);
1003   }
1004   else
1005     *d = 0;
1006 }
1007
1008 void mutt_safe_path (char *s, size_t l, ADDRESS *a)
1009 {
1010   char *p;
1011
1012   mutt_save_path (s, l, a);
1013   for (p = s; *p; p++)
1014     if (*p == '/' || ISSPACE (*p) || !IsPrint ((unsigned char) *p))
1015       *p = '_';
1016 }
1017
1018
1019 void mutt_FormatString (char *dest,             /* output buffer */
1020                         size_t destlen,         /* output buffer len */
1021                         size_t col,             /* starting column (nonzero when called recursively) */
1022                         const char *src,        /* template string */
1023                         format_t *callback,     /* callback for processing */
1024                         unsigned long data,     /* callback data */
1025                         format_flag flags)      /* callback flags */
1026 {
1027   char prefix[SHORT_STRING], buf[LONG_STRING], *cp, *wptr = dest, ch;
1028   char ifstring[SHORT_STRING], elsestring[SHORT_STRING];
1029   size_t wlen, count, len, wid;
1030   pid_t pid;
1031   FILE *filter;
1032   int n;
1033   char *recycler;
1034
1035   prefix[0] = '\0';
1036   destlen--; /* save room for the terminal \0 */
1037   wlen = (flags & M_FORMAT_ARROWCURSOR && option (OPTARROWCURSOR)) ? 3 : 0;
1038   col += wlen;
1039
1040   if ((flags & M_FORMAT_NOFILTER) == 0)
1041   {
1042     int off = -1;
1043
1044     /* Do not consider filters if no pipe at end */
1045     n = mutt_strlen(src);
1046     if (n > 1 && src[n-1] == '|')
1047     {
1048       /* Scan backwards for backslashes */
1049       off = n;
1050       while (off > 0 && src[off-2] == '\\')
1051         off--;
1052     }
1053
1054     /* If number of backslashes is even, the pipe is real. */
1055     /* n-off is the number of backslashes. */
1056     if (off > 0 && ((n-off) % 2) == 0)
1057     {
1058       BUFFER *srcbuf, *word, *command;
1059       char    srccopy[LONG_STRING];
1060 #ifdef DEBUG
1061       int     i = 0;
1062 #endif
1063
1064       dprint(3, (debugfile, "fmtpipe = %s\n", src));
1065
1066       strncpy(srccopy, src, n);
1067       srccopy[n-1] = '\0';
1068
1069       /* prepare BUFFERs */
1070       srcbuf = mutt_buffer_from(NULL, srccopy);
1071       srcbuf->dptr = srcbuf->data;
1072       word = mutt_buffer_init(NULL);
1073       command = mutt_buffer_init(NULL);
1074
1075       /* Iterate expansions across successive arguments */
1076       do {
1077         char *p;
1078
1079         /* Extract the command name and copy to command line */
1080         dprint(3, (debugfile, "fmtpipe +++: %s\n", srcbuf->dptr));
1081         if (word->data)
1082           *word->data = '\0';
1083         mutt_extract_token(word, srcbuf, 0);
1084         dprint(3, (debugfile, "fmtpipe %2d: %s\n", i++, word->data));
1085         mutt_buffer_addch(command, '\'');
1086         mutt_FormatString(buf, sizeof(buf), 0, word->data, callback, data,
1087                           flags | M_FORMAT_NOFILTER);
1088         for (p = buf; p && *p; p++)
1089         {
1090           if (*p == '\'')
1091             /* shell quoting doesn't permit escaping a single quote within
1092              * single-quoted material.  double-quoting instead will lead
1093              * shell variable expansions, so break out of the single-quoted
1094              * span, insert a double-quoted single quote, and resume. */
1095             mutt_buffer_addstr(command, "'\"'\"'");
1096           else
1097             mutt_buffer_addch(command, *p);
1098         }
1099         mutt_buffer_addch(command, '\'');
1100         mutt_buffer_addch(command, ' ');
1101       } while (MoreArgs(srcbuf));
1102
1103       dprint(3, (debugfile, "fmtpipe > %s\n", command->data));
1104
1105       col -= wlen;      /* reset to passed in value */
1106       wptr = dest;      /* reset write ptr */
1107       wlen = (flags & M_FORMAT_ARROWCURSOR && option (OPTARROWCURSOR)) ? 3 : 0;
1108       if ((pid = mutt_create_filter(command->data, NULL, &filter, NULL)))
1109       {
1110         n = fread(dest, 1, destlen /* already decremented */, filter);
1111         safe_fclose (&filter);
1112         dest[n] = '\0';
1113         while (dest[n-1] == '\n' || dest[n-1] == '\r')
1114           dest[--n] = '\0';
1115         dprint(3, (debugfile, "fmtpipe < %s\n", dest));
1116
1117         if (pid != -1)
1118           mutt_wait_filter(pid);
1119   
1120         /* If the result ends with '%', this indicates that the filter
1121          * generated %-tokens that mutt can expand.  Eliminate the '%'
1122          * marker and recycle the string through mutt_FormatString().
1123          * To literally end with "%", use "%%". */
1124         if (dest[--n] == '%')
1125         {
1126           dest[n] = '\0';               /* remove '%' */
1127           if (dest[--n] != '%')
1128           {
1129             recycler = safe_strdup(dest);
1130             if (recycler)
1131             {
1132               mutt_FormatString(dest, destlen++, col, recycler, callback, data, flags);
1133               FREE(&recycler);
1134             }
1135           }
1136         }
1137       }
1138       else
1139       {
1140         /* Filter failed; erase write buffer */
1141         *wptr = '\0';
1142       }
1143
1144       mutt_buffer_free(&command);
1145       mutt_buffer_free(&srcbuf);
1146       mutt_buffer_free(&word);
1147       return;
1148     }
1149   }
1150
1151   while (*src && wlen < destlen)
1152   {
1153     if (*src == '%')
1154     {
1155       if (*++src == '%')
1156       {
1157         *wptr++ = '%';
1158         wlen++;
1159         col++;
1160         src++;
1161         continue;
1162       }
1163
1164       if (*src == '?')
1165       {
1166         flags |= M_FORMAT_OPTIONAL;
1167         src++;
1168       }
1169       else
1170       {
1171         flags &= ~M_FORMAT_OPTIONAL;
1172
1173         /* eat the format string */
1174         cp = prefix;
1175         count = 0;
1176         while (count < sizeof (prefix) &&
1177                (isdigit ((unsigned char) *src) || *src == '.' || *src == '-' || *src == '='))
1178         {
1179           *cp++ = *src++;
1180           count++;
1181         }
1182         *cp = 0;
1183       }
1184
1185       if (!*src)
1186         break; /* bad format */
1187
1188       ch = *src++; /* save the character to switch on */
1189
1190       if (flags & M_FORMAT_OPTIONAL)
1191       {
1192         if (*src != '?')
1193           break; /* bad format */
1194         src++;
1195
1196         /* eat the `if' part of the string */
1197         cp = ifstring;
1198         count = 0;
1199         while (count < sizeof (ifstring) && *src && *src != '?' && *src != '&')
1200         {
1201           *cp++ = *src++;
1202           count++;
1203         }
1204         *cp = 0;
1205
1206         /* eat the `else' part of the string (optional) */
1207         if (*src == '&')
1208           src++; /* skip the & */
1209         cp = elsestring;
1210         count = 0;
1211         while (count < sizeof (elsestring) && *src && *src != '?')
1212         {
1213           *cp++ = *src++;
1214           count++;
1215         }
1216         *cp = 0;
1217
1218         if (!*src)
1219           break; /* bad format */
1220
1221         src++; /* move past the trailing `?' */
1222       }
1223
1224       /* handle generic cases first */
1225       if (ch == '>' || ch == '*')
1226       {
1227         /* %>X: right justify to EOL, left takes precedence
1228          * %*X: right justify to EOL, right takes precedence */
1229         int soft = ch == '*';
1230         int pl, pw;
1231         if ((pl = mutt_charlen (src, &pw)) <= 0)
1232           pl = pw = 1;
1233
1234         /* see if there's room to add content, else ignore */
1235         if ((col < COLS && wlen < destlen) || soft)
1236         {
1237           int pad;
1238
1239           /* get contents after padding */
1240           mutt_FormatString (buf, sizeof (buf), 0, src + pl, callback, data, flags);
1241           len = mutt_strlen (buf);
1242           wid = mutt_strwidth (buf);
1243
1244           /* try to consume as many columns as we can, if we don't have
1245            * memory for that, use as much memory as possible */
1246           pad = (COLS - col - wid) / pw;
1247           if (pad > 0 && wlen + (pad * pl) + len > destlen)
1248             pad = ((signed)(destlen - wlen - len)) / pl;
1249           if (pad > 0)
1250           {
1251             while (pad--)
1252             {
1253               memcpy (wptr, src, pl);
1254               wptr += pl;
1255               wlen += pl;
1256               col += pw;
1257             }
1258           }
1259           else if (soft && pad < 0)
1260           {
1261             /* \0-terminate dest for length computation in mutt_wstr_trunc() */
1262             *wptr = 0;
1263             /* make sure right part is at most as wide as display */
1264             len = mutt_wstr_trunc (buf, destlen, COLS, &wid);
1265             /* truncate left so that right part fits completely in */
1266             wlen = mutt_wstr_trunc (dest, destlen - len, col + pad, &col);
1267             wptr = dest + wlen;
1268           }
1269           if (len + wlen > destlen)
1270             len = mutt_wstr_trunc (buf, destlen - wlen, COLS - col, NULL);
1271           memcpy (wptr, buf, len);
1272           wptr += len;
1273           wlen += len;
1274           col += wid;
1275           src += pl;
1276         }
1277         break; /* skip rest of input */
1278       }
1279       else if (ch == '|')
1280       {
1281         /* pad to EOL */
1282         int pl, pw, c;
1283         if ((pl = mutt_charlen (src, &pw)) <= 0)
1284           pl = pw = 1;
1285
1286         /* see if there's room to add content, else ignore */
1287         if (col < COLS && wlen < destlen)
1288         {
1289           c = (COLS - col) / pw;
1290           if (c > 0 && wlen + (c * pl) > destlen)
1291             c = ((signed)(destlen - wlen)) / pl;
1292           while (c > 0)
1293           {
1294             memcpy (wptr, src, pl);
1295             wptr += pl;
1296             wlen += pl;
1297             col += pw;
1298             c--;
1299           }
1300           src += pl;
1301         }
1302         break; /* skip rest of input */
1303       }
1304       else
1305       {
1306         short tolower =  0;
1307         short nodots  = 0;
1308         
1309         while (ch == '_' || ch == ':') 
1310         {
1311           if (ch == '_')
1312             tolower = 1;
1313           else if (ch == ':') 
1314             nodots = 1;
1315           
1316           ch = *src++;
1317         }
1318         
1319         /* use callback function to handle this case */
1320         src = callback (buf, sizeof (buf), col, ch, src, prefix, ifstring, elsestring, data, flags);
1321
1322         if (tolower)
1323           mutt_strlower (buf);
1324         if (nodots) 
1325         {
1326           char *p = buf;
1327           for (; *p; p++)
1328             if (*p == '.')
1329                 *p = '_';
1330         }
1331         
1332         if ((len = mutt_strlen (buf)) + wlen > destlen)
1333           len = mutt_wstr_trunc (buf, destlen - wlen, COLS - col, NULL);
1334
1335         memcpy (wptr, buf, len);
1336         wptr += len;
1337         wlen += len;
1338         col += mutt_strwidth (buf);
1339       }
1340     }
1341     else if (*src == '\\')
1342     {
1343       if (!*++src)
1344         break;
1345       switch (*src)
1346       {
1347         case 'n':
1348           *wptr = '\n';
1349           break;
1350         case 't':
1351           *wptr = '\t';
1352           break;
1353         case 'r':
1354           *wptr = '\r';
1355           break;
1356         case 'f':
1357           *wptr = '\f';
1358           break;
1359         case 'v':
1360           *wptr = '\v';
1361           break;
1362         default:
1363           *wptr = *src;
1364           break;
1365       }
1366       src++;
1367       wptr++;
1368       wlen++;
1369       col++;
1370     }
1371     else
1372     {
1373       int tmp, w;
1374       /* in case of error, simply copy byte */
1375       if ((tmp = mutt_charlen (src, &w)) < 0)
1376         tmp = w = 1;
1377       if (tmp > 0 && wlen + tmp < destlen)
1378       {
1379         memcpy (wptr, src, tmp);
1380         wptr += tmp;
1381         src += tmp;
1382         wlen += tmp;
1383         col += w;
1384       }
1385       else
1386       {
1387         src += destlen - wlen;
1388         wlen = destlen;
1389       }
1390     }
1391   }
1392   *wptr = 0;
1393
1394 #if 0
1395   if (flags & M_FORMAT_MAKEPRINT)
1396   {
1397     /* Make sure that the string is printable by changing all non-printable
1398        chars to dots, or spaces for non-printable whitespace */
1399     for (cp = dest ; *cp ; cp++)
1400       if (!IsPrint (*cp) &&
1401           !((flags & M_FORMAT_TREE) && (*cp <= M_TREE_MAX)))
1402         *cp = isspace ((unsigned char) *cp) ? ' ' : '.';
1403   }
1404 #endif
1405 }
1406
1407 /* This function allows the user to specify a command to read stdout from in
1408    place of a normal file.  If the last character in the string is a pipe (|),
1409    then we assume it is a commmand to run instead of a normal file. */
1410 FILE *mutt_open_read (const char *path, pid_t *thepid)
1411 {
1412   FILE *f;
1413   struct stat s;
1414
1415   int len = mutt_strlen (path);
1416
1417   if (path[len - 1] == '|')
1418   {
1419     /* read from a pipe */
1420
1421     char *s = safe_strdup (path);
1422
1423     s[len - 1] = 0;
1424     mutt_endwin (NULL);
1425     *thepid = mutt_create_filter (s, NULL, &f, NULL);
1426     FREE (&s);
1427   }
1428   else
1429   {
1430     if (stat (path, &s) < 0)
1431       return (NULL);
1432     if (S_ISDIR (s.st_mode))
1433     {
1434       errno = EINVAL;
1435       return (NULL);
1436     }
1437     f = fopen (path, "r");
1438     *thepid = -1;
1439   }
1440   return (f);
1441 }
1442
1443 /* returns 0 if OK to proceed, -1 to abort, 1 to retry */
1444 int mutt_save_confirm (const char *s, struct stat *st)
1445 {
1446   char tmp[_POSIX_PATH_MAX];
1447   int ret = 0;
1448   int rc;
1449   int magic = 0;
1450
1451   magic = mx_get_magic (s);
1452
1453 #ifdef USE_POP
1454   if (magic == M_POP)
1455   {
1456     mutt_error _("Can't save message to POP mailbox.");
1457     return 1;
1458   }
1459 #endif
1460
1461   if (magic > 0 && !mx_access (s, W_OK))
1462   {
1463     if (option (OPTCONFIRMAPPEND))
1464     {
1465       snprintf (tmp, sizeof (tmp), _("Append messages to %s?"), s);
1466       if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
1467         ret = 1;
1468       else if (rc == -1)
1469         ret = -1;
1470     }
1471   }
1472
1473   if (stat (s, st) != -1)
1474   {
1475     if (magic == -1)
1476     {
1477       mutt_error (_("%s is not a mailbox!"), s);
1478       return 1;
1479     }
1480   }
1481   else
1482 #ifdef USE_IMAP
1483   if (magic != M_IMAP)
1484 #endif /* execute the block unconditionally if we don't use imap */
1485   {
1486     st->st_mtime = 0;
1487     st->st_atime = 0;
1488
1489     if (errno == ENOENT)
1490     {
1491       if (option (OPTCONFIRMCREATE))
1492       {
1493         snprintf (tmp, sizeof (tmp), _("Create %s?"), s);
1494         if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
1495           ret = 1;
1496         else if (rc == -1)
1497           ret = -1;
1498       }
1499     }
1500     else
1501     {
1502       mutt_perror (s);
1503       return 1;
1504     }
1505   }
1506
1507   CLEARLINE (LINES-1);
1508   return (ret);
1509 }
1510
1511 void state_prefix_putc (char c, STATE *s)
1512 {
1513   if (s->flags & M_PENDINGPREFIX)
1514   {
1515     state_reset_prefix (s);
1516     if (s->prefix)
1517       state_puts (s->prefix, s);
1518   }
1519
1520   state_putc (c, s);
1521
1522   if (c == '\n')
1523     state_set_prefix (s);
1524 }
1525
1526 int state_printf (STATE *s, const char *fmt, ...)
1527 {
1528   int rv;
1529   va_list ap;
1530
1531   va_start (ap, fmt);
1532   rv = vfprintf (s->fpout, fmt, ap);
1533   va_end (ap);
1534   
1535   return rv;
1536 }
1537
1538 void state_mark_attach (STATE *s)
1539 {
1540   if ((s->flags & M_DISPLAY) && !mutt_strcmp (Pager, "builtin"))
1541     state_puts (AttachmentMarker, s);
1542 }
1543
1544 void state_attach_puts (const char *t, STATE *s)
1545 {
1546   if (*t != '\n') state_mark_attach (s);
1547   while (*t)
1548   {
1549     state_putc (*t, s);
1550     if (*t++ == '\n' && *t)
1551       if (*t != '\n') state_mark_attach (s);
1552   }
1553 }
1554
1555 void mutt_display_sanitize (char *s)
1556 {
1557   for (; *s; s++)
1558   {
1559     if (!IsPrint (*s))
1560       *s = '?';
1561   }
1562 }
1563       
1564 void mutt_sleep (short s)
1565 {
1566   if (SleepTime > s)
1567     sleep (SleepTime);
1568   else if (s)
1569     sleep (s);
1570 }
1571
1572 /*
1573  * Creates and initializes a BUFFER*. If passed an existing BUFFER*,
1574  * just initializes. Frees anything already in the buffer.
1575  *
1576  * Disregards the 'destroy' flag, which seems reserved for caller.
1577  * This is bad, but there's no apparent protocol for it.
1578  */
1579 BUFFER * mutt_buffer_init(BUFFER *b)
1580 {
1581   if (!b)
1582   {
1583     b = safe_malloc(sizeof(BUFFER));
1584     if (!b)
1585       return NULL;
1586   }
1587   else
1588   {
1589     FREE(&b->data);
1590   }
1591   memset(b, 0, sizeof(BUFFER));
1592   return b;
1593 }
1594
1595 /*
1596  * Creates and initializes a BUFFER*. If passed an existing BUFFER*,
1597  * just initializes. Frees anything already in the buffer. Copies in
1598  * the seed string.
1599  *
1600  * Disregards the 'destroy' flag, which seems reserved for caller.
1601  * This is bad, but there's no apparent protocol for it.
1602  */
1603 BUFFER * mutt_buffer_from(BUFFER *b, char *seed)
1604 {
1605   if (!seed)
1606     return NULL;
1607
1608   b = mutt_buffer_init(b);
1609   b->data = safe_strdup (seed);
1610   b->dsize = mutt_strlen (seed);
1611   b->dptr = (char *) b->data + b->dsize;
1612   return b;
1613 }
1614
1615 int mutt_buffer_printf (BUFFER* buf, const char* fmt, ...)
1616 {
1617   va_list ap, ap_retry;
1618   int len, blen, doff;
1619   
1620   va_start (ap, fmt);
1621   va_copy (ap_retry, ap);
1622
1623   doff = buf->dptr - buf->data;
1624   blen = buf->dsize - doff;
1625   /* solaris 9 vsnprintf barfs when blen is 0 */
1626   if (!blen)
1627   {
1628     blen = 128;
1629     buf->dsize += blen;
1630     safe_realloc (&buf->data, buf->dsize);
1631     buf->dptr = buf->data + doff;
1632   }
1633   if ((len = vsnprintf (buf->dptr, blen, fmt, ap)) >= blen)
1634   {
1635     blen = ++len - blen;
1636     if (blen < 128)
1637       blen = 128;
1638     buf->dsize += blen;
1639     safe_realloc (&buf->data, buf->dsize);
1640     buf->dptr = buf->data + doff;
1641     len = vsnprintf (buf->dptr, len, fmt, ap_retry);
1642   }
1643   if (len > 0)
1644     buf->dptr += len;
1645
1646   va_end (ap);
1647   va_end (ap_retry);
1648
1649   return len;
1650 }
1651
1652 void mutt_buffer_addstr (BUFFER* buf, const char* s)
1653 {
1654   mutt_buffer_add (buf, s, mutt_strlen (s));
1655 }
1656
1657 void mutt_buffer_addch (BUFFER* buf, char c)
1658 {
1659   mutt_buffer_add (buf, &c, 1);
1660 }
1661
1662 void mutt_buffer_free (BUFFER **p)
1663 {
1664   if (!p || !*p) 
1665     return;
1666
1667    FREE(&(*p)->data);
1668    /* dptr is just an offset to data and shouldn't be freed */
1669    FREE(p);             /* __FREE_CHECKED__ */
1670 }
1671
1672 /* dynamically grows a BUFFER to accomodate s, in increments of 128 bytes.
1673  * Always one byte bigger than necessary for the null terminator, and
1674  * the buffer is always null-terminated */
1675 void mutt_buffer_add (BUFFER* buf, const char* s, size_t len)
1676 {
1677   size_t offset;
1678
1679   if (buf->dptr + len + 1 > buf->data + buf->dsize)
1680   {
1681     offset = buf->dptr - buf->data;
1682     buf->dsize += len < 128 ? 128 : len + 1;
1683     /* suppress compiler aliasing warning */
1684     safe_realloc ((void**) (void*) &buf->data, buf->dsize);
1685     buf->dptr = buf->data + offset;
1686   }
1687   memcpy (buf->dptr, s, len);
1688   buf->dptr += len;
1689   *(buf->dptr) = '\0';
1690 }
1691
1692 /* Decrease a file's modification time by 1 second */
1693
1694 time_t mutt_decrease_mtime (const char *f, struct stat *st)
1695 {
1696   struct utimbuf utim;
1697   struct stat _st;
1698   time_t mtime;
1699   
1700   if (!st)
1701   {
1702     if (stat (f, &_st) == -1)
1703       return -1;
1704     st = &_st;
1705   }
1706
1707   if ((mtime = st->st_mtime) == time (NULL))
1708   {
1709     mtime -= 1;
1710     utim.actime = mtime;
1711     utim.modtime = mtime;
1712     utime (f, &utim);
1713   }
1714   
1715   return mtime;
1716 }
1717
1718 /* sets mtime of 'to' to mtime of 'from' */
1719 void mutt_set_mtime (const char* from, const char* to)
1720 {
1721   struct utimbuf utim;
1722   struct stat st;
1723
1724   if (stat (from, &st) != -1)
1725   {
1726     utim.actime = st.st_mtime;
1727     utim.modtime = st.st_mtime;
1728     utime (to, &utim);
1729   }
1730 }
1731
1732 const char *mutt_make_version (void)
1733 {
1734   static char vstring[STRING];
1735   snprintf (vstring, sizeof (vstring), "Mutt %s (%s)",
1736             MUTT_VERSION, ReleaseDate);
1737   return vstring;
1738 }
1739
1740 REGEXP *mutt_compile_regexp (const char *s, int flags)
1741 {
1742   REGEXP *pp = safe_calloc (sizeof (REGEXP), 1);
1743   pp->pattern = safe_strdup (s);
1744   pp->rx = safe_calloc (sizeof (regex_t), 1);
1745   if (REGCOMP (pp->rx, NONULL(s), flags) != 0)
1746     mutt_free_regexp (&pp);
1747
1748   return pp;
1749 }
1750
1751 void mutt_free_regexp (REGEXP **pp)
1752 {
1753   FREE (&(*pp)->pattern);
1754   regfree ((*pp)->rx);
1755   FREE (&(*pp)->rx);
1756   FREE (pp);            /* __FREE_CHECKED__ */
1757 }
1758
1759 void mutt_free_rx_list (RX_LIST **list)
1760 {
1761   RX_LIST *p;
1762   
1763   if (!list) return;
1764   while (*list)
1765   {
1766     p = *list;
1767     *list = (*list)->next;
1768     mutt_free_regexp (&p->rx);
1769     FREE (&p);
1770   }
1771 }
1772
1773 void mutt_free_spam_list (SPAM_LIST **list)
1774 {
1775   SPAM_LIST *p;
1776   
1777   if (!list) return;
1778   while (*list)
1779   {
1780     p = *list;
1781     *list = (*list)->next;
1782     mutt_free_regexp (&p->rx);
1783     FREE (&p->template);
1784     FREE (&p);
1785   }
1786 }
1787
1788 int mutt_match_rx_list (const char *s, RX_LIST *l)
1789 {
1790   if (!s)  return 0;
1791   
1792   for (; l; l = l->next)
1793   {
1794     if (regexec (l->rx->rx, s, (size_t) 0, (regmatch_t *) 0, (int) 0) == 0)
1795     {
1796       dprint (5, (debugfile, "mutt_match_rx_list: %s matches %s\n", s, l->rx->pattern));
1797       return 1;
1798     }
1799   }
1800
1801   return 0;
1802 }
1803
1804 int mutt_match_spam_list (const char *s, SPAM_LIST *l, char *text, int x)
1805 {
1806   static regmatch_t *pmatch = NULL;
1807   static int nmatch = 0;
1808   int i, n, tlen;
1809   char *p;
1810
1811   if (!s)  return 0;
1812
1813   tlen = 0;
1814
1815   for (; l; l = l->next)
1816   {
1817     /* If this pattern needs more matches, expand pmatch. */
1818     if (l->nmatch > nmatch)
1819     {
1820       safe_realloc (&pmatch, l->nmatch * sizeof(regmatch_t));
1821       nmatch = l->nmatch;
1822     }
1823
1824     /* Does this pattern match? */
1825     if (regexec (l->rx->rx, s, (size_t) l->nmatch, (regmatch_t *) pmatch, (int) 0) == 0)
1826     {
1827       dprint (5, (debugfile, "mutt_match_spam_list: %s matches %s\n", s, l->rx->pattern));
1828       dprint (5, (debugfile, "mutt_match_spam_list: %d subs\n", (int)l->rx->rx->re_nsub));
1829
1830       /* Copy template into text, with substitutions. */
1831       for (p = l->template; *p;)
1832       {
1833         if (*p == '%')
1834         {
1835           n = atoi(++p);                        /* find pmatch index */
1836           while (isdigit((unsigned char)*p))
1837             ++p;                                /* skip subst token */
1838           for (i = pmatch[n].rm_so; (i < pmatch[n].rm_eo) && (tlen < x); i++)
1839             text[tlen++] = s[i];
1840         }
1841         else
1842         {
1843           text[tlen++] = *p++;
1844         }
1845       }
1846       text[tlen] = '\0';
1847       dprint (5, (debugfile, "mutt_match_spam_list: \"%s\"\n", text));
1848       return 1;
1849     }
1850   }
1851
1852   return 0;
1853 }
1854