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