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 <frm.h>
18 
19 char *show_field;   /* Show this header field instead of the default
20 			      `From: Subject:' pair. -f option */
21 int show_to;        /* Additionally display To: field. -l option */
22 int show_number;    /* Prefix each line with the message number. -n */
23 int frm_debug;
24 
25 
26 
27 /* Get the number of columns on the screen
28    First try an ioctl() call, not all shells set the COLUMNS environ.
29    If ioctl does not succeed on stdout, try it on /dev/tty, as we
30    may work via a pipe.
31 
32    This function was taken from mail/util.c. It should probably reside
33    in the library */
34 int
util_getcols(void)35 util_getcols (void)
36 {
37   struct winsize ws;
38 
39   ws.ws_col = ws.ws_row = 0;
40   if (ioctl (1, TIOCGWINSZ, (char *) &ws) < 0)
41     {
42       int fd = open ("/dev/tty", O_RDWR);
43       ioctl (fd, TIOCGWINSZ, (char *) &ws);
44       close (fd);
45     }
46   if (ws.ws_row == 0)
47     {
48       const char *columns = getenv ("COLUMNS");
49       if (columns)
50 	ws.ws_col = strtol (columns, NULL, 10);
51     }
52   return ws.ws_col;
53 }
54 
55 
56 /* Charset magic */
57 static char *output_charset = NULL;
58 
59 const char *
get_charset()60 get_charset ()
61 {
62   if (!output_charset)
63     {
64       char *tmp;
65 
66       /* Try to deduce the charset from LC_ALL or LANG variables */
67 
68       tmp = getenv ("LC_ALL");
69       if (!tmp)
70 	tmp = getenv ("LANG");
71 
72       if (tmp)
73 	{
74 	  struct mu_lc_all lc_all;
75 
76 	  if (mu_parse_lc_all (tmp, &lc_all, MU_LC_CSET) == 0)
77 	    output_charset = lc_all.charset;
78 	}
79 
80       if (!output_charset)
81 	output_charset = mu_strdup ("ASCII");
82     }
83   return output_charset;
84 }
85 
86 
87 /* BIDI support (will be moved to lib when it's ready) */
88 #ifdef HAVE_LIBFRIBIDI
89 
90 # ifdef HAVE_FRIBIDI_WCWIDTH
91 #  define mu_fribidi_wcwidth fribidi_wcwidth
92 # else
93 #  if defined(HAVE_WCHAR_H) && defined(HAVE_WCWIDTH)
94 #   include <wchar.h>
95 #   define mu_fribidi_wcwidth(c) wcwidth((wchar_t)c)
96 #  else
97 #   undef HAVE_LIBFRIBIDI
98 #  endif
99 # endif
100 #endif
101 
102 #ifdef HAVE_LIBFRIBIDI
103 
104 static int fb_charset_num = -1;
105 FriBidiChar *logical;
106 char *outstring;
107 size_t logical_size;
108 
109 void
alloc_logical(size_t size)110 alloc_logical (size_t size)
111 {
112   logical = mu_alloc (size * sizeof (logical[0]));
113   logical_size = size;
114   outstring = mu_alloc (size);
115 }
116 
117 void
puts_bidi(char * string)118 puts_bidi (char *string)
119 {
120   if (fb_charset_num == -1)
121     {
122       fb_charset_num = fribidi_parse_charset ((char*) get_charset ());
123       if (fb_charset_num && frm_debug)
124 	mu_error (_("fribidi failed to recognize charset `%s'"),
125 		  get_charset ());
126     }
127 
128   if (fb_charset_num == 0)
129     mu_printf ("%s\n", string);
130   else
131     {
132       FriBidiStrIndex len;
133       FriBidiParType base = FRIBIDI_TYPE_ON;
134       fribidi_boolean log2vis;
135 
136       static FriBidiChar *visual;
137       static size_t visual_size;
138 
139 
140       len = fribidi_charset_to_unicode (fb_charset_num,
141 					string, strlen (string),
142 					logical);
143 
144       if (len + 1 > visual_size)
145 	{
146 	  visual_size = len + 1;
147 	  visual = mu_realloc (visual, visual_size * sizeof *visual);
148 	}
149 
150       /* Create a bidi string. */
151       log2vis = fribidi_log2vis (logical, len, &base,
152 				 /* output */
153 				 visual, NULL, NULL, NULL);
154 
155       if (log2vis)
156 	{
157 	  FriBidiStrIndex idx, st;
158 
159 	  for (idx = 0; idx < len;)
160 	    {
161 	      FriBidiStrIndex wid, inlen;
162 
163 	      wid = 3 * logical_size;
164 	      st = idx;
165 
166 	      if (fb_charset_num != FRIBIDI_CHARSET_CAP_RTL)
167 		{
168 		  while (wid > 0 && idx < len)
169 		    wid -= mu_fribidi_wcwidth (visual[idx++]);
170 		}
171 	      else
172 		{
173 		  while (wid > 0 && idx < len)
174 		    {
175 		      wid--;
176 		      idx++;
177 		    }
178 		}
179 
180 	      if (wid < 0 && idx > st + 1)
181 		idx--;
182 	      inlen = idx - st;
183 
184 	      fribidi_unicode_to_charset (fb_charset_num,
185 					  visual + st, inlen,
186 					  outstring);
187 	      mu_printf ("%s", outstring);
188 	    }
189 	  mu_printf ("\n");
190 	}
191       else
192 	{
193 	  /* Print the string as is */
194 	  mu_printf ("%s\n", string);
195 	}
196     }
197 }
198 #else
199 # define alloc_logical(s)
200 # define puts_bidi(s) mu_printf ("%s\n", s)
201 #endif
202 
203 
204 /* Output functions */
205 
206 /* Number of columns in output:
207 
208      Maximum     4     message number, to, from, subject   -ln
209      Default     2     from, subject                       [none]
210      Minimum     1     FIELD                               -f FIELD
211 */
212 
213 static int numfields;      /* Number of output fields */
214 static int fieldwidth[4];  /* Field start positions */
215 static char *linebuf;      /* Output line buffer */
216 static size_t linemax;     /* Size of linebuf */
217 static size_t linepos;     /* Position in the output line buffer */
218 static int curfield;       /* Current output field */
219 static int nextstart;      /* Start position of the next field */
220 static int curcol;         /* Current output column */
221 
222 typedef void (*fmt_formatter) (const char *fmt, ...);
223 
224 static fmt_formatter format_field;
225 
226 void
print_line()227 print_line ()
228 {
229   if (linebuf)
230     {
231       puts_bidi (linebuf);
232       linebuf[0] = 0;
233       linepos = 0;
234       curcol = nextstart = 0;
235     }
236   else
237     mu_printf ("\n");
238   curfield = 0;
239 }
240 
241 void
format_field_simple(const char * fmt,...)242 format_field_simple (const char *fmt, ...)
243 {
244   va_list ap;
245   if (curfield++)
246     mu_printf ("\t");
247   va_start (ap, fmt);
248   mu_stream_vprintf (mu_strout, fmt, ap);
249   va_end (ap);
250 }
251 
252 void
format_field_align(const char * fmt,...)253 format_field_align (const char *fmt, ...)
254 {
255   size_t n, width;
256   va_list ap;
257 
258   va_start (ap, fmt);
259   if (nextstart != 0)
260     {
261       if (curcol >= nextstart)
262 	{
263 	  if (curfield == numfields - 1)
264 	    {
265 	      puts_bidi (linebuf);
266 	      linepos = 0;
267 	      mu_printf ("%*s", nextstart, "");
268 	    }
269 	  else
270 	    {
271 	      linebuf[linepos++] = ' ';
272 	      curcol++;
273 	    }
274 	}
275       else if (nextstart != curcol)
276 	{
277 	  /* align to field start */
278 	  n = snprintf (linebuf + linepos, linemax - linepos,
279 			"%*s", nextstart - curcol, "");
280 	  linepos += n;
281 	  curcol = nextstart;
282 	}
283     }
284 
285   n = vsnprintf (linebuf + linepos, linemax - linepos, fmt, ap);
286   va_end (ap);
287 
288   /* Compute output width */
289   if (curfield == numfields - 1)
290     {
291       for ( ; n > 0; n--)
292 	{
293 	  int c = linebuf[linepos + n];
294 	  linebuf[linepos + n] = 0;
295 	  width = mbswidth (linebuf + linepos, 0);
296 	  if (width <= fieldwidth[curfield])
297 	    break;
298 	  linebuf[linepos + n] = c;
299 	}
300     }
301   else
302     width = mbswidth (linebuf + linepos, 0);
303 
304   /* Increment counters */
305   linepos += n;
306   curcol += width;
307   nextstart += fieldwidth[curfield++];
308 }
309 
310 void
init_output(size_t s)311 init_output (size_t s)
312 {
313   int i;
314   size_t width = 0;
315 
316   if (s == 0)
317     {
318       format_field = format_field_simple;
319       return;
320     }
321 
322   format_field = format_field_align;
323 
324   /* Allocate the line buffer */
325   linemax = s * MB_LEN_MAX + 1;
326   linebuf = mu_alloc (linemax);
327   alloc_logical (s);
328 
329   /* Set up column widths */
330   if (show_number)
331     fieldwidth[numfields++] = 5;
332 
333   if (show_to)
334     fieldwidth[numfields++] = 20;
335 
336   if (show_field)
337     fieldwidth[numfields++] = 0;
338   else
339     {
340       fieldwidth[numfields++] = 20;
341       fieldwidth[numfields++] = 0;
342     }
343 
344   for (i = 0; i < numfields; i++)
345     width += fieldwidth[i];
346 
347   fieldwidth[numfields-1] = util_getcols () - width;
348 }
349 
350 
351 /*
352   FIXME: Generalize this function and move it
353   to `mailbox/locale.c'. Do the same with the one
354   from `from/from.c' and `mail/util.c'...
355 */
356 static char *
rfc2047_decode_wrapper(const char * buf,size_t buflen)357 rfc2047_decode_wrapper (const char *buf, size_t buflen)
358 {
359   int rc;
360   char *tmp;
361   const char *charset = get_charset ();
362 
363   if (strcmp (charset, "ASCII") == 0)
364     return mu_strdup (buf);
365 
366   rc = mu_rfc2047_decode (charset, buf, &tmp);
367   if (rc)
368     {
369       if (frm_debug)
370 	mu_error (_("cannot decode line `%s': %s"),
371 		  buf, mu_strerror (rc));
372       return mu_strdup (buf);
373     }
374 
375   return tmp;
376 }
377 
378 /* Retrieve the Personal Name from the header To: or From:  */
379 static int
get_personal(mu_header_t hdr,const char * field,char ** personal)380 get_personal (mu_header_t hdr, const char *field, char **personal)
381 {
382   char *hfield;
383   int status;
384 
385   status = mu_header_aget_value_unfold (hdr, field, &hfield);
386   if (status == 0)
387     {
388       mu_address_t address = NULL;
389       const char *s = NULL;
390 
391       mu_address_create (&address, hfield);
392 
393       mu_address_sget_personal (address, 1, &s);
394       if (s == NULL)
395 	s = hfield;
396       *personal = rfc2047_decode_wrapper (s, strlen (s));
397       mu_address_destroy (&address);
398     }
399   return status;
400 }
401 
402 /* Observer action is used to perform mailbox scanning. See the comment
403    to frm_scan below. */
404 
405 struct frm_action_closure
406 {
407   frm_select_t select_message;  /* Message selection function */
408   size_t msg_index;             /* Index (1-based) of the current
409 				   message */
410 };
411 
412 /* Observable action is being called on discovery of each message. */
413 /* FIXME: The format of the display is poorly done, please correct.  */
414 static int
action(mu_observer_t o,size_t type,void * data,void * action_data)415 action (mu_observer_t o, size_t type, void *data, void *action_data)
416 {
417   int status;
418   struct frm_action_closure *clos = action_data;
419 
420   switch (type)
421     {
422     case MU_EVT_MESSAGE_ADD:
423       {
424 	mu_mailbox_t mbox = mu_observer_get_owner (o);
425 	mu_message_t msg = NULL;
426 	mu_header_t hdr = NULL;
427 	mu_attribute_t attr = NULL;
428 
429 	clos->msg_index++;
430 
431 	mu_mailbox_get_message (mbox, clos->msg_index, &msg);
432 	mu_message_get_attribute (msg, &attr);
433 	mu_message_get_header (msg, &hdr);
434 
435 	if (!clos->select_message (clos->msg_index, msg))
436 	  break;
437 
438 	if (show_number)
439 	  format_field ("%4lu:", (u_long) clos->msg_index);
440 
441 	if (show_to)
442 	  {
443 	    char *hto;
444 	    status = get_personal (hdr, MU_HEADER_TO, &hto);
445 
446 	    if (status == 0)
447 	      {
448 		format_field ("(%s)", hto);
449 		free (hto);
450 	      }
451 	    else
452 	      format_field ("(none)");
453 	  }
454 
455 	if (show_field) /* FIXME: This should be also mu_rfc2047_decode. */
456 	  {
457 	    char *hfield;
458 	    status = mu_header_aget_value_unfold (hdr, show_field, &hfield);
459 	    if (status == 0)
460 	      {
461 		format_field ("%s", hfield);
462 		free (hfield);
463 	      }
464 	    else
465 	      format_field ("");
466 	  }
467 	else
468 	  {
469 	    char *tmp;
470 	    status = get_personal (hdr, MU_HEADER_FROM, &tmp);
471 	    if (status == 0)
472 	      {
473 		format_field ("%s", tmp);
474 		free (tmp);
475 	      }
476 	    else
477 	      format_field ("");
478 
479 	    status = mu_header_aget_value_unfold (hdr, MU_HEADER_SUBJECT,
480 						  &tmp);
481 	    if (status == 0)
482 	      {
483 		char *s = rfc2047_decode_wrapper (tmp, strlen (tmp));
484 		format_field ("%s", s);
485 		free (s);
486 		free (tmp);
487 	      }
488 	  }
489 	print_line ();
490 	break;
491       }
492 
493     case MU_EVT_MAILBOX_PROGRESS:
494       /* Noop.  */
495       break;
496     }
497   return 0;
498 }
499 
500 static void
frm_abort(mu_mailbox_t * mbox)501 frm_abort (mu_mailbox_t *mbox)
502 {
503   int status;
504 
505   if ((status = mu_mailbox_close (*mbox)) != 0)
506     {
507       mu_url_t url;
508       mu_mailbox_get_url (*mbox, &url);
509       mu_error (_("could not close mailbox `%s': %s"),
510 		mu_url_to_string (url), mu_strerror (status));
511       exit (3);
512     }
513 
514   mu_mailbox_destroy (mbox);
515   exit (3);
516 }
517 
518 /* Scan the mailbox MAILBOX_NAME using FUN as the selection function.
519    FUN takes as its argument message number and the message itself
520    (mu_message_t). It returns non-zero if that message is to be displayed
521    and zero otherwise.
522 
523    Upon finishing scanning, the function places total number of
524    the messages processed into the memory location pointed to by
525    TOTAL */
526 void
frm_scan(char * mailbox_name,frm_select_t fun,size_t * total)527 frm_scan (char *mailbox_name, frm_select_t fun, size_t *total)
528 {
529   mu_mailbox_t mbox;
530   int status;
531   mu_url_t url;
532 
533   status = mu_mailbox_create_default (&mbox, mailbox_name);
534   if (status != 0)
535     {
536       if (mailbox_name)
537 	mu_error (_("could not create mailbox `%s': %s"),
538 		  mailbox_name,  mu_strerror (status));
539       else
540 	mu_error (_("could not create default mailbox: %s"),
541 		  mu_strerror (status));
542       exit (3);
543     }
544 
545   if (frm_debug)
546     {
547       mu_debug_set_category_level (MU_DEBCAT_MAILBOX,
548                                    MU_DEBUG_LEVEL_UPTO (MU_DEBUG_PROT));
549     }
550 
551   mu_mailbox_get_url (mbox, &url);
552 
553   status = mu_mailbox_open (mbox, MU_STREAM_READ);
554   if (status == ENOENT)
555     *total = 0;
556   else if (status != 0)
557     {
558       mu_error (_("could not open mailbox `%s': %s"),
559 		mu_url_to_string (url), mu_strerror (status));
560       mu_mailbox_destroy (&mbox);
561       exit (3);
562     }
563   else
564     {
565       mu_observer_t observer;
566       mu_observable_t observable;
567       struct frm_action_closure closure = { fun, 0 };
568 
569       mu_observer_create (&observer, mbox);
570       mu_observer_set_action (observer, action, mbox);
571       mu_observer_set_action_data (observer, &closure, mbox);
572       mu_mailbox_get_observable (mbox, &observable);
573       mu_observable_attach (observable, MU_EVT_MESSAGE_ADD, observer);
574 
575       status = mu_mailbox_scan (mbox, 1, total);
576       if (status != 0)
577 	{
578 	  mu_error (_("could not scan mailbox `%s': %s"),
579 		    mu_url_to_string (url), mu_strerror (status));
580 	  frm_abort (&mbox);
581 	}
582 
583       mu_observable_detach (observable, observer);
584       mu_observer_destroy (&observer, mbox);
585 
586       if ((status = mu_mailbox_close (mbox)) != 0)
587 	{
588 	  mu_error (_("could not close mailbox `%s': %s"),
589 		    mu_url_to_string (url), mu_strerror (status));
590 	  exit (3);
591 	}
592     }
593   mu_mailbox_destroy (&mbox);
594 }
595