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