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