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