/* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 1999-2021 Free Software Foundation, Inc. GNU Mailutils is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Mailutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Mailutils. If not, see . */ #include "mail.h" #include #define ALIGN_UNDEF -1 #define ALIGN_RIGHT 0 #define ALIGN_LEFT 1 struct header_call_args { msgset_t *mspec; mu_message_t msg; size_t cols_rest; char *buf; size_t size; }; struct header_segm { struct header_segm *next; int align; size_t width; void *data; char *(*get) (struct header_call_args *args, void *data); }; void header_ensure_space (struct header_call_args *args, size_t size) { if (size > args->size) { args->buf = mu_realloc (args->buf, size); args->size = size; } } static char * header_buf_string_len (struct header_call_args *args, const char *str, size_t len) { header_ensure_space (args, len + 1); memcpy (args->buf, str, len); args->buf[len] = 0; return args->buf; } static char * header_buf_string (struct header_call_args *args, const char *str) { if (!str) return header_buf_string_len (args, "", 0); return header_buf_string_len (args, str, strlen (str)); } static void format_pad (size_t n) { for (; n; n--) mu_stream_write (mu_strout, " ", 1, NULL); } static void format_headline (struct header_segm *seg, msgset_t *mspec, mu_message_t msg) { int screen_cols = util_screen_columns () - 2; int out_cols = 0; struct header_call_args args; args.mspec = mspec; args.msg = msg; args.buf = NULL; args.size = 0; for (; seg; seg = seg->next) { size_t width, len; size_t cols_rest = screen_cols - out_cols; char *p; args.cols_rest = cols_rest; p = seg->get (&args, seg->data); if (!p) p = ""; len = strlen (p); if (seg->width) width = seg->width; else width = len; if (width > cols_rest) width = cols_rest; if (len > width) len = width; if (seg->align == ALIGN_RIGHT) { format_pad (width - len); mu_printf ("%*.*s", (int) len, (int) len, p); } else { mu_printf ("%*.*s", (int) len, (int) len, p); format_pad (width - len); } out_cols += width; } mu_printf ("\n"); free (args.buf); } static void free_headline (struct header_segm *seg) { while (seg) { struct header_segm *next = seg->next; if (seg->data) free (seg->data); free (seg); seg = next; } } static char * hdr_text (struct header_call_args *args, void *data) { return data; } static char * hdr_cur (struct header_call_args *args, void *data) { if (is_current_message (msgset_msgno (args->mspec))) return (char*) data; return " "; } /* %a */ static char * hdr_attr (struct header_call_args *args, void *data) { mu_attribute_t attr; char cflag; mu_message_get_attribute (args->msg, &attr); if (mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_MBOXED)) cflag = 'M'; else if (mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_PRESERVED)) cflag = 'P'; else if (mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_SAVED)) cflag = '*'; else if (mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_TAGGED)) cflag = 'T'; else if (mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_SHOWN)) cflag = 'R'; else if (mu_attribute_is_recent (attr)) cflag = 'N'; else if (!mu_attribute_is_read (attr)) cflag = 'U'; else cflag = ' '; return header_buf_string_len (args, &cflag, 1); } /* %d and %D*/ static char * hdr_date (struct header_call_args *args, void *data) { char date[80]; mu_header_t hdr; char const *fmt = data ? data : "%a %b %e %H:%M"; mu_message_get_header (args->msg, &hdr); date[0] = 0; if (mailvar_is_true (mailvar_name_datefield) && mu_header_get_value (hdr, MU_HEADER_DATE, date, sizeof (date), NULL) == 0) { time_t t; if (mu_parse_date (date, &t, NULL) == 0) strftime (date, sizeof(date), fmt, localtime (&t)); else date[0] = 0; } if (date[0] == 0) { const char *p; struct tm tm; struct mu_timezone tz; mu_envelope_t env; mu_message_get_envelope (args->msg, &env); if (mu_envelope_sget_date (env, &p) == 0 && mu_scan_datetime (p, MU_DATETIME_FROM, &tm, &tz, NULL) == 0) strftime (date, sizeof(date), fmt, &tm); } return header_buf_string (args, date); } char * sender_string (mu_message_t msg) { char *from = NULL; if (mailvar_is_true (mailvar_name_fromfield)) { mu_header_t hdr; if (mu_message_get_header (msg, &hdr) == 0 && mu_header_aget_value_unfold (hdr, MU_HEADER_FROM, &from) == 0) { mu_address_t address = NULL; if (mu_address_create (&address, from) == 0) { char *name; const char *email; if (mu_address_sget_email (address, 1, &email) == 0 && email) { if (mailvar_is_true (mailvar_name_showto) && mail_is_my_name (email)) { char *tmp; if (mu_header_aget_value_unfold (hdr, MU_HEADER_TO, &tmp) == 0) { mu_address_t addr_to; if (mu_address_create (&addr_to, tmp) == 0) { mu_address_destroy (&address); address = addr_to; } free (tmp); } } } if ((mu_address_aget_personal (address, 1, &name) == 0 && name) || (mu_address_aget_email (address, 1, &name) == 0 && name)) { free (from); from = name; } mu_address_destroy (&address); } } util_rfc2047_decode (&from); } else { mu_envelope_t env = NULL; const char *sender = ""; if (mu_message_get_envelope (msg, &env) == 0) mu_envelope_sget_sender (env, &sender); from = mu_strdup (sender); } return from; } /* %f */ static char * hdr_from (struct header_call_args *args, void *data) { char *from = sender_string (args->msg); header_buf_string (args, from); free (from); return args->buf; } /* %l */ static char * hdr_lines (struct header_call_args *args, void *data) { size_t m_lines; char buf[UINTMAX_STRSIZE_BOUND]; mu_message_lines (args->msg, &m_lines); return header_buf_string (args, umaxtostr (m_lines, buf)); } /* %L */ static char * hdr_quick_lines (struct header_call_args *args, void *data) { size_t m_lines; char buf[UINTMAX_STRSIZE_BOUND]; int rc; const char *p; rc = mu_message_quick_lines (args->msg, &m_lines); if (rc == 0) p = umaxtostr (m_lines, buf); else p = "NA"; return header_buf_string (args, p); } /* %m */ static char * hdr_number (struct header_call_args *args, void *data) { char buf[UINTMAX_STRSIZE_BOUND]; return header_buf_string (args, umaxtostr (msgset_msgno (args->mspec), buf)); } /* %o */ static char * hdr_size (struct header_call_args *args, void *data) { size_t m_size; char buf[UINTMAX_STRSIZE_BOUND]; mu_message_size (args->msg, &m_size); return header_buf_string (args, umaxtostr (m_size, buf)); } /* %s */ static char * hdr_subject (struct header_call_args *args, void *data) { mu_header_t hdr; char *subj = NULL; mu_message_get_header (args->msg, &hdr); mu_header_aget_value_unfold (hdr, MU_HEADER_SUBJECT, &subj); util_rfc2047_decode (&subj); header_buf_string (args, subj); free (subj); return args->buf; } /* %S */ static char * hdr_q_subject (struct header_call_args *args, void *data) { mu_header_t hdr; char *subj = NULL; size_t len; if (args->cols_rest <= 2) return "\"\""; mu_message_get_header (args->msg, &hdr); mu_header_aget_value_unfold (hdr, MU_HEADER_SUBJECT, &subj); if (!subj) return ""; util_rfc2047_decode (&subj); len = strlen (subj); if (len + 2 > args->cols_rest) len = args->cols_rest - 2; header_ensure_space (args, len + 3); args->buf[0] = '"'; memcpy (args->buf + 1, subj, len); args->buf[len+1] = '"'; args->buf[len+2] = 0; free (subj); return args->buf; } static struct header_segm * new_header_segment (int align, size_t width, void *data, char *(*get) (struct header_call_args *, void *)) { struct header_segm *seg = mu_alloc (sizeof (*seg)); seg->next = NULL; seg->align = align; seg->width = width; seg->data = data; seg->get = get; return seg; } struct header_segm * compile_headline (const char *str) { struct header_segm *head = NULL, *tail = NULL; char *text; int align; size_t width; #define ALIGN_STRING (align == ALIGN_UNDEF ? ALIGN_LEFT : align) #define ALIGN_NUMBER (align == ALIGN_UNDEF ? ALIGN_RIGHT : align) #define ATTACH(p) \ do \ { \ if (!head) \ head = p; \ else \ tail->next = p; \ tail = p; \ } \ while (0) while (*str) { struct header_segm *seg; size_t len; char *p = strchr (str, '%'); if (!p) len = strlen (str); else len = p - str; if (len) { text = mu_alloc (len + 1); memcpy (text, str, len); text[len] = 0; seg = new_header_segment (ALIGN_LEFT, 0, text, hdr_text); ATTACH (seg); } if (!p) break; str = ++p; if (*str == '-') { str++; align = ALIGN_LEFT; } else if (*str == '+') { str++; align = ALIGN_RIGHT; } else align = ALIGN_UNDEF; if (mu_isdigit (*str)) width = strtoul (str, (char**)&str, 10); else width = 0; switch (*str++) { case '%': seg = new_header_segment (ALIGN_LEFT, 0, mu_strdup ("%"), hdr_text); break; case 'a': /* Message attributes. */ seg = new_header_segment (ALIGN_STRING, width, NULL, hdr_attr); break; /* FIXME: %c The score of the message. */ case 'd': /* Message date; See also 'D', below. */ seg = new_header_segment (ALIGN_STRING, width, NULL, hdr_date); break; /* FIXME: %e The indenting level in threaded mode. */ case 'f': /* Message sender */ seg = new_header_segment (ALIGN_STRING, width, NULL, hdr_from); break; /* FIXME: %i The message thread structure. */ case 'l': /* The number of lines of the message */ seg = new_header_segment (ALIGN_NUMBER, width, NULL, hdr_lines); break; case 'L': /* Same, but in quick mode */ seg = new_header_segment (ALIGN_NUMBER, width, NULL, hdr_quick_lines); break; case 'm': /* Message number */ seg = new_header_segment (ALIGN_NUMBER, width, NULL, hdr_number); break; case 'o': /* The number of octets (bytes) in the message */ seg = new_header_segment (ALIGN_NUMBER, width, NULL, hdr_size); break; case 's': /* Message subject (if any) */ seg = new_header_segment (ALIGN_STRING, width, NULL, hdr_subject); break; case 'S': /* Message subject (if any) in double quotes */ seg = new_header_segment (ALIGN_STRING, width, NULL, hdr_q_subject); break; /* FIXME: %t The position in threaded/sorted order. */ case '>': /* A `>' for the current message, otherwise ` ' */ seg = new_header_segment (ALIGN_STRING, width, mu_strdup (">"), hdr_cur); break; case '<': /* A `<' for the current message, otherwise ` ' */ seg = new_header_segment (ALIGN_STRING, width, mu_strdup ("<"), hdr_cur); break; case 'D': { int i; /* strftime conversion specifiers */ static char timespec[] = "aAbBcCdDeFGghHIjklmMnpPrRsStTuUVwWxXyYzZ+%"; /* Specifiers that can follow the E modifier */ static char espec[] = "cCxXyY"; /* Specifiers that can follow the O modifier */ static char ospec[] = "deHImMSuUVwWy"; if (*str == '{') { for (i = 1; str[i] && str[i] != '}'; i++) if (str[i] == '\\') i++; if (str[i]) { text = mu_alloc (i); memcpy (text, str + 1, i - 1); text[i - 1] = 0; mu_c_str_unescape_inplace (text, "\\{}", NULL); seg = new_header_segment (ALIGN_STRING, width, text, hdr_date); str += i + 1; break; } } else if (str[1] && ((*str == 'E' && strchr (espec, str[1])) || (*str == 'O' && strchr (ospec, str[1])))) { text = mu_alloc (4); text[0] = '%'; text[1] = *str++; text[2] = *str++; text[3] = 0; seg = new_header_segment (ALIGN_STRING, width, text, hdr_date); break; } else if (strchr (timespec, *str)) { text = mu_alloc (3); text[0] = '%'; text[1] = *str++; text[2] = 0; seg = new_header_segment (ALIGN_STRING, width, text, hdr_date); break; } } default: mu_error (_("unknown format specifier: %%%c"), str[-1]); len = str - p; text = mu_alloc (len); memcpy (text, p, len-1); text[len-1] = 0; seg = new_header_segment (ALIGN_STRING, width, text, hdr_text); } ATTACH (seg); } return head; #undef ALIGN_STRING #undef ALIGN_NUMBER #undef ATTACH } /* FIXME: Should it be part of struct mailvar_variable for "headline"? */ static struct header_segm *mail_header_line; void mail_compile_headline (char const *str) { free_headline (mail_header_line); mail_header_line = compile_headline (str); } /* * f[rom] [msglist] */ int mail_from0 (msgset_t *mspec, mu_message_t msg, void *data) { format_headline (mail_header_line, mspec, msg); return 0; } int mail_from (int argc, char **argv) { return util_foreach_msg (argc, argv, MSG_NODELETED|MSG_SILENT, mail_from0, NULL); }