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