1 /**
2  * @file
3  * String processing routines to generate the mail index
4  *
5  * @authors
6  * Copyright (C) 1996-2000,2002,2007 Michael R. Elkins <me@mutt.org>
7  * Copyright (C) 2016 Richard Russon <rich@flatcap.org>
8  * Copyright (C) 2016 Ian Zimmerman <itz@primate.net>
9  * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
10  *
11  * @copyright
12  * This program is free software: you can redistribute it and/or modify it under
13  * the terms of the GNU General Public License as published by the Free Software
14  * Foundation, either version 2 of the License, or (at your option) any later
15  * version.
16  *
17  * This program is distributed in the hope that it will be useful, but WITHOUT
18  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
20  * details.
21  *
22  * You should have received a copy of the GNU General Public License along with
23  * this program.  If not, see <http://www.gnu.org/licenses/>.
24  */
25 
26 /**
27  * @page neo_hdrline String processing routines to generate the mail index
28  *
29  * String processing routines to generate the mail index
30  */
31 
32 #include "config.h"
33 #include <locale.h>
34 #include <stdbool.h>
35 #include <stdint.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <time.h>
40 #include "mutt/lib.h"
41 #include "address/lib.h"
42 #include "config/lib.h"
43 #include "email/lib.h"
44 #include "core/lib.h"
45 #include "alias/lib.h"
46 #include "gui/lib.h"
47 #include "hdrline.h"
48 #include "attach/lib.h"
49 #include "color/lib.h"
50 #include "ncrypt/lib.h"
51 #include "format_flags.h"
52 #include "hook.h"
53 #include "maillist.h"
54 #include "mutt_globals.h"
55 #include "mutt_thread.h"
56 #include "muttlib.h"
57 #include "mx.h"
58 #include "sort.h"
59 #include "subjectrx.h"
60 #ifdef USE_NOTMUCH
61 #include "notmuch/lib.h"
62 #endif
63 
64 /**
65  * struct HdrFormatInfo - Data passed to index_format_str()
66  */
67 struct HdrFormatInfo
68 {
69   struct Mailbox *mailbox;    ///< Current Mailbox
70   int msg_in_pager;           ///< Index of Email displayed in the Pager
71   struct Email *email;        ///< Current Email
72   const char *pager_progress; ///< String representing Pager postiion through Email
73 };
74 
75 /**
76  * enum FlagChars - Index into the `$flag_chars` variable ($flag_chars)
77  */
78 enum FlagChars
79 {
80   FLAG_CHAR_TAGGED,         ///< Character denoting a tagged email
81   FLAG_CHAR_IMPORTANT,      ///< Character denoting a important (flagged) email
82   FLAG_CHAR_DELETED,        ///< Character denoting a deleted email
83   FLAG_CHAR_DELETED_ATTACH, ///< Character denoting a deleted attachment
84   FLAG_CHAR_REPLIED, ///< Character denoting an email that has been replied to
85   FLAG_CHAR_OLD,     ///< Character denoting an email that has been read
86   FLAG_CHAR_NEW,     ///< Character denoting an unread email
87   FLAG_CHAR_OLD_THREAD, ///< Character denoting a thread of emails that has been read
88   FLAG_CHAR_NEW_THREAD, ///< Character denoting a thread containing at least one new email
89   FLAG_CHAR_SEMPTY, ///< Character denoting a read email, $index_format %S expando
90   FLAG_CHAR_ZEMPTY, ///< Character denoting a read email, $index_format %Z expando
91 };
92 
93 /**
94  * enum CryptChars - Index into the `$crypt_chars` variable ($crypt_chars)
95  */
96 enum CryptChars
97 {
98   FLAG_CHAR_CRYPT_GOOD_SIGN, ///< Character denoting a message signed with a verified key
99   FLAG_CHAR_CRYPT_ENCRYPTED, ///< Character denoting a message is PGP-encrypted
100   FLAG_CHAR_CRYPT_SIGNED,    ///< Character denoting a message is signed
101   FLAG_CHAR_CRYPT_CONTAINS_KEY, ///< Character denoting a message contains a PGP key
102   FLAG_CHAR_CRYPT_NO_CRYPTO, ///< Character denoting a message has no cryptography information
103 };
104 
105 /**
106  * enum FieldType - Header types
107  *
108  * Strings for printing headers
109  */
110 enum FieldType
111 {
112   DISP_TO,    ///< To: string
113   DISP_CC,    ///< Cc: string
114   DISP_BCC,   ///< Bcc: string
115   DISP_FROM,  ///< From: string
116   DISP_PLAIN, ///< Empty string
117   DISP_MAX,
118 };
119 
120 /**
121  * add_index_color - Insert a color marker into a string
122  * @param buf    Buffer to store marker
123  * @param buflen Buffer length
124  * @param flags  Flags, see #MuttFormatFlags
125  * @param color  Color, e.g. #MT_COLOR_MESSAGE
126  * @retval num Characters written
127  *
128  * The colors are stored as "magic" strings embedded in the text.
129  */
add_index_color(char * buf,size_t buflen,MuttFormatFlags flags,char color)130 static size_t add_index_color(char *buf, size_t buflen, MuttFormatFlags flags, char color)
131 {
132   /* only add color markers if we are operating on main index entries. */
133   if (!(flags & MUTT_FORMAT_INDEX))
134     return 0;
135 
136   /* this item is going to be passed to an external filter */
137   if (flags & MUTT_FORMAT_NOFILTER)
138     return 0;
139 
140   if (color == MT_COLOR_INDEX)
141   { /* buf might be uninitialized other cases */
142     const size_t len = mutt_str_len(buf);
143     buf += len;
144     buflen -= len;
145   }
146 
147   if (buflen <= 2)
148     return 0;
149 
150   buf[0] = MUTT_SPECIAL_INDEX;
151   buf[1] = color;
152   buf[2] = '\0';
153 
154   return 2;
155 }
156 
157 /**
158  * get_nth_wchar - Extract one char from a multi-byte table
159  * @param table  Multi-byte table
160  * @param index  Select this character
161  * @retval ptr String pointer to the character
162  *
163  * Extract one multi-byte character from a string table.
164  * If the index is invalid, then a space character will be returned.
165  * If the character selected is '\n' (Ctrl-M), then "" will be returned.
166  */
get_nth_wchar(const struct MbTable * table,int index)167 static const char *get_nth_wchar(const struct MbTable *table, int index)
168 {
169   if (!table || !table->chars || (index < 0) || (index >= table->len))
170     return " ";
171 
172   if (table->chars[index][0] == '\r')
173     return "";
174 
175   return table->chars[index];
176 }
177 
178 /**
179  * make_from_prefix - Create a prefix for an author field
180  * @param disp   Type of field
181  * @retval ptr Prefix string (do not free it)
182  *
183  * If $from_chars is set, pick an appropriate character from it.
184  * If not, use the default prefix: "To", "Cc", etc
185  */
make_from_prefix(enum FieldType disp)186 static const char *make_from_prefix(enum FieldType disp)
187 {
188   /* need 2 bytes at the end, one for the space, another for NUL */
189   static char padded[8];
190   static const char *long_prefixes[DISP_MAX] = {
191     [DISP_TO] = "To ", [DISP_CC] = "Cc ", [DISP_BCC] = "Bcc ",
192     [DISP_FROM] = "",  [DISP_PLAIN] = "",
193   };
194 
195   const struct MbTable *c_from_chars =
196       cs_subset_mbtable(NeoMutt->sub, "from_chars");
197 
198   if (!c_from_chars || !c_from_chars->chars || (c_from_chars->len == 0))
199     return long_prefixes[disp];
200 
201   const char *pchar = get_nth_wchar(c_from_chars, disp);
202   if (mutt_str_len(pchar) == 0)
203     return "";
204 
205   snprintf(padded, sizeof(padded), "%s ", pchar);
206   return padded;
207 }
208 
209 /**
210  * make_from - Generate a From: field (with optional prefix)
211  * @param env      Envelope of the email
212  * @param buf      Buffer to store the result
213  * @param buflen   Size of the buffer
214  * @param do_lists Should we check for mailing lists?
215  * @param flags    Format flags, see #MuttFormatFlags
216  *
217  * Generate the %F or %L field in $index_format.
218  * This is the author, or recipient of the email.
219  *
220  * The field can optionally be prefixed by a character from $from_chars.
221  * If $from_chars is not set, the prefix will be, "To", "Cc", etc
222  */
make_from(struct Envelope * env,char * buf,size_t buflen,bool do_lists,MuttFormatFlags flags)223 static void make_from(struct Envelope *env, char *buf, size_t buflen,
224                       bool do_lists, MuttFormatFlags flags)
225 {
226   if (!env || !buf)
227     return;
228 
229   bool me;
230   enum FieldType disp;
231   struct AddressList *name = NULL;
232 
233   me = mutt_addr_is_user(TAILQ_FIRST(&env->from));
234 
235   if (do_lists || me)
236   {
237     if (check_for_mailing_list(&env->to, make_from_prefix(DISP_TO), buf, buflen))
238       return;
239     if (check_for_mailing_list(&env->cc, make_from_prefix(DISP_CC), buf, buflen))
240       return;
241   }
242 
243   if (me && !TAILQ_EMPTY(&env->to))
244   {
245     disp = (flags & MUTT_FORMAT_PLAIN) ? DISP_PLAIN : DISP_TO;
246     name = &env->to;
247   }
248   else if (me && !TAILQ_EMPTY(&env->cc))
249   {
250     disp = DISP_CC;
251     name = &env->cc;
252   }
253   else if (me && !TAILQ_EMPTY(&env->bcc))
254   {
255     disp = DISP_BCC;
256     name = &env->bcc;
257   }
258   else if (!TAILQ_EMPTY(&env->from))
259   {
260     disp = DISP_FROM;
261     name = &env->from;
262   }
263   else
264   {
265     *buf = '\0';
266     return;
267   }
268 
269   snprintf(buf, buflen, "%s%s", make_from_prefix(disp), mutt_get_name(TAILQ_FIRST(name)));
270 }
271 
272 /**
273  * make_from_addr - Create a 'from' address for a reply email
274  * @param env      Envelope of current email
275  * @param buf      Buffer for the result
276  * @param buflen   Length of buffer
277  * @param do_lists If true, check for mailing lists
278  */
make_from_addr(struct Envelope * env,char * buf,size_t buflen,bool do_lists)279 static void make_from_addr(struct Envelope *env, char *buf, size_t buflen, bool do_lists)
280 {
281   if (!env || !buf)
282     return;
283 
284   bool me = mutt_addr_is_user(TAILQ_FIRST(&env->from));
285 
286   if (do_lists || me)
287   {
288     if (check_for_mailing_list_addr(&env->to, buf, buflen))
289       return;
290     if (check_for_mailing_list_addr(&env->cc, buf, buflen))
291       return;
292   }
293 
294   if (me && !TAILQ_EMPTY(&env->to))
295     snprintf(buf, buflen, "%s", TAILQ_FIRST(&env->to)->mailbox);
296   else if (me && !TAILQ_EMPTY(&env->cc))
297     snprintf(buf, buflen, "%s", TAILQ_FIRST(&env->cc)->mailbox);
298   else if (!TAILQ_EMPTY(&env->from))
299     mutt_str_copy(buf, TAILQ_FIRST(&env->from)->mailbox, buflen);
300   else
301     *buf = '\0';
302 }
303 
304 /**
305  * user_in_addr - Do any of the addresses refer to the user?
306  * @param al AddressList
307  * @retval true Any of the addresses match one of the user's addresses
308  */
user_in_addr(struct AddressList * al)309 static bool user_in_addr(struct AddressList *al)
310 {
311   struct Address *a = NULL;
312   TAILQ_FOREACH(a, al, entries)
313   if (mutt_addr_is_user(a))
314     return true;
315   return false;
316 }
317 
318 /**
319  * user_is_recipient - Is the user a recipient of the message
320  * @param e Email to test
321  * @retval 0 User is not in list
322  * @retval 1 User is unique recipient
323  * @retval 2 User is in the TO list
324  * @retval 3 User is in the CC list
325  * @retval 4 User is originator
326  * @retval 5 Sent to a subscribed mailinglist
327  * @retval 6 User is in the Reply-To list
328  */
user_is_recipient(struct Email * e)329 static int user_is_recipient(struct Email *e)
330 {
331   if (!e || !e->env)
332     return 0;
333 
334   struct Envelope *env = e->env;
335 
336   if (!e->recip_valid)
337   {
338     e->recip_valid = true;
339 
340     if (mutt_addr_is_user(TAILQ_FIRST(&env->from)))
341       e->recipient = 4;
342     else if (user_in_addr(&env->to))
343     {
344       if (TAILQ_NEXT(TAILQ_FIRST(&env->to), entries) || !TAILQ_EMPTY(&env->cc))
345         e->recipient = 2; /* non-unique recipient */
346       else
347         e->recipient = 1; /* unique recipient */
348     }
349     else if (user_in_addr(&env->cc))
350       e->recipient = 3;
351     else if (check_for_mailing_list(&env->to, NULL, NULL, 0))
352       e->recipient = 5;
353     else if (check_for_mailing_list(&env->cc, NULL, NULL, 0))
354       e->recipient = 5;
355     else if (user_in_addr(&env->reply_to))
356       e->recipient = 6;
357     else
358       e->recipient = 0;
359   }
360 
361   return e->recipient;
362 }
363 
364 /**
365  * thread_is_new - Does the email thread contain any new emails?
366  * @param e Email
367  * @retval true Thread contains new mail
368  */
thread_is_new(struct Email * e)369 static bool thread_is_new(struct Email *e)
370 {
371   return e->collapsed && (e->num_hidden > 1) && (mutt_thread_contains_unread(e) == 1);
372 }
373 
374 /**
375  * thread_is_old - Does the email thread contain any unread emails?
376  * @param e Email
377  * @retval true Thread contains unread mail
378  */
thread_is_old(struct Email * e)379 static bool thread_is_old(struct Email *e)
380 {
381   return e->collapsed && (e->num_hidden > 1) && (mutt_thread_contains_unread(e) == 2);
382 }
383 
384 /**
385  * index_format_str - Format a string for the index list - Implements ::format_t - @ingroup expando_api
386  *
387  * | Expando | Description
388  * |:--------|:-----------------------------------------------------------------
389  * | \%a     | Address of the author
390  * | \%A     | Reply-to address (if present; otherwise: address of author)
391  * | \%b     | Filename of the original message folder (think mailbox)
392  * | \%B     | The list to which the email was sent, or else the folder name (%b)
393  * | \%C     | Current message number
394  * | \%c     | Number of characters (bytes) in the body of the message
395  * | \%cr    | Number of characters (bytes) in the message, including header
396  * | \%D     | Date and time of message using `$date_format` and local timezone
397  * | \%d     | Date and time of message using `$date_format` and sender's timezone
398  * | \%e     | Current message number in thread
399  * | \%E     | Number of messages in current thread
400  * | \%Fp    | Like %F, but plain. No contextual formatting is applied to recipient name
401  * | \%F     | Author name, or recipient name if the message is from you
402  * | \%f     | Sender (address + real name), either From: or Return-Path:
403  * | \%Gx    | Individual message tag (e.g. notmuch tags/imap flags)
404  * | \%g     | Message tags (e.g. notmuch tags/imap flags)
405  * | \%H     | Spam attribute(s) of this message
406  * | \%I     | Initials of author
407  * | \%i     | Message-id of the current message
408  * | \%J     | Message tags (if present, tree unfolded, and != parent's tags)
409  * | \%K     | The list to which the email was sent (if any; otherwise: empty)
410  * | \%L     | Like %F, except 'lists' are displayed first
411  * | \%l     | Number of lines in the message
412  * | \%M     | Number of hidden messages if the thread is collapsed
413  * | \%m     | Total number of message in the mailbox
414  * | \%n     | Author's real name (or address if missing)
415  * | \%N     | Message score
416  * | \%O     | Like %L, except using address instead of name
417  * | \%P     | Progress indicator for the built-in pager (how much of the file has been displayed)
418  * | \%q     | Newsgroup name (if compiled with NNTP support)
419  * | \%R     | Comma separated list of Cc: recipients
420  * | \%r     | Comma separated list of To: recipients
421  * | \%S     | Single character status of the message (N/O/D/d/!/r/-)
422  * | \%s     | Subject of the message
423  * | \%t     | 'To:' field (recipients)
424  * | \%T     | The appropriate character from the `$to_chars` string
425  * | \%u     | User (login) name of the author
426  * | \%v     | First name of the author, or the recipient if the message is from you
427  * | \%W     | Name of organization of author ('Organization:' field)
428  * | \%x     | 'X-Comment-To:' field (if present and compiled with NNTP support)
429  * | \%X     | Number of MIME attachments
430  * | \%y     | 'X-Label:' field (if present)
431  * | \%Y     | 'X-Label:' field (if present, tree unfolded, and != parent's x-label)
432  * | \%zc    | Message crypto flags
433  * | \%zs    | Message status flags
434  * | \%zt    | Message tag flags
435  * | \%Z     | Combined message flags
436  * | \%\@name\@ | Insert and evaluate format-string from the matching "$index-format-hook" command
437  * | \%(fmt) | Date/time when the message was received
438  * | \%[fmt] | Message date/time converted to the local time zone
439  * | \%{fmt} | Message date/time converted to sender's time zone
440  */
index_format_str(char * buf,size_t buflen,size_t col,int cols,char op,const char * src,const char * prec,const char * if_str,const char * else_str,intptr_t data,MuttFormatFlags flags)441 static const char *index_format_str(char *buf, size_t buflen, size_t col, int cols,
442                                     char op, const char *src, const char *prec,
443                                     const char *if_str, const char *else_str,
444                                     intptr_t data, MuttFormatFlags flags)
445 {
446   struct HdrFormatInfo *hfi = (struct HdrFormatInfo *) data;
447   char fmt[128], tmp[1024];
448   char *p = NULL, *tags = NULL;
449   bool optional = (flags & MUTT_FORMAT_OPTIONAL);
450   const bool threads = mutt_using_threads();
451   int is_index = (flags & MUTT_FORMAT_INDEX);
452   size_t colorlen;
453 
454   struct Email *e = hfi->email;
455   size_t msg_in_pager = hfi->msg_in_pager;
456   struct Mailbox *m = hfi->mailbox;
457 
458   if (!e || !e->env)
459     return src;
460 
461   const struct Address *reply_to = TAILQ_FIRST(&e->env->reply_to);
462   const struct Address *from = TAILQ_FIRST(&e->env->from);
463   const struct Address *to = TAILQ_FIRST(&e->env->to);
464   const struct Address *cc = TAILQ_FIRST(&e->env->cc);
465 
466   const struct MbTable *c_crypt_chars =
467       cs_subset_mbtable(NeoMutt->sub, "crypt_chars");
468   const struct MbTable *c_flag_chars =
469       cs_subset_mbtable(NeoMutt->sub, "flag_chars");
470   const struct MbTable *c_to_chars =
471       cs_subset_mbtable(NeoMutt->sub, "to_chars");
472   const char *const c_date_format =
473       cs_subset_string(NeoMutt->sub, "date_format");
474 
475   buf[0] = '\0';
476   switch (op)
477   {
478     case 'A':
479     case 'I':
480       if (op == 'A')
481       {
482         if (reply_to && reply_to->mailbox)
483         {
484           colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_AUTHOR);
485           mutt_format_s(buf + colorlen, buflen - colorlen, prec,
486                         mutt_addr_for_display(reply_to));
487           add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
488           break;
489         }
490       }
491       else
492       {
493         if (mutt_mb_get_initials(mutt_get_name(from), tmp, sizeof(tmp)))
494         {
495           colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_AUTHOR);
496           mutt_format_s(buf + colorlen, buflen - colorlen, prec, tmp);
497           add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
498           break;
499         }
500       }
501       /* fallthrough */
502 
503     case 'a':
504       colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_AUTHOR);
505       if (from && from->mailbox)
506       {
507         mutt_format_s(buf + colorlen, buflen - colorlen, prec, mutt_addr_for_display(from));
508       }
509       else
510         mutt_format_s(buf + colorlen, buflen - colorlen, prec, "");
511       add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
512       break;
513 
514     case 'B':
515     case 'K':
516       if (first_mailing_list(buf, buflen, &e->env->to) ||
517           first_mailing_list(buf, buflen, &e->env->cc))
518       {
519         mutt_str_copy(tmp, buf, sizeof(tmp));
520         mutt_format_s(buf, buflen, prec, tmp);
521       }
522       else if (optional)
523       {
524         optional = false;
525       }
526       break;
527 
528     case 'b':
529       if (m)
530       {
531         p = strrchr(mailbox_path(m), '/');
532 #ifdef USE_NOTMUCH
533         if (m->type == MUTT_NOTMUCH)
534         {
535           char *rel_path = nm_email_get_folder_rel_db(m, e);
536           if (rel_path)
537             p = rel_path;
538         }
539 #endif
540 
541         if (p)
542           mutt_str_copy(buf, p + 1, buflen);
543         else
544           mutt_str_copy(buf, mailbox_path(m), buflen);
545       }
546       else
547         mutt_str_copy(buf, "(null)", buflen);
548       mutt_str_copy(tmp, buf, sizeof(tmp));
549       mutt_format_s(buf, buflen, prec, tmp);
550       break;
551 
552     case 'c':
553       colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_SIZE);
554       if (src[0] == 'r')
555       {
556         mutt_str_pretty_size(tmp, sizeof(tmp), email_size(e));
557         src++;
558       }
559       else
560       {
561         mutt_str_pretty_size(tmp, sizeof(tmp), e->body->length);
562       }
563       mutt_format_s(buf + colorlen, buflen - colorlen, prec, tmp);
564       add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
565       break;
566 
567     case 'C':
568       colorlen = add_index_color(fmt, sizeof(fmt), flags, MT_COLOR_INDEX_NUMBER);
569       snprintf(fmt + colorlen, sizeof(fmt) - colorlen, "%%%sd", prec);
570       add_index_color(fmt + colorlen, sizeof(fmt) - colorlen, flags, MT_COLOR_INDEX);
571       snprintf(buf, buflen, fmt, e->msgno + 1);
572       break;
573 
574     case 'd':
575     case 'D':
576     case '{':
577     case '[':
578     case '(':
579     case '<':
580       /* preprocess $date_format to handle %Z */
581       {
582         const char *cp = NULL;
583         time_t now;
584         int j = 0;
585 
586         if (optional && ((op == '[') || (op == '(')))
587         {
588           now = mutt_date_epoch();
589           struct tm tm = mutt_date_localtime(now);
590           now -= (op == '(') ? e->received : e->date_sent;
591 
592           char *is = (char *) prec;
593           bool invert = false;
594           if (*is == '>')
595           {
596             invert = true;
597             is++;
598           }
599 
600           while (*is && (*is != '?'))
601           {
602             int t = strtol(is, &is, 10);
603             /* semi-broken (assuming 30 days in all months) */
604             switch (*(is++))
605             {
606               case 'y':
607                 if (t > 1)
608                 {
609                   t--;
610                   t *= (60 * 60 * 24 * 365);
611                 }
612                 t += ((tm.tm_mon * 60 * 60 * 24 * 30) + (tm.tm_mday * 60 * 60 * 24) +
613                       (tm.tm_hour * 60 * 60) + (tm.tm_min * 60) + tm.tm_sec);
614                 break;
615 
616               case 'm':
617                 if (t > 1)
618                 {
619                   t--;
620                   t *= (60 * 60 * 24 * 30);
621                 }
622                 t += ((tm.tm_mday * 60 * 60 * 24) + (tm.tm_hour * 60 * 60) +
623                       (tm.tm_min * 60) + tm.tm_sec);
624                 break;
625 
626               case 'w':
627                 if (t > 1)
628                 {
629                   t--;
630                   t *= (60 * 60 * 24 * 7);
631                 }
632                 t += ((tm.tm_wday * 60 * 60 * 24) + (tm.tm_hour * 60 * 60) +
633                       (tm.tm_min * 60) + tm.tm_sec);
634                 break;
635 
636               case 'd':
637                 if (t > 1)
638                 {
639                   t--;
640                   t *= (60 * 60 * 24);
641                 }
642                 t += ((tm.tm_hour * 60 * 60) + (tm.tm_min * 60) + tm.tm_sec);
643                 break;
644 
645               case 'H':
646                 if (t > 1)
647                 {
648                   t--;
649                   t *= (60 * 60);
650                 }
651                 t += ((tm.tm_min * 60) + tm.tm_sec);
652                 break;
653 
654               case 'M':
655                 if (t > 1)
656                 {
657                   t--;
658                   t *= (60);
659                 }
660                 t += (tm.tm_sec);
661                 break;
662 
663               default:
664                 break;
665             }
666             j += t;
667           }
668 
669           if (j < 0)
670             j *= -1;
671 
672           if (((now > j) || (now < (-1 * j))) ^ invert)
673             optional = false;
674           break;
675         }
676 
677         p = buf;
678 
679         cp = ((op == 'd') || (op == 'D')) ? (NONULL(c_date_format)) : src;
680         bool do_locales;
681         if (*cp == '!')
682         {
683           do_locales = false;
684           cp++;
685         }
686         else
687           do_locales = true;
688 
689         size_t len = buflen - 1;
690         while ((len > 0) &&
691                ((((op == 'd') || (op == 'D')) && *cp) ||
692                 ((op == '{') && (*cp != '}')) || ((op == '[') && (*cp != ']')) ||
693                 ((op == '(') && (*cp != ')')) || ((op == '<') && (*cp != '>'))))
694         {
695           if (*cp == '%')
696           {
697             cp++;
698             if (((*cp == 'Z') || (*cp == 'z')) && ((op == 'd') || (op == '{')))
699             {
700               if (len >= 5)
701               {
702                 sprintf(p, "%c%02u%02u", e->zoccident ? '-' : '+', e->zhours, e->zminutes);
703                 p += 5;
704                 len -= 5;
705               }
706               else
707                 break; /* not enough space left */
708             }
709             else
710             {
711               if (len >= 2)
712               {
713                 *p++ = '%';
714                 *p++ = *cp;
715                 len -= 2;
716               }
717               else
718                 break; /* not enough space */
719             }
720             cp++;
721           }
722           else
723           {
724             *p++ = *cp++;
725             len--;
726           }
727         }
728         *p = '\0';
729 
730         struct tm tm;
731         if ((op == '[') || (op == 'D'))
732           tm = mutt_date_localtime(e->date_sent);
733         else if (op == '(')
734           tm = mutt_date_localtime(e->received);
735         else if (op == '<')
736         {
737           tm = mutt_date_localtime(MUTT_DATE_NOW);
738         }
739         else
740         {
741           /* restore sender's time zone */
742           now = e->date_sent;
743           if (e->zoccident)
744             now -= (e->zhours * 3600 + e->zminutes * 60);
745           else
746             now += (e->zhours * 3600 + e->zminutes * 60);
747           tm = mutt_date_gmtime(now);
748         }
749 
750         if (!do_locales)
751           setlocale(LC_TIME, "C");
752         strftime(tmp, sizeof(tmp), buf, &tm);
753         if (!do_locales)
754           setlocale(LC_TIME, "");
755 
756         colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_DATE);
757         mutt_format_s(buf + colorlen, buflen - colorlen, prec, tmp);
758         add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
759 
760         if ((len > 0) && (op != 'd') && (op != 'D')) /* Skip ending op */
761           src = cp + 1;
762         break;
763       }
764 
765     case 'e':
766       snprintf(fmt, sizeof(fmt), "%%%sd", prec);
767       snprintf(buf, buflen, fmt, mutt_messages_in_thread(m, e, MIT_POSITION));
768       break;
769 
770     case 'E':
771       if (!optional)
772       {
773         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
774         snprintf(buf, buflen, fmt, mutt_messages_in_thread(m, e, MIT_NUM_MESSAGES));
775       }
776       else if (mutt_messages_in_thread(m, e, MIT_NUM_MESSAGES) <= 1)
777         optional = false;
778       break;
779 
780     case 'f':
781       tmp[0] = '\0';
782       mutt_addrlist_write(&e->env->from, tmp, sizeof(tmp), true);
783       mutt_format_s(buf, buflen, prec, tmp);
784       break;
785 
786     case 'F':
787       if (!optional)
788       {
789         const bool is_plain = (src[0] == 'p');
790         colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_AUTHOR);
791         make_from(e->env, tmp, sizeof(tmp), false,
792                   (is_plain ? MUTT_FORMAT_PLAIN : MUTT_FORMAT_NO_FLAGS));
793         mutt_format_s(buf + colorlen, buflen - colorlen, prec, tmp);
794         add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
795 
796         if (is_plain)
797           src++;
798       }
799       else if (mutt_addr_is_user(from))
800       {
801         optional = false;
802       }
803       break;
804 
805     case 'g':
806       tags = driver_tags_get_transformed(&e->tags);
807       if (!optional)
808       {
809         colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_TAGS);
810         mutt_format_s(buf + colorlen, buflen - colorlen, prec, NONULL(tags));
811         add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
812       }
813       else if (!tags)
814         optional = false;
815       FREE(&tags);
816       break;
817 
818     case 'G':
819     {
820       char format[3];
821       char *tag = NULL;
822 
823       if (!optional)
824       {
825         format[0] = op;
826         format[1] = *src;
827         format[2] = '\0';
828 
829         tag = mutt_hash_find(TagFormats, format);
830         if (tag)
831         {
832           tags = driver_tags_get_transformed_for(&e->tags, tag);
833           colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_TAG);
834           mutt_format_s(buf + colorlen, buflen - colorlen, prec, NONULL(tags));
835           add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
836           FREE(&tags);
837         }
838         src++;
839       }
840       else
841       {
842         format[0] = op;
843         format[1] = *prec;
844         format[2] = '\0';
845 
846         tag = mutt_hash_find(TagFormats, format);
847         if (tag)
848         {
849           tags = driver_tags_get_transformed_for(&e->tags, tag);
850           if (!tags)
851             optional = false;
852           FREE(&tags);
853         }
854       }
855       break;
856     }
857 
858     case 'H':
859       /* (Hormel) spam score */
860       if (optional)
861         optional = !mutt_buffer_is_empty(&e->env->spam);
862 
863       mutt_format_s(buf, buflen, prec, mutt_buffer_string(&e->env->spam));
864       break;
865 
866     case 'i':
867       mutt_format_s(buf, buflen, prec, e->env->message_id ? e->env->message_id : "<no.id>");
868       break;
869 
870     case 'J':
871     {
872       bool have_tags = true;
873       tags = driver_tags_get_transformed(&e->tags);
874       if (tags)
875       {
876         if (flags & MUTT_FORMAT_TREE)
877         {
878           char *parent_tags = NULL;
879           if (e->thread->prev && e->thread->prev->message)
880           {
881             parent_tags = driver_tags_get_transformed(&e->thread->prev->message->tags);
882           }
883           if (!parent_tags && e->thread->parent && e->thread->parent->message)
884           {
885             parent_tags =
886                 driver_tags_get_transformed(&e->thread->parent->message->tags);
887           }
888           if (parent_tags && mutt_istr_equal(tags, parent_tags))
889             have_tags = false;
890           FREE(&parent_tags);
891         }
892       }
893       else
894         have_tags = false;
895 
896       if (optional)
897         optional = have_tags;
898 
899       colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_TAGS);
900       if (have_tags)
901         mutt_format_s(buf + colorlen, buflen - colorlen, prec, tags);
902       else
903         mutt_format_s(buf + colorlen, buflen - colorlen, prec, "");
904       add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
905       FREE(&tags);
906       break;
907     }
908 
909     case 'l':
910       if (!optional)
911       {
912         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
913         colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_SIZE);
914         snprintf(buf + colorlen, buflen - colorlen, fmt, (int) e->lines);
915         add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
916       }
917       else if (e->lines <= 0)
918         optional = false;
919       break;
920 
921     case 'L':
922       if (!optional)
923       {
924         colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_AUTHOR);
925         make_from(e->env, tmp, sizeof(tmp), true, flags);
926         mutt_format_s(buf + colorlen, buflen - colorlen, prec, tmp);
927         add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
928       }
929       else if (!check_for_mailing_list(&e->env->to, NULL, NULL, 0) &&
930                !check_for_mailing_list(&e->env->cc, NULL, NULL, 0))
931       {
932         optional = false;
933       }
934       break;
935 
936     case 'm':
937       if (m)
938       {
939         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
940         snprintf(buf, buflen, fmt, m->msg_count);
941       }
942       else
943         mutt_str_copy(buf, "(null)", buflen);
944       break;
945 
946     case 'n':
947       colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_AUTHOR);
948       mutt_format_s(buf + colorlen, buflen - colorlen, prec, mutt_get_name(from));
949       add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
950       break;
951 
952     case 'M':
953       snprintf(fmt, sizeof(fmt), "%%%sd", prec);
954       if (!optional)
955       {
956         colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_COLLAPSED);
957         if (threads && is_index && e->collapsed && (e->num_hidden > 1))
958         {
959           snprintf(buf + colorlen, buflen - colorlen, fmt, e->num_hidden);
960           add_index_color(buf, buflen - colorlen, flags, MT_COLOR_INDEX);
961         }
962         else if (is_index && threads)
963         {
964           mutt_format_s(buf + colorlen, buflen - colorlen, prec, " ");
965           add_index_color(buf, buflen - colorlen, flags, MT_COLOR_INDEX);
966         }
967         else
968           *buf = '\0';
969       }
970       else
971       {
972         if (!(threads && is_index && e->collapsed && (e->num_hidden > 1)))
973           optional = false;
974       }
975       break;
976 
977     case 'N':
978       if (!optional)
979       {
980         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
981         snprintf(buf, buflen, fmt, e->score);
982       }
983       else
984       {
985         if (e->score == 0)
986           optional = false;
987       }
988       break;
989 
990     case 'O':
991       if (!optional)
992       {
993         make_from_addr(e->env, tmp, sizeof(tmp), true);
994         const bool c_save_address =
995             cs_subset_bool(NeoMutt->sub, "save_address");
996         if (!c_save_address && (p = strpbrk(tmp, "%@")))
997           *p = '\0';
998         mutt_format_s(buf, buflen, prec, tmp);
999       }
1000       else if (!check_for_mailing_list_addr(&e->env->to, NULL, 0) &&
1001                !check_for_mailing_list_addr(&e->env->cc, NULL, 0))
1002       {
1003         optional = false;
1004       }
1005       break;
1006 
1007     case 'P':
1008       mutt_str_copy(buf, hfi->pager_progress, buflen);
1009       break;
1010 
1011 #ifdef USE_NNTP
1012     case 'q':
1013       mutt_format_s(buf, buflen, prec, e->env->newsgroups ? e->env->newsgroups : "");
1014       break;
1015 #endif
1016 
1017     case 'r':
1018       tmp[0] = '\0';
1019       mutt_addrlist_write(&e->env->to, tmp, sizeof(tmp), true);
1020       if (optional && (tmp[0] == '\0'))
1021         optional = false;
1022       mutt_format_s(buf, buflen, prec, tmp);
1023       break;
1024 
1025     case 'R':
1026       tmp[0] = '\0';
1027       mutt_addrlist_write(&e->env->cc, tmp, sizeof(tmp), true);
1028       if (optional && (tmp[0] == '\0'))
1029         optional = false;
1030       mutt_format_s(buf, buflen, prec, tmp);
1031       break;
1032 
1033     case 's':
1034     {
1035       subjrx_apply_mods(e->env);
1036       char *subj = NULL;
1037       if (e->env->disp_subj)
1038         subj = e->env->disp_subj;
1039       else
1040         subj = e->env->subject;
1041       if (flags & MUTT_FORMAT_TREE && !e->collapsed)
1042       {
1043         if (flags & MUTT_FORMAT_FORCESUBJ)
1044         {
1045           colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_SUBJECT);
1046           mutt_format_s(buf + colorlen, buflen - colorlen, "", NONULL(subj));
1047           add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
1048           snprintf(tmp, sizeof(tmp), "%s%s", e->tree, buf);
1049           mutt_format_s_tree(buf, buflen, prec, tmp);
1050         }
1051         else
1052           mutt_format_s_tree(buf, buflen, prec, e->tree);
1053       }
1054       else
1055       {
1056         colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_SUBJECT);
1057         mutt_format_s(buf + colorlen, buflen - colorlen, prec, NONULL(subj));
1058         add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
1059       }
1060       break;
1061     }
1062 
1063     case 'S':
1064     {
1065       const char *wch = NULL;
1066       if (e->deleted)
1067         wch = get_nth_wchar(c_flag_chars, FLAG_CHAR_DELETED);
1068       else if (e->attach_del)
1069         wch = get_nth_wchar(c_flag_chars, FLAG_CHAR_DELETED_ATTACH);
1070       else if (e->tagged)
1071         wch = get_nth_wchar(c_flag_chars, FLAG_CHAR_TAGGED);
1072       else if (e->flagged)
1073         wch = get_nth_wchar(c_flag_chars, FLAG_CHAR_IMPORTANT);
1074       else if (e->replied)
1075         wch = get_nth_wchar(c_flag_chars, FLAG_CHAR_REPLIED);
1076       else if (e->read && (msg_in_pager != e->msgno))
1077         wch = get_nth_wchar(c_flag_chars, FLAG_CHAR_SEMPTY);
1078       else if (e->old)
1079         wch = get_nth_wchar(c_flag_chars, FLAG_CHAR_OLD);
1080       else
1081         wch = get_nth_wchar(c_flag_chars, FLAG_CHAR_NEW);
1082 
1083       snprintf(tmp, sizeof(tmp), "%s", wch);
1084       colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_FLAGS);
1085       mutt_format_s(buf + colorlen, buflen - colorlen, prec, tmp);
1086       add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
1087       break;
1088     }
1089 
1090     case 't':
1091       tmp[0] = '\0';
1092       if (!check_for_mailing_list(&e->env->to, "To ", tmp, sizeof(tmp)) &&
1093           !check_for_mailing_list(&e->env->cc, "Cc ", tmp, sizeof(tmp)))
1094       {
1095         if (to)
1096           snprintf(tmp, sizeof(tmp), "To %s", mutt_get_name(to));
1097         else if (cc)
1098           snprintf(tmp, sizeof(tmp), "Cc %s", mutt_get_name(cc));
1099       }
1100       mutt_format_s(buf, buflen, prec, tmp);
1101       break;
1102 
1103     case 'T':
1104     {
1105       int i;
1106       snprintf(fmt, sizeof(fmt), "%%%ss", prec);
1107       snprintf(buf, buflen, fmt,
1108                (c_to_chars && ((i = user_is_recipient(e))) < c_to_chars->len) ?
1109                    c_to_chars->chars[i] :
1110                    " ");
1111       break;
1112     }
1113 
1114     case 'u':
1115       if (from && from->mailbox)
1116       {
1117         mutt_str_copy(tmp, mutt_addr_for_display(from), sizeof(tmp));
1118         p = strpbrk(tmp, "%@");
1119         if (p)
1120           *p = '\0';
1121       }
1122       else
1123         tmp[0] = '\0';
1124       mutt_format_s(buf, buflen, prec, tmp);
1125       break;
1126 
1127     case 'v':
1128       if (mutt_addr_is_user(from))
1129       {
1130         if (to)
1131           mutt_format_s(tmp, sizeof(tmp), prec, mutt_get_name(to));
1132         else if (cc)
1133           mutt_format_s(tmp, sizeof(tmp), prec, mutt_get_name(cc));
1134         else
1135           *tmp = '\0';
1136       }
1137       else
1138         mutt_format_s(tmp, sizeof(tmp), prec, mutt_get_name(from));
1139       p = strpbrk(tmp, " %@");
1140       if (p)
1141         *p = '\0';
1142       mutt_format_s(buf, buflen, prec, tmp);
1143       break;
1144 
1145     case 'W':
1146       if (!optional)
1147       {
1148         mutt_format_s(buf, buflen, prec, e->env->organization ? e->env->organization : "");
1149       }
1150       else if (!e->env->organization)
1151         optional = false;
1152       break;
1153 
1154 #ifdef USE_NNTP
1155     case 'x':
1156       if (!optional)
1157       {
1158         mutt_format_s(buf, buflen, prec, e->env->x_comment_to ? e->env->x_comment_to : "");
1159       }
1160       else if (!e->env->x_comment_to)
1161         optional = false;
1162       break;
1163 #endif
1164 
1165     case 'X':
1166     {
1167       struct Message *msg = mx_msg_open(m, e->msgno);
1168       if (msg)
1169       {
1170         int count = mutt_count_body_parts(m, e, msg->fp);
1171         mx_msg_close(m, &msg);
1172 
1173         /* The recursion allows messages without depth to return 0. */
1174         if (optional)
1175           optional = (count != 0);
1176 
1177         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
1178         snprintf(buf, buflen, fmt, count);
1179       }
1180       break;
1181     }
1182 
1183     case 'y':
1184       if (optional)
1185         optional = (e->env->x_label != NULL);
1186 
1187       colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_LABEL);
1188       mutt_format_s(buf + colorlen, buflen - colorlen, prec, NONULL(e->env->x_label));
1189       add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
1190       break;
1191 
1192     case 'Y':
1193     {
1194       bool label = true;
1195       if (e->env->x_label)
1196       {
1197         struct Email *e_tmp = NULL;
1198         if (flags & MUTT_FORMAT_TREE && (e->thread->prev && e->thread->prev->message &&
1199                                          e->thread->prev->message->env->x_label))
1200         {
1201           e_tmp = e->thread->prev->message;
1202         }
1203         else if (flags & MUTT_FORMAT_TREE &&
1204                  (e->thread->parent && e->thread->parent->message &&
1205                   e->thread->parent->message->env->x_label))
1206         {
1207           e_tmp = e->thread->parent->message;
1208         }
1209         if (e_tmp && mutt_istr_equal(e->env->x_label, e_tmp->env->x_label))
1210           label = false;
1211       }
1212       else
1213         label = false;
1214 
1215       if (optional)
1216         optional = label;
1217 
1218       colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_LABEL);
1219       if (label)
1220         mutt_format_s(buf + colorlen, buflen - colorlen, prec, NONULL(e->env->x_label));
1221       else
1222         mutt_format_s(buf + colorlen, buflen - colorlen, prec, "");
1223       add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
1224       break;
1225     }
1226 
1227     case 'z':
1228       if (src[0] == 's') /* status: deleted/new/old/replied */
1229       {
1230         const char *ch = NULL;
1231         if (e->deleted)
1232           ch = get_nth_wchar(c_flag_chars, FLAG_CHAR_DELETED);
1233         else if (e->attach_del)
1234           ch = get_nth_wchar(c_flag_chars, FLAG_CHAR_DELETED_ATTACH);
1235         else if (threads && thread_is_new(e))
1236           ch = get_nth_wchar(c_flag_chars, FLAG_CHAR_NEW_THREAD);
1237         else if (threads && thread_is_old(e))
1238           ch = get_nth_wchar(c_flag_chars, FLAG_CHAR_OLD_THREAD);
1239         else if (e->read && (msg_in_pager != e->msgno))
1240         {
1241           if (e->replied)
1242             ch = get_nth_wchar(c_flag_chars, FLAG_CHAR_REPLIED);
1243           else
1244             ch = get_nth_wchar(c_flag_chars, FLAG_CHAR_ZEMPTY);
1245         }
1246         else
1247         {
1248           if (e->old)
1249             ch = get_nth_wchar(c_flag_chars, FLAG_CHAR_OLD);
1250           else
1251             ch = get_nth_wchar(c_flag_chars, FLAG_CHAR_NEW);
1252         }
1253 
1254         snprintf(tmp, sizeof(tmp), "%s", ch);
1255         src++;
1256       }
1257       else if (src[0] == 'c') /* crypto */
1258       {
1259         const char *ch = "";
1260         if ((WithCrypto != 0) && (e->security & SEC_GOODSIGN))
1261           ch = get_nth_wchar(c_crypt_chars, FLAG_CHAR_CRYPT_GOOD_SIGN);
1262         else if ((WithCrypto != 0) && (e->security & SEC_ENCRYPT))
1263           ch = get_nth_wchar(c_crypt_chars, FLAG_CHAR_CRYPT_ENCRYPTED);
1264         else if ((WithCrypto != 0) && (e->security & SEC_SIGN))
1265           ch = get_nth_wchar(c_crypt_chars, FLAG_CHAR_CRYPT_SIGNED);
1266         else if (((WithCrypto & APPLICATION_PGP) != 0) && ((e->security & PGP_KEY) == PGP_KEY))
1267         {
1268           ch = get_nth_wchar(c_crypt_chars, FLAG_CHAR_CRYPT_CONTAINS_KEY);
1269         }
1270         else
1271           ch = get_nth_wchar(c_crypt_chars, FLAG_CHAR_CRYPT_NO_CRYPTO);
1272 
1273         snprintf(tmp, sizeof(tmp), "%s", ch);
1274         src++;
1275       }
1276       else if (src[0] == 't') /* tagged, flagged, recipient */
1277       {
1278         const char *ch = "";
1279         if (e->tagged)
1280           ch = get_nth_wchar(c_flag_chars, FLAG_CHAR_TAGGED);
1281         else if (e->flagged)
1282           ch = get_nth_wchar(c_flag_chars, FLAG_CHAR_IMPORTANT);
1283         else
1284           ch = get_nth_wchar(c_to_chars, user_is_recipient(e));
1285 
1286         snprintf(tmp, sizeof(tmp), "%s", ch);
1287         src++;
1288       }
1289       else /* fallthrough */
1290         break;
1291 
1292       colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_FLAGS);
1293       mutt_format_s(buf + colorlen, buflen - colorlen, prec, tmp);
1294       add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
1295       break;
1296 
1297     case 'Z':
1298     {
1299       /* New/Old for threads; replied; New/Old for messages */
1300       const char *first = NULL;
1301       if (threads && thread_is_new(e))
1302         first = get_nth_wchar(c_flag_chars, FLAG_CHAR_NEW_THREAD);
1303       else if (threads && thread_is_old(e))
1304         first = get_nth_wchar(c_flag_chars, FLAG_CHAR_OLD_THREAD);
1305       else if (e->read && (msg_in_pager != e->msgno))
1306       {
1307         if (e->replied)
1308           first = get_nth_wchar(c_flag_chars, FLAG_CHAR_REPLIED);
1309         else
1310           first = get_nth_wchar(c_flag_chars, FLAG_CHAR_ZEMPTY);
1311       }
1312       else
1313       {
1314         if (e->old)
1315           first = get_nth_wchar(c_flag_chars, FLAG_CHAR_OLD);
1316         else
1317           first = get_nth_wchar(c_flag_chars, FLAG_CHAR_NEW);
1318       }
1319 
1320       /* Marked for deletion; deleted attachments; crypto */
1321       const char *second = "";
1322       if (e->deleted)
1323         second = get_nth_wchar(c_flag_chars, FLAG_CHAR_DELETED);
1324       else if (e->attach_del)
1325         second = get_nth_wchar(c_flag_chars, FLAG_CHAR_DELETED_ATTACH);
1326       else if ((WithCrypto != 0) && (e->security & SEC_GOODSIGN))
1327         second = get_nth_wchar(c_crypt_chars, FLAG_CHAR_CRYPT_GOOD_SIGN);
1328       else if ((WithCrypto != 0) && (e->security & SEC_ENCRYPT))
1329         second = get_nth_wchar(c_crypt_chars, FLAG_CHAR_CRYPT_ENCRYPTED);
1330       else if ((WithCrypto != 0) && (e->security & SEC_SIGN))
1331         second = get_nth_wchar(c_crypt_chars, FLAG_CHAR_CRYPT_SIGNED);
1332       else if (((WithCrypto & APPLICATION_PGP) != 0) && (e->security & PGP_KEY))
1333         second = get_nth_wchar(c_crypt_chars, FLAG_CHAR_CRYPT_CONTAINS_KEY);
1334       else
1335         second = get_nth_wchar(c_crypt_chars, FLAG_CHAR_CRYPT_NO_CRYPTO);
1336 
1337       /* Tagged, flagged and recipient flag */
1338       const char *third = "";
1339       if (e->tagged)
1340         third = get_nth_wchar(c_flag_chars, FLAG_CHAR_TAGGED);
1341       else if (e->flagged)
1342         third = get_nth_wchar(c_flag_chars, FLAG_CHAR_IMPORTANT);
1343       else
1344         third = get_nth_wchar(c_to_chars, user_is_recipient(e));
1345 
1346       snprintf(tmp, sizeof(tmp), "%s%s%s", first, second, third);
1347     }
1348 
1349       colorlen = add_index_color(buf, buflen, flags, MT_COLOR_INDEX_FLAGS);
1350       mutt_format_s(buf + colorlen, buflen - colorlen, prec, tmp);
1351       add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
1352       break;
1353 
1354     case '@':
1355     {
1356       if (!m)
1357         break;
1358 
1359       const char *end = src;
1360       static unsigned char recurse = 0;
1361 
1362       while ((*end != '\0') && (*end != '@'))
1363         end++;
1364       if ((*end == '@') && (recurse < 20))
1365       {
1366         recurse++;
1367         mutt_strn_copy(tmp, src, end - src, sizeof(tmp));
1368         mutt_expando_format(tmp, sizeof(tmp), col, cols,
1369                             NONULL(mutt_idxfmt_hook(tmp, m, e)),
1370                             index_format_str, data, flags);
1371         mutt_format_s_x(buf, buflen, prec, tmp, true);
1372         recurse--;
1373 
1374         src = end + 1;
1375         break;
1376       }
1377     }
1378       /* fallthrough */
1379 
1380     default:
1381       snprintf(buf, buflen, "%%%s%c", prec, op);
1382       break;
1383   }
1384 
1385   if (optional)
1386   {
1387     mutt_expando_format(buf, buflen, col, cols, if_str, index_format_str, data, flags);
1388   }
1389   else if (flags & MUTT_FORMAT_OPTIONAL)
1390   {
1391     mutt_expando_format(buf, buflen, col, cols, else_str, index_format_str, data, flags);
1392   }
1393 
1394   /* We return the format string, unchanged */
1395   return src;
1396 }
1397 
1398 /**
1399  * mutt_make_string - Create formatted strings using mailbox expandos
1400  * @param buf      Buffer for the result
1401  * @param buflen   Buffer length
1402  * @param cols     Number of screen columns (OPTIONAL)
1403  * @param s        printf-line format string
1404  * @param m        Mailbox
1405  * @param inpgr    Message shown in the pager
1406  * @param e        Email
1407  * @param flags    Flags, see #MuttFormatFlags
1408  * @param progress Pager progress string
1409  */
mutt_make_string(char * buf,size_t buflen,int cols,const char * s,struct Mailbox * m,int inpgr,struct Email * e,MuttFormatFlags flags,const char * progress)1410 void mutt_make_string(char *buf, size_t buflen, int cols, const char *s,
1411                       struct Mailbox *m, int inpgr, struct Email *e,
1412                       MuttFormatFlags flags, const char *progress)
1413 {
1414   struct HdrFormatInfo hfi = { 0 };
1415 
1416   hfi.email = e;
1417   hfi.mailbox = m;
1418   hfi.msg_in_pager = inpgr;
1419   hfi.pager_progress = progress;
1420 
1421   mutt_expando_format(buf, buflen, 0, cols, s, index_format_str, (intptr_t) &hfi, flags);
1422 }
1423