remove a mutt-ng thing that makes no sense at all.
[apps/madmutt.git] / lib-ui / hdrline.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000,2002 Michael R. Elkins <me@mutt.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #include <lib-lib/lib-lib.h>
11
12 #include <lib-mime/mime.h>
13 #include <lib-ui/curses.h>
14 #include <lib-mx/mx.h>
15
16 #include "mutt.h"
17 #include "alias.h"
18 #include "sort.h"
19 #include "thread.h"
20 #include "charset.h"
21 #include <lib-crypt/crypt.h>
22 #include "mutt_idna.h"
23
24 int mutt_is_mail_list (address_t * addr)
25 {
26   if (!rx_list_match (UnMailLists, addr->mailbox))
27     return rx_list_match (MailLists, addr->mailbox);
28   return 0;
29 }
30
31 int mutt_is_subscribed_list (address_t * addr)
32 {
33   if (!rx_list_match (UnMailLists, addr->mailbox)
34       && !rx_list_match (UnSubscribedLists, addr->mailbox))
35     return rx_list_match (SubscribedLists, addr->mailbox);
36   return 0;
37 }
38
39 /* Search for a mailing list in the list of addresses pointed to by adr.
40  * If one is found, print pfx and the name of the list into buf, then
41  * return 1.  Otherwise, simply return 0.
42  */
43 static int
44 check_for_mailing_list (address_t * adr, const char *pfx, char *buf, int buflen)
45 {
46   for (; adr; adr = adr->next) {
47     if (mutt_is_subscribed_list (adr)) {
48       if (pfx && buf && buflen)
49         snprintf (buf, buflen, "%s%s", pfx, mutt_get_name (adr));
50       return 1;
51     }
52   }
53   return 0;
54 }
55
56 /* Search for a mailing list in the list of addresses pointed to by adr.
57  * If one is found, print the address of the list into buf, then return 1.
58  * Otherwise, simply return 0.
59  */
60 static int check_for_mailing_list_addr (address_t * adr, char *buf, int buflen)
61 {
62   for (; adr; adr = adr->next) {
63     if (mutt_is_subscribed_list (adr)) {
64       if (buf && buflen)
65         snprintf (buf, buflen, "%s", adr->mailbox);
66       return 1;
67     }
68   }
69   return 0;
70 }
71
72
73 static int first_mailing_list (char *buf, ssize_t buflen, address_t * a)
74 {
75   for (; a; a = a->next) {
76     if (mutt_is_subscribed_list (a)) {
77       mutt_save_path (buf, buflen, a);
78       return 1;
79     }
80   }
81   return 0;
82 }
83
84 static void make_from (ENVELOPE * hdr, char *buf, ssize_t len, int do_lists)
85 {
86   int me;
87
88   me = mutt_addr_is_user (hdr->from);
89
90   if (do_lists || me) {
91     if (check_for_mailing_list (hdr->to, "To ", buf, len))
92       return;
93     if (check_for_mailing_list (hdr->cc, "Cc ", buf, len))
94       return;
95   }
96
97   if (me && hdr->to)
98     snprintf (buf, len, "To %s", mutt_get_name (hdr->to));
99   else if (me && hdr->cc)
100     snprintf (buf, len, "Cc %s", mutt_get_name (hdr->cc));
101   else if (hdr->from)
102     m_strcpy(buf, len, mutt_get_name(hdr->from));
103   else
104     *buf = 0;
105 }
106
107 static void make_from_addr (ENVELOPE * hdr, char *buf, ssize_t len,
108                             int do_lists)
109 {
110   int me;
111
112   me = mutt_addr_is_user (hdr->from);
113
114   if (do_lists || me) {
115     if (check_for_mailing_list_addr (hdr->to, buf, len))
116       return;
117     if (check_for_mailing_list_addr (hdr->cc, buf, len))
118       return;
119   }
120
121   if (me && hdr->to)
122     snprintf (buf, len, "%s", hdr->to->mailbox);
123   else if (me && hdr->cc)
124     snprintf (buf, len, "%s", hdr->cc->mailbox);
125   else if (hdr->from)
126     m_strcpy(buf, len, hdr->from->mailbox);
127   else
128     *buf = 0;
129 }
130
131 static int user_in_addr (address_t * a)
132 {
133   for (; a; a = a->next)
134     if (mutt_addr_is_user (a))
135       return 1;
136   return 0;
137 }
138
139 /* Return values:
140  * 0: user is not in list
141  * 1: user is unique recipient
142  * 2: user is in the TO list
143  * 3: user is in the CC list
144  * 4: user is originator
145  * 5: sent to a subscribed mailinglist
146  */
147 int mutt_user_is_recipient (HEADER * h)
148 {
149   ENVELOPE *env = h->env;
150
151   if (!h->recip_valid) {
152     h->recip_valid = 1;
153
154     if (mutt_addr_is_user (env->from))
155       h->recipient = 4;
156     else if (user_in_addr (env->to)) {
157       if (env->to->next || env->cc)
158         h->recipient = 2;       /* non-unique recipient */
159       else
160         h->recipient = 1;       /* unique recipient */
161     }
162     else if (user_in_addr (env->cc))
163       h->recipient = 3;
164     else if (check_for_mailing_list (env->to, NULL, NULL, 0))
165       h->recipient = 5;
166     else if (check_for_mailing_list (env->cc, NULL, NULL, 0))
167       h->recipient = 5;
168     else
169       h->recipient = 0;
170   }
171
172   return h->recipient;
173 }
174
175 /* %a = address of author
176  * %A = reply-to address (if present; otherwise: address of author
177  * %b = filename of the originating folder
178  * %B = the list to which the letter was sent
179  * %c = size of message in bytes
180  * %C = current message number
181  * %d = date and time of message using $date_format and sender's timezone
182  * %D = date and time of message using $date_format and local timezone
183  * %e = current message number in thread
184  * %E = number of messages in current thread
185  * %f = entire from line
186  * %F = like %n, unless from self
187  * %g = newsgroup name (if compiled with nntp support)
188  * %i = message-id
189  * %I = initials of author
190  * %l = number of lines in the message
191  * %L = like %F, except `lists' are displayed first
192  * %m = number of messages in the mailbox
193  * %n = name of author
194  * %N = score
195  * %O = like %L, except using address instead of name
196  * %s = subject
197  * %S = short message status (e.g., N/O/D/!/r/-)
198  * %t = `to:' field (recipients)
199  * %T = $to_chars
200  * %u = user (login) name of author
201  * %v = first name of author, unless from self
202  * %W = where user is (organization)
203  * %X = number of MIME attachments
204  * %y = `x-label:' field (if present)
205  * %Y = `x-label:' field (if present, tree unfolded, and != parent's x-label)
206  * %Z = status flags    */
207
208 struct hdr_format_info {
209   CONTEXT *ctx;
210   HEADER *hdr;
211 };
212
213 static const char *hdr_format_str (char *dest,
214                                    ssize_t destlen,
215                                    char op,
216                                    const char *src,
217                                    const char *prefix,
218                                    const char *ifstring,
219                                    const char *elsestring,
220                                    unsigned long data, format_flag flags)
221 {
222   struct hdr_format_info *hfi = (struct hdr_format_info *) data;
223   HEADER *hdr, *htmp;
224   CONTEXT *ctx;
225   char fmt[SHORT_STRING], buf2[SHORT_STRING], ch, *p;
226   int do_locales, i;
227   int optional = (flags & M_FORMAT_OPTIONAL);
228   int threads = ((Sort & SORT_MASK) == SORT_THREADS);
229   int is_index = (flags & M_FORMAT_INDEX);
230
231 #define THREAD_NEW (threads && hdr->collapsed && hdr->num_hidden > 1 && mutt_thread_contains_unread (ctx, hdr) == 1)
232 #define THREAD_OLD (threads && hdr->collapsed && hdr->num_hidden > 1 && mutt_thread_contains_unread (ctx, hdr) == 2)
233   ssize_t len;
234
235   hdr = hfi->hdr;
236   ctx = hfi->ctx;
237
238   dest[0] = 0;
239   switch (op) {
240   case 'A':
241     if (hdr->env->reply_to && hdr->env->reply_to->mailbox) {
242       mutt_format_s (dest, destlen, prefix,
243                      mutt_addr_for_display (hdr->env->reply_to));
244       break;
245     }
246     /* fall through if 'A' returns nothing */
247
248   case 'a':
249     if (hdr->env->from && hdr->env->from->mailbox) {
250       mutt_format_s (dest, destlen, prefix,
251                      mutt_addr_for_display (hdr->env->from));
252     }
253     else
254       dest[0] = '\0';
255     break;
256
257   case 'B':
258     if (!first_mailing_list (dest, destlen, hdr->env->to) &&
259         !first_mailing_list (dest, destlen, hdr->env->cc))
260       dest[0] = 0;
261     if (dest[0]) {
262       m_strcpy(buf2, sizeof(buf2), dest);
263       mutt_format_s (dest, destlen, prefix, buf2);
264       break;
265     }
266     /* fall through if 'B' returns nothing */
267
268   case 'b':
269     if (ctx) {
270       if ((p = strrchr (ctx->path, '/')))
271         m_strcpy(dest, destlen, p + 1);
272       else
273         m_strcpy(dest, destlen, ctx->path);
274     }
275     else
276       m_strcpy(dest, destlen, "(null)");
277     m_strcpy(buf2, sizeof(buf2), dest);
278     mutt_format_s (dest, destlen, prefix, buf2);
279     break;
280
281   case 'c':
282     mutt_pretty_size (buf2, sizeof (buf2), (long) hdr->content->length);
283     mutt_format_s (dest, destlen, prefix, buf2);
284     break;
285
286   case 'C':
287     snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
288     snprintf (dest, destlen, fmt, hdr->msgno + 1);
289     break;
290
291   case 'd':
292   case 'D':
293   case '{':
294   case '[':
295   case '(':
296   case '<':
297
298     /* preprocess $date_format to handle %Z */
299     {
300       const char *cp;
301       struct tm *tm;
302       time_t T;
303
304       p = dest;
305
306       cp = (op == 'd' || op == 'D') ? (NONULL (DateFmt)) : src;
307       if (*cp == '!') {
308         do_locales = 0;
309         cp++;
310       }
311       else
312         do_locales = 1;
313
314       len = destlen - 1;
315       while (len > 0 && (((op == 'd' || op == 'D') && *cp) ||
316                          (op == '{' && *cp != '}') ||
317                          (op == '[' && *cp != ']') ||
318                          (op == '(' && *cp != ')') ||
319                          (op == '<' && *cp != '>'))) {
320         if (*cp == '%') {
321           cp++;
322           if ((*cp == 'Z' || *cp == 'z') && (op == 'd' || op == '{')) {
323             if (len >= 5) {
324               sprintf (p, "%c%02u%02u", hdr->zoccident ? '-' : '+',
325                        hdr->zhours, hdr->zminutes);
326               p += 5;
327               len -= 5;
328             }
329             else
330               break;            /* not enough space left */
331           }
332           else {
333             if (len >= 2) {
334               *p++ = '%';
335               *p++ = *cp;
336               len -= 2;
337             }
338             else
339               break;            /* not enough space */
340           }
341           cp++;
342         }
343         else {
344           *p++ = *cp++;
345           len--;
346         }
347       }
348       *p = 0;
349
350       if (do_locales && Locale)
351         setlocale (LC_TIME, Locale);
352
353       if (op == '[' || op == 'D')
354         tm = localtime (&hdr->date_sent);
355       else if (op == '(')
356         tm = localtime (&hdr->received);
357       else if (op == '<') {
358         T = time (NULL);
359         tm = localtime (&T);
360       }
361       else {
362         /* restore sender's time zone */
363         T = hdr->date_sent;
364         if (hdr->zoccident)
365           T -= (hdr->zhours * 3600 + hdr->zminutes * 60);
366         else
367           T += (hdr->zhours * 3600 + hdr->zminutes * 60);
368         tm = gmtime (&T);
369       }
370
371       strftime (buf2, sizeof (buf2), dest, tm);
372
373       if (do_locales)
374         setlocale (LC_TIME, "C");
375
376       mutt_format_s (dest, destlen, prefix, buf2);
377       if (len > 0 && op != 'd' && op != 'D')    /* Skip ending op */
378         src = cp + 1;
379     }
380     break;
381
382   case 'e':
383     snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
384     snprintf (dest, destlen, fmt, mutt_messages_in_thread (ctx, hdr, 1));
385     break;
386
387   case 'E':
388     if (!optional) {
389       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
390       snprintf (dest, destlen, fmt, mutt_messages_in_thread (ctx, hdr, 0));
391     }
392     else if (mutt_messages_in_thread (ctx, hdr, 0) <= 1)
393       optional = 0;
394     break;
395
396   case 'f':
397     buf2[0] = 0;
398     rfc822_write_address (buf2, sizeof (buf2), hdr->env->from, 1);
399     mutt_format_s (dest, destlen, prefix, buf2);
400     break;
401
402   case 'F':
403     if (!optional) {
404       make_from (hdr->env, buf2, sizeof (buf2), 0);
405       mutt_format_s (dest, destlen, prefix, buf2);
406     }
407     else if (mutt_addr_is_user (hdr->env->from))
408       optional = 0;
409     break;
410
411 #ifdef USE_NNTP
412   case 'g':
413     mutt_format_s (dest, destlen, prefix,
414                    hdr->env->newsgroups ? hdr->env->newsgroups : "");
415     break;
416 #endif
417
418   case 'H':
419     /* (Hormel) spam score */
420     if (optional)
421       optional = hdr->env->spam ? 1 : 0;
422
423     if (hdr->env->spam)
424       mutt_format_s (dest, destlen, prefix, NONULL (hdr->env->spam->data));
425     else
426       mutt_format_s (dest, destlen, prefix, "");
427
428     break;
429
430   case 'i':
431     mutt_format_s (dest, destlen, prefix,
432                    hdr->env->message_id ? hdr->env->message_id : "<no.id>");
433     break;
434
435   case 'I':
436     {
437       int iflag = FALSE;
438       int j = 0;
439
440       for (i = 0; hdr->env->from && hdr->env->from->personal &&
441            hdr->env->from->personal[i] && j < SHORT_STRING - 1; i++) {
442         if (isalpha ((int) hdr->env->from->personal[i])) {
443           if (!iflag) {
444             buf2[j++] = hdr->env->from->personal[i];
445             iflag = TRUE;
446           }
447         }
448         else
449           iflag = FALSE;
450       }
451
452       buf2[j] = '\0';
453     }
454     mutt_format_s (dest, destlen, prefix, buf2);
455     break;
456
457   case 'l':
458     if (!optional) {
459       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
460       snprintf (dest, destlen, fmt, (int) hdr->lines);
461     }
462     else if (hdr->lines <= 0)
463       optional = 0;
464     break;
465
466   case 'L':
467     if (!optional) {
468       make_from (hdr->env, buf2, sizeof (buf2), 1);
469       mutt_format_s (dest, destlen, prefix, buf2);
470     }
471     else if (!check_for_mailing_list (hdr->env->to, NULL, NULL, 0) &&
472              !check_for_mailing_list (hdr->env->cc, NULL, NULL, 0)) {
473       optional = 0;
474     }
475     break;
476
477   case 'm':
478     if (ctx) {
479       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
480       snprintf (dest, destlen, fmt, ctx->msgcount);
481     }
482     else
483       m_strcpy(dest, destlen, "(null)");
484     break;
485
486   case 'n':
487     mutt_format_s (dest, destlen, prefix, mutt_get_name (hdr->env->from));
488     break;
489
490   case 'N':
491     if (!optional) {
492       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
493       snprintf (dest, destlen, fmt, hdr->score);
494     }
495     else {
496       if (hdr->score == 0)
497         optional = 0;
498     }
499     break;
500
501   case 'O':
502     if (!optional) {
503       make_from_addr (hdr->env, buf2, sizeof (buf2), 1);
504       if (!option (OPTSAVEADDRESS) && (p = strpbrk (buf2, "%@")))
505         *p = 0;
506       mutt_format_s (dest, destlen, prefix, buf2);
507     }
508     else if (!check_for_mailing_list_addr (hdr->env->to, NULL, 0) &&
509              !check_for_mailing_list_addr (hdr->env->cc, NULL, 0)) {
510       optional = 0;
511     }
512     break;
513
514   case 'M':
515     snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
516     if (!optional) {
517       if (threads && is_index && hdr->collapsed && hdr->num_hidden > 1)
518         snprintf (dest, destlen, fmt, hdr->num_hidden);
519       else if (is_index && threads)
520         mutt_format_s (dest, destlen, prefix, " ");
521       else
522         *dest = '\0';
523     }
524     else {
525       if (!(threads && is_index && hdr->collapsed && hdr->num_hidden > 1))
526         optional = 0;
527     }
528     break;
529
530   case 's':
531
532     if (flags & M_FORMAT_TREE && !hdr->collapsed) {
533       if (flags & M_FORMAT_FORCESUBJ) {
534         mutt_format_s (dest, destlen, "", NONULL (hdr->env->subject));
535         snprintf (buf2, sizeof (buf2), "%s%s", hdr->tree, dest);
536         mutt_format_s_tree (dest, destlen, prefix, buf2);
537       }
538       else
539         mutt_format_s_tree (dest, destlen, prefix, hdr->tree);
540     }
541     else
542       mutt_format_s (dest, destlen, prefix, NONULL (hdr->env->subject));
543     break;
544
545   case 'S':
546     if (hdr->deleted)
547       ch = 'D';
548     else if (hdr->attach_del)
549       ch = 'd';
550     else if (hdr->tagged)
551       ch = '*';
552     else if (hdr->flagged)
553       ch = '!';
554     else if (hdr->replied)
555       ch = 'r';
556     else if (hdr->read && (ctx && ctx->msgnotreadyet != hdr->msgno))
557       ch = '-';
558     else if (hdr->old)
559       ch = 'O';
560     else
561       ch = 'N';
562
563     /* FOO - this is probably unsafe, but we are not likely to have such
564        a short string passed into this routine */
565     *dest = ch;
566     *(dest + 1) = 0;
567     break;
568
569   case 't':
570     buf2[0] = 0;
571     if (!check_for_mailing_list (hdr->env->to, "To ", buf2, sizeof (buf2)) &&
572         !check_for_mailing_list (hdr->env->cc, "Cc ", buf2, sizeof (buf2))) {
573       if (hdr->env->to)
574         snprintf (buf2, sizeof (buf2), "To %s", mutt_get_name (hdr->env->to));
575       else if (hdr->env->cc)
576         snprintf (buf2, sizeof (buf2), "Cc %s", mutt_get_name (hdr->env->cc));
577     }
578     mutt_format_s (dest, destlen, prefix, buf2);
579     break;
580
581   case 'T':
582     snprintf (fmt, sizeof (fmt), "%%%sc", prefix);
583     snprintf (dest, destlen, fmt,
584               (Tochars
585                && ((i = mutt_user_is_recipient (hdr))) <
586                m_strlen(Tochars)) ? Tochars[i] : ' ');
587     break;
588
589   case 'u':
590     if (hdr->env->from && hdr->env->from->mailbox) {
591       m_strcpy(buf2, sizeof(buf2), mutt_addr_for_display(hdr->env->from));
592       if ((p = strpbrk (buf2, "%@")))
593         *p = 0;
594     }
595     else
596       buf2[0] = 0;
597     mutt_format_s (dest, destlen, prefix, buf2);
598     break;
599
600   case 'v':
601     if (mutt_addr_is_user (hdr->env->from)) {
602       if (hdr->env->to)
603         mutt_format_s (buf2, sizeof (buf2), prefix,
604                        mutt_get_name (hdr->env->to));
605       else if (hdr->env->cc)
606         mutt_format_s (buf2, sizeof (buf2), prefix,
607                        mutt_get_name (hdr->env->cc));
608       else
609         *buf2 = 0;
610     }
611     else
612       mutt_format_s (buf2, sizeof (buf2), prefix,
613                      mutt_get_name (hdr->env->from));
614     if ((p = strpbrk (buf2, " %@")))
615       *p = 0;
616     mutt_format_s (dest, destlen, prefix, buf2);
617     break;
618
619   case 'W':
620     if (!optional)
621       mutt_format_s (dest, destlen, prefix,
622                      hdr->env->organization ? hdr->env->organization : "");
623     else if (!hdr->env->organization)
624       optional = 0;
625     break;
626
627   case 'X':
628     {
629       int count = 0;
630
631       /* The recursion allows messages without depth to return 0. */
632       if (optional)
633         optional = count != 0;
634
635       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
636       snprintf (dest, destlen, fmt, count);
637     }
638     break;
639
640   case 'Z':
641
642     ch = ' ';
643
644     if (hdr->security & GOODSIGN)
645       ch = 'S';
646     else if (hdr->security & ENCRYPT)
647       ch = 'P';
648     else if (hdr->security & SIGN)
649       ch = 's';
650     else if (hdr->security & PGPKEY)
651       ch = 'K';
652
653     snprintf(buf2, sizeof (buf2), "%c%c%c",
654              (THREAD_NEW ? 'n'
655               : (THREAD_OLD
656                  ?  'o'
657                  : ((hdr->read && (ctx && ctx->msgnotreadyet != hdr->msgno))
658                     ? (hdr->replied ? 'r' : ' ')
659                     : (hdr->old ?  'O' : 'N')))),
660               hdr->deleted ? 'D' : (hdr->attach_del ? 'd' : ch),
661               hdr->tagged ? '*'
662               : (hdr->flagged ? '!'
663                  : (Tochars && ((i = mutt_user_is_recipient(hdr)) < m_strlen(Tochars))
664                     ?  Tochars[i] : ' ')));
665     mutt_format_s (dest, destlen, prefix, buf2);
666     break;
667
668   case 'y':
669     if (optional)
670       optional = hdr->env->x_label ? 1 : 0;
671
672     mutt_format_s (dest, destlen, prefix, NONULL (hdr->env->x_label));
673     break;
674
675   case 'Y':
676     if (hdr->env->x_label) {
677       i = 1;                    /* reduce reuse recycle */
678       htmp = NULL;
679       if (flags & M_FORMAT_TREE
680           && (hdr->thread->prev && hdr->thread->prev->message
681               && hdr->thread->prev->message->env->x_label))
682         htmp = hdr->thread->prev->message;
683       else if (flags & M_FORMAT_TREE
684                && (hdr->thread->parent && hdr->thread->parent->message
685                    && hdr->thread->parent->message->env->x_label))
686         htmp = hdr->thread->parent->message;
687       if (htmp && m_strcasecmp(hdr->env->x_label,
688                                    htmp->env->x_label) == 0)
689         i = 0;
690     }
691     else
692       i = 0;
693
694     if (optional)
695       optional = i;
696
697     if (i)
698       mutt_format_s (dest, destlen, prefix, NONULL (hdr->env->x_label));
699     else
700       mutt_format_s (dest, destlen, prefix, "");
701
702     break;
703
704   default:
705     snprintf (dest, destlen, "%%%s%c", prefix, op);
706     break;
707   }
708
709   if (optional)
710     mutt_FormatString (dest, destlen, ifstring, hdr_format_str,
711                        (unsigned long) hfi, flags);
712   else if (flags & M_FORMAT_OPTIONAL)
713     mutt_FormatString (dest, destlen, elsestring, hdr_format_str,
714                        (unsigned long) hfi, flags);
715
716   return (src);
717 #undef THREAD_NEW
718 #undef THREAD_OLD
719 }
720
721 void
722 _mutt_make_string (char *dest, ssize_t destlen, const char *s, CONTEXT * ctx,
723                    HEADER * hdr, format_flag flags)
724 {
725   struct hdr_format_info hfi;
726
727   hfi.hdr = hdr;
728   hfi.ctx = ctx;
729
730   mutt_FormatString (dest, destlen, s, hdr_format_str, (unsigned long) &hfi,
731                      flags);
732 }