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