use m_strdup and m_strlen that are inlined for efficiency
[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/str.h>
17 #include <lib-lib/macros.h>
18
19 #include "mutt.h"
20 #include "ascii.h"
21 #include "buffer.h"
22 #include "enter.h"
23 #include "mutt_curses.h"
24 #include "mime.h"
25 #include "mx.h"
26 #include "url.h"
27 #include "attach.h"
28
29 #include "reldate.h"
30 #include "version.h"
31
32 #ifdef USE_IMAP
33 #include "imap.h"
34 #include "imap/mx_imap.h"
35 #endif
36
37 #include "mutt_crypt.h"
38
39 #include "lib/debug.h"
40
41 #include <string.h>
42 #include <ctype.h>
43 #include <unistd.h>
44 #include <stdlib.h>
45 #include <sys/wait.h>
46 #include <errno.h>
47 #include <sys/stat.h>
48 #include <fcntl.h>
49 #include <time.h>
50 #include <sys/types.h>
51 #include <utime.h>
52
53 #define SW              (option(OPTMBOXPANE)?SidebarWidth:0)
54
55 BODY *mutt_new_body (void)
56 {
57   BODY *p = p_new(BODY, 1);
58
59   p->disposition = DISPATTACH;
60   p->use_disp = 1;
61   return (p);
62 }
63
64
65 /* Modified by blong to accept a "suggestion" for file name.  If
66  * that file exists, then construct one with unique name but 
67  * keep any extension.  This might fail, I guess.
68  * Renamed to mutt_adv_mktemp so I only have to change where it's
69  * called, and not all possible cases.
70  */
71 void mutt_adv_mktemp (const char* dir, char *s, size_t l)
72 {
73   char buf[_POSIX_PATH_MAX];
74   char tmp[_POSIX_PATH_MAX];
75   char *period;
76   size_t sl;
77   struct stat sb;
78
79   strfcpy (buf, dir && *dir ? dir : NONULL (Tempdir), sizeof (buf));
80   mutt_expand_path (buf, sizeof (buf));
81   if (s[0] == '\0') {
82     snprintf (s, l, "%s/muttXXXXXX", buf);
83     mktemp (s);
84   }
85   else {
86     strfcpy (tmp, s, sizeof (tmp));
87     mutt_sanitize_filename (tmp, 1);
88     snprintf (s, l, "%s/%s", buf, tmp);
89     if (lstat (s, &sb) == -1 && errno == ENOENT)
90       return;
91     if ((period = strrchr (tmp, '.')) != NULL)
92       *period = 0;
93     snprintf (s, l, "%s/%s.XXXXXX", buf, tmp);
94     mktemp (s);
95     if (period != NULL) {
96       *period = '.';
97       sl = m_strlen(s);
98       strfcpy (s + sl, period, l - sl);
99     }
100   }
101 }
102
103 /* create a send-mode duplicate from a receive-mode body */
104
105 int mutt_copy_body (FILE * fp, BODY ** tgt, BODY * src)
106 {
107   char tmp[_POSIX_PATH_MAX];
108   BODY *b;
109
110   PARAMETER *par, **ppar;
111
112   short use_disp;
113
114   if (src->filename) {
115     use_disp = 1;
116     strfcpy (tmp, src->filename, sizeof (tmp));
117   }
118   else {
119     use_disp = 0;
120     tmp[0] = '\0';
121   }
122
123   mutt_adv_mktemp (NULL, tmp, sizeof (tmp));
124   if (mutt_save_attachment (fp, src, tmp, 0, NULL) == -1)
125     return -1;
126
127   *tgt = mutt_new_body ();
128   b = *tgt;
129
130   memcpy (b, src, sizeof (BODY));
131   b->parts = NULL;
132   b->next = NULL;
133
134   b->filename = m_strdup(tmp);
135   b->use_disp = use_disp;
136   b->unlink = 1;
137
138   if (mutt_is_text_part (b))
139     b->noconv = 1;
140
141   b->xtype = m_strdup(b->xtype);
142   b->subtype = m_strdup(b->subtype);
143   b->form_name = m_strdup(b->form_name);
144   b->filename = m_strdup(b->filename);
145   b->d_filename = m_strdup(b->d_filename);
146   b->description = m_strdup(b->description);
147
148   /* 
149    * we don't seem to need the HEADER structure currently.
150    * XXX - this may change in the future
151    */
152
153   if (b->hdr)
154     b->hdr = NULL;
155
156   /* copy parameters */
157   for (par = b->parameter, ppar = &b->parameter; par;
158        ppar = &(*ppar)->next, par = par->next) {
159     *ppar = mutt_new_parameter ();
160     (*ppar)->attribute = m_strdup(par->attribute);
161     (*ppar)->value = m_strdup(par->value);
162   }
163
164   mutt_stamp_attachment (b);
165
166   return 0;
167 }
168
169
170
171 void mutt_free_body (BODY ** p)
172 {
173   BODY *a = *p, *b;
174
175   while (a) {
176     b = a;
177     a = a->next;
178
179     if (b->parameter)
180       mutt_free_parameter (&b->parameter);
181     if (b->unlink && b->filename) {
182       debug_print (1, ("unlinking %s.\n", b->filename));
183       unlink (b->filename);
184     }
185     else if (b->filename)
186       debug_print (1, ("not unlinking %s.\n", b->filename));
187
188     p_delete(&b->filename);
189     p_delete(&b->content);
190     p_delete(&b->xtype);
191     p_delete(&b->subtype);
192     p_delete(&b->description);
193     p_delete(&b->form_name);
194
195     if (b->hdr) {
196       /* Don't free twice (b->hdr->content = b->parts) */
197       b->hdr->content = NULL;
198       mutt_free_header (&b->hdr);
199     }
200
201     if (b->parts)
202       mutt_free_body (&b->parts);
203
204     p_delete(&b);
205   }
206
207   *p = 0;
208 }
209
210 void mutt_free_parameter (PARAMETER ** p)
211 {
212   PARAMETER *t = *p;
213   PARAMETER *o;
214
215   while (t) {
216     p_delete(&t->attribute);
217     p_delete(&t->value);
218     o = t;
219     t = t->next;
220     p_delete(&o);
221   }
222   *p = 0;
223 }
224
225 HEADER *mutt_dup_header (HEADER * h)
226 {
227   HEADER *hnew;
228
229   hnew = mutt_new_header ();
230   memcpy (hnew, h, sizeof (HEADER));
231   return hnew;
232 }
233
234 void mutt_free_header (HEADER ** h)
235 {
236   if (!h || !*h)
237     return;
238   mutt_free_envelope (&(*h)->env);
239   mutt_free_body (&(*h)->content);
240   p_delete(&(*h)->maildir_flags);
241   p_delete(&(*h)->tree);
242   p_delete(&(*h)->path);
243 #ifdef MIXMASTER
244   mutt_free_list (&(*h)->chain);
245 #endif
246 #if defined USE_POP || defined USE_IMAP || defined USE_NNTP
247   p_delete(&(*h)->data);
248 #endif
249   p_delete(h);
250 }
251
252 /* returns true if the header contained in "s" is in list "t" */
253 int mutt_matches_ignore (const char *s, LIST * t)
254 {
255   for (; t; t = t->next) {
256     if (!ascii_strncasecmp (s, t->data, m_strlen(t->data))
257         || *t->data == '*')
258       return 1;
259   }
260   return 0;
261 }
262
263 /* prepend the path part of *path to *lnk */
264 void mutt_expand_link (char *newpath, const char *path, const char *lnk)
265 {
266   const char *lb = NULL;
267   size_t len;
268
269   /* lnk is full path */
270   if (*lnk == '/') {
271     strfcpy (newpath, lnk, _POSIX_PATH_MAX);
272     return;
273   }
274
275   if ((lb = strrchr (path, '/')) == NULL) {
276     /* no path in lnk */
277     strfcpy (newpath, lnk, _POSIX_PATH_MAX);
278     return;
279   }
280
281   len = lb - path + 1;
282   memcpy (newpath, path, len);
283   strfcpy (newpath + len, lnk, _POSIX_PATH_MAX - len);
284 }
285
286 char *mutt_expand_path (char *s, size_t slen)
287 {
288   return _mutt_expand_path (s, slen, 0);
289 }
290
291 char *_mutt_expand_path (char *s, size_t slen, int rx)
292 {
293   char p[_POSIX_PATH_MAX] = "";
294   char q[_POSIX_PATH_MAX] = "";
295   char tmp[_POSIX_PATH_MAX];
296   char *t;
297
298   const char *tail = "";
299
300   int recurse = 0;
301
302   do {
303     recurse = 0;
304
305     switch (*s) {
306     case '~':
307       {
308         if (*(s + 1) == '/' || *(s + 1) == 0) {
309           strfcpy (p, NONULL (Homedir), sizeof (p));
310           tail = s + 1;
311         }
312         else {
313           struct passwd *pw;
314
315           if ((t = strchr (s + 1, '/')))
316             *t = 0;
317
318           if ((pw = getpwnam (s + 1))) {
319             strfcpy (p, pw->pw_dir, sizeof (p));
320             if (t) {
321               *t = '/';
322               tail = t;
323             }
324             else
325               tail = "";
326           }
327           else {
328             /* user not found! */
329             if (t)
330               *t = '/';
331             *p = '\0';
332             tail = s;
333           }
334         }
335       }
336       break;
337
338     case '=':
339     case '+':
340       {
341 #ifdef USE_IMAP
342         /* if folder = imap[s]://host/: don't append slash */
343         if (imap_is_magic (NONULL (Maildir), NULL) == M_IMAP && 
344             Maildir[m_strlen(Maildir) - 1] == '/')
345           strfcpy (p, NONULL (Maildir), sizeof (p));
346         else
347 #endif
348           snprintf (p, sizeof (p), "%s/", NONULL (Maildir));
349
350         tail = s + 1;
351       }
352       break;
353
354       /* elm compatibility, @ expands alias to user name */
355
356     case '@':
357       {
358         HEADER *h;
359         ADDRESS *alias;
360
361         if ((alias = mutt_lookup_alias (s + 1))) {
362           h = mutt_new_header ();
363           h->env = mutt_new_envelope ();
364           h->env->from = h->env->to = alias;
365           mutt_default_save (p, sizeof (p), h);
366           h->env->from = h->env->to = NULL;
367           mutt_free_header (&h);
368           /* Avoid infinite recursion if the resulting folder starts with '@' */
369           if (*p != '@')
370             recurse = 1;
371
372           tail = "";
373         }
374       }
375       break;
376
377     case '>':
378       {
379         strfcpy (p, NONULL (Inbox), sizeof (p));
380         tail = s + 1;
381       }
382       break;
383
384     case '<':
385       {
386         strfcpy (p, NONULL (Outbox), sizeof (p));
387         tail = s + 1;
388       }
389       break;
390
391     case '!':
392       {
393         if (*(s + 1) == '!') {
394           strfcpy (p, NONULL (LastFolder), sizeof (p));
395           tail = s + 2;
396         }
397         else {
398           strfcpy (p, NONULL (Spoolfile), sizeof (p));
399           tail = s + 1;
400         }
401       }
402       break;
403
404     case '-':
405       {
406         strfcpy (p, NONULL (LastFolder), sizeof (p));
407         tail = s + 1;
408       }
409       break;
410
411     case '^':
412       {
413         strfcpy (p, NONULL (CurrentFolder), sizeof (p));
414         tail = s + 1;
415       }
416       break;
417
418     default:
419       {
420         *p = '\0';
421         tail = s;
422       }
423     }
424
425     if (rx && *p && !recurse) {
426       mutt_rx_sanitize_string (q, sizeof (q), p);
427       snprintf (tmp, sizeof (tmp), "%s%s", q, tail);
428     }
429     else
430       snprintf (tmp, sizeof (tmp), "%s%s", p, tail);
431
432     strfcpy (s, tmp, slen);
433   }
434   while (recurse);
435
436   return (s);
437 }
438
439 /* Extract the real name from /etc/passwd's GECOS field.
440  * When set, honor the regular expression in GecosMask,
441  * otherwise assume that the GECOS field is a 
442  * comma-separated list.
443  * Replace "&" by a capitalized version of the user's login
444  * name.
445  */
446
447 char *mutt_gecos_name (char *dest, size_t destlen, struct passwd *pw)
448 {
449   regmatch_t pat_match[1];
450   size_t pwnl;
451   int idx;
452   char *p;
453
454   if (!pw || !pw->pw_gecos)
455     return NULL;
456
457   memset (dest, 0, destlen);
458
459   if (GecosMask.rx) {
460     if (regexec (GecosMask.rx, pw->pw_gecos, 1, pat_match, 0) == 0)
461       strfcpy (dest, pw->pw_gecos + pat_match[0].rm_so,
462                MIN (pat_match[0].rm_eo - pat_match[0].rm_so + 1, destlen));
463   }
464   else if ((p = strchr (pw->pw_gecos, ',')))
465     strfcpy (dest, pw->pw_gecos, MIN (destlen, p - pw->pw_gecos + 1));
466   else
467     strfcpy (dest, pw->pw_gecos, destlen);
468
469   pwnl = m_strlen(pw->pw_name);
470
471   for (idx = 0; dest[idx]; idx++) {
472     if (dest[idx] == '&') {
473       memmove (&dest[idx + pwnl], &dest[idx + 1],
474                MAX (destlen - idx - pwnl - 1, 0));
475       memcpy (&dest[idx], pw->pw_name, MIN (destlen - idx - 1, pwnl));
476       dest[idx] = toupper ((unsigned char) dest[idx]);
477     }
478   }
479
480   return dest;
481 }
482
483
484 char *mutt_get_parameter (const char *s, PARAMETER * p)
485 {
486   for (; p; p = p->next)
487     if (ascii_strcasecmp (s, p->attribute) == 0)
488       return (p->value);
489
490   return NULL;
491 }
492
493 void mutt_set_parameter (const char *attribute, const char *value,
494                          PARAMETER ** p)
495 {
496   PARAMETER *q;
497
498   if (!value) {
499     mutt_delete_parameter (attribute, p);
500     return;
501   }
502
503   for (q = *p; q; q = q->next) {
504     if (ascii_strcasecmp (attribute, q->attribute) == 0) {
505       str_replace (&q->value, value);
506       return;
507     }
508   }
509
510   q = mutt_new_parameter ();
511   q->attribute = m_strdup(attribute);
512   q->value = m_strdup(value);
513   q->next = *p;
514   *p = q;
515 }
516
517 void mutt_delete_parameter (const char *attribute, PARAMETER ** p)
518 {
519   PARAMETER *q;
520
521   for (q = *p; q; p = &q->next, q = q->next) {
522     if (ascii_strcasecmp (attribute, q->attribute) == 0) {
523       *p = q->next;
524       q->next = NULL;
525       mutt_free_parameter (&q);
526       return;
527     }
528   }
529 }
530
531 /* returns 1 if Mutt can't display this type of data, 0 otherwise */
532 int mutt_needs_mailcap (BODY * m)
533 {
534   switch (m->type) {
535   case TYPETEXT:
536
537     if (!ascii_strcasecmp ("plain", m->subtype) ||
538         !ascii_strcasecmp ("rfc822-headers", m->subtype) ||
539         !ascii_strcasecmp ("enriched", m->subtype))
540       return 0;
541     break;
542
543   case TYPEAPPLICATION:
544     if ((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp (m))
545       return 0;
546     if ((WithCrypto & APPLICATION_SMIME) && mutt_is_application_smime (m))
547       return 0;
548     break;
549
550   case TYPEMULTIPART:
551   case TYPEMESSAGE:
552     return 0;
553   }
554
555   return 1;
556 }
557
558 int mutt_is_text_part (BODY * b)
559 {
560   int t = b->type;
561   char *s = b->subtype;
562
563   if ((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp (b))
564     return 0;
565
566   if (t == TYPETEXT)
567     return 1;
568
569   if (t == TYPEMESSAGE) {
570     if (!ascii_strcasecmp ("delivery-status", s))
571       return 1;
572   }
573
574   if ((WithCrypto & APPLICATION_PGP) && t == TYPEAPPLICATION) {
575     if (!ascii_strcasecmp ("pgp-keys", s))
576       return 1;
577   }
578
579   return 0;
580 }
581
582 void mutt_free_envelope (ENVELOPE ** p)
583 {
584   if (!*p)
585     return;
586   rfc822_free_address (&(*p)->return_path);
587   rfc822_free_address (&(*p)->from);
588   rfc822_free_address (&(*p)->to);
589   rfc822_free_address (&(*p)->cc);
590   rfc822_free_address (&(*p)->bcc);
591   rfc822_free_address (&(*p)->sender);
592   rfc822_free_address (&(*p)->reply_to);
593   rfc822_free_address (&(*p)->mail_followup_to);
594
595   p_delete(&(*p)->list_post);
596   p_delete(&(*p)->subject);
597   /* real_subj is just an offset to subject and shouldn't be freed */
598   p_delete(&(*p)->message_id);
599   p_delete(&(*p)->supersedes);
600   p_delete(&(*p)->date);
601   p_delete(&(*p)->x_label);
602   p_delete(&(*p)->organization);
603 #ifdef USE_NNTP
604   p_delete(&(*p)->newsgroups);
605   p_delete(&(*p)->xref);
606   p_delete(&(*p)->followup_to);
607   p_delete(&(*p)->x_comment_to);
608 #endif
609
610   mutt_buffer_free (&(*p)->spam);
611   mutt_free_list (&(*p)->references);
612   mutt_free_list (&(*p)->in_reply_to);
613   mutt_free_list (&(*p)->userhdrs);
614   p_delete(p);
615 }
616
617 /* move all the headers from extra not present in base into base */
618 void mutt_merge_envelopes(ENVELOPE* base, ENVELOPE** extra)
619 {
620   /* copies each existing element if necessary, and sets the element
621   * to NULL in the source so that mutt_free_envelope doesn't leave us
622   * with dangling pointers. */
623 #define MOVE_ELEM(h) if (!base->h) { base->h = (*extra)->h; (*extra)->h = NULL; }
624   MOVE_ELEM(return_path);
625   MOVE_ELEM(from);
626   MOVE_ELEM(to);
627   MOVE_ELEM(cc);
628   MOVE_ELEM(bcc);
629   MOVE_ELEM(sender);
630   MOVE_ELEM(reply_to);
631   MOVE_ELEM(mail_followup_to);
632   MOVE_ELEM(list_post);
633   MOVE_ELEM(message_id);
634   MOVE_ELEM(supersedes);
635   MOVE_ELEM(date);
636   MOVE_ELEM(x_label);
637   if (!base->refs_changed) {
638     MOVE_ELEM(references);
639   }
640   if (!base->irt_changed) {
641     MOVE_ELEM(in_reply_to);
642   }
643   /* real_subj is subordinate to subject */
644   if (!base->subject) {
645     base->subject = (*extra)->subject;
646     base->real_subj = (*extra)->real_subj;
647     (*extra)->subject = NULL;
648     (*extra)->real_subj = NULL;
649   }
650   /* spam and user headers should never be hashed, and the new envelope may
651    * have better values. Use new versions regardless. */
652   mutt_buffer_free (&base->spam);
653   mutt_free_list (&base->userhdrs);
654   MOVE_ELEM(spam);
655   MOVE_ELEM(userhdrs);
656 #undef MOVE_ELEM
657   
658   mutt_free_envelope(extra);
659 }
660
661 void _mutt_mktemp (char *s, const char *src, int line)
662 {
663
664   snprintf (s, _POSIX_PATH_MAX, "%s/muttng-%s-%d-%d-%d-%x%x", NONULL (Tempdir),
665             NONULL (Hostname), (int) getuid (), (int) getpid (), Counter++, 
666             (unsigned int) rand(), (unsigned int) rand());
667   debug_print (1, ("%s:%d: mutt_mktemp returns \"%s\".\n", src, line, s));
668   unlink (s);
669 }
670
671 void mutt_free_alias (ALIAS ** p)
672 {
673   ALIAS *t;
674
675   while (*p) {
676     t = *p;
677     *p = (*p)->next;
678     p_delete(&t->name);
679     rfc822_free_address (&t->addr);
680     p_delete(&t);
681   }
682 }
683
684 /* collapse the pathname using ~ or = when possible */
685 void mutt_pretty_mailbox (char *s)
686 {
687   char *p = s, *q = s;
688   size_t len;
689   url_scheme_t scheme;
690
691   scheme = url_check_scheme (s);
692
693 #ifdef USE_IMAP
694   if (scheme == U_IMAP || scheme == U_IMAPS) {
695     imap_pretty_mailbox (s);
696     return;
697   }
698 #endif
699
700   /* if s is an url, only collapse path component */
701   if (scheme != U_UNKNOWN) {
702     p = strchr (s, ':') + 1;
703     if (!strncmp (p, "//", 2))
704       q = strchr (p + 2, '/');
705     if (!q)
706       q = strchr (p, '\0');
707     p = q;
708   }
709
710   /* first attempt to collapse the pathname */
711   while (*p) {
712     if (*p == '/' && p[1] == '/') {
713       *q++ = '/';
714       p += 2;
715     }
716     else if (p[0] == '/' && p[1] == '.' && p[2] == '/') {
717       *q++ = '/';
718       p += 3;
719     }
720     else
721       *q++ = *p++;
722   }
723   *q = 0;
724
725   if (str_ncmp (s, Maildir, (len = m_strlen(Maildir))) == 0 &&
726       s[len] == '/') {
727     *s++ = '=';
728     memmove (s, s + len, m_strlen(s + len) + 1);
729   }
730   else if (str_ncmp (s, Homedir, (len = m_strlen(Homedir))) == 0 &&
731            s[len] == '/') {
732     *s++ = '~';
733     memmove (s, s + len - 1, m_strlen(s + len - 1) + 1);
734   }
735 }
736
737 void mutt_pretty_size (char *s, size_t len, long n)
738 {
739   if (n == 0)
740     strfcpy (s, "0K", len);
741   else if (n < 10189)           /* 0.1K - 9.9K */
742     snprintf (s, len, "%3.1fK", (n < 103) ? 0.1 : n / 1024.0);
743   else if (n < 1023949) {       /* 10K - 999K */
744     /* 51 is magic which causes 10189/10240 to be rounded up to 10 */
745     snprintf (s, len, "%ldK", (n + 51) / 1024);
746   }
747   else if (n < 10433332)        /* 1.0M - 9.9M */
748     snprintf (s, len, "%3.1fM", n / 1048576.0);
749   else {                        /* 10M+ */
750
751     /* (10433332 + 52428) / 1048576 = 10 */
752     snprintf (s, len, "%ldM", (n + 52428) / 1048576);
753   }
754 }
755
756 void mutt_expand_file_fmt (char *dest, size_t destlen, const char *fmt,
757                            const char *src)
758 {
759   char tmp[LONG_STRING];
760
761   mutt_quote_filename (tmp, sizeof (tmp), src);
762   mutt_expand_fmt (dest, destlen, fmt, tmp);
763 }
764
765 void mutt_expand_fmt (char *dest, size_t destlen, const char *fmt,
766                       const char *src)
767 {
768   const char *p;
769   char *d;
770   size_t slen;
771   int found = 0;
772
773   slen = m_strlen(src);
774   destlen--;
775
776   for (p = fmt, d = dest; destlen && *p; p++) {
777     if (*p == '%') {
778       switch (p[1]) {
779       case '%':
780         *d++ = *p++;
781         destlen--;
782         break;
783       case 's':
784         found = 1;
785         strfcpy (d, src, destlen + 1);
786         d += destlen > slen ? slen : destlen;
787         destlen -= destlen > slen ? slen : destlen;
788         p++;
789         break;
790       default:
791         *d++ = *p;
792         destlen--;
793         break;
794       }
795     }
796     else {
797       *d++ = *p;
798       destlen--;
799     }
800   }
801
802   *d = '\0';
803
804   if (!found && destlen > 0) {
805     str_cat (dest, destlen, " ");
806     str_cat (dest, destlen, src);
807   }
808
809 }
810
811 /* return 0 on success, -1 on abort, 1 on error */
812 int mutt_check_overwrite (const char *attname, const char *path,
813                           char *fname, size_t flen, int *append,
814                           char **directory)
815 {
816   int rc = 0;
817   char tmp[_POSIX_PATH_MAX];
818   struct stat st;
819
820   strfcpy (fname, path, flen);
821   if (access (fname, F_OK) != 0)
822     return 0;
823   if (stat (fname, &st) != 0)
824     return -1;
825   if (S_ISDIR (st.st_mode)) {
826     if (directory) {
827       switch (mutt_multi_choice
828               (_("File is a directory, save under it? [(y)es, (n)o, (a)ll]"),
829                _("yna"))) {
830       case 3:                  /* all */
831         str_replace (directory, fname);
832         break;
833       case 1:                  /* yes */
834         p_delete(directory);
835         break;
836       case -1:                 /* abort */
837         p_delete(directory);
838         return -1;
839       case 2:                  /* no */
840         p_delete(directory);
841         return 1;
842       }
843     }
844     else
845       if ((rc =
846            mutt_yesorno (_("File is a directory, save under it?"),
847                          M_YES)) != M_YES)
848       return (rc == M_NO) ? 1 : -1;
849
850     if (!attname || !attname[0]) {
851       tmp[0] = 0;
852       if (mutt_get_field (_("File under directory: "), tmp, sizeof (tmp),
853                           M_FILE | M_CLEAR) != 0 || !tmp[0])
854         return (-1);
855       mutt_concat_path (fname, path, tmp, flen);
856     }
857     else
858       mutt_concat_path (fname, path, mutt_basename (attname), flen);
859   }
860
861   if (*append == 0 && access (fname, F_OK) == 0) {
862     switch (mutt_multi_choice
863             (_("File exists, (o)verwrite, (a)ppend, or (c)ancel?"), _("oac")))
864     {
865     case -1:                   /* abort */
866       return -1;
867     case 3:                    /* cancel */
868       return 1;
869
870     case 2:                    /* append */
871       *append = M_SAVE_APPEND;
872       break;
873     case 1:                    /* overwrite */
874       *append = M_SAVE_OVERWRITE;
875       break;
876     }
877   }
878   return 0;
879 }
880
881 void mutt_save_path (char *d, size_t dsize, ADDRESS * a)
882 {
883   if (a && a->mailbox) {
884     strfcpy (d, a->mailbox, dsize);
885     if (!option (OPTSAVEADDRESS)) {
886       char *p;
887
888       if ((p = strpbrk (d, "%@")))
889         *p = 0;
890     }
891     str_tolower (d);
892   }
893   else
894     *d = 0;
895 }
896
897 void mutt_safe_path (char *s, size_t l, ADDRESS * a)
898 {
899   char *p;
900
901   mutt_save_path (s, l, a);
902   for (p = s; *p; p++)
903     if (*p == '/' || ISSPACE (*p) || !IsPrint ((unsigned char) *p))
904       *p = '_';
905 }
906
907 /* counts how many characters in s can be skipped while none of the
908  * characters of c appears */
909 int mutt_skipchars (const char *s, const char *c)
910 {
911   int ret = 0;
912   const char *p = s;
913
914   while (s && *s) {
915     register const char *t = c;
916
917     while (t && *t) {
918       if (*t == *s)
919         return (ret);
920       t++;
921     }
922     ret++;
923     s++;
924   }
925   return (m_strlen(p));
926 }
927
928 void mutt_FormatString (char *dest,     /* output buffer */
929                         size_t destlen, /* output buffer len */
930                         const char *src,        /* template string */
931                         format_t * callback,    /* callback for processing */
932                         unsigned long data,     /* callback data */
933                         format_flag flags)
934 {                               /* callback flags */
935   char prefix[SHORT_STRING], buf[LONG_STRING], *cp, *wptr = dest, ch;
936   char ifstring[SHORT_STRING], elsestring[SHORT_STRING];
937   size_t wlen, count, len, col, wid;
938
939   prefix[0] = '\0';
940   destlen--;                    /* save room for the terminal \0 */
941   wlen = (flags & M_FORMAT_ARROWCURSOR && option (OPTARROWCURSOR)) ? 3 : 0;
942   col = wlen;
943
944   while (*src && wlen < destlen) {
945     if (*src == '%') {
946       if (*++src == '%') {
947         *wptr++ = '%';
948         wlen++;
949         col++;
950         src++;
951         continue;
952       }
953
954       if (*src == '?') {
955         flags |= M_FORMAT_OPTIONAL;
956         src++;
957       }
958       else {
959         flags &= ~M_FORMAT_OPTIONAL;
960
961         /* eat the format string */
962         cp = prefix;
963         count = 0;
964         while (count < sizeof (prefix) &&
965                (isdigit ((unsigned char) *src) || *src == '.' || *src == '-'))
966         {
967           *cp++ = *src++;
968           count++;
969         }
970         *cp = 0;
971       }
972
973       if (!*src)
974         break;                  /* bad format */
975
976       ch = *src++;              /* save the character to switch on */
977
978       if (flags & M_FORMAT_OPTIONAL) {
979         if (*src != '?')
980           break;                /* bad format */
981         src++;
982
983         /* eat the `if' part of the string */
984         cp = ifstring;
985         count = 0;
986         while (count < sizeof (ifstring) && *src && *src != '?'
987                && *src != '&') {
988           *cp++ = *src++;
989           count++;
990         }
991         *cp = 0;
992
993         /* eat the `else' part of the string (optional) */
994         if (*src == '&')
995           src++;                /* skip the & */
996         cp = elsestring;
997         count = 0;
998         while (count < sizeof (elsestring) && *src && *src != '?') {
999           *cp++ = *src++;
1000           count++;
1001         }
1002         *cp = 0;
1003
1004         if (!*src)
1005           break;                /* bad format */
1006
1007         src++;                  /* move past the trailing `?' */
1008       }
1009
1010       /* handle generic cases first */
1011       if (ch == '>') {
1012         /* right justify to EOL */
1013         ch = *src++;            /* pad char */
1014         /* calculate space left on line.  if we've already written more data
1015            than will fit on the line, ignore the rest of the line */
1016         if (DrawFullLine || option (OPTSTATUSONTOP))
1017           count = (COLS < destlen ? COLS : destlen);
1018         else
1019           count = ((COLS - SW) < destlen ? (COLS - SW) : destlen);
1020         if (count > col) {
1021           count -= col;         /* how many columns left on this line */
1022           mutt_FormatString (buf, sizeof (buf), src, callback, data, flags);
1023           wid = m_strlen(buf);
1024           if (count > wid) {
1025             count -= wid;       /* how many chars to pad */
1026             memset (wptr, ch, count);
1027             wptr += count;
1028             col += count;
1029           }
1030           if (wid + wlen > destlen)
1031             len = destlen - wlen;
1032           else
1033             len = wid;
1034           memcpy (wptr, buf, len);
1035           wptr += len;
1036           wlen += len;
1037           col += mutt_strwidth (buf);
1038         }
1039         break;                  /* skip rest of input */
1040       }
1041       else if (ch == '|') {
1042         /* pad to EOL */
1043         ch = *src++;
1044         if (destlen > COLS)
1045           destlen = COLS;
1046         if (destlen > wlen) {
1047           count = destlen - wlen;
1048           memset (wptr, ch, count);
1049           wptr += count;
1050         }
1051         break;                  /* skip rest of input */
1052       }
1053       else {
1054         short lower = 0;
1055         short nodots = 0;
1056
1057         while (ch == '_' || ch == ':') {
1058           if (ch == '_')
1059             lower = 1;
1060           else if (ch == ':')
1061             nodots = 1;
1062
1063           ch = *src++;
1064         }
1065
1066         /* use callback function to handle this case */
1067         src =
1068           callback (buf, sizeof (buf), ch, src, prefix, ifstring, elsestring,
1069                     data, flags);
1070
1071         if (lower)
1072           str_tolower (buf);
1073         if (nodots) {
1074           char *p = buf;
1075
1076           for (; *p; p++)
1077             if (*p == '.')
1078               *p = '_';
1079         }
1080
1081         if ((len = m_strlen(buf)) + wlen > destlen)
1082           len = (destlen - wlen > 0) ? (destlen - wlen) : 0;
1083
1084         memcpy (wptr, buf, len);
1085         wptr += len;
1086         wlen += len;
1087         col += mutt_strwidth (buf);
1088       }
1089     }
1090     else if (*src == '\\') {
1091       if (!*++src)
1092         break;
1093       switch (*src) {
1094       case 'n':
1095         *wptr = '\n';
1096         break;
1097       case 't':
1098         *wptr = '\t';
1099         break;
1100       case 'r':
1101         *wptr = '\r';
1102         break;
1103       case 'f':
1104         *wptr = '\f';
1105         break;
1106       case 'v':
1107         *wptr = '\v';
1108         break;
1109       default:
1110         *wptr = *src;
1111         break;
1112       }
1113       src++;
1114       wptr++;
1115       wlen++;
1116       col++;
1117     }
1118     else {
1119       unsigned int bar = mutt_skipchars (src, "%\\");
1120       char *bar2 = p_dupstr(src, bar);
1121
1122       while (bar--) {
1123         *wptr++ = *src++;
1124         wlen++;
1125       }
1126       col += mutt_strwidth (bar2);
1127       p_delete(&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 = m_strlen(path);
1149     FILE *f;
1150
1151     if (path[len - 1] == '|') {
1152         char *s = m_strdup(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         p_delete(&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     p_delete(&p->template);
1318     p_delete(&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       p_realloc(&pmatch, l->nmatch);
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 }