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