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