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