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