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