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