more fixes
[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 "mutt.h"
16 #include "ascii.h"
17 #include "buffer.h"
18 #include "enter.h"
19 #include "mutt_curses.h"
20 #include "mime.h"
21 #include "mx.h"
22 #include "url.h"
23 #include "attach.h"
24
25 #include "reldate.h"
26 #include "version.h"
27
28 #ifdef USE_IMAP
29 #include "imap.h"
30 #include "imap/mx_imap.h"
31 #endif
32
33 #include "mutt_crypt.h"
34
35 #include "lib/mem.h"
36 #include "lib/intl.h"
37 #include "lib/str.h"
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 = (BODY *) mem_calloc (1, sizeof (BODY));
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   strfcpy (buf, dir && *dir ? dir : NONULL (Tempdir), sizeof (buf));
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     strfcpy (tmp, s, sizeof (tmp));
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 = str_len (s);
97       strfcpy (s + sl, period, l - sl);
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     strfcpy (tmp, src->filename, sizeof (tmp));
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 = str_dup (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 = str_dup (b->xtype);
141   b->subtype = str_dup (b->subtype);
142   b->form_name = str_dup (b->form_name);
143   b->filename = str_dup (b->filename);
144   b->d_filename = str_dup (b->d_filename);
145   b->description = str_dup (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 = str_dup (par->attribute);
160     (*ppar)->value = str_dup (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     mem_free (&b->filename);
188     mem_free (&b->content);
189     mem_free (&b->xtype);
190     mem_free (&b->subtype);
191     mem_free (&b->description);
192     mem_free (&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     mem_free (&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     mem_free (&t->attribute);
216     mem_free (&t->value);
217     o = t;
218     t = t->next;
219     mem_free (&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   mem_free (&(*h)->maildir_flags);
240   mem_free (&(*h)->tree);
241   mem_free (&(*h)->path);
242 #ifdef MIXMASTER
243   mutt_free_list (&(*h)->chain);
244 #endif
245 #if defined USE_POP || defined USE_IMAP || defined USE_NNTP
246   mem_free (&(*h)->data);
247 #endif
248   mem_free (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, str_len (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     strfcpy (newpath, lnk, _POSIX_PATH_MAX);
271     return;
272   }
273
274   if ((lb = strrchr (path, '/')) == NULL) {
275     /* no path in lnk */
276     strfcpy (newpath, lnk, _POSIX_PATH_MAX);
277     return;
278   }
279
280   len = lb - path + 1;
281   memcpy (newpath, path, len);
282   strfcpy (newpath + len, lnk, _POSIX_PATH_MAX - len);
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           strfcpy (p, NONULL (Homedir), sizeof (p));
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             strfcpy (p, pw->pw_dir, sizeof (p));
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[str_len (Maildir) - 1] == '/')
344           strfcpy (p, NONULL (Maildir), sizeof (p));
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 *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         strfcpy (p, NONULL (Inbox), sizeof (p));
379         tail = s + 1;
380       }
381       break;
382
383     case '<':
384       {
385         strfcpy (p, NONULL (Outbox), sizeof (p));
386         tail = s + 1;
387       }
388       break;
389
390     case '!':
391       {
392         if (*(s + 1) == '!') {
393           strfcpy (p, NONULL (LastFolder), sizeof (p));
394           tail = s + 2;
395         }
396         else {
397           strfcpy (p, NONULL (Spoolfile), sizeof (p));
398           tail = s + 1;
399         }
400       }
401       break;
402
403     case '-':
404       {
405         strfcpy (p, NONULL (LastFolder), sizeof (p));
406         tail = s + 1;
407       }
408       break;
409
410     case '^':
411       {
412         strfcpy (p, NONULL (CurrentFolder), sizeof (p));
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     strfcpy (s, tmp, slen);
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   memset (dest, 0, destlen);
457
458   if (GecosMask.rx) {
459     if (regexec (GecosMask.rx, pw->pw_gecos, 1, pat_match, 0) == 0)
460       strfcpy (dest, pw->pw_gecos + pat_match[0].rm_so,
461                MIN (pat_match[0].rm_eo - pat_match[0].rm_so + 1, destlen));
462   }
463   else if ((p = strchr (pw->pw_gecos, ',')))
464     strfcpy (dest, pw->pw_gecos, MIN (destlen, p - pw->pw_gecos + 1));
465   else
466     strfcpy (dest, pw->pw_gecos, destlen);
467
468   pwnl = str_len (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       str_replace (&q->value, value);
505       return;
506     }
507   }
508
509   q = mutt_new_parameter ();
510   q->attribute = str_dup (attribute);
511   q->value = str_dup (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 ((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp (m))
544       return 0;
545     if ((WithCrypto & APPLICATION_SMIME) && 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 ((WithCrypto & APPLICATION_PGP) && 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 ((WithCrypto & APPLICATION_PGP) && 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   rfc822_free_address (&(*p)->return_path);
586   rfc822_free_address (&(*p)->from);
587   rfc822_free_address (&(*p)->to);
588   rfc822_free_address (&(*p)->cc);
589   rfc822_free_address (&(*p)->bcc);
590   rfc822_free_address (&(*p)->sender);
591   rfc822_free_address (&(*p)->reply_to);
592   rfc822_free_address (&(*p)->mail_followup_to);
593
594   mem_free (&(*p)->list_post);
595   mem_free (&(*p)->subject);
596   /* real_subj is just an offset to subject and shouldn't be freed */
597   mem_free (&(*p)->message_id);
598   mem_free (&(*p)->supersedes);
599   mem_free (&(*p)->date);
600   mem_free (&(*p)->x_label);
601   mem_free (&(*p)->organization);
602 #ifdef USE_NNTP
603   mem_free (&(*p)->newsgroups);
604   mem_free (&(*p)->xref);
605   mem_free (&(*p)->followup_to);
606   mem_free (&(*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   mem_free (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     mem_free (&t->name);
678     rfc822_free_address (&t->addr);
679     mem_free (&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 (str_ncmp (s, Maildir, (len = str_len (Maildir))) == 0 &&
725       s[len] == '/') {
726     *s++ = '=';
727     memmove (s, s + len, str_len (s + len) + 1);
728   }
729   else if (str_ncmp (s, Homedir, (len = str_len (Homedir))) == 0 &&
730            s[len] == '/') {
731     *s++ = '~';
732     memmove (s, s + len - 1, str_len (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     strfcpy (s, "0K", len);
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 = str_len (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         strfcpy (d, src, destlen + 1);
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     str_cat (dest, destlen, " ");
805     str_cat (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   strfcpy (fname, path, flen);
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         str_replace (directory, fname);
831         break;
832       case 1:                  /* yes */
833         mem_free (directory);
834         break;
835       case -1:                 /* abort */
836         mem_free (directory);
837         return -1;
838       case 2:                  /* no */
839         mem_free (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, path, tmp, flen);
855     }
856     else
857       mutt_concat_path (fname, path, mutt_basename (attname), flen);
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 * a)
881 {
882   if (a && a->mailbox) {
883     strfcpy (d, a->mailbox, dsize);
884     if (!option (OPTSAVEADDRESS)) {
885       char *p;
886
887       if ((p = strpbrk (d, "%@")))
888         *p = 0;
889     }
890     str_tolower (d);
891   }
892   else
893     *d = 0;
894 }
895
896 void mutt_safe_path (char *s, size_t l, ADDRESS * 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 (str_len (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 = str_len (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           str_tolower (buf);
1072         if (nodots) {
1073           char *p = buf;
1074
1075           for (; *p; p++)
1076             if (*p == '.')
1077               *p = '_';
1078         }
1079
1080         if ((len = str_len (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 = mem_malloc (bar + 1);
1120
1121       strfcpy (bar2, src, bar + 1);
1122       while (bar--) {
1123         *wptr++ = *src++;
1124         wlen++;
1125       }
1126       col += mutt_strwidth (bar2);
1127       mem_free (&bar2);
1128     }
1129   }
1130   *wptr = 0;
1131
1132 #if 0
1133   if (flags & M_FORMAT_MAKEPRINT) {
1134     /* Make sure that the string is printable by changing all non-printable
1135        chars to dots, or spaces for non-printable whitespace */
1136     for (cp = dest; *cp; cp++)
1137       if (!IsPrint (*cp) && !((flags & M_FORMAT_TREE) && (*cp <= M_TREE_MAX)))
1138         *cp = isspace ((unsigned char) *cp) ? ' ' : '.';
1139   }
1140 #endif
1141 }
1142
1143 /* This function allows the user to specify a command to read stdout from in
1144    place of a normal file.  If the last character in the string is a pipe (|),
1145    then we assume it is a commmand to run instead of a normal file. */
1146 FILE *mutt_open_read (const char *path, pid_t * thepid)
1147 {
1148     int len = str_len (path);
1149     FILE *f;
1150
1151     if (path[len - 1] == '|') {
1152         char *s = str_dup (path);
1153
1154         /* read from a pipe */
1155
1156         s[len - 1] = 0;
1157         mutt_endwin (NULL);
1158         *thepid = mutt_create_filter (s, NULL, &f, NULL);
1159         mem_free (&s);
1160     } else {
1161         f = fopen (path, "r");
1162         if (!f)
1163             return NULL;
1164         *thepid = -1;
1165     }
1166
1167     return (f);
1168 }
1169
1170 /* returns 0 if OK to proceed, -1 to abort, 1 to retry */
1171 int mutt_save_confirm (const char *s, struct stat *st)
1172 {
1173   char tmp[_POSIX_PATH_MAX];
1174   int ret = 0;
1175   int rc;
1176   int magic = 0;
1177
1178   magic = mx_get_magic (s);
1179
1180 #ifdef USE_POP
1181   if (magic == M_POP) {
1182     mutt_error _("Can't save message to POP mailbox.");
1183
1184     return 1;
1185   }
1186 #endif
1187
1188 #ifdef USE_NNTP
1189   if (magic == M_NNTP) {
1190     mutt_error _("Can't save message to newsserver.");
1191
1192     return 0;
1193   }
1194 #endif
1195
1196   if (magic > 0 && !mx_access (s, W_OK)) {
1197     if (option (OPTCONFIRMAPPEND) &&
1198         (!TrashPath || (str_cmp (s, TrashPath) != 0))) {
1199       /* if we're appending to the trash, there's no point in asking */
1200       snprintf (tmp, sizeof (tmp), _("Append messages to %s?"), s);
1201       if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
1202         ret = 1;
1203       else if (rc == -1)
1204         ret = -1;
1205     }
1206   }
1207
1208   if (stat (s, st) != -1) {
1209     if (magic == -1) {
1210       mutt_error (_("%s is not a mailbox!"), s);
1211       return 1;
1212     }
1213   }
1214   else {
1215 #ifdef USE_IMAP
1216     if (magic != M_IMAP)
1217 #endif /* execute the block unconditionally if we don't use imap */
1218     {
1219       st->st_mtime = 0;
1220       st->st_atime = 0;
1221
1222       if (errno == ENOENT) {
1223         if (option (OPTCONFIRMCREATE)) {
1224           snprintf (tmp, sizeof (tmp), _("Create %s?"), s);
1225           if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
1226             ret = 1;
1227           else if (rc == -1)
1228             ret = -1;
1229         }
1230       }
1231       else {
1232         mutt_perror (s);
1233         return 1;
1234       }
1235     }
1236   }
1237
1238   CLEARLINE (LINES - 1);
1239   return (ret);
1240 }
1241
1242 void mutt_display_sanitize (char *s)
1243 {
1244   for (; *s; s++) {
1245     if (!IsPrint (*s))
1246       *s = '?';
1247   }
1248 }
1249
1250 void mutt_sleep (short s)
1251 {
1252   if (SleepTime > s)
1253     sleep (SleepTime);
1254   else if (s)
1255     sleep (s);
1256 }
1257
1258 /* Decrease a file's modification time by 1 second */
1259 time_t mutt_decrease_mtime (const char *f, struct stat *st)
1260 {
1261   struct utimbuf utim;
1262   struct stat _st;
1263   time_t mtime;
1264
1265   if (!st) {
1266     if (stat (f, &_st) == -1)
1267       return -1;
1268     st = &_st;
1269   }
1270
1271   if ((mtime = st->st_mtime) == time (NULL)) {
1272     mtime -= 1;
1273     utim.actime = mtime;
1274     utim.modtime = mtime;
1275     utime (f, &utim);
1276   }
1277
1278   return mtime;
1279 }
1280
1281 /* sets mtime of 'to' to mtime of 'from' */
1282 void mutt_set_mtime (const char* from, const char* to) {
1283   struct utimbuf utim;
1284   struct stat st;
1285
1286   if (stat (from, &st) != -1) {
1287     utim.actime = st.st_mtime;
1288     utim.modtime = st.st_mtime;
1289     utime (to, &utim);
1290   }
1291 }
1292
1293 const char *mutt_make_version (int full)
1294 {
1295   static char vstring[STRING];
1296
1297   if (full)
1298     snprintf (vstring, sizeof (vstring), "Mutt-ng %s-r%s (based "
1299               "on Mutt 1.5.11/%s)", MUTT_VERSION, MUTT_REVISION,
1300               ReleaseDate);
1301   else
1302     snprintf (vstring, sizeof (vstring), "mutt-ng/%s-r%s",
1303               MUTT_VERSION, MUTT_REVISION);
1304   return vstring;
1305 }
1306
1307 void mutt_free_spam_list (SPAM_LIST ** list)
1308 {
1309   SPAM_LIST *p;
1310
1311   if (!list)
1312     return;
1313   while (*list) {
1314     p = *list;
1315     *list = (*list)->next;
1316     rx_free (&p->rx);
1317     mem_free(&p->template);
1318     mem_free(&p);
1319   }
1320 }
1321
1322 int mutt_match_spam_list (const char *s, SPAM_LIST * l, char *text, int x)
1323 {
1324   static regmatch_t *pmatch = NULL;
1325   static int nmatch = 0;
1326   int i, n, tlen;
1327   char *p;
1328
1329   if (!s)
1330     return 0;
1331
1332   tlen = 0;
1333
1334   for (; l; l = l->next) {
1335     /* If this pattern needs more matches, expand pmatch. */
1336     if (l->nmatch > nmatch) {
1337       mem_realloc (&pmatch, l->nmatch * sizeof (regmatch_t));
1338       nmatch = l->nmatch;
1339     }
1340
1341     /* Does this pattern match? */
1342     if (regexec
1343         (l->rx->rx, s, (size_t) l->nmatch, (regmatch_t *) pmatch,
1344          (int) 0) == 0) {
1345       debug_print (5, ("%s matches %s\n%d subst", s, l->rx->pattern, l->rx->rx->re_nsub));
1346
1347       /* Copy template into text, with substitutions. */
1348       for (p = l->template; *p;) {
1349         if (*p == '%') {
1350           n = atoi (++p);       /* find pmatch index */
1351           while (isdigit ((unsigned char) *p))
1352             ++p;                /* skip subst token */
1353           for (i = pmatch[n].rm_so; (i < pmatch[n].rm_eo) && (tlen < x); i++)
1354             text[tlen++] = s[i];
1355         }
1356         else {
1357           text[tlen++] = *p++;
1358         }
1359       }
1360       text[tlen] = '\0';
1361       debug_print (5, ("\"%s\"\n", text));
1362       return 1;
1363     }
1364   }
1365
1366   return 0;
1367 }
1368
1369 int mutt_cmp_header (const HEADER * h1, const HEADER * h2) {
1370   if (h1 && h2) {
1371     if (h1->received != h2->received ||
1372         h1->date_sent != h2->date_sent ||
1373         h1->content->length != h2->content->length ||
1374         h1->lines != h2->lines ||
1375         h1->zhours != h2->zhours ||
1376         h1->zminutes != h2->zminutes ||
1377         h1->zoccident != h2->zoccident ||
1378         h1->mime != h2->mime ||
1379         !mutt_cmp_env (h1->env, h2->env) ||
1380         !mutt_cmp_body (h1->content, h2->content))
1381       return (0);
1382     else
1383       return (1);
1384   }
1385   else {
1386     if (h1 == NULL && h2 == NULL)
1387       return (1);
1388     else
1389       return (0);
1390   }
1391 }
1392
1393 /* return 1 if address lists are strictly identical */
1394 int mutt_cmp_addr (const ADDRESS * a, const ADDRESS * b)
1395 {
1396   while (a && b) {
1397     if (str_cmp (a->mailbox, b->mailbox) ||
1398         str_cmp (a->personal, b->personal))
1399       return (0);
1400
1401     a = a->next;
1402     b = b->next;
1403   }
1404   if (a || b)
1405     return (0);
1406
1407   return (1);
1408 }
1409
1410 int mutt_cmp_list (const LIST * a, const LIST * b)
1411 {
1412   while (a && b) {
1413     if (str_cmp (a->data, b->data))
1414       return (0);
1415
1416     a = a->next;
1417     b = b->next;
1418   }
1419   if (a || b)
1420     return (0);
1421
1422   return (1);
1423 }
1424
1425 int mutt_cmp_env (const ENVELOPE * e1, const ENVELOPE * e2)
1426 {
1427   if (e1 && e2) {
1428     if (str_cmp (e1->message_id, e2->message_id) ||
1429         str_cmp (e1->subject, e2->subject) ||
1430         !mutt_cmp_list (e1->references, e2->references) ||
1431         !mutt_cmp_addr (e1->from, e2->from) ||
1432         !mutt_cmp_addr (e1->sender, e2->sender) ||
1433         !mutt_cmp_addr (e1->reply_to, e2->reply_to) ||
1434         !mutt_cmp_addr (e1->to, e2->to) ||
1435         !mutt_cmp_addr (e1->cc, e2->cc) ||
1436         !mutt_cmp_addr (e1->return_path, e2->return_path))
1437       return (0);
1438     else
1439       return (1);
1440   }
1441   else {
1442     if (e1 == NULL && e2 == NULL)
1443       return (1);
1444     else
1445       return (0);
1446   }
1447 }
1448
1449 int mutt_cmp_param (const PARAMETER * p1, const PARAMETER * p2)
1450 {
1451   while (p1 && p2) {
1452     if (str_cmp (p1->attribute, p2->attribute) ||
1453         str_cmp (p1->value, p2->value))
1454       return (0);
1455
1456     p1 = p1->next;
1457     p2 = p2->next;
1458   }
1459   if (p1 || p2)
1460     return (0);
1461
1462   return (1);
1463 }
1464
1465 int mutt_cmp_body (const BODY * b1, const BODY * b2)
1466 {
1467   if (b1->type != b2->type ||
1468       b1->encoding != b2->encoding ||
1469       str_cmp (b1->subtype, b2->subtype) ||
1470       str_cmp (b1->description, b2->description) ||
1471       !mutt_cmp_param (b1->parameter, b2->parameter) ||
1472       b1->length != b2->length)
1473     return (0);
1474   return (1);
1475 }