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