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