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