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