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