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