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