simplify some bits of code, also simplify includes.
[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 <ctype.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <grp.h>
19 #include <pwd.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #include <sys/wait.h>
25 #include <time.h>
26 #include <unistd.h>
27 #include <utime.h>
28
29 #include <lib-lib/mem.h>
30 #include <lib-lib/ascii.h>
31 #include <lib-lib/str.h>
32 #include <lib-lib/macros.h>
33 #include <lib-lib/buffer.h>
34 #include <lib-lib/file.h>
35 #include <lib-lib/url.h>
36
37 #include <lib-mime/mime.h>
38
39 #include <lib-ui/curses.h>
40 #include <lib-ui/enter.h>
41
42 #include "alias.h"
43 #include "mutt.h"
44 #include "mx.h"
45 #include "attach.h"
46
47 #include "version.h"
48
49 #include <imap/imap.h>
50 #include <imap/mx_imap.h>
51
52 #include <lib-crypt/crypt.h>
53
54 #define SW              (option(OPTMBOXPANE)?SidebarWidth:0)
55
56 /* Modified by blong to accept a "suggestion" for file name.  If
57  * that file exists, then construct one with unique name but 
58  * keep any extension.  This might fail, I guess.
59  * Renamed to mutt_adv_mktemp so I only have to change where it's
60  * called, and not all possible cases.
61  */
62 void mutt_adv_mktemp (const char* dir, char *s, ssize_t l)
63 {
64   char buf[_POSIX_PATH_MAX];
65   char tmp[_POSIX_PATH_MAX];
66   char *period;
67   ssize_t sl;
68   struct stat sb;
69
70   m_strcpy(buf, sizeof(buf), m_strisempty(dir) ? NONULL(Tempdir) : dir);
71   mutt_expand_path (buf, sizeof (buf));
72   if (s[0] == '\0') {
73     snprintf (s, l, "%s/muttXXXXXX", buf);
74     mktemp (s);
75   }
76   else {
77     m_strcpy(tmp, sizeof(tmp), s);
78     mutt_sanitize_filename (tmp, 1);
79     snprintf (s, l, "%s/%s", buf, tmp);
80     if (lstat (s, &sb) == -1 && errno == ENOENT)
81       return;
82     if ((period = strrchr (tmp, '.')) != NULL)
83       *period = 0;
84     snprintf (s, l, "%s/%s.XXXXXX", buf, tmp);
85     mktemp (s);
86     if (period != NULL) {
87       *period = '.';
88       sl = m_strlen(s);
89       m_strcpy(s + sl, l - sl, period);
90     }
91   }
92 }
93
94 /* create a send-mode duplicate from a receive-mode body */
95
96 int mutt_copy_body (FILE * fp, BODY ** tgt, BODY * src)
97 {
98   char tmp[_POSIX_PATH_MAX];
99   BODY *b;
100
101   parameter_t *par, **ppar;
102
103   short use_disp;
104
105   if (src->filename) {
106     use_disp = 1;
107     m_strcpy(tmp, sizeof(tmp), src->filename);
108   }
109   else {
110     use_disp = 0;
111     tmp[0] = '\0';
112   }
113
114   mutt_adv_mktemp (NULL, tmp, sizeof (tmp));
115   if (mutt_save_attachment (fp, src, tmp, 0, NULL) == -1)
116     return -1;
117
118   *tgt = body_new();
119   b = *tgt;
120
121   memcpy (b, src, sizeof (BODY));
122   b->parts = NULL;
123   b->next = NULL;
124
125   b->filename = m_strdup(tmp);
126   b->use_disp = use_disp;
127   b->unlink = 1;
128
129   if (mutt_is_text_part (b))
130     b->noconv = 1;
131
132   b->xtype = m_strdup(b->xtype);
133   b->subtype = m_strdup(b->subtype);
134   b->form_name = m_strdup(b->form_name);
135   b->filename = m_strdup(b->filename);
136   b->d_filename = m_strdup(b->d_filename);
137   b->description = m_strdup(b->description);
138
139   /* 
140    * we don't seem to need the HEADER structure currently.
141    * XXX - this may change in the future
142    */
143
144   if (b->hdr)
145     b->hdr = NULL;
146
147   /* copy parameters */
148   for (par = b->parameter, ppar = &b->parameter; par;
149        ppar = &(*ppar)->next, par = par->next) {
150     *ppar = parameter_new();
151     (*ppar)->attribute = m_strdup(par->attribute);
152     (*ppar)->value = m_strdup(par->value);
153   }
154
155   mutt_stamp_attachment (b);
156
157   return 0;
158 }
159
160 HEADER *mutt_dup_header (HEADER * h)
161 {
162   HEADER *hnew;
163
164   hnew = header_new();
165   memcpy (hnew, h, sizeof (HEADER));
166   return hnew;
167 }
168
169 /* returns true if the header contained in "s" is in list "t" */
170 int mutt_matches_ignore (const char *s, string_list_t * t)
171 {
172   for (; t; t = t->next) {
173     if (!ascii_strncasecmp (s, t->data, m_strlen(t->data))
174         || *t->data == '*')
175       return 1;
176   }
177   return 0;
178 }
179
180 /* prepend the path part of *path to *lnk */
181 void mutt_expand_link (char *newpath, const char *path, const char *lnk)
182 {
183   const char *lb = NULL;
184   ssize_t len;
185
186   /* lnk is full path */
187   if (*lnk == '/') {
188     m_strcpy(newpath, _POSIX_PATH_MAX, lnk);
189     return;
190   }
191
192   if ((lb = strrchr (path, '/')) == NULL) {
193     /* no path in lnk */
194     m_strcpy(newpath, _POSIX_PATH_MAX, lnk);
195     return;
196   }
197
198   len = lb - path + 1;
199   memcpy (newpath, path, len);
200   m_strcpy(newpath + len, _POSIX_PATH_MAX - len, lnk);
201 }
202
203 char *mutt_expand_path (char *s, ssize_t slen)
204 {
205   return _mutt_expand_path (s, slen, 0);
206 }
207
208 char *_mutt_expand_path (char *s, ssize_t slen, int rx)
209 {
210   char p[_POSIX_PATH_MAX] = "";
211   char q[_POSIX_PATH_MAX] = "";
212   char tmp[_POSIX_PATH_MAX];
213   char *t;
214
215   const char *tail = "";
216
217   int recurse = 0;
218
219   do {
220     recurse = 0;
221
222     switch (*s) {
223     case '~':
224       {
225         if (*(s + 1) == '/' || *(s + 1) == 0) {
226           m_strcpy(p, sizeof(p), NONULL(Homedir));
227           tail = s + 1;
228         }
229         else {
230           struct passwd *pw;
231
232           if ((t = strchr (s + 1, '/')))
233             *t = 0;
234
235           if ((pw = getpwnam (s + 1))) {
236             m_strcpy(p, sizeof(p), pw->pw_dir);
237             if (t) {
238               *t = '/';
239               tail = t;
240             }
241             else
242               tail = "";
243           }
244           else {
245             /* user not found! */
246             if (t)
247               *t = '/';
248             *p = '\0';
249             tail = s;
250           }
251         }
252       }
253       break;
254
255     case '=':
256     case '+':
257       {
258         /* if folder = imap[s]://host/: don't append slash */
259         if (imap_is_magic (NONULL (Maildir), NULL) == M_IMAP && 
260             Maildir[m_strlen(Maildir) - 1] == '/')
261           m_strcpy(p, sizeof(p), NONULL(Maildir));
262         else
263           snprintf (p, sizeof (p), "%s/", NONULL (Maildir));
264
265         tail = s + 1;
266       }
267       break;
268
269       /* elm compatibility, @ expands alias to user name */
270
271     case '@':
272       {
273         HEADER *h;
274         /* FIXME: BUG ? */
275         address_t *alias;
276
277         if ((alias = alias_lookup(s + 1))) {
278           h = header_new();
279           h->env = envelope_new();
280           h->env->from = h->env->to = alias;
281           mutt_default_save (p, sizeof (p), h);
282           h->env->from = h->env->to = NULL;
283           header_delete(&h);
284           /* Avoid infinite recursion if the resulting folder starts with '@' */
285           if (*p != '@')
286             recurse = 1;
287
288           tail = "";
289         }
290       }
291       break;
292
293     case '>':
294       {
295         m_strcpy(p, sizeof(p), NONULL(Inbox));
296         tail = s + 1;
297       }
298       break;
299
300     case '<':
301       {
302         m_strcpy(p, sizeof(p), NONULL(Outbox));
303         tail = s + 1;
304       }
305       break;
306
307     case '!':
308       {
309         if (*(s + 1) == '!') {
310           m_strcpy(p, sizeof(p), NONULL(LastFolder));
311           tail = s + 2;
312         }
313         else {
314           m_strcpy(p, sizeof(p), NONULL(Spoolfile));
315           tail = s + 1;
316         }
317       }
318       break;
319
320     case '-':
321       {
322         m_strcpy(p, sizeof(p), NONULL(LastFolder));
323         tail = s + 1;
324       }
325       break;
326
327     case '^':
328       {
329         m_strcpy(p, sizeof(p), NONULL(CurrentFolder));
330         tail = s + 1;
331       }
332       break;
333
334     default:
335       {
336         *p = '\0';
337         tail = s;
338       }
339     }
340
341     if (rx && *p && !recurse) {
342       rx_sanitize_string (q, sizeof (q), p);
343       snprintf (tmp, sizeof (tmp), "%s%s", q, tail);
344     }
345     else
346       snprintf (tmp, sizeof (tmp), "%s%s", p, tail);
347
348     m_strcpy(s, slen, tmp);
349   }
350   while (recurse);
351
352   return (s);
353 }
354
355 /* returns 1 if Mutt can't display this type of data, 0 otherwise */
356 int mutt_needs_mailcap (BODY * m)
357 {
358   switch (m->type) {
359   case TYPETEXT:
360
361     if (!ascii_strcasecmp ("plain", m->subtype) ||
362         !ascii_strcasecmp ("rfc822-headers", m->subtype) ||
363         !ascii_strcasecmp ("enriched", m->subtype))
364       return 0;
365     break;
366
367   case TYPEAPPLICATION:
368     if (mutt_is_application_pgp (m))
369       return 0;
370     if (mutt_is_application_smime (m))
371       return 0;
372     break;
373
374   case TYPEMULTIPART:
375   case TYPEMESSAGE:
376     return 0;
377   }
378
379   return 1;
380 }
381
382 int mutt_is_text_part (BODY * b)
383 {
384   int t = b->type;
385   char *s = b->subtype;
386
387   if (mutt_is_application_pgp (b))
388     return 0;
389
390   if (t == TYPETEXT)
391     return 1;
392
393   if (t == TYPEMESSAGE) {
394     if (!ascii_strcasecmp ("delivery-status", s))
395       return 1;
396   }
397
398   if (t == TYPEAPPLICATION) {
399     if (!ascii_strcasecmp ("pgp-keys", s))
400       return 1;
401   }
402
403   return 0;
404 }
405
406 /* move all the headers from extra not present in base into base */
407 void mutt_merge_envelopes(ENVELOPE* base, ENVELOPE** extra)
408 {
409   /* copies each existing element if necessary, and sets the element
410   * to NULL in the source so that envelope_delete doesn't leave us
411   * with dangling pointers. */
412 #define MOVE_ELEM(h) if (!base->h) { base->h = (*extra)->h; (*extra)->h = NULL; }
413   MOVE_ELEM(return_path);
414   MOVE_ELEM(from);
415   MOVE_ELEM(to);
416   MOVE_ELEM(cc);
417   MOVE_ELEM(bcc);
418   MOVE_ELEM(sender);
419   MOVE_ELEM(reply_to);
420   MOVE_ELEM(mail_followup_to);
421   MOVE_ELEM(list_post);
422   MOVE_ELEM(message_id);
423   MOVE_ELEM(supersedes);
424   MOVE_ELEM(date);
425   MOVE_ELEM(x_label);
426   if (!base->refs_changed) {
427     MOVE_ELEM(references);
428   }
429   if (!base->irt_changed) {
430     MOVE_ELEM(in_reply_to);
431   }
432   /* real_subj is subordinate to subject */
433   if (!base->subject) {
434     base->subject = (*extra)->subject;
435     base->real_subj = (*extra)->real_subj;
436     (*extra)->subject = NULL;
437     (*extra)->real_subj = NULL;
438   }
439   /* spam and user headers should never be hashed, and the new envelope may
440    * have better values. Use new versions regardless. */
441   mutt_buffer_free (&base->spam);
442   string_list_wipe(&base->userhdrs);
443   MOVE_ELEM(spam);
444   MOVE_ELEM(userhdrs);
445 #undef MOVE_ELEM
446   
447   envelope_delete(extra);
448 }
449
450 void _mutt_mktemp (char *s, const char *src, int line)
451 {
452
453   snprintf (s, _POSIX_PATH_MAX, "%s/madmutt-%s-%d-%d-%d-%x%x", NONULL (Tempdir),
454             NONULL (Hostname), (int) getuid (), (int) getpid (), Counter++, 
455             (unsigned int) rand(), (unsigned int) rand());
456   unlink (s);
457 }
458
459 /* collapse the pathname using ~ or = when possible */
460 void mutt_pretty_mailbox (char *s)
461 {
462   char *p = s, *q = s;
463   ssize_t len;
464   url_scheme_t scheme;
465
466   scheme = url_check_scheme (s);
467
468   if (scheme == U_IMAP || scheme == U_IMAPS) {
469     imap_pretty_mailbox (s);
470     return;
471   }
472
473   /* if s is an url, only collapse path component */
474   if (scheme != U_UNKNOWN) {
475     p = strchr (s, ':') + 1;
476     if (!strncmp (p, "//", 2))
477       q = strchr (p + 2, '/');
478     if (!q)
479       q = strchr (p, '\0');
480     p = q;
481   }
482
483   /* first attempt to collapse the pathname */
484   while (*p) {
485     if (*p == '/' && p[1] == '/') {
486       *q++ = '/';
487       p += 2;
488     }
489     else if (p[0] == '/' && p[1] == '.' && p[2] == '/') {
490       *q++ = '/';
491       p += 3;
492     }
493     else
494       *q++ = *p++;
495   }
496   *q = 0;
497
498   if (m_strncmp(s, Maildir, (len = m_strlen(Maildir))) == 0 &&
499       s[len] == '/') {
500     *s++ = '=';
501     memmove (s, s + len, m_strlen(s + len) + 1);
502   }
503   else if (m_strncmp(s, Homedir, (len = m_strlen(Homedir))) == 0 &&
504            s[len] == '/') {
505     *s++ = '~';
506     memmove (s, s + len - 1, m_strlen(s + len - 1) + 1);
507   }
508 }
509
510 void mutt_pretty_size (char *s, ssize_t len, long n)
511 {
512   if (n == 0)
513     m_strcpy(s, len, "0K");
514   else if (n < 10189)           /* 0.1K - 9.9K */
515     snprintf (s, len, "%3.1fK", (n < 103) ? 0.1 : n / 1024.0);
516   else if (n < 1023949) {       /* 10K - 999K */
517     /* 51 is magic which causes 10189/10240 to be rounded up to 10 */
518     snprintf (s, len, "%ldK", (n + 51) / 1024);
519   }
520   else if (n < 10433332)        /* 1.0M - 9.9M */
521     snprintf (s, len, "%3.1fM", n / 1048576.0);
522   else {                        /* 10M+ */
523
524     /* (10433332 + 52428) / 1048576 = 10 */
525     snprintf (s, len, "%ldM", (n + 52428) / 1048576);
526   }
527 }
528
529 void mutt_expand_file_fmt (char *dest, ssize_t destlen, const char *fmt,
530                            const char *src)
531 {
532   char tmp[LONG_STRING];
533
534   mutt_quote_filename (tmp, sizeof (tmp), src);
535   mutt_expand_fmt (dest, destlen, fmt, tmp);
536 }
537
538 void mutt_expand_fmt (char *dest, ssize_t destlen, const char *fmt,
539                       const char *src)
540 {
541   const char *p;
542   char *d;
543   ssize_t slen;
544   int found = 0;
545
546   slen = m_strlen(src);
547   destlen--;
548
549   for (p = fmt, d = dest; destlen && *p; p++) {
550     if (*p == '%') {
551       switch (p[1]) {
552       case '%':
553         *d++ = *p++;
554         destlen--;
555         break;
556       case 's':
557         found = 1;
558         m_strcpy(d, destlen + 1, src);
559         d += destlen > slen ? slen : destlen;
560         destlen -= destlen > slen ? slen : destlen;
561         p++;
562         break;
563       default:
564         *d++ = *p;
565         destlen--;
566         break;
567       }
568     }
569     else {
570       *d++ = *p;
571       destlen--;
572     }
573   }
574
575   *d = '\0';
576
577   if (!found && destlen > 0) {
578     m_strcat(dest, destlen, " ");
579     m_strcat(dest, destlen, src);
580   }
581
582 }
583
584 /* return 0 on success, -1 on abort, 1 on error */
585 int mutt_check_overwrite (const char *attname, const char *path,
586                           char *fname, ssize_t flen, int *append,
587                           char **directory)
588 {
589   int rc = 0;
590   char tmp[_POSIX_PATH_MAX];
591   struct stat st;
592
593   m_strcpy(fname, flen, path);
594   if (access (fname, F_OK) != 0)
595     return 0;
596   if (stat (fname, &st) != 0)
597     return -1;
598   if (S_ISDIR (st.st_mode)) {
599     if (directory) {
600       switch (mutt_multi_choice
601               (_("File is a directory, save under it? [(y)es, (n)o, (a)ll]"),
602                _("yna"))) {
603       case 3:                  /* all */
604         m_strreplace(directory, fname);
605         break;
606       case 1:                  /* yes */
607         p_delete(directory);
608         break;
609       case -1:                 /* abort */
610         p_delete(directory);
611         return -1;
612       case 2:                  /* no */
613         p_delete(directory);
614         return 1;
615       }
616     }
617     else
618       if ((rc =
619            mutt_yesorno (_("File is a directory, save under it?"),
620                          M_YES)) != M_YES)
621       return (rc == M_NO) ? 1 : -1;
622
623     if (!attname || !attname[0]) {
624       tmp[0] = 0;
625       if (mutt_get_field (_("File under directory: "), tmp, sizeof (tmp),
626                           M_FILE | M_CLEAR) != 0 || !tmp[0])
627         return (-1);
628       mutt_concat_path(fname, flen, path, tmp);
629     }
630     else
631       mutt_concat_path(fname, flen, path, mutt_basename(attname));
632   }
633
634   if (*append == 0 && access (fname, F_OK) == 0) {
635     switch (mutt_multi_choice
636             (_("File exists, (o)verwrite, (a)ppend, or (c)ancel?"), _("oac")))
637     {
638     case -1:                   /* abort */
639       return -1;
640     case 3:                    /* cancel */
641       return 1;
642
643     case 2:                    /* append */
644       *append = M_SAVE_APPEND;
645       break;
646     case 1:                    /* overwrite */
647       *append = M_SAVE_OVERWRITE;
648       break;
649     }
650   }
651   return 0;
652 }
653
654 void mutt_save_path (char *d, ssize_t dsize, address_t * a)
655 {
656   if (a && a->mailbox) {
657     m_strcpy(d, dsize, a->mailbox);
658     if (!option (OPTSAVEADDRESS)) {
659       char *p;
660
661       if ((p = strpbrk (d, "%@")))
662         *p = 0;
663     }
664     m_strtolower(d);
665   }
666   else
667     *d = 0;
668 }
669
670 void mutt_safe_path (char *s, ssize_t l, address_t * a)
671 {
672   char *p;
673
674   mutt_save_path (s, l, a);
675   for (p = s; *p; p++)
676     if (*p == '/' || ISSPACE (*p) || !IsPrint ((unsigned char) *p))
677       *p = '_';
678 }
679
680 /* counts how many characters in s can be skipped while none of the
681  * characters of c appears */
682 int mutt_skipchars (const char *s, const char *c)
683 {
684   int ret = 0;
685   const char *p = s;
686
687   while (s && *s) {
688     register const char *t = c;
689
690     while (t && *t) {
691       if (*t == *s)
692         return (ret);
693       t++;
694     }
695     ret++;
696     s++;
697   }
698   return (m_strlen(p));
699 }
700
701 void mutt_FormatString (char *dest,     /* output buffer */
702                         ssize_t destlen, /* output buffer len */
703                         const char *src,        /* template string */
704                         format_t * callback,    /* callback for processing */
705                         unsigned long data,     /* callback data */
706                         format_flag flags)
707 {                               /* callback flags */
708   char prefix[SHORT_STRING], buf[LONG_STRING], *cp, *wptr = dest, ch;
709   char ifstring[SHORT_STRING], elsestring[SHORT_STRING];
710   ssize_t wlen, wid, count, col, len;
711
712   prefix[0] = '\0';
713   destlen--;                    /* save room for the terminal \0 */
714   wlen = (flags & M_FORMAT_ARROWCURSOR && option (OPTARROWCURSOR)) ? 3 : 0;
715   col = wlen;
716
717   while (*src && wlen < destlen) {
718     if (*src == '%') {
719       if (*++src == '%') {
720         *wptr++ = '%';
721         wlen++;
722         col++;
723         src++;
724         continue;
725       }
726
727       if (*src == '?') {
728         flags |= M_FORMAT_OPTIONAL;
729         src++;
730       }
731       else {
732         flags &= ~M_FORMAT_OPTIONAL;
733
734         /* eat the format string */
735         cp = prefix;
736         count = 0;
737         while (count < ssizeof (prefix) &&
738                (isdigit ((unsigned char) *src) || *src == '.' || *src == '-'))
739         {
740           *cp++ = *src++;
741           count++;
742         }
743         *cp = 0;
744       }
745
746       if (!*src)
747         break;                  /* bad format */
748
749       ch = *src++;              /* save the character to switch on */
750
751       if (flags & M_FORMAT_OPTIONAL) {
752         if (*src != '?')
753           break;                /* bad format */
754         src++;
755
756         /* eat the `if' part of the string */
757         cp = ifstring;
758         count = 0;
759         while (count < ssizeof (ifstring) && *src && *src != '?'
760                && *src != '&') {
761           *cp++ = *src++;
762           count++;
763         }
764         *cp = 0;
765
766         /* eat the `else' part of the string (optional) */
767         if (*src == '&')
768           src++;                /* skip the & */
769         cp = elsestring;
770         count = 0;
771         while (count < ssizeof (elsestring) && *src && *src != '?') {
772           *cp++ = *src++;
773           count++;
774         }
775         *cp = 0;
776
777         if (!*src)
778           break;                /* bad format */
779
780         src++;                  /* move past the trailing `?' */
781       }
782
783       /* handle generic cases first */
784       if (ch == '>') {
785         /* right justify to EOL */
786         ch = *src++;            /* pad char */
787         /* calculate space left on line.  if we've already written more data
788            than will fit on the line, ignore the rest of the line */
789         if (DrawFullLine || option (OPTSTATUSONTOP))
790           count = (COLS < destlen ? COLS : destlen);
791         else
792           count = ((COLS - SW) < destlen ? (COLS - SW) : destlen);
793         if (count > col) {
794           count -= col;         /* how many columns left on this line */
795           mutt_FormatString (buf, sizeof (buf), src, callback, data, flags);
796           wid = m_strlen(buf);
797           if (count > wid) {
798             count -= wid;       /* how many chars to pad */
799             memset (wptr, ch, count);
800             wptr += count;
801             col += count;
802           }
803           if (wid + wlen > destlen)
804             len = destlen - wlen;
805           else
806             len = wid;
807           memcpy (wptr, buf, len);
808           wptr += len;
809           wlen += len;
810           col += mutt_strwidth (buf);
811         }
812         break;                  /* skip rest of input */
813       }
814       else if (ch == '|') {
815         /* pad to EOL */
816         ch = *src++;
817         if (destlen > COLS)
818           destlen = COLS;
819         if (destlen > wlen) {
820           count = destlen - wlen;
821           memset (wptr, ch, count);
822           wptr += count;
823         }
824         break;                  /* skip rest of input */
825       }
826       else {
827         short lower = 0;
828         short nodots = 0;
829
830         while (ch == '_' || ch == ':') {
831           if (ch == '_')
832             lower = 1;
833           else if (ch == ':')
834             nodots = 1;
835
836           ch = *src++;
837         }
838
839         /* use callback function to handle this case */
840         src =
841           callback (buf, sizeof (buf), ch, src, prefix, ifstring, elsestring,
842                     data, flags);
843
844         if (lower)
845           m_strtolower(buf);
846         if (nodots) {
847           char *p = buf;
848
849           for (; *p; p++)
850             if (*p == '.')
851               *p = '_';
852         }
853
854         if ((len = m_strlen(buf)) + wlen > destlen)
855           len = (destlen - wlen > 0) ? (destlen - wlen) : 0;
856
857         memcpy (wptr, buf, len);
858         wptr += len;
859         wlen += len;
860         col += mutt_strwidth (buf);
861       }
862     }
863     else if (*src == '\\') {
864       if (!*++src)
865         break;
866       switch (*src) {
867       case 'n':
868         *wptr = '\n';
869         break;
870       case 't':
871         *wptr = '\t';
872         break;
873       case 'r':
874         *wptr = '\r';
875         break;
876       case 'f':
877         *wptr = '\f';
878         break;
879       case 'v':
880         *wptr = '\v';
881         break;
882       default:
883         *wptr = *src;
884         break;
885       }
886       src++;
887       wptr++;
888       wlen++;
889       col++;
890     }
891     else {
892       unsigned int bar = mutt_skipchars (src, "%\\");
893       char *bar2 = p_dupstr(src, bar);
894
895       while (bar--) {
896         *wptr++ = *src++;
897         wlen++;
898       }
899       col += mutt_strwidth (bar2);
900       p_delete(&bar2);
901     }
902   }
903   *wptr = 0;
904
905 #if 0
906   if (flags & M_FORMAT_MAKEPRINT) {
907     /* Make sure that the string is printable by changing all non-printable
908        chars to dots, or spaces for non-printable whitespace */
909     for (cp = dest; *cp; cp++)
910       if (!IsPrint (*cp) && !((flags & M_FORMAT_TREE) && (*cp <= M_TREE_MAX)))
911         *cp = isspace ((unsigned char) *cp) ? ' ' : '.';
912   }
913 #endif
914 }
915
916 /* This function allows the user to specify a command to read stdout from in
917    place of a normal file.  If the last character in the string is a pipe (|),
918    then we assume it is a commmand to run instead of a normal file. */
919 FILE *mutt_open_read (const char *path, pid_t * thepid)
920 {
921     int len = m_strlen(path);
922     FILE *f;
923
924     if (path[len - 1] == '|') {
925         char *s = m_strdup(path);
926
927         /* read from a pipe */
928
929         s[len - 1] = 0;
930         mutt_endwin (NULL);
931         *thepid = mutt_create_filter (s, NULL, &f, NULL);
932         p_delete(&s);
933     } else {
934         f = fopen (path, "r");
935         if (!f)
936             return NULL;
937         *thepid = -1;
938     }
939
940     return (f);
941 }
942
943 /* returns 0 if OK to proceed, -1 to abort, 1 to retry */
944 int mutt_save_confirm (const char *s, struct stat *st)
945 {
946   char tmp[_POSIX_PATH_MAX];
947   int ret = 0;
948   int rc;
949   int magic = 0;
950
951   magic = mx_get_magic (s);
952
953   if (magic == M_POP) {
954     mutt_error _("Can't save message to POP mailbox.");
955
956     return 1;
957   }
958
959 #ifdef USE_NNTP
960   if (magic == M_NNTP) {
961     mutt_error _("Can't save message to newsserver.");
962
963     return 0;
964   }
965 #endif
966
967   if (magic > 0 && !mx_access (s, W_OK)) {
968     if (option (OPTCONFIRMAPPEND) &&
969         (!TrashPath || (m_strcmp(s, TrashPath) != 0))) {
970       /* if we're appending to the trash, there's no point in asking */
971       snprintf (tmp, sizeof (tmp), _("Append messages to %s?"), s);
972       if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
973         ret = 1;
974       else if (rc == -1)
975         ret = -1;
976     }
977   }
978
979   if (stat (s, st) != -1) {
980     if (magic == -1) {
981       mutt_error (_("%s is not a mailbox!"), s);
982       return 1;
983     }
984   }
985   else {
986     if (magic != M_IMAP)
987     {
988       st->st_mtime = 0;
989       st->st_atime = 0;
990
991       if (errno == ENOENT) {
992         if (option (OPTCONFIRMCREATE)) {
993           snprintf (tmp, sizeof (tmp), _("Create %s?"), s);
994           if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
995             ret = 1;
996           else if (rc == -1)
997             ret = -1;
998         }
999       }
1000       else {
1001         mutt_perror (s);
1002         return 1;
1003       }
1004     }
1005   }
1006
1007   CLEARLINE (LINES - 1);
1008   return (ret);
1009 }
1010
1011 void mutt_display_sanitize (char *s)
1012 {
1013   for (; *s; s++) {
1014     if (!IsPrint (*s))
1015       *s = '?';
1016   }
1017 }
1018
1019 void mutt_sleep (short s)
1020 {
1021   if (SleepTime > s)
1022     sleep (SleepTime);
1023   else if (s)
1024     sleep (s);
1025 }
1026
1027 /* Decrease a file's modification time by 1 second */
1028 time_t mutt_decrease_mtime (const char *f, struct stat *st)
1029 {
1030   struct utimbuf utim;
1031   struct stat _st;
1032   time_t mtime;
1033
1034   if (!st) {
1035     if (stat (f, &_st) == -1)
1036       return -1;
1037     st = &_st;
1038   }
1039
1040   if ((mtime = st->st_mtime) == time (NULL)) {
1041     mtime -= 1;
1042     utim.actime = mtime;
1043     utim.modtime = mtime;
1044     utime (f, &utim);
1045   }
1046
1047   return mtime;
1048 }
1049
1050 /* sets mtime of 'to' to mtime of 'from' */
1051 void mutt_set_mtime (const char* from, const char* to) {
1052   struct utimbuf utim;
1053   struct stat st;
1054
1055   if (stat (from, &st) != -1) {
1056     utim.actime = st.st_mtime;
1057     utim.modtime = st.st_mtime;
1058     utime (to, &utim);
1059   }
1060 }
1061
1062 const char *mutt_make_version (int full)
1063 {
1064   static char vstring[STRING];
1065
1066   if (full)
1067     snprintf (vstring, sizeof (vstring),
1068               "Madmutt/%s-r%s (based on Mutt 1.5.11)",
1069               MUTT_VERSION, MUTT_REVISION);
1070   else
1071     snprintf (vstring, sizeof (vstring), "Madmutt/%s-%s",
1072               MUTT_VERSION, MUTT_REVISION);
1073   return vstring;
1074 }
1075
1076 void mutt_free_spam_list (SPAM_LIST ** list)
1077 {
1078   SPAM_LIST *p;
1079
1080   if (!list)
1081     return;
1082   while (*list) {
1083     p = *list;
1084     *list = (*list)->next;
1085     rx_delete(&p->rx);
1086     p_delete(&p->template);
1087     p_delete(&p);
1088   }
1089 }
1090
1091 int mutt_match_spam_list (const char *s, SPAM_LIST * l, char *text, int x)
1092 {
1093   static regmatch_t *pmatch = NULL;
1094   static int nmatch = 0;
1095   int i, n, tlen;
1096   char *p;
1097
1098   if (!s)
1099     return 0;
1100
1101   tlen = 0;
1102
1103   for (; l; l = l->next) {
1104     /* If this pattern needs more matches, expand pmatch. */
1105     if (l->nmatch > nmatch) {
1106       p_realloc(&pmatch, l->nmatch);
1107       nmatch = l->nmatch;
1108     }
1109
1110     /* Does this pattern match? */
1111     if (regexec(l->rx->rx, s, l->nmatch, (regmatch_t *)pmatch, (int) 0) == 0)
1112     {
1113       /* Copy template into text, with substitutions. */
1114       for (p = l->template; *p;) {
1115         if (*p == '%') {
1116           n = atoi (++p);       /* find pmatch index */
1117           while (isdigit ((unsigned char) *p))
1118             ++p;                /* skip subst token */
1119           for (i = pmatch[n].rm_so; (i < pmatch[n].rm_eo) && (tlen < x); i++)
1120             text[tlen++] = s[i];
1121         }
1122         else {
1123           text[tlen++] = *p++;
1124         }
1125       }
1126       text[tlen] = '\0';
1127       return 1;
1128     }
1129   }
1130
1131   return 0;
1132 }
1133
1134 int mutt_cmp_header (const HEADER * h1, const HEADER * h2) {
1135   if (h1 && h2) {
1136     if (h1->received != h2->received ||
1137         h1->date_sent != h2->date_sent ||
1138         h1->content->length != h2->content->length ||
1139         h1->lines != h2->lines ||
1140         h1->zhours != h2->zhours ||
1141         h1->zminutes != h2->zminutes ||
1142         h1->zoccident != h2->zoccident ||
1143         h1->mime != h2->mime ||
1144         !mutt_cmp_env (h1->env, h2->env) ||
1145         !mutt_cmp_body (h1->content, h2->content))
1146       return (0);
1147     else
1148       return (1);
1149   }
1150   else {
1151     if (h1 == NULL && h2 == NULL)
1152       return (1);
1153     else
1154       return (0);
1155   }
1156 }
1157
1158 /* return 1 if address lists are strictly identical */
1159 int mutt_cmp_addr (const address_t * a, const address_t * b)
1160 {
1161   while (a && b) {
1162     if (m_strcmp(a->mailbox, b->mailbox) ||
1163         m_strcmp(a->personal, b->personal))
1164       return (0);
1165
1166     a = a->next;
1167     b = b->next;
1168   }
1169   if (a || b)
1170     return (0);
1171
1172   return (1);
1173 }
1174
1175 int mutt_cmp_list (const string_list_t * a, const string_list_t * b)
1176 {
1177   while (a && b) {
1178     if (m_strcmp(a->data, b->data))
1179       return (0);
1180
1181     a = a->next;
1182     b = b->next;
1183   }
1184   if (a || b)
1185     return (0);
1186
1187   return (1);
1188 }
1189
1190 int mutt_cmp_env (const ENVELOPE * e1, const ENVELOPE * e2)
1191 {
1192   if (e1 && e2) {
1193     if (m_strcmp(e1->message_id, e2->message_id) ||
1194         m_strcmp(e1->subject, e2->subject) ||
1195         !mutt_cmp_list (e1->references, e2->references) ||
1196         !mutt_cmp_addr (e1->from, e2->from) ||
1197         !mutt_cmp_addr (e1->sender, e2->sender) ||
1198         !mutt_cmp_addr (e1->reply_to, e2->reply_to) ||
1199         !mutt_cmp_addr (e1->to, e2->to) ||
1200         !mutt_cmp_addr (e1->cc, e2->cc) ||
1201         !mutt_cmp_addr (e1->return_path, e2->return_path))
1202       return (0);
1203     else
1204       return (1);
1205   }
1206   else {
1207     if (e1 == NULL && e2 == NULL)
1208       return (1);
1209     else
1210       return (0);
1211   }
1212 }
1213
1214 int mutt_cmp_body (const BODY * b1, const BODY * b2)
1215 {
1216   if (b1->type != b2->type ||
1217       b1->encoding != b2->encoding ||
1218       m_strcmp(b1->subtype, b2->subtype) ||
1219       m_strcmp(b1->description, b2->description) ||
1220       !parameter_equal(b1->parameter, b2->parameter) ||
1221       b1->length != b2->length)
1222     return (0);
1223   return (1);
1224 }