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