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