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