1 /* GNU Mailutils -- a suite of utilities for electronic mail
2 Copyright (C) 1999-2021 Free Software Foundation, Inc.
3
4 GNU Mailutils is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3, or (at your option)
7 any later version.
8
9 GNU Mailutils is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
16
17 #include "mail.h"
18 #include <mu_umaxtostr.h>
19
20 #define ALIGN_UNDEF -1
21 #define ALIGN_RIGHT 0
22 #define ALIGN_LEFT 1
23
24 struct header_call_args
25 {
26 msgset_t *mspec;
27 mu_message_t msg;
28 size_t cols_rest;
29 char *buf;
30 size_t size;
31 };
32
33 struct header_segm
34 {
35 struct header_segm *next;
36 int align;
37 size_t width;
38 void *data;
39 char *(*get) (struct header_call_args *args, void *data);
40 };
41
42 void
header_ensure_space(struct header_call_args * args,size_t size)43 header_ensure_space (struct header_call_args *args, size_t size)
44 {
45 if (size > args->size)
46 {
47 args->buf = mu_realloc (args->buf, size);
48 args->size = size;
49 }
50 }
51
52 static char *
header_buf_string_len(struct header_call_args * args,const char * str,size_t len)53 header_buf_string_len (struct header_call_args *args, const char *str,
54 size_t len)
55 {
56 header_ensure_space (args, len + 1);
57 memcpy (args->buf, str, len);
58 args->buf[len] = 0;
59 return args->buf;
60 }
61
62 static char *
header_buf_string(struct header_call_args * args,const char * str)63 header_buf_string (struct header_call_args *args, const char *str)
64 {
65 if (!str)
66 return header_buf_string_len (args, "", 0);
67 return header_buf_string_len (args, str, strlen (str));
68 }
69
70 static void
format_pad(size_t n)71 format_pad (size_t n)
72 {
73 for (; n; n--)
74 mu_stream_write (mu_strout, " ", 1, NULL);
75 }
76
77 static void
format_headline(struct header_segm * seg,msgset_t * mspec,mu_message_t msg)78 format_headline (struct header_segm *seg, msgset_t *mspec, mu_message_t msg)
79 {
80 int screen_cols = util_screen_columns () - 2;
81 int out_cols = 0;
82 struct header_call_args args;
83
84 args.mspec = mspec;
85 args.msg = msg;
86 args.buf = NULL;
87 args.size = 0;
88
89 for (; seg; seg = seg->next)
90 {
91 size_t width, len;
92 size_t cols_rest = screen_cols - out_cols;
93 char *p;
94
95 args.cols_rest = cols_rest;
96 p = seg->get (&args, seg->data);
97
98 if (!p)
99 p = "";
100 len = strlen (p);
101
102 if (seg->width)
103 width = seg->width;
104 else
105 width = len;
106 if (width > cols_rest)
107 width = cols_rest;
108
109 if (len > width)
110 len = width;
111
112 if (seg->align == ALIGN_RIGHT)
113 {
114 format_pad (width - len);
115 mu_printf ("%*.*s", (int) len, (int) len, p);
116 }
117 else
118 {
119 mu_printf ("%*.*s", (int) len, (int) len, p);
120 format_pad (width - len);
121 }
122 out_cols += width;
123 }
124
125 mu_printf ("\n");
126 free (args.buf);
127 }
128
129 static void
free_headline(struct header_segm * seg)130 free_headline (struct header_segm *seg)
131 {
132 while (seg)
133 {
134 struct header_segm *next = seg->next;
135 if (seg->data)
136 free (seg->data);
137 free (seg);
138 seg = next;
139 }
140 }
141
142
143 static char *
hdr_text(struct header_call_args * args,void * data)144 hdr_text (struct header_call_args *args, void *data)
145 {
146 return data;
147 }
148
149 static char *
hdr_cur(struct header_call_args * args,void * data)150 hdr_cur (struct header_call_args *args, void *data)
151 {
152 if (is_current_message (msgset_msgno (args->mspec)))
153 return (char*) data;
154 return " ";
155 }
156
157 /* %a */
158 static char *
hdr_attr(struct header_call_args * args,void * data)159 hdr_attr (struct header_call_args *args, void *data)
160 {
161 mu_attribute_t attr;
162 char cflag;
163
164 mu_message_get_attribute (args->msg, &attr);
165
166 if (mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_MBOXED))
167 cflag = 'M';
168 else if (mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_PRESERVED))
169 cflag = 'P';
170 else if (mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_SAVED))
171 cflag = '*';
172 else if (mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_TAGGED))
173 cflag = 'T';
174 else if (mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_SHOWN))
175 cflag = 'R';
176 else if (mu_attribute_is_recent (attr))
177 cflag = 'N';
178 else if (!mu_attribute_is_read (attr))
179 cflag = 'U';
180 else
181 cflag = ' ';
182 return header_buf_string_len (args, &cflag, 1);
183 }
184
185 /* %d and %D*/
186 static char *
hdr_date(struct header_call_args * args,void * data)187 hdr_date (struct header_call_args *args, void *data)
188 {
189 char date[80];
190 mu_header_t hdr;
191 char const *fmt = data ? data : "%a %b %e %H:%M";
192
193 mu_message_get_header (args->msg, &hdr);
194
195 date[0] = 0;
196 if (mailvar_is_true (mailvar_name_datefield)
197 && mu_header_get_value (hdr, MU_HEADER_DATE,
198 date, sizeof (date), NULL) == 0)
199 {
200 time_t t;
201 if (mu_parse_date (date, &t, NULL) == 0)
202 strftime (date, sizeof(date), fmt, localtime (&t));
203 else
204 date[0] = 0;
205 }
206
207 if (date[0] == 0)
208 {
209 const char *p;
210 struct tm tm;
211 struct mu_timezone tz;
212 mu_envelope_t env;
213
214 mu_message_get_envelope (args->msg, &env);
215 if (mu_envelope_sget_date (env, &p) == 0
216 && mu_scan_datetime (p, MU_DATETIME_FROM, &tm, &tz, NULL) == 0)
217 strftime (date, sizeof(date), fmt, &tm);
218 }
219 return header_buf_string (args, date);
220 }
221
222 char *
sender_string(mu_message_t msg)223 sender_string (mu_message_t msg)
224 {
225 char *from = NULL;
226
227 if (mailvar_is_true (mailvar_name_fromfield))
228 {
229 mu_header_t hdr;
230
231 if (mu_message_get_header (msg, &hdr) == 0
232 && mu_header_aget_value_unfold (hdr, MU_HEADER_FROM, &from) == 0)
233 {
234 mu_address_t address = NULL;
235 if (mu_address_create (&address, from) == 0)
236 {
237 char *name;
238 const char *email;
239
240 if (mu_address_sget_email (address, 1, &email) == 0 && email)
241 {
242 if (mailvar_is_true (mailvar_name_showto)
243 && mail_is_my_name (email))
244 {
245 char *tmp;
246
247 if (mu_header_aget_value_unfold (hdr, MU_HEADER_TO,
248 &tmp) == 0)
249 {
250 mu_address_t addr_to;
251 if (mu_address_create (&addr_to, tmp) == 0)
252 {
253 mu_address_destroy (&address);
254 address = addr_to;
255 }
256 free (tmp);
257 }
258 }
259 }
260
261 if ((mu_address_aget_personal (address, 1, &name) == 0
262 && name)
263 || (mu_address_aget_email (address, 1, &name) == 0
264 && name))
265 {
266 free (from);
267 from = name;
268 }
269 mu_address_destroy (&address);
270 }
271 }
272 util_rfc2047_decode (&from);
273 }
274 else
275 {
276 mu_envelope_t env = NULL;
277 const char *sender = "";
278
279 if (mu_message_get_envelope (msg, &env) == 0)
280 mu_envelope_sget_sender (env, &sender);
281 from = mu_strdup (sender);
282 }
283 return from;
284 }
285
286 /* %f */
287 static char *
hdr_from(struct header_call_args * args,void * data)288 hdr_from (struct header_call_args *args, void *data)
289 {
290 char *from = sender_string (args->msg);
291 header_buf_string (args, from);
292 free (from);
293 return args->buf;
294 }
295
296 /* %l */
297 static char *
hdr_lines(struct header_call_args * args,void * data)298 hdr_lines (struct header_call_args *args, void *data)
299 {
300 size_t m_lines;
301 char buf[UINTMAX_STRSIZE_BOUND];
302
303 mu_message_lines (args->msg, &m_lines);
304
305 return header_buf_string (args, umaxtostr (m_lines, buf));
306 }
307
308 /* %L */
309 static char *
hdr_quick_lines(struct header_call_args * args,void * data)310 hdr_quick_lines (struct header_call_args *args, void *data)
311 {
312 size_t m_lines;
313 char buf[UINTMAX_STRSIZE_BOUND];
314 int rc;
315 const char *p;
316
317 rc = mu_message_quick_lines (args->msg, &m_lines);
318 if (rc == 0)
319 p = umaxtostr (m_lines, buf);
320 else
321 p = "NA";
322 return header_buf_string (args, p);
323 }
324
325 /* %m */
326 static char *
hdr_number(struct header_call_args * args,void * data)327 hdr_number (struct header_call_args *args, void *data)
328 {
329 char buf[UINTMAX_STRSIZE_BOUND];
330 return header_buf_string (args, umaxtostr (msgset_msgno (args->mspec), buf));
331 }
332
333 /* %o */
334 static char *
hdr_size(struct header_call_args * args,void * data)335 hdr_size (struct header_call_args *args, void *data)
336 {
337 size_t m_size;
338 char buf[UINTMAX_STRSIZE_BOUND];
339 mu_message_size (args->msg, &m_size);
340
341 return header_buf_string (args, umaxtostr (m_size, buf));
342 }
343
344 /* %s */
345 static char *
hdr_subject(struct header_call_args * args,void * data)346 hdr_subject (struct header_call_args *args, void *data)
347 {
348 mu_header_t hdr;
349 char *subj = NULL;
350
351 mu_message_get_header (args->msg, &hdr);
352 mu_header_aget_value_unfold (hdr, MU_HEADER_SUBJECT, &subj);
353 util_rfc2047_decode (&subj);
354
355 header_buf_string (args, subj);
356 free (subj);
357 return args->buf;
358 }
359
360 /* %S */
361 static char *
hdr_q_subject(struct header_call_args * args,void * data)362 hdr_q_subject (struct header_call_args *args, void *data)
363 {
364 mu_header_t hdr;
365 char *subj = NULL;
366 size_t len;
367
368 if (args->cols_rest <= 2)
369 return "\"\"";
370
371 mu_message_get_header (args->msg, &hdr);
372 mu_header_aget_value_unfold (hdr, MU_HEADER_SUBJECT, &subj);
373 if (!subj)
374 return "";
375 util_rfc2047_decode (&subj);
376
377 len = strlen (subj);
378 if (len + 2 > args->cols_rest)
379 len = args->cols_rest - 2;
380 header_ensure_space (args, len + 3);
381 args->buf[0] = '"';
382 memcpy (args->buf + 1, subj, len);
383 args->buf[len+1] = '"';
384 args->buf[len+2] = 0;
385 free (subj);
386 return args->buf;
387 }
388
389
390 static struct header_segm *
new_header_segment(int align,size_t width,void * data,char * (* get)(struct header_call_args *,void *))391 new_header_segment (int align, size_t width,
392 void *data,
393 char *(*get) (struct header_call_args *, void *))
394 {
395 struct header_segm *seg = mu_alloc (sizeof (*seg));
396 seg->next = NULL;
397 seg->align = align;
398 seg->width = width;
399 seg->data = data;
400 seg->get = get;
401 return seg;
402 }
403
404 struct header_segm *
compile_headline(const char * str)405 compile_headline (const char *str)
406 {
407 struct header_segm *head = NULL, *tail = NULL;
408 char *text;
409 int align;
410 size_t width;
411
412 #define ALIGN_STRING (align == ALIGN_UNDEF ? ALIGN_LEFT : align)
413 #define ALIGN_NUMBER (align == ALIGN_UNDEF ? ALIGN_RIGHT : align)
414 #define ATTACH(p) \
415 do \
416 { \
417 if (!head) \
418 head = p; \
419 else \
420 tail->next = p; \
421 tail = p; \
422 } \
423 while (0)
424
425 while (*str)
426 {
427 struct header_segm *seg;
428 size_t len;
429 char *p = strchr (str, '%');
430 if (!p)
431 len = strlen (str);
432 else
433 len = p - str;
434 if (len)
435 {
436 text = mu_alloc (len + 1);
437 memcpy (text, str, len);
438 text[len] = 0;
439 seg = new_header_segment (ALIGN_LEFT, 0, text, hdr_text);
440 ATTACH (seg);
441 }
442 if (!p)
443 break;
444
445 str = ++p;
446
447 if (*str == '-')
448 {
449 str++;
450 align = ALIGN_LEFT;
451 }
452 else if (*str == '+')
453 {
454 str++;
455 align = ALIGN_RIGHT;
456 }
457 else
458 align = ALIGN_UNDEF;
459
460 if (mu_isdigit (*str))
461 width = strtoul (str, (char**)&str, 10);
462 else
463 width = 0;
464
465 switch (*str++)
466 {
467 case '%':
468 seg = new_header_segment (ALIGN_LEFT, 0, mu_strdup ("%"), hdr_text);
469 break;
470
471 case 'a': /* Message attributes. */
472 seg = new_header_segment (ALIGN_STRING, width, NULL, hdr_attr);
473 break;
474
475 /* FIXME: %c The score of the message. */
476
477 case 'd': /* Message date; See also 'D', below. */
478 seg = new_header_segment (ALIGN_STRING, width, NULL, hdr_date);
479 break;
480
481 /* FIXME: %e The indenting level in threaded mode. */
482
483 case 'f': /* Message sender */
484 seg = new_header_segment (ALIGN_STRING, width, NULL, hdr_from);
485 break;
486
487 /* FIXME: %i The message thread structure. */
488
489 case 'l': /* The number of lines of the message */
490 seg = new_header_segment (ALIGN_NUMBER, width, NULL, hdr_lines);
491 break;
492
493 case 'L': /* Same, but in quick mode */
494 seg = new_header_segment (ALIGN_NUMBER, width, NULL,
495 hdr_quick_lines);
496 break;
497
498 case 'm': /* Message number */
499 seg = new_header_segment (ALIGN_NUMBER, width, NULL, hdr_number);
500 break;
501
502 case 'o': /* The number of octets (bytes) in the message */
503 seg = new_header_segment (ALIGN_NUMBER, width, NULL, hdr_size);
504 break;
505
506 case 's': /* Message subject (if any) */
507 seg = new_header_segment (ALIGN_STRING, width, NULL, hdr_subject);
508 break;
509
510 case 'S': /* Message subject (if any) in double quotes */
511 seg = new_header_segment (ALIGN_STRING, width, NULL, hdr_q_subject);
512 break;
513
514 /* FIXME: %t The position in threaded/sorted order. */
515
516 case '>': /* A `>' for the current message, otherwise ` ' */
517 seg = new_header_segment (ALIGN_STRING, width, mu_strdup (">"),
518 hdr_cur);
519 break;
520
521 case '<': /* A `<' for the current message, otherwise ` ' */
522 seg = new_header_segment (ALIGN_STRING, width, mu_strdup ("<"),
523 hdr_cur);
524 break;
525
526 case 'D':
527 {
528 int i;
529 /* strftime conversion specifiers */
530 static char timespec[] =
531 "aAbBcCdDeFGghHIjklmMnpPrRsStTuUVwWxXyYzZ+%";
532 /* Specifiers that can follow the E modifier */
533 static char espec[] =
534 "cCxXyY";
535 /* Specifiers that can follow the O modifier */
536 static char ospec[] =
537 "deHImMSuUVwWy";
538
539 if (*str == '{')
540 {
541 for (i = 1; str[i] && str[i] != '}'; i++)
542 if (str[i] == '\\')
543 i++;
544 if (str[i])
545 {
546 text = mu_alloc (i);
547 memcpy (text, str + 1, i - 1);
548 text[i - 1] = 0;
549 mu_c_str_unescape_inplace (text, "\\{}", NULL);
550 seg = new_header_segment (ALIGN_STRING, width, text,
551 hdr_date);
552 str += i + 1;
553 break;
554 }
555 }
556 else if (str[1] &&
557 ((*str == 'E' && strchr (espec, str[1]))
558 || (*str == 'O' && strchr (ospec, str[1]))))
559 {
560 text = mu_alloc (4);
561 text[0] = '%';
562 text[1] = *str++;
563 text[2] = *str++;
564 text[3] = 0;
565 seg = new_header_segment (ALIGN_STRING, width, text,
566 hdr_date);
567 break;
568 }
569 else if (strchr (timespec, *str))
570 {
571 text = mu_alloc (3);
572 text[0] = '%';
573 text[1] = *str++;
574 text[2] = 0;
575 seg = new_header_segment (ALIGN_STRING, width, text,
576 hdr_date);
577 break;
578 }
579 }
580
581 default:
582 mu_error (_("unknown format specifier: %%%c"), str[-1]);
583 len = str - p;
584 text = mu_alloc (len);
585 memcpy (text, p, len-1);
586 text[len-1] = 0;
587 seg = new_header_segment (ALIGN_STRING, width, text, hdr_text);
588 }
589 ATTACH (seg);
590 }
591 return head;
592 #undef ALIGN_STRING
593 #undef ALIGN_NUMBER
594 #undef ATTACH
595 }
596
597 /* FIXME: Should it be part of struct mailvar_variable for "headline"? */
598 static struct header_segm *mail_header_line;
599
600 void
mail_compile_headline(char const * str)601 mail_compile_headline (char const *str)
602 {
603 free_headline (mail_header_line);
604 mail_header_line = compile_headline (str);
605 }
606
607
608 /*
609 * f[rom] [msglist]
610 */
611
612 int
mail_from0(msgset_t * mspec,mu_message_t msg,void * data)613 mail_from0 (msgset_t *mspec, mu_message_t msg, void *data)
614 {
615 format_headline (mail_header_line, mspec, msg);
616 return 0;
617 }
618
619 int
mail_from(int argc,char ** argv)620 mail_from (int argc, char **argv)
621 {
622 return util_foreach_msg (argc, argv, MSG_NODELETED|MSG_SILENT,
623 mail_from0, NULL);
624 }
625
626