oops :)
[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 #include <lib-lib/lib-lib.h>
12
13 #include <grp.h>
14 #include <pwd.h>
15
16 #include <lib-mime/mime.h>
17 #include <lib-ui/curses.h>
18 #include <lib-ui/enter.h>
19 #include <lib-sys/unix.h>
20 #include <lib-mx/mx.h>
21
22 #include "alias.h"
23 #include "mutt.h"
24 #include "attach.h"
25
26 #include "version.h"
27
28 #include <imap/imap.h>
29
30 #include <lib-crypt/crypt.h>
31
32 /* Modified by blong to accept a "suggestion" for file name.  If
33  * that file exists, then construct one with unique name but 
34  * keep any extension.  This might fail, I guess.
35  * Renamed to mutt_adv_mktemp so I only have to change where it's
36  * called, and not all possible cases.
37  */
38 void mutt_adv_mktemp (const char* dir, char *s, ssize_t l)
39 {
40     int fd;
41
42     fd = m_tempfd(s, l, m_strisempty(dir) ? NONULL(Tempdir) : dir, s);
43     if (fd < 0) {
44         *s = '\0';
45     } else {
46         close(fd);
47         unlink(s);
48     }
49 }
50
51 /* returns true if the header contained in "s" is in list "t" */
52 int mutt_matches_ignore (const char *s, string_list_t * t)
53 {
54   for (; t; t = t->next) {
55     if (!ascii_strncasecmp (s, t->data, m_strlen(t->data))
56         || *t->data == '*')
57       return 1;
58   }
59   return 0;
60 }
61
62 ssize_t _mutt_expand_path(char *s, ssize_t slen, int rx)
63 {
64     char p[_POSIX_PATH_MAX] = "";
65     char tmp[_POSIX_PATH_MAX];
66     const char *tail = "";
67
68     do {
69         const address_t *alias;
70
71         switch (*s) {
72           case '~':
73             if (s[1] == '/' || s[1] == '\0') {
74                 m_strcpy(p, sizeof(p), Homedir);
75                 tail = s + 1;
76             } else {
77                 struct passwd *pw;
78                 tail = m_strchrnul(s + 1, '/');
79
80                 m_strncpy(tmp, sizeof(tmp), s + 1, tail - s - 1);
81
82                 if ((pw = getpwnam(tmp))) {
83                     m_strcpy(p, sizeof(p), pw->pw_dir);
84                 } else {
85                     /* user not found! */
86                     tail = s;
87                 }
88             }
89             break;
90
91           case '=':
92           case '+':
93             /* if folder = imap[s]://host/: don't append slash */
94             if (imap_is_magic(NONULL(Maildir), NULL) == M_IMAP
95             &&  Maildir[m_strlen(Maildir) - 1] == '/') {
96                 m_strcpy(p, sizeof(p), Maildir);
97             } else {
98                 snprintf(p, sizeof(p), "%s/", NONULL(Maildir));
99             }
100
101             tail = s + 1;
102             break;
103
104             /* elm compatibility, @ expands alias to user name */
105
106           case '@':
107             if ((alias = alias_lookup(s + 1))) {
108                 HEADER h;
109                 header_init(&h);
110                 h.env = envelope_new();
111                 h.env->from = h.env->to = (address_t *)alias;
112                 mutt_default_save(p, sizeof (p), &h);
113                 h.env->from = h.env->to = NULL;
114                 header_wipe(&h);
115
116                 if (*p != '@') {
117                     /* recurse iff the result do not starts with '@' */
118                     m_strcpy(s, slen, p);
119                     continue;
120                 }
121             }
122             break;
123
124           case '>':
125             m_strcpy(p, sizeof(p), Inbox);
126             tail = s + 1;
127             break;
128
129           case '<':
130             m_strcpy(p, sizeof(p), Outbox);
131             tail = s + 1;
132             break;
133
134           case '!':
135             if (s[1] == '!') {
136                 m_strcpy(p, sizeof(p), LastFolder);
137                 tail = s + 2;
138             } else {
139                 m_strcpy(p, sizeof(p), Spoolfile);
140                 tail = s + 1;
141             }
142             break;
143
144           case '-':
145             m_strcpy(p, sizeof(p), NONULL(LastFolder));
146             tail = s + 1;
147             break;
148
149           case '^':
150             m_strcpy(p, sizeof(p), NONULL(CurrentFolder));
151             tail = s + 1;
152             break;
153
154           default:
155             *p = '\0';
156             tail = s;
157         }
158     } while (0);
159
160     if (rx) {
161         char q[_POSIX_PATH_MAX];
162         rx_sanitize_string(q, sizeof(q), p);
163         snprintf(tmp, sizeof(tmp), "%s%s", q, tail);
164     } else {
165         snprintf(tmp, sizeof(tmp), "%s%s", p, tail);
166     }
167
168     return m_strcpy(s, slen, tmp);
169 }
170
171 void mutt_mktemp(char *s)
172 {
173     int fd = m_tempfd(s, _POSIX_PATH_MAX, NONULL(Tempdir), NULL);
174     if (fd < 0) {
175         *s = '\0';
176     } else {
177         close(fd);
178         unlink(s);
179     }
180 }
181
182 /* collapse the pathname using ~ or = when possible */
183 void mutt_pretty_mailbox (char *s)
184 {
185   char *p = s, *q = s;
186   ssize_t len;
187   url_scheme_t scheme;
188
189   scheme = url_check_scheme (s);
190
191   if (scheme == U_IMAP || scheme == U_IMAPS) {
192     imap_pretty_mailbox (s);
193     return;
194   }
195
196   /* if s is an url, only collapse path component */
197   if (scheme != U_UNKNOWN) {
198     p = strchr (s, ':') + 1;
199     if (!m_strncmp (p, "//", 2))
200       q = strchr (p + 2, '/');
201     if (!q)
202       q = strchr (p, '\0');
203     p = q;
204   }
205
206   /* first attempt to collapse the pathname */
207   while (*p) {
208     if (*p == '/' && p[1] == '/') {
209       *q++ = '/';
210       p += 2;
211     }
212     else if (p[0] == '/' && p[1] == '.' && p[2] == '/') {
213       *q++ = '/';
214       p += 3;
215     }
216     else
217       *q++ = *p++;
218   }
219   *q = 0;
220
221   if (m_strncmp(s, Maildir, (len = m_strlen(Maildir))) == 0 &&
222       s[len] == '/') {
223     *s++ = '=';
224     memmove (s, s + len, m_strlen(s + len) + 1);
225   }
226   else if (m_strncmp(s, Homedir, (len = m_strlen(Homedir))) == 0 &&
227            s[len] == '/') {
228     *s++ = '~';
229     memmove (s, s + len - 1, m_strlen(s + len - 1) + 1);
230   }
231 }
232
233 /* return 0 on success, -1 on abort, 1 on error */
234 int mutt_check_overwrite (const char *attname, const char *path,
235                           char *fname, ssize_t flen, int *append,
236                           char **directory)
237 {
238   int rc = 0;
239   char tmp[_POSIX_PATH_MAX];
240   struct stat st;
241
242   m_strcpy(fname, flen, path);
243   if (access (fname, F_OK) != 0)
244     return 0;
245   if (stat (fname, &st) != 0)
246     return -1;
247   if (S_ISDIR (st.st_mode)) {
248     if (directory) {
249       switch (mutt_multi_choice
250               (_("File is a directory, save under it? [(y)es, (n)o, (a)ll]"),
251                _("yna"))) {
252       case 3:                  /* all */
253         m_strreplace(directory, fname);
254         break;
255       case 1:                  /* yes */
256         p_delete(directory);
257         break;
258       case -1:                 /* abort */
259         p_delete(directory);
260         return -1;
261       case 2:                  /* no */
262         p_delete(directory);
263         return 1;
264       }
265     }
266     else
267       if ((rc = mutt_yesorno(_("File is a directory, save under it?"),
268                              M_YES)) != M_YES)
269       return (rc == M_NO) ? 1 : -1;
270
271     if (!attname || !attname[0]) {
272       tmp[0] = 0;
273       if (mutt_get_field (_("File under directory: "), tmp, sizeof (tmp),
274                           M_FILE | M_CLEAR) != 0 || !tmp[0])
275         return (-1);
276       mutt_concat_path(fname, flen, path, tmp);
277     }
278     else
279       mutt_concat_path(fname, flen, path, mutt_basename(attname));
280   }
281
282   if (*append == 0 && access (fname, F_OK) == 0) {
283     switch (mutt_multi_choice
284             (_("File exists, (o)verwrite, (a)ppend, or (c)ancel?"), _("oac")))
285     {
286     case -1:                   /* abort */
287       return -1;
288     case 3:                    /* cancel */
289       return 1;
290
291     case 2:                    /* append */
292       *append = M_SAVE_APPEND;
293       break;
294     case 1:                    /* overwrite */
295       *append = M_SAVE_OVERWRITE;
296       break;
297     }
298   }
299   return 0;
300 }
301
302 void mutt_save_path(char *d, ssize_t dsize, address_t *a)
303 {
304     if (a && a->mailbox) {
305         m_strcpy(d, dsize, a->mailbox);
306
307         if (!option(OPTSAVEADDRESS)) {
308             char *p = strpbrk(d, "%@");
309             if (p)
310                 *p = '\0';
311         }
312         m_strtolower(d);
313     } else {
314         *d = '\0';
315     }
316 }
317
318 void mutt_safe_path(char *s, ssize_t l, address_t *a)
319 {
320     mutt_save_path(s, l, a);
321
322     while (*s) {
323         if (*s == '/' || ISSPACE(*s) || !isprint((unsigned char)*s))
324             *s = '_';
325         s++;
326     }
327 }
328
329 /* returns 0 if OK to proceed, -1 to abort, 1 to retry */
330 int mutt_save_confirm (const char *s, struct stat *st)
331 {
332   char tmp[_POSIX_PATH_MAX];
333   int ret = 0;
334   int rc;
335   int magic = 0;
336
337   magic = mx_get_magic (s);
338
339   if (magic == M_POP) {
340     mutt_error _("Can't save message to POP mailbox.");
341
342     return 1;
343   }
344
345 #ifdef USE_NNTP
346   if (magic == M_NNTP) {
347     mutt_error _("Can't save message to newsserver.");
348
349     return 0;
350   }
351 #endif
352
353   if (magic > 0 && !mx_access (s, W_OK)) {
354     if (option (OPTCONFIRMAPPEND) &&
355         (!TrashPath || (m_strcmp(s, TrashPath) != 0))) {
356       /* if we're appending to the trash, there's no point in asking */
357       snprintf (tmp, sizeof (tmp), _("Append messages to %s?"), s);
358       if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
359         ret = 1;
360       else if (rc == -1)
361         ret = -1;
362     }
363   }
364
365   if (stat (s, st) != -1) {
366     if (magic == -1) {
367       mutt_error (_("%s is not a mailbox!"), s);
368       return 1;
369     }
370   } else {
371     if (magic != M_IMAP)
372     {
373       st->st_mtime = 0;
374       st->st_atime = 0;
375
376       if (errno == ENOENT) {
377         if (option (OPTCONFIRMCREATE)) {
378           snprintf (tmp, sizeof (tmp), _("Create %s?"), s);
379           if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
380             ret = 1;
381           else if (rc == -1)
382             ret = -1;
383         }
384       } else {
385         mutt_perror (s);
386         return 1;
387       }
388     }
389   }
390
391   CLEARLINE (LINES - 1);
392   return (ret);
393 }
394
395 void mutt_sleep (short s)
396 {
397     sleep(MAX(s, SleepTime));
398 }
399
400 const char *mutt_make_version (int full)
401 {
402   static char vstring[STRING];
403
404   if (full)
405     snprintf (vstring, sizeof (vstring),
406               "Madmutt/%s-r%s (based on Mutt 1.5.11)",
407               MUTT_VERSION, MUTT_REVISION);
408   else
409     snprintf (vstring, sizeof (vstring), "Madmutt/%s-%s",
410               MUTT_VERSION, MUTT_REVISION);
411   return vstring;
412 }
413
414 /* return 1 if address lists are strictly identical */
415 static int mutt_cmp_addr (const address_t * a, const address_t * b)
416 {
417   while (a && b) {
418     if (m_strcmp(a->mailbox, b->mailbox) ||
419         m_strcmp(a->personal, b->personal))
420       return (0);
421
422     a = a->next;
423     b = b->next;
424   }
425   if (a || b)
426     return (0);
427
428   return (1);
429 }
430
431 static int mutt_cmp_list (const string_list_t * a, const string_list_t * b)
432 {
433   while (a && b) {
434     if (m_strcmp(a->data, b->data))
435       return (0);
436
437     a = a->next;
438     b = b->next;
439   }
440   if (a || b)
441     return (0);
442
443   return (1);
444 }
445
446 static int mutt_cmp_env (const ENVELOPE * e1, const ENVELOPE * e2)
447 {
448   if (e1 && e2) {
449     if (m_strcmp(e1->message_id, e2->message_id) ||
450         m_strcmp(e1->subject, e2->subject) ||
451         !mutt_cmp_list (e1->references, e2->references) ||
452         !mutt_cmp_addr (e1->from, e2->from) ||
453         !mutt_cmp_addr (e1->sender, e2->sender) ||
454         !mutt_cmp_addr (e1->reply_to, e2->reply_to) ||
455         !mutt_cmp_addr (e1->to, e2->to) ||
456         !mutt_cmp_addr (e1->cc, e2->cc) ||
457         !mutt_cmp_addr (e1->return_path, e2->return_path))
458       return (0);
459     else
460       return (1);
461   }
462   else {
463     if (e1 == NULL && e2 == NULL)
464       return (1);
465     else
466       return (0);
467   }
468 }
469
470 static int mutt_cmp_body (const BODY * b1, const BODY * b2)
471 {
472   if (b1->type != b2->type ||
473       b1->encoding != b2->encoding ||
474       m_strcmp(b1->subtype, b2->subtype) ||
475       m_strcmp(b1->description, b2->description) ||
476       !parameter_equal(b1->parameter, b2->parameter) ||
477       b1->length != b2->length)
478     return (0);
479   return (1);
480 }
481 int mutt_cmp_header (const HEADER * h1, const HEADER * h2) {
482   if (h1 && h2) {
483     if (h1->received != h2->received ||
484         h1->date_sent != h2->date_sent ||
485         h1->content->length != h2->content->length ||
486         h1->lines != h2->lines ||
487         h1->zhours != h2->zhours ||
488         h1->zminutes != h2->zminutes ||
489         h1->zoccident != h2->zoccident ||
490         h1->mime != h2->mime ||
491         !mutt_cmp_env (h1->env, h2->env) ||
492         !mutt_cmp_body (h1->content, h2->content))
493       return (0);
494     else
495       return (1);
496   }
497   else {
498     if (h1 == NULL && h2 == NULL)
499       return (1);
500     else
501       return (0);
502   }
503 }
504
505
506 int mutt_extract_token(BUFFER *dest, BUFFER *tok, int flags)
507 {
508     char ch;
509     char qc = 0;                  /* quote char */
510     char *pc;
511
512     /* reset the destination pointer to the beginning of the buffer */
513     dest->dptr = dest->data;
514
515     tok->dptr = vskipspaces(tok->dptr);
516     while ((ch = *tok->dptr)) {
517         if (!qc) {
518             if ((ISSPACE(ch) && !(flags & M_TOKEN_SPACE))
519             || (ch == '#' && !(flags & M_TOKEN_COMMENT))
520             || (ch == '=' && (flags & M_TOKEN_EQUAL))
521             || (ch == ';' && !(flags & M_TOKEN_SEMICOLON))
522             || ((flags & M_TOKEN_PATTERN) && strchr("~=!|", ch)))
523             {
524                 break;
525             }
526         }
527
528         tok->dptr++;
529
530         if (ch == qc) {
531             qc = 0;                     /* end of quote */
532         } else
533         if (!qc && (ch == '\'' || ch == '"') && !(flags & M_TOKEN_QUOTE)) {
534             qc = ch;
535         } else
536         if (ch == '\\' && qc != '\'') {
537             if (!*tok->dptr)
538                 return -1;              /* premature end of token */
539
540             switch (ch = *tok->dptr++) {
541               case 'c':
542               case 'C':
543                 if (!*tok->dptr)
544                     return -1;          /* premature end of token */
545                 mutt_buffer_addch(dest,
546                                   (toupper((unsigned char)*tok->dptr) - 'A' + 1) & 0x7f);
547                 tok->dptr++;
548                 break;
549               case 'r':
550                 mutt_buffer_addch(dest, '\r');
551                 break;
552               case 'n':
553                 mutt_buffer_addch(dest, '\n');
554                 break;
555               case 't':
556                 mutt_buffer_addch(dest, '\t');
557                 break;
558               case 'f':
559                 mutt_buffer_addch(dest, '\f');
560                 break;
561               case 'e':
562                 mutt_buffer_addch(dest, '\033');
563                 break;
564               default:
565                 if (isdigit((unsigned char)ch)
566                 &&  isdigit((unsigned char)*tok->dptr)
567                 &&  isdigit((unsigned char)*(tok->dptr + 1)))
568                 {
569                     mutt_buffer_addch(dest, (ch << 6) + (*tok->dptr << 3) +
570                                             *(tok->dptr + 1) - 3504);
571                     tok->dptr += 2;
572                 } else {
573                     mutt_buffer_addch(dest, ch);
574                 }
575             }
576         } else
577         if (ch == '^' && (flags & M_TOKEN_CONDENSE)) {
578             if (!*tok->dptr)
579                 return -1;              /* premature end of token */
580             ch = *tok->dptr++;
581             if (ch == '^') {
582                 mutt_buffer_addch(dest, ch);
583             } else
584             if (ch == '[') {
585                 mutt_buffer_addch(dest, '\033');
586             } else
587             if (isalpha((unsigned char)ch)) {
588                 mutt_buffer_addch(dest, toupper((unsigned char)ch) - 'A' + 1);
589             } else {
590                 mutt_buffer_addch(dest, '^');
591                 mutt_buffer_addch(dest, ch);
592             }
593         } else
594         if (ch == '`' && (!qc || qc == '"')) {
595             FILE *fp;
596             pid_t pid;
597             char *cmd, *ptr;
598             ssize_t expnlen;
599             BUFFER expn;
600             int line = 0;
601
602             pc = tok->dptr;
603             do {
604                 if ((pc = strpbrk(pc, "\\`"))) {
605                     /* skip any quoted chars */
606                     if (*pc == '\\')
607                         pc += 2;
608                 }
609             } while (pc && *pc != '`');
610             if (!pc) {
611                 return (-1);
612             }
613
614             cmd = p_dupstr(tok->dptr, pc - tok->dptr);
615             if ((pid = mutt_create_filter(cmd, NULL, &fp, NULL)) < 0) {
616                 p_delete(&cmd);
617                 return -1;
618             }
619             p_delete(&cmd);
620
621             tok->dptr = pc + 1;
622
623             /* read line */
624             p_clear(&expn, 1);
625             expn.data = mutt_read_line(NULL, &expn.dsize, fp, &line);
626             m_fclose(&fp);
627             mutt_wait_filter(pid);
628
629             /* if we got output, make a new string consiting of the shell ouptput
630                plus whatever else was left on the original line */
631             /* BUT: If this is inside a quoted string, directly add output to 
632              * the token */
633             if (expn.data && qc) {
634                 mutt_buffer_addstr(dest, expn.data);
635                 p_delete(&expn.data);
636             } else
637             if (expn.data) {
638                 expnlen = m_strlen(expn.data);
639                 tok->dsize = expnlen + m_strlen(tok->dptr) + 1;
640                 ptr = xmalloc(tok->dsize);
641                 memcpy(ptr, expn.data, expnlen);
642                 m_strcpy(ptr + expnlen, tok->dsize - expnlen, tok->dptr);
643                 if (tok->destroy)
644                     p_delete(&tok->data);
645                 tok->data = ptr;
646                 tok->dptr = ptr;
647                 tok->destroy = 1;       /* mark that the caller should destroy this data */
648                 ptr = NULL;
649                 p_delete(&expn.data);
650             }
651         } else
652         if (ch == '$' && (!qc || qc == '"')
653         && (*tok->dptr == '{' || isalpha((unsigned char)*tok->dptr)))
654         {
655             char *env = NULL, *var = NULL;
656
657             if (*tok->dptr == '{') {
658                 tok->dptr++;
659                 if ((pc = strchr (tok->dptr, '}'))) {
660                     var = p_dupstr(tok->dptr, pc - tok->dptr);
661                     tok->dptr = pc + 1;
662                 }
663             } else {
664                 for (pc = tok->dptr; isalnum((unsigned char)*pc) || *pc == '_';
665                      pc++);
666                 var = p_dupstr(tok->dptr, pc - tok->dptr);
667                 tok->dptr = pc;
668             }
669             if (var) {
670                 char tmp[STRING];
671                 if ((env = getenv (var))
672                 || (mutt_option_value (var, tmp, sizeof (tmp)) && (env = tmp)))
673                 {
674                     mutt_buffer_addstr (dest, env);
675                 }
676             }
677             p_delete(&var);
678         } else {
679             mutt_buffer_addch(dest, ch);
680         }
681     }
682     mutt_buffer_addch(dest, 0);  /* terminate the string */
683     tok->dptr = vskipspaces(tok->dptr);
684     return 0;
685 }