d607b4ff719b23460501779dded5e966e4532a25
[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     while (p) {
406         if (!ascii_strcasecmp(s, p->attribute))
407             return (p->value);
408         p = p->next;
409     }
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     parameter_list_push(p, q);
435 }
436
437 void mutt_delete_parameter (const char *attribute, PARAMETER ** p)
438 {
439     while (*p) {
440         if (!ascii_strcasecmp(attribute, (*p)->attribute)) {
441             PARAMETER *q = parameter_list_pop(p);
442             parameter_delete(&q);
443             return;
444         }
445
446         p = &(*p)->next;
447     }
448 }
449
450 /* returns 1 if Mutt can't display this type of data, 0 otherwise */
451 int mutt_needs_mailcap (BODY * m)
452 {
453   switch (m->type) {
454   case TYPETEXT:
455
456     if (!ascii_strcasecmp ("plain", m->subtype) ||
457         !ascii_strcasecmp ("rfc822-headers", m->subtype) ||
458         !ascii_strcasecmp ("enriched", m->subtype))
459       return 0;
460     break;
461
462   case TYPEAPPLICATION:
463     if (mutt_is_application_pgp (m))
464       return 0;
465     if (mutt_is_application_smime (m))
466       return 0;
467     break;
468
469   case TYPEMULTIPART:
470   case TYPEMESSAGE:
471     return 0;
472   }
473
474   return 1;
475 }
476
477 int mutt_is_text_part (BODY * b)
478 {
479   int t = b->type;
480   char *s = b->subtype;
481
482   if (mutt_is_application_pgp (b))
483     return 0;
484
485   if (t == TYPETEXT)
486     return 1;
487
488   if (t == TYPEMESSAGE) {
489     if (!ascii_strcasecmp ("delivery-status", s))
490       return 1;
491   }
492
493   if (t == TYPEAPPLICATION) {
494     if (!ascii_strcasecmp ("pgp-keys", s))
495       return 1;
496   }
497
498   return 0;
499 }
500
501 /* move all the headers from extra not present in base into base */
502 void mutt_merge_envelopes(ENVELOPE* base, ENVELOPE** extra)
503 {
504   /* copies each existing element if necessary, and sets the element
505   * to NULL in the source so that envelope_delete doesn't leave us
506   * with dangling pointers. */
507 #define MOVE_ELEM(h) if (!base->h) { base->h = (*extra)->h; (*extra)->h = NULL; }
508   MOVE_ELEM(return_path);
509   MOVE_ELEM(from);
510   MOVE_ELEM(to);
511   MOVE_ELEM(cc);
512   MOVE_ELEM(bcc);
513   MOVE_ELEM(sender);
514   MOVE_ELEM(reply_to);
515   MOVE_ELEM(mail_followup_to);
516   MOVE_ELEM(list_post);
517   MOVE_ELEM(message_id);
518   MOVE_ELEM(supersedes);
519   MOVE_ELEM(date);
520   MOVE_ELEM(x_label);
521   if (!base->refs_changed) {
522     MOVE_ELEM(references);
523   }
524   if (!base->irt_changed) {
525     MOVE_ELEM(in_reply_to);
526   }
527   /* real_subj is subordinate to subject */
528   if (!base->subject) {
529     base->subject = (*extra)->subject;
530     base->real_subj = (*extra)->real_subj;
531     (*extra)->subject = NULL;
532     (*extra)->real_subj = NULL;
533   }
534   /* spam and user headers should never be hashed, and the new envelope may
535    * have better values. Use new versions regardless. */
536   mutt_buffer_free (&base->spam);
537   string_list_wipe(&base->userhdrs);
538   MOVE_ELEM(spam);
539   MOVE_ELEM(userhdrs);
540 #undef MOVE_ELEM
541   
542   envelope_delete(extra);
543 }
544
545 void _mutt_mktemp (char *s, const char *src, int line)
546 {
547
548   snprintf (s, _POSIX_PATH_MAX, "%s/madmutt-%s-%d-%d-%d-%x%x", NONULL (Tempdir),
549             NONULL (Hostname), (int) getuid (), (int) getpid (), Counter++, 
550             (unsigned int) rand(), (unsigned int) rand());
551   unlink (s);
552 }
553
554 /* collapse the pathname using ~ or = when possible */
555 void mutt_pretty_mailbox (char *s)
556 {
557   char *p = s, *q = s;
558   ssize_t len;
559   url_scheme_t scheme;
560
561   scheme = url_check_scheme (s);
562
563   if (scheme == U_IMAP || scheme == U_IMAPS) {
564     imap_pretty_mailbox (s);
565     return;
566   }
567
568   /* if s is an url, only collapse path component */
569   if (scheme != U_UNKNOWN) {
570     p = strchr (s, ':') + 1;
571     if (!strncmp (p, "//", 2))
572       q = strchr (p + 2, '/');
573     if (!q)
574       q = strchr (p, '\0');
575     p = q;
576   }
577
578   /* first attempt to collapse the pathname */
579   while (*p) {
580     if (*p == '/' && p[1] == '/') {
581       *q++ = '/';
582       p += 2;
583     }
584     else if (p[0] == '/' && p[1] == '.' && p[2] == '/') {
585       *q++ = '/';
586       p += 3;
587     }
588     else
589       *q++ = *p++;
590   }
591   *q = 0;
592
593   if (m_strncmp(s, Maildir, (len = m_strlen(Maildir))) == 0 &&
594       s[len] == '/') {
595     *s++ = '=';
596     memmove (s, s + len, m_strlen(s + len) + 1);
597   }
598   else if (m_strncmp(s, Homedir, (len = m_strlen(Homedir))) == 0 &&
599            s[len] == '/') {
600     *s++ = '~';
601     memmove (s, s + len - 1, m_strlen(s + len - 1) + 1);
602   }
603 }
604
605 void mutt_pretty_size (char *s, ssize_t len, long n)
606 {
607   if (n == 0)
608     m_strcpy(s, len, "0K");
609   else if (n < 10189)           /* 0.1K - 9.9K */
610     snprintf (s, len, "%3.1fK", (n < 103) ? 0.1 : n / 1024.0);
611   else if (n < 1023949) {       /* 10K - 999K */
612     /* 51 is magic which causes 10189/10240 to be rounded up to 10 */
613     snprintf (s, len, "%ldK", (n + 51) / 1024);
614   }
615   else if (n < 10433332)        /* 1.0M - 9.9M */
616     snprintf (s, len, "%3.1fM", n / 1048576.0);
617   else {                        /* 10M+ */
618
619     /* (10433332 + 52428) / 1048576 = 10 */
620     snprintf (s, len, "%ldM", (n + 52428) / 1048576);
621   }
622 }
623
624 void mutt_expand_file_fmt (char *dest, ssize_t destlen, const char *fmt,
625                            const char *src)
626 {
627   char tmp[LONG_STRING];
628
629   mutt_quote_filename (tmp, sizeof (tmp), src);
630   mutt_expand_fmt (dest, destlen, fmt, tmp);
631 }
632
633 void mutt_expand_fmt (char *dest, ssize_t destlen, const char *fmt,
634                       const char *src)
635 {
636   const char *p;
637   char *d;
638   ssize_t slen;
639   int found = 0;
640
641   slen = m_strlen(src);
642   destlen--;
643
644   for (p = fmt, d = dest; destlen && *p; p++) {
645     if (*p == '%') {
646       switch (p[1]) {
647       case '%':
648         *d++ = *p++;
649         destlen--;
650         break;
651       case 's':
652         found = 1;
653         m_strcpy(d, destlen + 1, src);
654         d += destlen > slen ? slen : destlen;
655         destlen -= destlen > slen ? slen : destlen;
656         p++;
657         break;
658       default:
659         *d++ = *p;
660         destlen--;
661         break;
662       }
663     }
664     else {
665       *d++ = *p;
666       destlen--;
667     }
668   }
669
670   *d = '\0';
671
672   if (!found && destlen > 0) {
673     m_strcat(dest, destlen, " ");
674     m_strcat(dest, destlen, src);
675   }
676
677 }
678
679 /* return 0 on success, -1 on abort, 1 on error */
680 int mutt_check_overwrite (const char *attname, const char *path,
681                           char *fname, ssize_t flen, int *append,
682                           char **directory)
683 {
684   int rc = 0;
685   char tmp[_POSIX_PATH_MAX];
686   struct stat st;
687
688   m_strcpy(fname, flen, path);
689   if (access (fname, F_OK) != 0)
690     return 0;
691   if (stat (fname, &st) != 0)
692     return -1;
693   if (S_ISDIR (st.st_mode)) {
694     if (directory) {
695       switch (mutt_multi_choice
696               (_("File is a directory, save under it? [(y)es, (n)o, (a)ll]"),
697                _("yna"))) {
698       case 3:                  /* all */
699         m_strreplace(directory, fname);
700         break;
701       case 1:                  /* yes */
702         p_delete(directory);
703         break;
704       case -1:                 /* abort */
705         p_delete(directory);
706         return -1;
707       case 2:                  /* no */
708         p_delete(directory);
709         return 1;
710       }
711     }
712     else
713       if ((rc =
714            mutt_yesorno (_("File is a directory, save under it?"),
715                          M_YES)) != M_YES)
716       return (rc == M_NO) ? 1 : -1;
717
718     if (!attname || !attname[0]) {
719       tmp[0] = 0;
720       if (mutt_get_field (_("File under directory: "), tmp, sizeof (tmp),
721                           M_FILE | M_CLEAR) != 0 || !tmp[0])
722         return (-1);
723       mutt_concat_path(fname, flen, path, tmp);
724     }
725     else
726       mutt_concat_path(fname, flen, path, mutt_basename(attname));
727   }
728
729   if (*append == 0 && access (fname, F_OK) == 0) {
730     switch (mutt_multi_choice
731             (_("File exists, (o)verwrite, (a)ppend, or (c)ancel?"), _("oac")))
732     {
733     case -1:                   /* abort */
734       return -1;
735     case 3:                    /* cancel */
736       return 1;
737
738     case 2:                    /* append */
739       *append = M_SAVE_APPEND;
740       break;
741     case 1:                    /* overwrite */
742       *append = M_SAVE_OVERWRITE;
743       break;
744     }
745   }
746   return 0;
747 }
748
749 void mutt_save_path (char *d, ssize_t dsize, address_t * a)
750 {
751   if (a && a->mailbox) {
752     m_strcpy(d, dsize, a->mailbox);
753     if (!option (OPTSAVEADDRESS)) {
754       char *p;
755
756       if ((p = strpbrk (d, "%@")))
757         *p = 0;
758     }
759     m_strtolower(d);
760   }
761   else
762     *d = 0;
763 }
764
765 void mutt_safe_path (char *s, ssize_t l, address_t * a)
766 {
767   char *p;
768
769   mutt_save_path (s, l, a);
770   for (p = s; *p; p++)
771     if (*p == '/' || ISSPACE (*p) || !IsPrint ((unsigned char) *p))
772       *p = '_';
773 }
774
775 /* counts how many characters in s can be skipped while none of the
776  * characters of c appears */
777 int mutt_skipchars (const char *s, const char *c)
778 {
779   int ret = 0;
780   const char *p = s;
781
782   while (s && *s) {
783     register const char *t = c;
784
785     while (t && *t) {
786       if (*t == *s)
787         return (ret);
788       t++;
789     }
790     ret++;
791     s++;
792   }
793   return (m_strlen(p));
794 }
795
796 void mutt_FormatString (char *dest,     /* output buffer */
797                         ssize_t destlen, /* output buffer len */
798                         const char *src,        /* template string */
799                         format_t * callback,    /* callback for processing */
800                         unsigned long data,     /* callback data */
801                         format_flag flags)
802 {                               /* callback flags */
803   char prefix[SHORT_STRING], buf[LONG_STRING], *cp, *wptr = dest, ch;
804   char ifstring[SHORT_STRING], elsestring[SHORT_STRING];
805   ssize_t wlen, wid, count, col, len;
806
807   prefix[0] = '\0';
808   destlen--;                    /* save room for the terminal \0 */
809   wlen = (flags & M_FORMAT_ARROWCURSOR && option (OPTARROWCURSOR)) ? 3 : 0;
810   col = wlen;
811
812   while (*src && wlen < destlen) {
813     if (*src == '%') {
814       if (*++src == '%') {
815         *wptr++ = '%';
816         wlen++;
817         col++;
818         src++;
819         continue;
820       }
821
822       if (*src == '?') {
823         flags |= M_FORMAT_OPTIONAL;
824         src++;
825       }
826       else {
827         flags &= ~M_FORMAT_OPTIONAL;
828
829         /* eat the format string */
830         cp = prefix;
831         count = 0;
832         while (count < ssizeof (prefix) &&
833                (isdigit ((unsigned char) *src) || *src == '.' || *src == '-'))
834         {
835           *cp++ = *src++;
836           count++;
837         }
838         *cp = 0;
839       }
840
841       if (!*src)
842         break;                  /* bad format */
843
844       ch = *src++;              /* save the character to switch on */
845
846       if (flags & M_FORMAT_OPTIONAL) {
847         if (*src != '?')
848           break;                /* bad format */
849         src++;
850
851         /* eat the `if' part of the string */
852         cp = ifstring;
853         count = 0;
854         while (count < ssizeof (ifstring) && *src && *src != '?'
855                && *src != '&') {
856           *cp++ = *src++;
857           count++;
858         }
859         *cp = 0;
860
861         /* eat the `else' part of the string (optional) */
862         if (*src == '&')
863           src++;                /* skip the & */
864         cp = elsestring;
865         count = 0;
866         while (count < ssizeof (elsestring) && *src && *src != '?') {
867           *cp++ = *src++;
868           count++;
869         }
870         *cp = 0;
871
872         if (!*src)
873           break;                /* bad format */
874
875         src++;                  /* move past the trailing `?' */
876       }
877
878       /* handle generic cases first */
879       if (ch == '>') {
880         /* right justify to EOL */
881         ch = *src++;            /* pad char */
882         /* calculate space left on line.  if we've already written more data
883            than will fit on the line, ignore the rest of the line */
884         if (DrawFullLine || option (OPTSTATUSONTOP))
885           count = (COLS < destlen ? COLS : destlen);
886         else
887           count = ((COLS - SW) < destlen ? (COLS - SW) : destlen);
888         if (count > col) {
889           count -= col;         /* how many columns left on this line */
890           mutt_FormatString (buf, sizeof (buf), src, callback, data, flags);
891           wid = m_strlen(buf);
892           if (count > wid) {
893             count -= wid;       /* how many chars to pad */
894             memset (wptr, ch, count);
895             wptr += count;
896             col += count;
897           }
898           if (wid + wlen > destlen)
899             len = destlen - wlen;
900           else
901             len = wid;
902           memcpy (wptr, buf, len);
903           wptr += len;
904           wlen += len;
905           col += mutt_strwidth (buf);
906         }
907         break;                  /* skip rest of input */
908       }
909       else if (ch == '|') {
910         /* pad to EOL */
911         ch = *src++;
912         if (destlen > COLS)
913           destlen = COLS;
914         if (destlen > wlen) {
915           count = destlen - wlen;
916           memset (wptr, ch, count);
917           wptr += count;
918         }
919         break;                  /* skip rest of input */
920       }
921       else {
922         short lower = 0;
923         short nodots = 0;
924
925         while (ch == '_' || ch == ':') {
926           if (ch == '_')
927             lower = 1;
928           else if (ch == ':')
929             nodots = 1;
930
931           ch = *src++;
932         }
933
934         /* use callback function to handle this case */
935         src =
936           callback (buf, sizeof (buf), ch, src, prefix, ifstring, elsestring,
937                     data, flags);
938
939         if (lower)
940           m_strtolower(buf);
941         if (nodots) {
942           char *p = buf;
943
944           for (; *p; p++)
945             if (*p == '.')
946               *p = '_';
947         }
948
949         if ((len = m_strlen(buf)) + wlen > destlen)
950           len = (destlen - wlen > 0) ? (destlen - wlen) : 0;
951
952         memcpy (wptr, buf, len);
953         wptr += len;
954         wlen += len;
955         col += mutt_strwidth (buf);
956       }
957     }
958     else if (*src == '\\') {
959       if (!*++src)
960         break;
961       switch (*src) {
962       case 'n':
963         *wptr = '\n';
964         break;
965       case 't':
966         *wptr = '\t';
967         break;
968       case 'r':
969         *wptr = '\r';
970         break;
971       case 'f':
972         *wptr = '\f';
973         break;
974       case 'v':
975         *wptr = '\v';
976         break;
977       default:
978         *wptr = *src;
979         break;
980       }
981       src++;
982       wptr++;
983       wlen++;
984       col++;
985     }
986     else {
987       unsigned int bar = mutt_skipchars (src, "%\\");
988       char *bar2 = p_dupstr(src, bar);
989
990       while (bar--) {
991         *wptr++ = *src++;
992         wlen++;
993       }
994       col += mutt_strwidth (bar2);
995       p_delete(&bar2);
996     }
997   }
998   *wptr = 0;
999
1000 #if 0
1001   if (flags & M_FORMAT_MAKEPRINT) {
1002     /* Make sure that the string is printable by changing all non-printable
1003        chars to dots, or spaces for non-printable whitespace */
1004     for (cp = dest; *cp; cp++)
1005       if (!IsPrint (*cp) && !((flags & M_FORMAT_TREE) && (*cp <= M_TREE_MAX)))
1006         *cp = isspace ((unsigned char) *cp) ? ' ' : '.';
1007   }
1008 #endif
1009 }
1010
1011 /* This function allows the user to specify a command to read stdout from in
1012    place of a normal file.  If the last character in the string is a pipe (|),
1013    then we assume it is a commmand to run instead of a normal file. */
1014 FILE *mutt_open_read (const char *path, pid_t * thepid)
1015 {
1016     int len = m_strlen(path);
1017     FILE *f;
1018
1019     if (path[len - 1] == '|') {
1020         char *s = m_strdup(path);
1021
1022         /* read from a pipe */
1023
1024         s[len - 1] = 0;
1025         mutt_endwin (NULL);
1026         *thepid = mutt_create_filter (s, NULL, &f, NULL);
1027         p_delete(&s);
1028     } else {
1029         f = fopen (path, "r");
1030         if (!f)
1031             return NULL;
1032         *thepid = -1;
1033     }
1034
1035     return (f);
1036 }
1037
1038 /* returns 0 if OK to proceed, -1 to abort, 1 to retry */
1039 int mutt_save_confirm (const char *s, struct stat *st)
1040 {
1041   char tmp[_POSIX_PATH_MAX];
1042   int ret = 0;
1043   int rc;
1044   int magic = 0;
1045
1046   magic = mx_get_magic (s);
1047
1048   if (magic == M_POP) {
1049     mutt_error _("Can't save message to POP mailbox.");
1050
1051     return 1;
1052   }
1053
1054 #ifdef USE_NNTP
1055   if (magic == M_NNTP) {
1056     mutt_error _("Can't save message to newsserver.");
1057
1058     return 0;
1059   }
1060 #endif
1061
1062   if (magic > 0 && !mx_access (s, W_OK)) {
1063     if (option (OPTCONFIRMAPPEND) &&
1064         (!TrashPath || (m_strcmp(s, TrashPath) != 0))) {
1065       /* if we're appending to the trash, there's no point in asking */
1066       snprintf (tmp, sizeof (tmp), _("Append messages to %s?"), s);
1067       if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
1068         ret = 1;
1069       else if (rc == -1)
1070         ret = -1;
1071     }
1072   }
1073
1074   if (stat (s, st) != -1) {
1075     if (magic == -1) {
1076       mutt_error (_("%s is not a mailbox!"), s);
1077       return 1;
1078     }
1079   }
1080   else {
1081     if (magic != M_IMAP)
1082     {
1083       st->st_mtime = 0;
1084       st->st_atime = 0;
1085
1086       if (errno == ENOENT) {
1087         if (option (OPTCONFIRMCREATE)) {
1088           snprintf (tmp, sizeof (tmp), _("Create %s?"), s);
1089           if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
1090             ret = 1;
1091           else if (rc == -1)
1092             ret = -1;
1093         }
1094       }
1095       else {
1096         mutt_perror (s);
1097         return 1;
1098       }
1099     }
1100   }
1101
1102   CLEARLINE (LINES - 1);
1103   return (ret);
1104 }
1105
1106 void mutt_display_sanitize (char *s)
1107 {
1108   for (; *s; s++) {
1109     if (!IsPrint (*s))
1110       *s = '?';
1111   }
1112 }
1113
1114 void mutt_sleep (short s)
1115 {
1116   if (SleepTime > s)
1117     sleep (SleepTime);
1118   else if (s)
1119     sleep (s);
1120 }
1121
1122 /* Decrease a file's modification time by 1 second */
1123 time_t mutt_decrease_mtime (const char *f, struct stat *st)
1124 {
1125   struct utimbuf utim;
1126   struct stat _st;
1127   time_t mtime;
1128
1129   if (!st) {
1130     if (stat (f, &_st) == -1)
1131       return -1;
1132     st = &_st;
1133   }
1134
1135   if ((mtime = st->st_mtime) == time (NULL)) {
1136     mtime -= 1;
1137     utim.actime = mtime;
1138     utim.modtime = mtime;
1139     utime (f, &utim);
1140   }
1141
1142   return mtime;
1143 }
1144
1145 /* sets mtime of 'to' to mtime of 'from' */
1146 void mutt_set_mtime (const char* from, const char* to) {
1147   struct utimbuf utim;
1148   struct stat st;
1149
1150   if (stat (from, &st) != -1) {
1151     utim.actime = st.st_mtime;
1152     utim.modtime = st.st_mtime;
1153     utime (to, &utim);
1154   }
1155 }
1156
1157 const char *mutt_make_version (int full)
1158 {
1159   static char vstring[STRING];
1160
1161   if (full)
1162     snprintf (vstring, sizeof (vstring),
1163               "Madmutt/%s-r%s (based on Mutt 1.5.11)",
1164               MUTT_VERSION, MUTT_REVISION);
1165   else
1166     snprintf (vstring, sizeof (vstring), "Madmutt/%s-%s",
1167               MUTT_VERSION, MUTT_REVISION);
1168   return vstring;
1169 }
1170
1171 void mutt_free_spam_list (SPAM_LIST ** list)
1172 {
1173   SPAM_LIST *p;
1174
1175   if (!list)
1176     return;
1177   while (*list) {
1178     p = *list;
1179     *list = (*list)->next;
1180     rx_delete(&p->rx);
1181     p_delete(&p->template);
1182     p_delete(&p);
1183   }
1184 }
1185
1186 int mutt_match_spam_list (const char *s, SPAM_LIST * l, char *text, int x)
1187 {
1188   static regmatch_t *pmatch = NULL;
1189   static int nmatch = 0;
1190   int i, n, tlen;
1191   char *p;
1192
1193   if (!s)
1194     return 0;
1195
1196   tlen = 0;
1197
1198   for (; l; l = l->next) {
1199     /* If this pattern needs more matches, expand pmatch. */
1200     if (l->nmatch > nmatch) {
1201       p_realloc(&pmatch, l->nmatch);
1202       nmatch = l->nmatch;
1203     }
1204
1205     /* Does this pattern match? */
1206     if (regexec(l->rx->rx, s, l->nmatch, (regmatch_t *)pmatch, (int) 0) == 0)
1207     {
1208       /* Copy template into text, with substitutions. */
1209       for (p = l->template; *p;) {
1210         if (*p == '%') {
1211           n = atoi (++p);       /* find pmatch index */
1212           while (isdigit ((unsigned char) *p))
1213             ++p;                /* skip subst token */
1214           for (i = pmatch[n].rm_so; (i < pmatch[n].rm_eo) && (tlen < x); i++)
1215             text[tlen++] = s[i];
1216         }
1217         else {
1218           text[tlen++] = *p++;
1219         }
1220       }
1221       text[tlen] = '\0';
1222       return 1;
1223     }
1224   }
1225
1226   return 0;
1227 }
1228
1229 int mutt_cmp_header (const HEADER * h1, const HEADER * h2) {
1230   if (h1 && h2) {
1231     if (h1->received != h2->received ||
1232         h1->date_sent != h2->date_sent ||
1233         h1->content->length != h2->content->length ||
1234         h1->lines != h2->lines ||
1235         h1->zhours != h2->zhours ||
1236         h1->zminutes != h2->zminutes ||
1237         h1->zoccident != h2->zoccident ||
1238         h1->mime != h2->mime ||
1239         !mutt_cmp_env (h1->env, h2->env) ||
1240         !mutt_cmp_body (h1->content, h2->content))
1241       return (0);
1242     else
1243       return (1);
1244   }
1245   else {
1246     if (h1 == NULL && h2 == NULL)
1247       return (1);
1248     else
1249       return (0);
1250   }
1251 }
1252
1253 /* return 1 if address lists are strictly identical */
1254 int mutt_cmp_addr (const address_t * a, const address_t * b)
1255 {
1256   while (a && b) {
1257     if (m_strcmp(a->mailbox, b->mailbox) ||
1258         m_strcmp(a->personal, b->personal))
1259       return (0);
1260
1261     a = a->next;
1262     b = b->next;
1263   }
1264   if (a || b)
1265     return (0);
1266
1267   return (1);
1268 }
1269
1270 int mutt_cmp_list (const string_list_t * a, const string_list_t * b)
1271 {
1272   while (a && b) {
1273     if (m_strcmp(a->data, b->data))
1274       return (0);
1275
1276     a = a->next;
1277     b = b->next;
1278   }
1279   if (a || b)
1280     return (0);
1281
1282   return (1);
1283 }
1284
1285 int mutt_cmp_env (const ENVELOPE * e1, const ENVELOPE * e2)
1286 {
1287   if (e1 && e2) {
1288     if (m_strcmp(e1->message_id, e2->message_id) ||
1289         m_strcmp(e1->subject, e2->subject) ||
1290         !mutt_cmp_list (e1->references, e2->references) ||
1291         !mutt_cmp_addr (e1->from, e2->from) ||
1292         !mutt_cmp_addr (e1->sender, e2->sender) ||
1293         !mutt_cmp_addr (e1->reply_to, e2->reply_to) ||
1294         !mutt_cmp_addr (e1->to, e2->to) ||
1295         !mutt_cmp_addr (e1->cc, e2->cc) ||
1296         !mutt_cmp_addr (e1->return_path, e2->return_path))
1297       return (0);
1298     else
1299       return (1);
1300   }
1301   else {
1302     if (e1 == NULL && e2 == NULL)
1303       return (1);
1304     else
1305       return (0);
1306   }
1307 }
1308
1309 int mutt_cmp_param (const PARAMETER * p1, const PARAMETER * p2)
1310 {
1311   while (p1 && p2) {
1312     if (m_strcmp(p1->attribute, p2->attribute) ||
1313         m_strcmp(p1->value, p2->value))
1314       return (0);
1315
1316     p1 = p1->next;
1317     p2 = p2->next;
1318   }
1319   if (p1 || p2)
1320     return (0);
1321
1322   return (1);
1323 }
1324
1325 int mutt_cmp_body (const BODY * b1, const BODY * b2)
1326 {
1327   if (b1->type != b2->type ||
1328       b1->encoding != b2->encoding ||
1329       m_strcmp(b1->subtype, b2->subtype) ||
1330       m_strcmp(b1->description, b2->description) ||
1331       !mutt_cmp_param (b1->parameter, b2->parameter) ||
1332       b1->length != b2->length)
1333     return (0);
1334   return (1);
1335 }