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