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