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