Implemented -t. Changed implementation of -Q.
Rewritten action() to correctly handle multibyte characters and to provide for eventual using the BIDI algorithm. (print_line,format_field,format_field_simple) (format_field_align): New functions (util_getcols): Measure /dev/tty if unable to open stdout. (to work correctly with piped output). (rfc2047_decode_wrapper): Cache the determined locale. (get_personal): Do not limit the returned string length. Do not attempt to decode the raw header text, since parse822 will skip non-ascii characters, decode the already obtained personal part instead.
Showing
1 changed file
with
258 additions
and
126 deletions
... | @@ -24,6 +24,7 @@ | ... | @@ -24,6 +24,7 @@ |
24 | #include <stdlib.h> | 24 | #include <stdlib.h> |
25 | #include <string.h> | 25 | #include <string.h> |
26 | #include <unistd.h> | 26 | #include <unistd.h> |
27 | #include <fcntl.h> | ||
27 | #include <sys/types.h> | 28 | #include <sys/types.h> |
28 | #ifdef HAVE_TERMIOS_H | 29 | #ifdef HAVE_TERMIOS_H |
29 | # include <termios.h> | 30 | # include <termios.h> |
... | @@ -31,6 +32,16 @@ | ... | @@ -31,6 +32,16 @@ |
31 | #include <sys/ioctl.h> | 32 | #include <sys/ioctl.h> |
32 | #include <sys/stat.h> | 33 | #include <sys/stat.h> |
33 | 34 | ||
35 | #ifdef HAVE_ICONV_H | ||
36 | # include <iconv.h> | ||
37 | #endif | ||
38 | #ifndef MB_LEN_MAX | ||
39 | # define MB_LEN_MAX 4 | ||
40 | #endif | ||
41 | |||
42 | #include <mbswidth.h> | ||
43 | #include <xalloc.h> | ||
44 | |||
34 | #include <mailutils/address.h> | 45 | #include <mailutils/address.h> |
35 | #include <mailutils/argp.h> | 46 | #include <mailutils/argp.h> |
36 | #include <mailutils/attribute.h> | 47 | #include <mailutils/attribute.h> |
... | @@ -50,16 +61,17 @@ | ... | @@ -50,16 +61,17 @@ |
50 | #include <mailutils/mutil.h> | 61 | #include <mailutils/mutil.h> |
51 | #include <mailutils/mime.h> | 62 | #include <mailutils/mime.h> |
52 | 63 | ||
53 | static char *show_field; | 64 | static char *show_field; /* Show this header field instead of the default |
54 | static int show_to; | 65 | `From: Subject:' pair. -f option */ |
55 | static int show_from = 1; | 66 | static int show_to; /* Additionally display To: field. -l option */ |
56 | static int show_subject = 1; | 67 | static int show_number; /* Prefix each line with the message number. -n */ |
57 | static int show_number; | 68 | static int show_summary; /* Summarize the number of messages by message |
58 | static int show_summary; | 69 | status in each mailbox. -S option */ |
59 | static int be_quiet; | 70 | static int be_quiet; /* Quiet mode. -q option. */ |
60 | static int align = 1; | 71 | static int show_query; /* Additional flag toggled by -q to display |
61 | static int show_query; | 72 | a one-line summary for each mailbox */ |
62 | static int dbug; | 73 | static int align = 0; /* Tidy mode. -t option. */ |
74 | static int dbug; /* Debug level. -d option.*/ | ||
63 | 75 | ||
64 | #define IS_READ 0x001 | 76 | #define IS_READ 0x001 |
65 | #define IS_OLD 0x010 | 77 | #define IS_OLD 0x010 |
... | @@ -179,10 +191,139 @@ static struct argp_option options[] = { | ... | @@ -179,10 +191,139 @@ static struct argp_option options[] = { |
179 | {"query", 'q', NULL, 0, N_("Print a message if the mailbox contains some unread mail"), 0}, | 191 | {"query", 'q', NULL, 0, N_("Print a message if the mailbox contains some unread mail"), 0}, |
180 | {"summary",'S', NULL, 0, N_("Print a summary of messages"), 0}, | 192 | {"summary",'S', NULL, 0, N_("Print a summary of messages"), 0}, |
181 | {"status", 's', N_("STATUS"), 0, attr_help, 0}, | 193 | {"status", 's', N_("STATUS"), 0, attr_help, 0}, |
182 | {"align", 't', NULL, 0, N_("Try to align"), 0}, | 194 | {"align", 't', NULL, 0, N_("Tidy mode: align subject lines"), 0}, |
183 | {0, 0, 0, 0} | 195 | {0, 0, 0, 0} |
184 | }; | 196 | }; |
185 | 197 | ||
198 | /* Number of columns in output: | ||
199 | |||
200 | Maximum 4 message number, to, from, subject -ln | ||
201 | Default 2 from, subject [none] | ||
202 | Minimum 1 FIELD -f FIELD | ||
203 | */ | ||
204 | |||
205 | static int numfields; /* Number of output fields */ | ||
206 | static int fieldwidth[4]; /* Field start positions */ | ||
207 | static char *linebuf; /* Output line buffer */ | ||
208 | static size_t linemax; /* Size of linebuf */ | ||
209 | static size_t linepos; /* Position in the output line buffer */ | ||
210 | static int curfield; /* Current output field */ | ||
211 | static int nextstart; /* Start position of the next field */ | ||
212 | static int curcol; /* Current output column */ | ||
213 | |||
214 | typedef void (*fmt_formatter) (const char *fmt, ...); | ||
215 | |||
216 | static fmt_formatter format_field; | ||
217 | |||
218 | void | ||
219 | print_line () | ||
220 | { | ||
221 | if (linebuf) | ||
222 | { | ||
223 | puts (linebuf); | ||
224 | linebuf[0] = 0; | ||
225 | linepos = 0; | ||
226 | curcol = nextstart = 0; | ||
227 | curfield = 0; | ||
228 | } | ||
229 | else | ||
230 | putchar ('\n'); | ||
231 | } | ||
232 | |||
233 | void | ||
234 | format_field_simple (const char *fmt, ...) | ||
235 | { | ||
236 | va_list ap; | ||
237 | va_start (ap, fmt); | ||
238 | vprintf (fmt, ap); | ||
239 | putchar (' '); | ||
240 | va_end (ap); | ||
241 | } | ||
242 | |||
243 | void | ||
244 | format_field_align (const char *fmt, ...) | ||
245 | { | ||
246 | size_t n, width; | ||
247 | va_list ap; | ||
248 | |||
249 | va_start (ap, fmt); | ||
250 | if (nextstart != 0) | ||
251 | { | ||
252 | if (curcol >= nextstart) | ||
253 | { | ||
254 | if (curfield == numfields - 1) | ||
255 | { | ||
256 | puts (linebuf); | ||
257 | linepos = 0; | ||
258 | printf ("%*s", nextstart, ""); | ||
259 | } | ||
260 | else | ||
261 | { | ||
262 | linebuf[linepos++] = ' '; | ||
263 | curcol++; | ||
264 | } | ||
265 | } | ||
266 | else if (nextstart != curcol) | ||
267 | { | ||
268 | /* align to field start */ | ||
269 | n = snprintf (linebuf + linepos, linemax - linepos, | ||
270 | "%*s", nextstart - curcol, ""); | ||
271 | linepos += n; | ||
272 | curcol = nextstart; | ||
273 | } | ||
274 | } | ||
275 | |||
276 | n = vsnprintf (linebuf + linepos, linemax - linepos, fmt, ap); | ||
277 | va_end (ap); | ||
278 | |||
279 | /* Compute output width */ | ||
280 | if (curfield == numfields - 1) | ||
281 | { | ||
282 | for ( ; n > 0; n--) | ||
283 | { | ||
284 | int c = linebuf[linepos + n]; | ||
285 | linebuf[linepos + n] = 0; | ||
286 | width = mbswidth (linebuf + linepos, 0); | ||
287 | if (width <= fieldwidth[curfield]) | ||
288 | break; | ||
289 | linebuf[linepos + n] = c; | ||
290 | } | ||
291 | } | ||
292 | else | ||
293 | width = mbswidth (linebuf + linepos, 0); | ||
294 | |||
295 | /* Increment counters */ | ||
296 | linepos += n; | ||
297 | curcol += width; | ||
298 | nextstart += fieldwidth[curfield++]; | ||
299 | } | ||
300 | |||
301 | /* | ||
302 | * Get the number of columns on the screen | ||
303 | * First try an ioctl() call not all shells set the COLUMNS environ. | ||
304 | * This function was taken from mail/util.c. | ||
305 | */ | ||
306 | int | ||
307 | util_getcols (void) | ||
308 | { | ||
309 | struct winsize ws; | ||
310 | |||
311 | ws.ws_col = ws.ws_row = 0; | ||
312 | if (ioctl (1, TIOCGWINSZ, (char *) &ws) < 0) | ||
313 | { | ||
314 | int fd = open ("/dev/tty", O_RDWR); | ||
315 | ioctl (fd, TIOCGWINSZ, (char *) &ws); | ||
316 | close (fd); | ||
317 | } | ||
318 | if (ws.ws_row == 0) | ||
319 | { | ||
320 | const char *columns = getenv ("COLUMNS"); | ||
321 | if (columns) | ||
322 | ws.ws_col = strtol (columns, NULL, 10); | ||
323 | } | ||
324 | return ws.ws_col; | ||
325 | } | ||
326 | |||
186 | static error_t | 327 | static error_t |
187 | parse_opt (int key, char *arg, struct argp_state *state) | 328 | parse_opt (int key, char *arg, struct argp_state *state) |
188 | { | 329 | { |
... | @@ -194,8 +335,6 @@ parse_opt (int key, char *arg, struct argp_state *state) | ... | @@ -194,8 +335,6 @@ parse_opt (int key, char *arg, struct argp_state *state) |
194 | 335 | ||
195 | case 'f': | 336 | case 'f': |
196 | show_field = arg; | 337 | show_field = arg; |
197 | show_from = 0; | ||
198 | show_subject = 0; | ||
199 | align = 0; | 338 | align = 0; |
200 | break; | 339 | break; |
201 | 340 | ||
... | @@ -210,11 +349,6 @@ parse_opt (int key, char *arg, struct argp_state *state) | ... | @@ -210,11 +349,6 @@ parse_opt (int key, char *arg, struct argp_state *state) |
210 | case 'Q': | 349 | case 'Q': |
211 | /* Very silent. */ | 350 | /* Very silent. */ |
212 | be_quiet += 2; | 351 | be_quiet += 2; |
213 | if (freopen ("/dev/null", "w", stdout) == NULL) | ||
214 | { | ||
215 | perror (_("Cannot be very quiet")); | ||
216 | exit (3); | ||
217 | } | ||
218 | break; | 352 | break; |
219 | 353 | ||
220 | case 'q': | 354 | case 'q': |
... | @@ -233,6 +367,42 @@ parse_opt (int key, char *arg, struct argp_state *state) | ... | @@ -233,6 +367,42 @@ parse_opt (int key, char *arg, struct argp_state *state) |
233 | case 't': | 367 | case 't': |
234 | align = 1; | 368 | align = 1; |
235 | break; | 369 | break; |
370 | |||
371 | case ARGP_KEY_FINI: | ||
372 | if (align && (linemax = util_getcols ())) | ||
373 | { | ||
374 | int i; | ||
375 | size_t width = 0; | ||
376 | |||
377 | format_field = format_field_align; | ||
378 | |||
379 | /* Allocate the line buffer */ | ||
380 | linemax = linemax * MB_LEN_MAX + 1; | ||
381 | linebuf = xmalloc (linemax); | ||
382 | |||
383 | /* Set up column widths */ | ||
384 | if (show_number) | ||
385 | fieldwidth[numfields++] = 5; | ||
386 | |||
387 | if (show_to) | ||
388 | fieldwidth[numfields++] = 20; | ||
389 | |||
390 | if (show_field) | ||
391 | fieldwidth[numfields++] = 0; | ||
392 | else | ||
393 | { | ||
394 | fieldwidth[numfields++] = 20; | ||
395 | fieldwidth[numfields++] = 0; | ||
396 | } | ||
397 | |||
398 | for (i = 0; i < numfields; i++) | ||
399 | width += fieldwidth[i]; | ||
400 | |||
401 | fieldwidth[numfields-1] = util_getcols () - width; | ||
402 | } | ||
403 | else | ||
404 | format_field = format_field_simple; | ||
405 | break; | ||
236 | 406 | ||
237 | default: | 407 | default: |
238 | return ARGP_ERR_UNKNOWN; | 408 | return ARGP_ERR_UNKNOWN; |
... | @@ -267,36 +437,43 @@ static const char *frm_argp_capa[] = { | ... | @@ -267,36 +437,43 @@ static const char *frm_argp_capa[] = { |
267 | static char * | 437 | static char * |
268 | rfc2047_decode_wrapper (char *buf, size_t buflen) | 438 | rfc2047_decode_wrapper (char *buf, size_t buflen) |
269 | { | 439 | { |
270 | char locale[32]; | ||
271 | char *charset = NULL; | ||
272 | char *tmp; | ||
273 | int rc; | 440 | int rc; |
441 | char *tmp; | ||
442 | static char *charset = NULL; | ||
274 | 443 | ||
275 | memset (locale, 0, sizeof (locale)); | 444 | if (!charset) |
445 | { | ||
446 | char locale[32]; | ||
276 | 447 | ||
277 | /* Try to deduce the charset from LC_ALL or LANG variables */ | 448 | memset (locale, 0, sizeof (locale)); |
278 | 449 | ||
279 | tmp = getenv ("LC_ALL"); | 450 | /* Try to deduce the charset from LC_ALL or LANG variables */ |
280 | if (!tmp) | ||
281 | tmp = getenv ("LANG"); | ||
282 | 451 | ||
283 | if (tmp) | 452 | tmp = getenv ("LC_ALL"); |
284 | { | 453 | if (!tmp) |
285 | char *sp = NULL; | 454 | tmp = getenv ("LANG"); |
286 | char *lang; | ||
287 | char *terr; | ||
288 | 455 | ||
289 | strncpy (locale, tmp, sizeof (locale) - 1); | 456 | if (tmp) |
457 | { | ||
458 | char *sp = NULL; | ||
459 | char *lang; | ||
460 | char *terr; | ||
290 | 461 | ||
291 | lang = strtok_r (locale, "_", &sp); | 462 | strncpy (locale, tmp, sizeof (locale) - 1); |
292 | terr = strtok_r (NULL, ".", &sp); | 463 | |
293 | charset = strtok_r (NULL, "@", &sp); | 464 | lang = strtok_r (locale, "_", &sp); |
465 | terr = strtok_r (NULL, ".", &sp); | ||
466 | charset = strtok_r (NULL, "@", &sp); | ||
467 | |||
468 | if (!charset) | ||
469 | charset = mu_charset_lookup (lang, terr); | ||
294 | 470 | ||
295 | if (!charset) | 471 | if (!charset) |
296 | charset = mu_charset_lookup (lang, terr); | 472 | charset = "ASCII"; |
473 | } | ||
297 | } | 474 | } |
298 | 475 | ||
299 | if (!charset) | 476 | if (strcmp (charset, "ASCII") == 0) |
300 | return strdup (buf); | 477 | return strdup (buf); |
301 | 478 | ||
302 | rc = rfc2047_decode (charset, buf, &tmp); | 479 | rc = rfc2047_decode (charset, buf, &tmp); |
... | @@ -313,69 +490,46 @@ rfc2047_decode_wrapper (char *buf, size_t buflen) | ... | @@ -313,69 +490,46 @@ rfc2047_decode_wrapper (char *buf, size_t buflen) |
313 | 490 | ||
314 | /* Retrieve the Personal Name from the header To: or From: */ | 491 | /* Retrieve the Personal Name from the header To: or From: */ |
315 | static int | 492 | static int |
316 | get_personal (header_t hdr, const char *field, char *personal, size_t buflen) | 493 | get_personal (header_t hdr, const char *field, char **personal) |
317 | { | 494 | { |
318 | char hfield[512]; | 495 | char *hfield; |
319 | int status; | 496 | int status; |
320 | 497 | ||
321 | /* Empty string. */ | 498 | status = header_aget_value_unfold (hdr, field, &hfield); |
322 | *hfield = '\0'; | ||
323 | |||
324 | status = header_get_value_unfold (hdr, field, hfield, sizeof (hfield), NULL); | ||
325 | if (status == 0) | 499 | if (status == 0) |
326 | { | 500 | { |
327 | address_t address = NULL; | 501 | address_t address = NULL; |
328 | size_t len = 0; | 502 | char *s; |
329 | 503 | ||
330 | char *s = rfc2047_decode_wrapper (hfield, strlen (hfield)); | 504 | address_create (&address, hfield); |
331 | address_create (&address, s); | ||
332 | free (s); | ||
333 | 505 | ||
334 | address_get_personal (address, 1, personal, buflen, &len); | 506 | address_aget_personal (address, 1, &s); |
335 | address_destroy (&address); | 507 | address_destroy (&address); |
508 | if (s == NULL) | ||
509 | s = hfield; | ||
510 | else | ||
511 | free (hfield); | ||
336 | 512 | ||
337 | if (len == 0) | 513 | *personal = rfc2047_decode_wrapper (s, strlen (s)); |
338 | strncpy (personal, hfield, buflen)[buflen - 1] = '\0'; | 514 | free (s); |
339 | } | 515 | } |
340 | return status; | 516 | return status; |
341 | } | 517 | } |
342 | 518 | ||
343 | static struct { | 519 | static struct |
520 | { | ||
344 | size_t index; | 521 | size_t index; |
345 | size_t new; | 522 | size_t new; |
346 | size_t read; | 523 | size_t read; |
347 | size_t unread; | 524 | size_t unread; |
348 | } counter; | 525 | } counter; |
349 | 526 | ||
350 | /* | ||
351 | * Get the number of columns on the screen | ||
352 | * First try an ioctl() call not all shells set the COLUMNS environ. | ||
353 | * This function was taken from mail/util.c. | ||
354 | */ | ||
355 | int | ||
356 | util_getcols (void) | ||
357 | { | ||
358 | struct winsize ws; | ||
359 | |||
360 | ws.ws_col = ws.ws_row = 0; | ||
361 | if ((ioctl(1, TIOCGWINSZ, (char *) &ws) < 0) || ws.ws_row == 0) | ||
362 | { | ||
363 | const char *columns = getenv ("COLUMNS"); | ||
364 | if (columns) | ||
365 | ws.ws_col = strtol (columns, NULL, 10); | ||
366 | } | ||
367 | |||
368 | /* FIXME: Should we exit()/abort() if col <= 0 ? */ | ||
369 | return ws.ws_col; | ||
370 | } | ||
371 | |||
372 | /* Observable action is being called on discovery of each message. */ | 527 | /* Observable action is being called on discovery of each message. */ |
373 | /* FIXME: The format of the display is poorly done, please correct. */ | 528 | /* FIXME: The format of the display is poorly done, please correct. */ |
374 | static int | 529 | static int |
375 | action (observer_t o, size_t type) | 530 | action (observer_t o, size_t type) |
376 | { | 531 | { |
377 | int status; | 532 | int status; |
378 | int col_cnt = 0; | ||
379 | 533 | ||
380 | switch (type) | 534 | switch (type) |
381 | { | 535 | { |
... | @@ -412,79 +566,57 @@ action (observer_t o, size_t type) | ... | @@ -412,79 +566,57 @@ action (observer_t o, size_t type) |
412 | break; | 566 | break; |
413 | 567 | ||
414 | if (show_number) | 568 | if (show_number) |
415 | { | 569 | format_field ("%4lu: ", (u_long) counter.index); |
416 | printf ("%4lu: ", (u_long) counter.index); | ||
417 | col_cnt += 6; | ||
418 | } | ||
419 | |||
420 | if (show_field) /* FIXME: This should be also rfc2047_decode. */ | ||
421 | { | ||
422 | char hfield[256]; | ||
423 | status = header_get_value_unfold (hdr, show_field, hfield, | ||
424 | sizeof (hfield), NULL); | ||
425 | if (status == 0) | ||
426 | printf ("%s", hfield); | ||
427 | } | ||
428 | 570 | ||
429 | if (show_to) | 571 | if (show_to) |
430 | { | 572 | { |
431 | char hto[16]; | 573 | char *hto; |
432 | status = get_personal (hdr, MU_HEADER_TO, hto, sizeof (hto)); | 574 | status = get_personal (hdr, MU_HEADER_TO, &hto); |
433 | 575 | ||
434 | if (status == 0) | 576 | if (status == 0) |
435 | { | 577 | { |
436 | printf ("(%s) ", hto); | 578 | format_field ("(%s) ", hto); |
437 | col_cnt += strlen (hto) + 3; | 579 | free (hto); |
438 | } | 580 | } |
439 | else | 581 | else |
440 | { | 582 | format_field ("(none)"); |
441 | printf ("(-----) "); | ||
442 | col_cnt += 8; | ||
443 | } | ||
444 | } | 583 | } |
445 | 584 | ||
446 | if (show_from) | 585 | if (show_field) /* FIXME: This should be also rfc2047_decode. */ |
447 | { | 586 | { |
448 | char hfrom[32]; | 587 | char *hfield; |
449 | status = get_personal (hdr, MU_HEADER_FROM, hfrom, sizeof (hfrom)); | 588 | status = header_aget_value_unfold (hdr, show_field, &hfield); |
450 | if (status == 0) | 589 | if (status == 0) |
451 | { | 590 | { |
452 | printf ("%s\t", hfrom); | 591 | format_field ("%s", hfield); |
453 | col_cnt += strlen (hfrom) + 4; // tab=4, sigh. | 592 | free (hfield); |
454 | } | 593 | } |
455 | else | 594 | else |
595 | format_field (""); | ||
596 | } | ||
597 | else | ||
598 | { | ||
599 | char *tmp; | ||
600 | status = get_personal (hdr, MU_HEADER_FROM, &tmp); | ||
601 | if (status == 0) | ||
456 | { | 602 | { |
457 | printf ("-----\t"); | 603 | format_field ("%s", tmp); |
458 | col_cnt += 9; | 604 | free (tmp); |
459 | } | 605 | } |
460 | } | 606 | else |
461 | 607 | format_field (""); | |
462 | /* | ||
463 | A temporary fix for (correct) displaying: | ||
464 | util_getcols() - col_cnt - ~1. It's ugly, I know. | ||
465 | */ | ||
466 | 608 | ||
467 | if (show_subject) | ||
468 | { | ||
469 | char *hsubject; | ||
470 | status = header_aget_value_unfold (hdr, MU_HEADER_SUBJECT, | 609 | status = header_aget_value_unfold (hdr, MU_HEADER_SUBJECT, |
471 | &hsubject); | 610 | &tmp); |
472 | if (status == 0) | 611 | if (status == 0) |
473 | { | 612 | { |
474 | char *s = rfc2047_decode_wrapper (hsubject, strlen (hsubject)); | 613 | char *s = rfc2047_decode_wrapper (tmp, strlen (tmp)); |
475 | char out[80]; | 614 | format_field ("%s", s); |
476 | int fspace = util_getcols () - col_cnt - 1; | ||
477 | |||
478 | fspace = (fspace > (sizeof (out) - 1)) | ||
479 | ? (sizeof (out) - 1) : fspace; | ||
480 | |||
481 | memset (out, 0, sizeof (out)); | ||
482 | strncpy (out, s, fspace); | ||
483 | printf ("%s", out); | ||
484 | free (s); | 615 | free (s); |
616 | free (tmp); | ||
485 | } | 617 | } |
486 | } | 618 | } |
487 | putchar ('\n'); | 619 | print_line (); |
488 | break; | 620 | break; |
489 | } | 621 | } |
490 | 622 | ... | ... |
-
Please register or sign in to post a comment