tin  2.4.4
About: TIN is a threaded NNTP and spool based UseNet newsreader.
  Fossies Dox: tin-2.4.4.tar.xz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

string.c
Go to the documentation of this file.
1 /*
2  * Project : tin - a Usenet reader
3  * Module : string.c
4  * Author : Urs Janssen <urs@tin.org>
5  * Created : 1997-01-20
6  * Updated : 2019-01-08
7  * Notes :
8  *
9  * Copyright (c) 1997-2020 Urs Janssen <urs@tin.org>
10  * All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  *
16  * 1. Redistributions of source code must retain the above copyright notice,
17  * this list of conditions and the following disclaimer.
18  *
19  * 2. Redistributions in binary form must reproduce the above copyright
20  * notice, this list of conditions and the following disclaimer in the
21  * documentation and/or other materials provided with the distribution.
22  *
23  * 3. Neither the name of the copyright holder nor the names of its
24  * contributors may be used to endorse or promote products derived from
25  * this software without specific prior written permission.
26  *
27  * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
31  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37  * POSSIBILITY OF SUCH DAMAGE.
38  */
39 
40 
41 #ifndef TIN_H
42 # include "tin.h"
43 #endif /* !TIN_H */
44 
45 #ifdef HAVE_UNICODE_NORMALIZATION
46 # ifdef HAVE_LIBUNISTRING
47 # ifdef HAVE_UNITYPES_H
48 # include <unitypes.h>
49 # endif /* HAVE_UNITYPES_H */
50 # ifdef HAVE_UNINORM_H
51 # include <uninorm.h>
52 # endif /* HAVE_UNINORM_H */
53 # else
54 # if defined(HAVE_LIBIDN) && defined(HAVE_STRINGPREP_H) && !defined(_STRINGPREP_H)
55 # include <stringprep.h>
56 # endif /* HAVE_LIBIDN && HAVE_STRINGPREP_H && !_STRINGPREP_H */
57 # endif /* HAVE_LIBUNISTRING */
58 #endif /* HAVE_UNICODE_NORMALIZATION */
59 
60 /*
61  * this file needs some work
62  */
63 
64 /*
65  * local prototypes
66  */
67 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
68  static wchar_t *my_wcsdup(const wchar_t *wstr);
69 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
70 
71 /*
72  * special ltoa()
73  * converts value into a string with a maxlen of digits (usually should be
74  * >=4), last char may be one of the following:
75  * 'k'ilo, 'M'ega, 'G'iga, 'T'era, 'P'eta, 'E'xa, 'Z'etta, 'Y'otta,
76  * 'X'ona, 'W'eka, 'V'unda, 'U'da (these last 4 are no official SI-prefixes)
77  * or 'e' if an error occurs
78  */
79 char *
82  int digits)
83 {
84  static char buffer[64];
85  static const char power[] = { ' ', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'X', 'W', 'V', 'U', '\0' };
86  int len;
87  size_t i = 0;
88 
89  if (digits <= 0) {
90  buffer[0] = 'e';
91  return buffer;
92  }
93 
94  snprintf(buffer, sizeof(buffer), "%"T_ARTNUM_PFMT, value);
95  len = (int) strlen(buffer);
96 
97  while (len > digits) {
98  len -= 3;
99  i++;
100  }
101 
102  if (i >= strlen(power)) { /* buffer is to small */
103  buffer[(digits & 0x7f) - 1] = 'e';
104  buffer[(digits & 0x7f)] = '\0';
105  return buffer;
106  }
107 
108  if (i) {
109  while (len < (digits - 1))
110  buffer[len++] = ' ';
111 
112  if (digits > len)
113  buffer[digits - 1] = power[i];
114  else /* overflow */
115  buffer[digits - 1] = 'e';
116 
117  buffer[digits] = '\0';
118  } else
119  snprintf(buffer, sizeof(buffer), "%*"T_ARTNUM_PFMT, digits, value);
120 
121  return buffer;
122 }
123 
124 
125 #if !defined(USE_DMALLOC) || (defined(USE_DMALLOC) && !defined(HAVE_STRDUP))
126 /*
127  * Handrolled version of strdup(), presumably to take advantage of
128  * the enhanced error detection in my_malloc
129  *
130  * also, strdup is not mandatory in ANSI-C
131  */
132 char *
134  const char *str)
135 {
136  size_t len = strlen(str) + 1;
137  void *ptr = my_malloc(len);
138 
139 # if 0 /* as my_malloc exits on error, ptr can't be NULL */
140  if (ptr == NULL)
141  return NULL;
142 # endif /* 0 */
143 
144  memcpy(ptr, str, len);
145  return (char *) ptr;
146 }
147 #endif /* !USE_DMALLOC || (USE_DMALLOC && !HAVE_STRDUP) */
148 
149 /*
150  * strtok that understands empty tokens
151  * ie 2 adjacent delims count as two delims around a \0
152  */
153 char *
155  char *str,
156  const char *delim)
157 {
158  static char *buff;
159  char *oldbuff, *ptr;
160 
161  /*
162  * First call, setup static ptr
163  */
164  if (str)
165  buff = str;
166 
167  /*
168  * If not at end of string find ptr to next token
169  * If delim found, break off token
170  */
171  if (buff && (ptr = strpbrk(buff, delim)) != NULL)
172  *ptr++ = '\0';
173  else
174  ptr = NULL;
175 
176  /*
177  * Advance position in string to next token
178  * return current token
179  */
180  oldbuff = buff;
181  buff = ptr;
182  return oldbuff;
183 }
184 
185 
186 /*
187  * strncpy that stops at a newline and null terminates
188  */
189 void
191  char *p,
192  const char *q,
193  size_t n)
194 {
195  while (n--) {
196  if (!*q || *q == '\n')
197  break;
198  *p++ = *q++;
199  }
200  *p = '\0';
201 }
202 
203 
204 #ifndef HAVE_STRCASESTR
205 /*
206  * case-insensitive version of strstr()
207  */
208 char *
210  const char *haystack,
211  const char *needle)
212 {
213  const char *h;
214  const char *n;
215 
216  h = haystack;
217  n = needle;
218  while (*haystack) {
219  if (my_tolower((unsigned char) *h) == my_tolower((unsigned char) *n)) {
220  h++;
221  n++;
222  if (!*n)
223  return (char *) haystack;
224  } else {
225  h = ++haystack;
226  n = needle;
227  }
228  }
229  return NULL;
230 }
231 #endif /* !HAVE_STRCASESTR */
232 
233 
234 size_t
236  char **t,
237  const char *s)
238 {
239  size_t len = 0;
240 
241  while (*s) {
242  *((*t)++) = *s++;
243  len++;
244  }
245  **t = '\0';
246  return len;
247 }
248 
249 /*
250  * get around broken tolower()/toupper() macros on
251  * ancient BSDs (e.g. 4.2, 4.3, 4.3-Tahoe, 4.3-Reno and Net/2)
252  */
253 int
255 int c)
256 {
257 #ifdef TOLOWER_BROKEN
258  if (c >= 'A' && c <= 'Z')
259  return (c - 'A' + 'a');
260  else
261  return (c);
262 #else
263  return tolower(c);
264 #endif /* TOLOWER_BROKEN */
265 }
266 
267 
268 int
270 int c)
271 {
272 #ifdef TOUPPER_BROKEN
273  if (c >= 'a' && c <= 'z')
274  return (c - 'a' + 'A');
275  else
276  return (c);
277 #else
278  return toupper(c);
279 #endif /* TOUPPER_BROKEN */
280 }
281 
282 
283 void
285  char *str)
286 {
287  char *dst = str;
288 
289  while (*str)
290  *dst++ = (char) my_tolower((unsigned char) *str++);
291 
292  *dst = '\0';
293 }
294 
295 
296 /*
297  * normal systems come with these...
298  */
299 
300 #ifndef HAVE_STRPBRK
301 /*
302  * find first occurrence of any char from str2 in str1
303  */
304 char *
306  const char *str1,
307  const char *str2)
308 {
309  const char *ptr1;
310  const char *ptr2;
311 
312  for (ptr1 = str1; *ptr1 != '\0'; ptr1++) {
313  for (ptr2 = str2; *ptr2 != '\0'; ) {
314  if (*ptr1 == *ptr2++)
315  return ptr1;
316  }
317  }
318  return NULL;
319 }
320 #endif /* !HAVE_STRPBRK */
321 
322 #ifndef HAVE_STRSTR
323 /*
324  * ANSI C strstr() - Uses Boyer-Moore algorithm.
325  */
326 char *
328  const char *text,
329  const char *pattern)
330 {
331  unsigned char *p, *t;
332  int i, j, *delta;
333  int deltaspace[256];
334  size_t p1;
335  size_t textlen;
336  size_t patlen;
337 
338  textlen = strlen(text);
339  patlen = strlen(pattern);
340 
341  /* algorithm fails if pattern is empty */
342  if ((p1 = patlen) == 0)
343  return text;
344 
345  /* code below fails (whenever i is unsigned) if pattern too long */
346  if (p1 > textlen)
347  return NULL;
348 
349  /* set up deltas */
350  delta = deltaspace;
351  for (i = 0; i <= 255; i++)
352  delta[i] = p1;
353  for (p = (unsigned char *) pattern, i = p1; --i > 0; )
354  delta[*p++] = i;
355 
356  /*
357  * From now on, we want patlen - 1.
358  * In the loop below, p points to the end of the pattern,
359  * t points to the end of the text to be tested against the
360  * pattern, and i counts the amount of text remaining, not
361  * including the part to be tested.
362  */
363  p1--;
364  p = (unsigned char *) pattern + p1;
365  t = (unsigned char *) text + p1;
366  i = textlen - patlen;
367  forever {
368  if (*p == *t && memcmp ((p - p1), (t - p1), p1) == 0)
369  return ((char *) t - p1);
370  j = delta[*t];
371  if (i < j)
372  break;
373  i -= j;
374  t += j;
375  }
376  return NULL;
377 }
378 #endif /* !HAVE_STRSTR */
379 
380 #ifndef HAVE_ATOL
381 /*
382  * handrolled atol
383  */
384 long
386  const char *s)
387 {
388  long ret = 0;
389 
390  /* skip leading whitespace(s) */
391  while (*s && isspace((unsigned char) *s))
392  s++;
393 
394  while (*s) {
395  if (isdigit(*s))
396  ret = ret * 10 + (*s - '0');
397  else
398  return -1;
399  s++;
400  }
401  return ret;
402 }
403 #endif /* !HAVE_ATOL */
404 
405 #ifndef HAVE_STRTOL
406 /* fix me - put me in tin.h */
407 # define DIGIT(x) (isdigit((unsigned char) x) ? ((x) - '0') : (10 + my_tolower((unsigned char) x) - 'a'))
408 # define MBASE 36
409 long
411  const char *str,
412  char **ptr,
413  int use_base)
414 {
415  long val = 0L;
416  int xx = 0, sign = 1;
417 
418  if (use_base < 0 || use_base > MBASE)
419  goto OUT;
420  while (isspace((unsigned char) *str))
421  ++str;
422  if (*str == '-') {
423  ++str;
424  sign = -1;
425  } else if (*str == '+')
426  ++str;
427  if (use_base == 0) {
428  if (*str == '0') {
429  ++str;
430  if (*str == 'x' || *str == 'X') {
431  ++str;
432  use_base = 16;
433  } else
434  use_base = 8;
435  } else
436  use_base = 10;
437  } else if (use_base == 16)
438  if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X'))
439  str += 2;
440  /*
441  * for any base > 10, the digits incrementally following
442  * 9 are assumed to be "abc...z" or "ABC...Z"
443  */
444  while (isalnum((unsigned char) *str) && (xx = DIGIT(*str)) < use_base) {
445  /* accumulate neg avoids surprises near maxint */
446  val = use_base * val - xx;
447  ++str;
448  }
449 OUT:
450  if (ptr != NULL)
451  *ptr = str;
452 
453  return (sign * (-val));
454 }
455 # undef DIGIT
456 # undef MBASE
457 #endif /* !HAVE_STRTOL */
458 
459 #if !defined(HAVE_STRCASECMP) || !defined(HAVE_STRNCASECMP)
460  /* fix me - put me in tin.h */
461 # define FOLD_TO_UPPER(a) (my_toupper((unsigned char) (a)))
462 #endif /* !HAVE_STRCASECMP || !HAVE_STRNCASECMP */
463 /*
464  * strcmp that ignores case
465  */
466 #ifndef HAVE_STRCASECMP
467 int
469  const char *p,
470  const char *q)
471 {
472  int r;
473  for (; (r = FOLD_TO_UPPER (*p) - FOLD_TO_UPPER (*q)) == 0; ++p, ++q) {
474  if (*p == '\0')
475  return 0;
476  }
477 
478  return r;
479 }
480 #endif /* !HAVE_STRCASECMP */
481 
482 #ifndef HAVE_STRNCASECMP
483 int
485  const char *p,
486  const char *q,
487  size_t n)
488 {
489  int r = 0;
490  for (; n && (r = (FOLD_TO_UPPER(*p) - FOLD_TO_UPPER(*q))) == 0; ++p, ++q, --n) {
491  if (*p == '\0')
492  return 0;
493  }
494  return n ? r : 0;
495 }
496 #endif /* !HAVE_STRNCASECMP */
497 
498 #ifndef HAVE_STRSEP
499 /*
500  * strsep() is not mandatory in ANSI-C
501  */
502 char *
504  char **stringp,
505  const char *delim)
506 {
507  char *s = *stringp;
508  char *p;
509 
510  if (!s)
511  return NULL;
512 
513  if ((p = strpbrk(s, delim)) != NULL)
514  *p++ = '\0';
515 
516  *stringp = p;
517  return s;
518 }
519 #endif /* !HAVE_STRSEP */
520 
521 
522 /*
523  * str_trim - leading and trailing whitespace
524  *
525  * INPUT: string - string to trim
526  *
527  * OUTPUT: string - trimmed string
528  *
529  * RETURN: trimmed string
530  */
531 char *
533  char *string)
534 {
535  char *rp; /* reading string pointer */
536  char *wp; /* writing string pointer */
537  char *ls; /* last space */
538 
539  if (string == NULL)
540  return NULL;
541 
542  for (rp = wp = ls = string; isspace((int) *rp); rp++) /* Skip leading space */
543  ;
544 
545  while (*rp) {
546  if (isspace((int) *rp)) {
547  if (ls == NULL) /* Remember last written space */
548  ls = wp;
549  } else
550  ls = NULL; /* It wasn't the last space */
551  *wp++ = *rp++;
552  }
553 
554  if (ls) /* ie, there is trailing space */
555  *ls = '\0';
556  else
557  *wp = '\0';
558 
559  return string;
560 }
561 
562 
563 /*
564  * Return a pointer into s eliminating any TAB, CR and LF.
565  */
566 char *
568  char *s)
569 {
570  char *p1 = s;
571  char *p2 = s;
572 
573  while (*p1) {
574  if (*p1 == '\t' || *p1 == '\r' || *p1 == '\n') {
575  p1++;
576  } else if (p1 != p2) {
577  *p2++ = *p1++;
578  } else {
579  p1++;
580  p2++;
581  }
582  }
583  if (p1 != p2)
584  *p2 = '\0';
585 
586  return s;
587 }
588 
589 
590 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
591 wchar_t *
592 wexpand_tab(
593  wchar_t *wstr,
594  size_t tab_width)
595 {
596  size_t cur_len = LEN, i = 0, tw;
597  wchar_t *wbuf = my_malloc(cur_len * sizeof(wchar_t));
598  wchar_t *wc = wstr;
599 
600  while (*wc) {
601  if (i > cur_len - (tab_width + 1)) {
602  cur_len <<= 1;
603  wbuf = my_realloc(wbuf, cur_len * sizeof(wchar_t));
604  }
605  if (*wc == (wchar_t) '\t') {
606  tw = tab_width;
607  for (; tw > 0; --tw)
608  wbuf[i++] = (wchar_t) ' ';
609  } else
610  wbuf[i++] = *wc;
611  wc++;
612  }
613  wbuf[i] = '\0';
614 
615  return wbuf;
616 }
617 
618 
619 #else /* !MULTIBYTE_ABLE || NO_LOCALE */
620 
621 
622 char *
624  char *str,
625  size_t tab_width)
626 {
627  size_t cur_len = LEN, i = 0, tw;
628  char *buf = my_malloc(cur_len);
629  char *c = str;
630 
631  while (*c) {
632  if (i > cur_len - (tab_width + 1)) {
633  cur_len <<= 1;
634  buf = my_realloc(buf, cur_len);
635  }
636  if (*c == '\t') {
637  tw = tab_width;
638  for (; tw > 0; --tw)
639  buf[i++] = ' ';
640  } else
641  buf[i++] = *c;
642  c++;
643  }
644  buf[i] = '\0';
645 
646  return buf;
647 }
648 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
649 
650 
651 /*
652  * Format a shell command, escaping blanks and other awkward characters that
653  * appear in the string arguments. Replaces sprintf, except that we pass in
654  * the buffer limit, so we can refrain from trashing memory on very long
655  * pathnames.
656  *
657  * Returns the number of characters written (not counting null), or -1 if there
658  * is not enough room in the 'dst' buffer.
659  */
660 
661 #define SH_FORMAT(c) if (++result >= (int) len) \
662  break; \
663  *dst++ = c
664 
665 #define SH_SINGLE "\\\'"
666 #define SH_DOUBLE "\\\'\"`$"
667 #define SH_META "\\\'\"`$*%?()[]{}|<>^&;#~"
668 
669 int
671  char *dst,
672  size_t len,
673  const char *fmt,
674  ...)
675 {
676  char *src;
677  char temp[20];
678  int result = 0;
679  int quote = 0;
680  va_list ap;
681 
682  va_start(ap, fmt);
683 
684  while (*fmt != 0) {
685  int ch;
686 
687  ch = *fmt++;
688 
689  if (ch == '\\') {
690  SH_FORMAT(ch);
691  if (*fmt != '\0') {
692  SH_FORMAT(*fmt++);
693  }
694  continue;
695  } else if (ch == '%') {
696  if (*fmt == '\0') {
697  SH_FORMAT('%');
698  break;
699  }
700 
701  switch (*fmt++) {
702  case '%':
703  src = strcpy(temp, "%");
704  break;
705 
706  case 's':
707  src = va_arg(ap, char *);
708  break;
709 
710  case 'd':
711  snprintf(temp, sizeof(temp), "%d", va_arg(ap, int));
712  src = temp;
713  break;
714 
715  default:
716  src = strcpy(temp, "");
717  break;
718  }
719 
720  while (*src != '\0') {
721  t_bool fix;
722 
723  /*
724  * This logic works for Unix. Non-Unix systems may require a
725  * different set of problem chars, and may need quotes around
726  * the whole string rather than escaping individual chars.
727  */
728  if (quote == '"') {
729  fix = (strchr(SH_DOUBLE, *src) != NULL);
730  } else if (quote == '\'') {
731  fix = (strchr(SH_SINGLE, *src) != NULL);
732  } else
733  fix = (strchr(SH_META, *src) != NULL);
734  if (fix) {
735  SH_FORMAT('\\');
736  }
737  SH_FORMAT(*src++);
738  }
739  } else {
740  if (quote) {
741  if (ch == quote)
742  quote = 0;
743  } else {
744  if (ch == '"' || ch == '\'')
745  quote = ch;
746  }
747  SH_FORMAT(ch);
748  }
749  }
750  va_end(ap);
751 
752  if (result + 1 >= (int) len)
753  result = -1;
754  else
755  *dst = '\0';
756 
757  return result;
758 }
759 
760 
761 #ifndef HAVE_STRERROR
762 # ifdef HAVE_SYS_ERRLIST
763  extern int sys_nerr;
764 # endif /* HAVE_SYS_ERRLIST */
765 # ifdef DECL_SYS_ERRLIST
766  extern char *sys_errlist[];
767 # endif /* DECL_SYS_ERRLIST */
768 char *
770  int n)
771 {
772  static char temp[32];
773 
774 # ifdef HAVE_SYS_ERRLIST
775  if (n >= 0 && n < sys_nerr)
776  return sys_errlist[n];
777 # endif /* HAVE_SYS_ERRLIST */
778  snprintf(temp, sizeof(temp), "Errno: %d", n);
779  return temp;
780 }
781 #endif /* !HAVE_STRERROR */
782 
783 
784 /* strrstr() based on Lars Wirzenius' <lars.wirzenius@helsinki.fi> code */
785 #ifndef HAVE_STRRSTR
786 char *
788  const char *str,
789  const char *pat)
790 {
791  const char *ptr;
792  size_t slen, plen;
793 
794  if ((str != NULL) && (pat != NULL)) {
795  slen = strlen(str);
796  plen = strlen(pat);
797 
798  if ((plen != 0) && (plen <= slen)) {
799  for (ptr = str + (slen - plen); ptr > str; --ptr) {
800  if (*ptr == *pat && strncmp(ptr, pat, plen) == 0)
801  return (char *) ptr;
802  }
803  }
804  }
805  return NULL;
806 }
807 #endif /* !HAVE_STRRSTR */
808 
809 
810 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
811 /*
812  * convert from char* to wchar_t*
813  */
814 wchar_t *
815 char2wchar_t(
816  const char *str)
817 {
818  char *test = my_strdup(str);
819  size_t len = (size_t) (-1);
820  size_t pos = strlen(test);
821  wchar_t *wstr;
822 
823  /* check for illegal sequences */
824  while (len == (size_t) (-1) && pos) {
825  if ((len = mbstowcs(NULL, test, 0)) == (size_t) (-1))
826  test[--pos] = '?';
827  }
828  /* shouldn't happen anymore */
829  if (len == (size_t) (-1)) {
830  free(test);
831  return NULL;
832  }
833 
834  wstr = my_malloc(sizeof(wchar_t) * (len + 1));
835  mbstowcs(wstr, test, len + 1);
836  free(test);
837 
838  return wstr;
839 }
840 
841 
842 /*
843  * convert from wchar_t* to char*
844  */
845 char *
846 wchar_t2char(
847  const wchar_t *wstr)
848 {
849  char *str;
850  size_t len;
851 
852  len = wcstombs(NULL, wstr, 0);
853  if (len == (size_t) (-1))
854  return NULL;
855 
856  str = my_malloc(len + 1);
857  wcstombs(str, wstr, len + 1);
858 
859  return str;
860 }
861 
862 
863 /*
864  * Interface to wcspart for non wide character strings
865  */
866 char *
867 spart(
868  const char *str,
869  int columns,
870  t_bool pad)
871 {
872  char *buf = NULL;
873  wchar_t *wbuf, *wbuf2;
874 
875  if ((wbuf = char2wchar_t(str)) != NULL) {
876  wbuf2 = wcspart(wbuf, columns, pad);
877  free(wbuf);
878  buf = wchar_t2char(wbuf2);
879  FreeIfNeeded(wbuf2);
880  }
881 
882  return buf;
883 }
884 
885 
886 /*
887  * returns a new string fitting into 'columns' columns
888  * if pad is TRUE the resulting string will be filled with spaces if necessary
889  */
890 wchar_t *
891 wcspart(
892  const wchar_t *wstr,
893  int columns,
894  t_bool pad)
895 {
896  int used = 0;
897  wchar_t *ptr, *wbuf;
898 
899  wbuf = my_wcsdup(wstr);
900  /* make sure all characters in wbuf are printable */
901  ptr = wconvert_to_printable(wbuf, FALSE);
902 
903  /* terminate wbuf after 'columns' columns */
904  while (*ptr && used + wcwidth(*ptr) <= columns)
905  used += wcwidth(*ptr++);
906  *ptr = (wchar_t) '\0';
907 
908  /* pad with spaces */
909  if (pad) {
910  int gap;
911 
912  gap = columns - wcswidth(wbuf, wcslen(wbuf) + 1);
913  assert(gap >= 0);
914  wbuf = my_realloc(wbuf, sizeof(wchar_t) * (wcslen(wbuf) + gap + 1));
915  ptr = wbuf + wcslen(wbuf); /* set ptr again to end of wbuf */
916 
917  while (gap-- > 0)
918  *ptr++ = (wchar_t) ' ';
919 
920  *ptr = (wchar_t) '\0';
921  } else
922  wbuf = my_realloc(wbuf, sizeof(wchar_t) * (wcslen(wbuf) + 1));
923 
924  return wbuf;
925 }
926 
927 
928 /*
929  * wcs version of abbr_groupname()
930  */
931 wchar_t *
932 abbr_wcsgroupname(
933  const wchar_t *grpname,
934  int len)
935 {
936  wchar_t *src, *dest, *tail, *new_grpname;
937  int tmplen, newlen;
938 
939  dest = new_grpname = my_wcsdup(grpname);
940 
941  if (wcswidth(grpname, wcslen(grpname)) > len) {
942  if ((src = wcschr(grpname, (wchar_t) '.')) != NULL) {
943  tmplen = wcwidth(*dest++);
944 
945  do {
946  *dest++ = *src;
947  tmplen += wcwidth(*src++);
948  *dest++ = *src;
949  tmplen += wcwidth(*src++);
950  tail = src;
951  newlen = wcswidth(tail, wcslen(tail)) + tmplen;
952  } while ((src = wcschr(src, (wchar_t) '.')) != NULL && newlen > len);
953 
954  if (newlen > len)
955  *dest++ = (wchar_t) '.';
956  else {
957  while (*tail)
958  *dest++ = *tail++;
959  }
960 
961  *dest = (wchar_t) '\0';
962  new_grpname = my_realloc(new_grpname, sizeof(wchar_t) * (wcslen(new_grpname) + 1));
963 
964  if (wcswidth(new_grpname, wcslen(new_grpname)) > len) {
965  dest = wstrunc(new_grpname, len);
966  free(new_grpname);
967  new_grpname = dest;
968  }
969  } else {
970  dest = wstrunc(new_grpname, len);
971  free(new_grpname);
972  new_grpname = dest;
973  }
974  }
975 
976  return new_grpname;
977 }
978 
979 #else /* !MULTIBYTE_ABLE || NO_LOCALE */
980 
981 /*
982  * Abbreviate a groupname like this:
983  * foo.bar.baz
984  * f.bar.baz
985  * f.b.baz
986  * f.b.b.
987  * depending on the given length
988  */
989 char *
991  const char *grpname,
992  size_t len)
993 {
994  char *src, *dest, *tail, *new_grpname;
995  size_t tmplen, newlen;
996 
997  dest = new_grpname = my_strdup(grpname);
998 
999  if (strlen(grpname) > len) {
1000  if ((src = strchr(grpname, '.')) != NULL) {
1001  dest++;
1002  tmplen = 1;
1003 
1004  do {
1005  *dest++ = *src++;
1006  *dest++ = *src++;
1007  tmplen += 2;
1008  tail = src;
1009  newlen = strlen(tail) + tmplen;
1010  } while ((src = strchr(src, '.')) != NULL && newlen > len);
1011 
1012  if (newlen > len)
1013  *dest++ = '.';
1014  else {
1015  while (*tail)
1016  *dest++ = *tail++;
1017  }
1018 
1019  *dest = '\0';
1020  new_grpname = my_realloc(new_grpname, strlen(new_grpname) + 1);
1021 
1022  if (strlen(new_grpname) > len) {
1023  dest = strunc(new_grpname, len);
1024  free(new_grpname);
1025  new_grpname = dest;
1026  }
1027  } else {
1028  dest = strunc(new_grpname, len);
1029  free(new_grpname);
1030  new_grpname = dest;
1031  }
1032  }
1033 
1034  return new_grpname;
1035 }
1036 #endif /* MULTIBYTE_ABLE && !NOLOCALE */
1037 
1038 
1039 /*
1040  * Returns the number of screen positions a string occupies
1041  */
1042 int
1044  const char *str)
1045 {
1046  int columns = (int) strlen(str);
1047 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1048  int width;
1049  wchar_t *wbuf;
1050 
1051  if ((wbuf = char2wchar_t(str)) != NULL) {
1052  if ((width = wcswidth(wbuf, wcslen(wbuf) + 1)) > 0)
1053  columns = width;
1054  free(wbuf);
1055  }
1056 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1057  return columns;
1058 }
1059 
1060 
1061 #define TRUNC_TAIL "..."
1062 /*
1063  * shortens 'mesg' that it occupies at most 'len' screen positions.
1064  * If it was necessary to truncate 'mesg', " ..." is appended to the
1065  * resulting string (still 'len' screen positions wide).
1066  * The resulting string is stored in 'buf'.
1067  */
1068 char *
1070  const char *message,
1071  int len)
1072 {
1073  char *tmp;
1074 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1075  wchar_t *wmessage, *wbuf;
1076 
1077  if ((wmessage = char2wchar_t(message)) != NULL) {
1078  wbuf = wstrunc(wmessage, len);
1079  free(wmessage);
1080 
1081  if ((tmp = wchar_t2char(wbuf)) != NULL) {
1082  free(wbuf);
1083 
1084  return tmp;
1085  }
1086  free(wbuf);
1087  }
1088  /* something went wrong using wide-chars, default back to normal chars */
1089 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1090 
1091  if ((int) strlen(message) <= len)
1092  tmp = my_strdup(message);
1093  else {
1094  tmp = my_malloc(len + 1);
1095  snprintf(tmp, len + 1, "%-.*s%s", len - 3, message, TRUNC_TAIL);
1096  }
1097 
1098  return tmp;
1099 }
1100 
1101 
1102 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1103 /* the wide-char equivalent of strunc() */
1104 wchar_t *
1105 wstrunc(
1106  const wchar_t *wmessage,
1107  int len)
1108 {
1109  wchar_t *wtmp;
1110 
1111  /* make sure all characters are printable */
1112  wtmp = my_wcsdup(wmessage);
1113  wconvert_to_printable(wtmp, FALSE);
1114 
1115  if (wcswidth(wtmp, wcslen(wtmp)) > len) {
1116  /* wtmp must be truncated */
1117  size_t len_tail;
1118  wchar_t *wtmp2, *tail;
1119 
1120  if (tinrc.utf8_graphics) {
1121  /*
1122  * use U+2026 (HORIZONTAL ELLIPSIS) instead of "..."
1123  * we gain two additional screen positions
1124  */
1125  tail = my_calloc(2, sizeof(wchar_t));
1126  tail[0] = 8230; /* U+2026 */
1127  } else
1128  tail = char2wchar_t(TRUNC_TAIL);
1129 
1130  len_tail = tail ? wcslen(tail) : 0;
1131  wtmp2 = wcspart(wtmp, len - len_tail, FALSE);
1132  free(wtmp);
1133  wtmp = my_realloc(wtmp2, sizeof(wchar_t) * (wcslen(wtmp2) + len_tail + 1)); /* wtmp2 isn't valid anymore and doesn't have to be free()ed */
1134  if (!tail)
1135  tail = my_calloc(1, sizeof(wchar_t));
1136  wcscat(wtmp, tail);
1137  free(tail);
1138  }
1139 
1140  return wtmp;
1141 }
1142 
1143 
1144 /*
1145  * duplicates a wide-char string
1146  */
1147 static wchar_t *
1148 my_wcsdup(
1149  const wchar_t *wstr)
1150 {
1151  size_t len = wcslen(wstr) + 1;
1152  void *ptr = my_malloc(sizeof(wchar_t) * len);
1153 
1154  memcpy(ptr, wstr, sizeof(wchar_t) * len);
1155  return (wchar_t *) ptr;
1156 }
1157 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1158 
1159 
1160 #if defined(HAVE_LIBICUUC) && defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1161 /*
1162  * convert from char* (UTF-8) to UChar* (UTF-16)
1163  * ICU expects strings as UChar*
1164  */
1165 UChar *
1166 char2UChar(
1167  const char *str)
1168 {
1169  int32_t needed;
1170  UChar *ustr;
1171  UErrorCode status = U_ZERO_ERROR;
1172 
1173  u_strFromUTF8(NULL, 0, &needed, str, -1, &status);
1174  status = U_ZERO_ERROR; /* reset status */
1175  ustr = my_malloc(sizeof(UChar) * (needed + 1));
1176  u_strFromUTF8(ustr, needed + 1, NULL, str, -1, &status);
1177 
1178  if (U_FAILURE(status)) {
1179  /* clean up and return NULL */
1180  free(ustr);
1181  return NULL;
1182  }
1183  return ustr;
1184 }
1185 
1186 
1187 /*
1188  * convert from UChar* (UTF-16) to char* (UTF-8)
1189  */
1190 char *
1191 UChar2char(
1192  const UChar *ustr)
1193 {
1194  char *str;
1195  int32_t needed;
1196  UErrorCode status = U_ZERO_ERROR;
1197 
1198  u_strToUTF8(NULL, 0, &needed, ustr, -1, &status);
1199  status = U_ZERO_ERROR; /* reset status */
1200  str = my_malloc(needed + 1);
1201  u_strToUTF8(str, needed + 1, NULL, ustr, -1, &status);
1202 
1203  if (U_FAILURE(status)) {
1204  /* clean up and return NULL */
1205  free(str);
1206  return NULL;
1207  }
1208  return str;
1209 }
1210 #endif /* HAVE_LIBICUUC && MULTIBYTE_ABLE && !NO_LOCALE */
1211 
1212 
1213 #ifdef HAVE_UNICODE_NORMALIZATION
1214 /*
1215  * unicode normalization
1216  *
1217  * str: the string to normalize (must be UTF-8)
1218  * returns the normalized string
1219  * if the normalization failed a copy of the original string will be returned
1220  *
1221  * don't forget to free() the allocated memory if not needed anymore
1222  */
1223 char *
1224 normalize(
1225  const char *str)
1226 {
1227  char *buf, *tmp;
1228 
1229  /* make sure str is valid UTF-8 */
1230  tmp = my_strdup(str);
1231  utf8_valid(tmp);
1232 
1233  if (tinrc.normalization_form == NORMALIZE_NONE) /* normalization is disabled */
1234  return tmp;
1235 
1236 # ifdef HAVE_LIBICUUC
1237  { /* ICU */
1238  int32_t needed, norm_len;
1239  UChar *ustr, *norm;
1240  UErrorCode status = U_ZERO_ERROR;
1241 #ifdef HAVE_UNICODE_UNORM2_H
1242  static const char *uname[] = {"nfc", "nfkc", "nfkc_cf"}; /* */
1243  const char *unamep;
1244  UNormalization2Mode mode;
1245 #else
1246  UNormalizationMode mode;
1247 #endif /* !HAVE_UNICODE_UNORM2_H */
1248 
1249  /* convert to UTF-16 which is used internally by ICU */
1250  if ((ustr = char2UChar(tmp)) == NULL) /* something went wrong, return the original string (as valid UTF8) */
1251  return tmp;
1252 
1253  switch (tinrc.normalization_form) {
1254  case NORMALIZE_NFD:
1255 #ifdef HAVE_UNICODE_UNORM2_H
1256  unamep = uname[0];
1257  mode = UNORM2_DECOMPOSE;
1258 #else
1259  mode = UNORM_NFD;
1260 #endif /* HAVE_UNICODE_UNORM2_H */
1261  break;
1262 
1263  case NORMALIZE_NFC:
1264 #ifdef HAVE_UNICODE_UNORM2_H
1265  unamep = uname[0];
1266  mode = UNORM2_COMPOSE;
1267 #else
1268  mode = UNORM_NFC;
1269 #endif /* HAVE_UNICODE_UNORM2_H */
1270  break;
1271 
1272  case NORMALIZE_NFKD:
1273 #ifdef HAVE_UNICODE_UNORM2_H
1274  unamep = uname[1];
1275  mode = UNORM2_DECOMPOSE;
1276 #else
1277  mode = UNORM_NFKD;
1278 #endif /* HAVE_UNICODE_UNORM2_H */
1279  break;
1280 #ifdef HAVE_UNICODE_UNORM2_H
1281  case NORMALIZE_NFKC_CF:
1282  unamep = uname[2];
1283  mode = UNORM2_COMPOSE;
1284  break;
1285 #endif /* HAVE_UNICODE_UNORM2_H */
1286 
1287  case NORMALIZE_NFKC:
1288  default:
1289 #ifdef HAVE_UNICODE_UNORM2_H
1290  unamep = uname[1];
1291  mode = UNORM2_COMPOSE;
1292 #else
1293  mode = UNORM_NFKC;
1294 #endif /* HAVE_UNICODE_UNORM2_H */
1295  }
1296 
1297 #ifdef HAVE_UNICODE_UNORM2_H
1298  needed = unorm2_normalize(unorm2_getInstance(NULL, unamep, mode, &status), ustr, -1, NULL, 0, &status);
1299 #else
1300  needed = unorm_normalize(ustr, -1, mode, 0, NULL, 0, &status);
1301 #endif /* HAVE_UNICODE_UNORM2_H */
1302 
1303  status = U_ZERO_ERROR; /* reset status */
1304  norm_len = needed + 1;
1305  norm = my_malloc(sizeof(UChar) * norm_len);
1306 #ifdef HAVE_UNICODE_UNORM2_H
1307  (void) unorm2_normalize(unorm2_getInstance(NULL, unamep, mode, &status), ustr, -1, norm, norm_len, &status);
1308 #else
1309  (void) unorm_normalize(ustr, -1, mode, 0, norm, norm_len, &status);
1310 #endif /* HAVE_UNICODE_UNORM2_H */
1311 
1312  if (U_FAILURE(status)) {
1313  /* something went wrong, return the original string (as valid UTF8) */
1314  free(ustr);
1315  free(norm);
1316  return tmp;
1317  }
1318 
1319  /* convert back to UTF-8 */
1320  if ((buf = UChar2char(norm)) == NULL) /* something went wrong, return the original string (as valid UTF8) */
1321  buf = tmp;
1322  else
1323  free(tmp);
1324 
1325  free(ustr);
1326  free(norm);
1327  return buf;
1328  }
1329 # else
1330 # ifdef HAVE_LIBUNISTRING
1331  /* unistring */
1332  {
1333  uninorm_t mode;
1334  size_t olen = 0;
1335 
1336  switch (tinrc.normalization_form) {
1337  case NORMALIZE_NFD:
1338  mode = UNINORM_NFD;
1339  break;
1340 
1341  case NORMALIZE_NFC:
1342  mode = UNINORM_NFC;
1343  break;
1344 
1345  case NORMALIZE_NFKD:
1346  mode = UNINORM_NFKD;
1347  break;
1348 
1349  case NORMALIZE_NFKC:
1350  default:
1351  mode = UNINORM_NFKC;
1352  }
1353  buf = (char *) u8_normalize(mode, (uint8_t *) tmp, strlen(tmp) + 1, NULL, &olen);
1354  free(tmp);
1355  return buf;
1356  }
1357 # else
1358 # ifdef HAVE_LIBIDN
1359  /* libidn */
1360 
1361  if ((buf = stringprep_utf8_nfkc_normalize(tmp, -1)) == NULL) /* normalization failed, return the original string (as valid UTF8) */
1362  buf = tmp;
1363  else
1364  free(tmp);
1365 
1366  return buf;
1367 # endif /* HAVE_LIBIDN */
1368 # endif /* HAVE_LIBUNISTRING */
1369 # endif /* HAVE_LIBICUUC */
1370 }
1371 #endif /* HAVE_UNICODE_NORMALIZATION */
1372 
1373 
1374 /*
1375  * returns a pointer to allocated buffer containing the formatted string
1376  * must be freed if not needed anymore
1377  */
1378 char *
1380  const char *fmt,
1381  ...)
1382 {
1383  va_list ap;
1384  size_t size = LEN;
1385  char *str = my_malloc(size);
1386  int used;
1387 
1388  while (1) {
1389  va_start(ap, fmt);
1390  used = vsnprintf(str, size, fmt, ap);
1391  va_end(ap);
1392 
1393  if (used > 0 && used < (int) size)
1394  break;
1395 
1396  size <<= 1;
1397  str = my_realloc(str, size);
1398  }
1399 
1400  return str;
1401 }
1402 
1403 
1404 /*
1405  * %% '%'
1406  * %d description (only selection level)
1407  * %D date, like date_format
1408  * %(formatstr)D date, formatstr gets passed to my_strftime()
1409  * %f newsgroup flag: 'D' bogus, 'X' not postable, 'M' moderated,
1410  * '=' renamed 'N' new, 'u' unsubscribed (only selection level)
1411  * %F from, name and/or address according to show_author
1412  * %G group name (only selection level)
1413  * %I initials
1414  * %L line count
1415  * %m article marks
1416  * %M Message-ID
1417  * %n current group/thread/article number (linenumber on screen)
1418  * %R number of responses in thread
1419  * %s subject (only group level)
1420  * %S score
1421  * %T thread tree (only thread level)
1422  * %U unread count (only selection level)
1423  *
1424  * TODO:
1425  * %A address
1426  * %C firstname
1427  * %N fullname
1428  */
1429 void
1431  const char *fmtstr,
1432  struct t_fmt *fmt)
1433 {
1434  char tmp_date_str[LEN];
1435  char *out, *d_fmt, *buf;
1436  const char *in;
1437  int min_cols;
1438  size_t cnt = 0;
1439  size_t len, len2, tmplen;
1440  time_t tmptime;
1441  enum {
1442  NO_FLAGS = 0,
1443  ART_MARKS = 1 << 0,
1444  DATE = 1 << 1,
1445  FROM = 1 << 2,
1446  GRP_DESC = 1 << 3,
1447  GRP_FLAGS = 1 << 4,
1448  GRP_NAME = 1 << 5,
1449  INITIALS = 1 << 6,
1450  LINE_CNT = 1 << 7,
1451  LINE_NUMBER = 1 << 8,
1452  MSGID = 1 << 9,
1453  RESP_COUNT = 1 << 10,
1454  SCORE = 1 << 11,
1455  SUBJECT = 1 << 12,
1456  THREAD_TREE = 1 << 13,
1457  U_CNT = 1 << 14
1458  };
1459  int flags = NO_FLAGS;
1460 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1461  char tmp[BUFSIZ];
1462  wchar_t *wtmp;
1463 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1464 
1465  fmt->len_date = 0;
1466  fmt->len_date_max = 0;
1467  fmt->len_grpdesc = 0;
1468  fmt->len_from = 0;
1469  fmt->len_grpname = 0;
1470  fmt->len_grpname_dsc = 0;
1471  fmt->len_grpname_max = 0;
1472  fmt->len_initials = 0;
1473  fmt->len_linenumber = 0;
1474  fmt->len_linecnt = 0;
1475  fmt->len_msgid = 0;
1476  fmt->len_respcnt = 0;
1477  fmt->len_score = 0;
1478  fmt->len_subj = 0;
1479  fmt->len_ucnt = 0;
1480  fmt->flags_offset = 0;
1481  fmt->mark_offset = 0;
1482  fmt->ucnt_offset = 0;
1483  fmt->show_grpdesc = FALSE;
1484  fmt->d_before_f = FALSE;
1485  fmt->g_before_f = FALSE;
1486  fmt->d_before_u = FALSE;
1487  fmt->g_before_u = FALSE;
1488  fmt->date_str[0] = '\0';
1489  tmp_date_str[0] = '\0';
1490  in = fmtstr;
1491  out = fmt->str;
1492 
1493  if (tinrc.draw_arrow)
1494  cnt += 2;
1495 
1496  for (; *in; in++) {
1497  if (*in != '%') {
1498  *out++ = *in;
1499  cnt++;
1500  continue;
1501  }
1502  *out++ = *in++;
1503  len = 0;
1504  len2 = 0;
1505  min_cols = 0;
1506  tmp_date_str[0] = '\0';
1507  d_fmt = tmp_date_str;
1508  if (*in > '0' && *in <= '9') {
1509  len = atoi(in);
1510  for (; *in >= '0' && *in <= '9'; in++)
1511  ;
1512  }
1513  if (*in == ',') {
1514  if (*++in > '0' && *in <= '9') {
1515  len2 = atoi(in);
1516  for (; *in >= '0' && *in <= '9'; in++)
1517  ;
1518  }
1519  }
1520  if (*in == '>') {
1521  if (*++in > '0' && *in <= '9') {
1522  min_cols = atoi(in);
1523  for (; *in >= '0' && *in <= '9'; in++)
1524  ;
1525  }
1526  }
1527  if (*in == '(') {
1528  char *tmpp;
1529  const char *endp = NULL;
1530  const char *startp = in;
1531 
1532  while ((tmpp = strstr(startp + 1, ")D")))
1533  endp = startp = tmpp;
1534 
1535  if (endp) {
1536  tmplen = endp - in;
1537 
1538  for (in++; *in && --tmplen; in++)
1539  *d_fmt++ = *in;
1540 
1541  *d_fmt = '\0';
1542  in++;
1543  } else {
1544  out -= 1;
1545  *out++ = *in;
1546  continue;
1547  }
1548  }
1549  *out++ = *in;
1550  switch (*in) {
1551  case '\0':
1552  break;
1553 
1554  case '%':
1555  cnt++;
1556  break;
1557 
1558  case 'd':
1559  /* Newsgroup description */
1560  if (cCOLS > min_cols && !(flags & GRP_DESC) && signal_context == cSelect) {
1561  flags |= GRP_DESC;
1562  fmt->show_grpdesc = TRUE;
1563  if (len) {
1564  fmt->len_grpdesc = len;
1565  }
1566  } else
1567  out -= 2;
1568  break;
1569 
1570  case 'D':
1571  /* Date */
1572  if (cCOLS > min_cols && (!(flags & DATE) && (signal_context == cGroup || signal_context == cThread))) {
1573  flags |= DATE;
1574  if (strlen(tmp_date_str))
1575  strcpy(fmt->date_str, tmp_date_str);
1576  else
1578  buf = my_malloc(LEN);
1579  (void) time(&tmptime);
1580  if (my_strftime(buf, LEN - 1, fmt->date_str, localtime(&tmptime))) {
1581 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1582  if ((wtmp = char2wchar_t(buf)) != NULL) {
1583  if (wcstombs(tmp, wtmp, sizeof(tmp) - 1) != (size_t) -1) {
1584  fmt->len_date = strwidth(tmp);
1585  }
1586  free(wtmp);
1587  }
1588 #else
1589  fmt->len_date = strlen(buf);
1590 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1591  }
1592  free(buf);
1593  if (len) {
1594  fmt->len_date_max = len;
1595  }
1596  } else
1597  out -= 2;
1598  break;
1599 
1600  case 'f':
1601  /* Newsgroup flags */
1602  if (cCOLS > min_cols && !(flags & GRP_FLAGS) && signal_context == cSelect) {
1603  flags |= GRP_FLAGS;
1604  fmt->flags_offset = cnt;
1605  if (flags & GRP_NAME)
1606  fmt->g_before_f = TRUE;
1607  if (flags & GRP_DESC)
1608  fmt->d_before_f = TRUE;
1609  ++cnt;
1610  } else
1611  out -= 2;
1612  break;
1613 
1614  case 'F':
1615  /* From */
1616  if (!(flags & FROM) && (signal_context == cGroup || signal_context == cThread)) {
1617  flags |= FROM;
1618  if (len) {
1619  fmt->len_from = len;
1620  }
1621  } else
1622  out -= 2;
1623  break;
1624 
1625  case 'G':
1626  /* Newsgroup name */
1627  if (cCOLS > min_cols && !(flags & GRP_NAME) && signal_context == cSelect) {
1628  flags |= GRP_NAME;
1629  if (len) {
1630  fmt->len_grpname = len;
1631  }
1632  fmt->len_grpname_dsc = (len2 ? len2 : 32);
1633  } else
1634  out -= 2;
1635  break;
1636 
1637  case 'I':
1638  /* Initials */
1639  if (cCOLS > (int) min_cols && !(flags & INITIALS) && (signal_context == cGroup || signal_context == cThread)) {
1640  flags |= INITIALS;
1641  fmt->len_initials = (len ? len : 3);
1642  cnt += fmt->len_initials;
1643  } else
1644  out -= 2;
1645  break;
1646 
1647  case 'L':
1648  /* Lines */
1649  if (cCOLS > min_cols && !(flags & LINE_CNT) && (signal_context == cGroup || signal_context == cThread)) {
1650  flags |= LINE_CNT;
1651  fmt->len_linecnt = (len ? len : 4);
1652  cnt += fmt->len_linecnt;
1653  } else
1654  out -= 2;
1655  break;
1656 
1657  case 'm':
1658  /* Article marks */
1659  if (cCOLS > (int) min_cols && !(flags & ART_MARKS) && (signal_context == cGroup || signal_context == cThread)) {
1660  flags |= ART_MARKS;
1661  cnt += 3;
1662  } else
1663  out -= 2;
1664  break;
1665 
1666  case 'M':
1667  /* Message-ID */
1668  if (cCOLS > min_cols && !(flags & MSGID) && (signal_context == cGroup || signal_context == cThread)) {
1669  flags |= MSGID;
1670  fmt->len_msgid = (len ? len : 10);
1671  cnt += fmt->len_msgid;
1672  } else
1673  out -= 2;
1674  break;
1675 
1676  case 'n':
1677  /* Number in the menu */
1678  if (cCOLS > min_cols && !(flags & LINE_NUMBER)) {
1679  flags |= LINE_NUMBER;
1680  fmt->len_linenumber = (len ? len : 4);
1681  cnt += fmt->len_linenumber;
1682  } else
1683  out -= 2;
1684  break;
1685 
1686  case 'R':
1687  /* Number of responses in the thread */
1688  if (cCOLS > min_cols && !(flags & RESP_COUNT) && signal_context == cGroup) {
1689  flags |= RESP_COUNT;
1690  fmt->len_respcnt = (len ? len : 3);
1691  cnt += fmt->len_respcnt;
1692  } else
1693  out -= 2;
1694  break;
1695 
1696  case 's':
1697  /* Subject */
1698  if (cCOLS > min_cols && !(flags & SUBJECT) && signal_context == cGroup) {
1699  flags |= SUBJECT;
1700  if (len) {
1701  fmt->len_subj = len;
1702  }
1703  } else
1704  out -= 2;
1705  break;
1706 
1707  case 'S':
1708  /* Score */
1709  if (cCOLS > min_cols && !(flags & SCORE) && (signal_context == cGroup || signal_context == cThread)) {
1710  flags |= SCORE;
1711  fmt->len_score = (len ? len : 6);
1712  cnt += fmt->len_score;
1713  } else
1714  out -= 2;
1715  break;
1716 
1717  case 'T':
1718  /* Thread tree */
1719  if (cCOLS > min_cols && !(flags & THREAD_TREE) && signal_context == cThread) {
1720  flags |= THREAD_TREE;
1721  show_subject = TRUE;
1722  if (len) {
1723  fmt->len_subj = len;
1724  }
1725  } else
1726  out -= 2;
1727  break;
1728 
1729  case 'U':
1730  /* Unread count */
1731  if (cCOLS > min_cols && !(flags & U_CNT) && signal_context == cSelect) {
1732  flags |= U_CNT;
1733  fmt->len_ucnt = (len ? len : 5);
1734  fmt->ucnt_offset = cnt;
1735  if (flags & GRP_NAME)
1736  fmt->g_before_u = TRUE;
1737  if (flags & GRP_DESC)
1738  fmt->d_before_u = TRUE;
1739  cnt += fmt->len_ucnt;
1740  } else
1741  out -= 2;
1742  break;
1743 
1744  default:
1745  out -= 2;
1746  *out++ = *in;
1747  cnt++;
1748  break;
1749  }
1750  }
1751 
1752  *out = '\0';
1753 
1754  /*
1755  * check the given values against the screen width, fallback
1756  * to a default format if necessary
1757  *
1758  * build defaults when no length were given
1759  *
1760  * if we draw no thread tree %F can use the entire space - otherwise
1761  * %F will use one third of the space
1762  */
1763  if (cnt > (size_t) cCOLS - 1) {
1764  flags = NO_FLAGS;
1765  fmt->len_linenumber = 4;
1766  switch (signal_context) {
1767  case cSelect:
1770  flags = (GRP_FLAGS | LINE_NUMBER | U_CNT | GRP_NAME | GRP_DESC);
1771  cnt = tinrc.draw_arrow ? 18 : 16;
1772  fmt->show_grpdesc = TRUE;
1773  fmt->flags_offset = tinrc.draw_arrow ? 2 : 0;
1774  fmt->ucnt_offset = tinrc.draw_arrow ? 10 : 8;
1775  fmt->len_grpname_dsc = 32;
1776  fmt->len_grpname_max = cCOLS - cnt - 1;
1777  fmt->len_ucnt = 5;
1778  break;
1779 
1780  case cGroup:
1783  flags = (LINE_NUMBER | ART_MARKS | RESP_COUNT | LINE_CNT | SUBJECT | FROM);
1784  cnt = tinrc.draw_arrow ? 23 : 21;
1785  fmt->len_linecnt = 4;
1786  fmt->len_respcnt = 3;
1787  break;
1788 
1789  case cThread:
1792  flags = (LINE_NUMBER | ART_MARKS | LINE_CNT | THREAD_TREE | FROM);
1793  cnt = tinrc.draw_arrow ? 22 : 20;
1794  fmt->len_linecnt = 4;
1795  break;
1796 
1797  default:
1798  break;
1799  }
1800 
1801  if (flags & SUBJECT)
1802  fmt->len_from = (cCOLS - cnt - 1) / 3;
1803  else
1804  fmt->len_from = (cCOLS - cnt - 1);
1805 
1806  fmt->len_subj = cCOLS - fmt->len_from - cnt - 1;
1807  } else {
1808  if (flags & (GRP_NAME | GRP_DESC))
1809  fmt->len_grpname_max = cCOLS - cnt - 1;
1810 
1811  if (!show_description && !(flags & GRP_NAME))
1812  fmt->len_grpname_max = 0;
1813 
1814  if (flags & DATE && fmt->len_date > (cCOLS - cnt - 1))
1815  fmt->len_date = (cCOLS - cnt - 1);
1816 
1817  if (flags & DATE && (!fmt->len_date_max || fmt->len_date_max > (cCOLS - cnt - 1)))
1818  fmt->len_date_max = fmt->len_date;
1819 
1820  if (flags & FROM && (!fmt->len_from || fmt->len_from > (cCOLS - fmt->len_date_max - cnt - 1))) {
1821  if (flags & (SUBJECT | THREAD_TREE)) {
1822  if (fmt->len_subj)
1823  fmt->len_from = cCOLS - fmt->len_date_max - fmt->len_subj - cnt - 1;
1824  else
1825  fmt->len_from = (cCOLS - fmt->len_date_max - cnt - 1) / 3;
1826  } else
1827  fmt->len_from = (cCOLS - fmt->len_date_max - cnt - 1);
1828  }
1829 
1830  if (flags & (SUBJECT | THREAD_TREE) && (!fmt->len_subj || fmt->len_subj > (cCOLS - fmt->len_from - fmt->len_date_max - cnt - 1)))
1831  fmt->len_subj = (cCOLS - fmt->len_from - fmt->len_date_max - cnt - 1);
1832  }
1833 }
1834 
1835 
1836 #if defined(HAVE_LIBICUUC) && defined(MULTIBYTE_ABLE) && defined(HAVE_UNICODE_UBIDI_H) && !defined(NO_LOCALE)
1837 /*
1838  * prepare a string with bi-directional text for display
1839  * (converts from logical order to visual order)
1840  *
1841  * str: original string (in UTF-8)
1842  * is_rtl: pointer to a t_bool where the direction of the resulting string
1843  * will be stored (left-to-right = FALSE, right-to-left = TRUE)
1844  * returns a pointer to the reordered string.
1845  * In case of error NULL is returned and the value of is_rtl indefinite
1846  */
1847 char *
1848 render_bidi(
1849  const char *str,
1850  t_bool *is_rtl)
1851 {
1852  char *tmp;
1853  int32_t ustr_len;
1854  UBiDi *bidi_data;
1855  UChar *ustr, *ustr_reordered;
1856  UErrorCode status = U_ZERO_ERROR;
1857 
1858  *is_rtl = FALSE;
1859 
1860  /* make sure str is valid UTF-8 */
1861  tmp = my_strdup(str);
1862  utf8_valid(tmp);
1863 
1864  if ((ustr = char2UChar(tmp)) == NULL) {
1865  free(tmp);
1866  return NULL;
1867  }
1868  free(tmp); /* tmp is not needed anymore */
1869 
1870  bidi_data = ubidi_open();
1871  ubidi_setPara(bidi_data, ustr, -1, UBIDI_DEFAULT_LTR, NULL, &status);
1872  if (U_FAILURE(status)) {
1873  ubidi_close(bidi_data);
1874  free(ustr);
1875  return NULL;
1876  }
1877 
1878  ustr_len = u_strlen(ustr) + 1;
1879  ustr_reordered = my_malloc(sizeof(UChar) * ustr_len);
1880  ubidi_writeReordered(bidi_data, ustr_reordered, ustr_len, UBIDI_REMOVE_BIDI_CONTROLS|UBIDI_DO_MIRRORING, &status);
1881  if (U_FAILURE(status)) {
1882  ubidi_close(bidi_data);
1883  free(ustr);
1884  free(ustr_reordered);
1885  return NULL;
1886  }
1887 
1888  /*
1889  * determine the direction of the text
1890  * is the bidi level even => left-to-right
1891  * is the bidi level odd => right-to-left
1892  */
1893  *is_rtl = (t_bool) (ubidi_getParaLevel(bidi_data) & 1);
1894  ubidi_close(bidi_data);
1895 
1896  /*
1897  * No need to check the return value. In both cases we must clean up
1898  * and return the returned value, will it be a pointer to the
1899  * resulting string or NULL in case of failure.
1900  */
1901  tmp = UChar2char(ustr_reordered);
1902  free(ustr);
1903  free(ustr_reordered);
1904 
1905  return tmp;
1906 }
1907 #endif /* HAVE_LIBICUUC && MULTIBYTE_ABLE && HAVE_UNICODE_UBIDI_H && !NO_LOCALE */
t_fmt::len_grpname
size_t len_grpname
Definition: tin.h:1816
sys_nerr
int sys_nerr
strwidth
int strwidth(const char *str)
Definition: string.c:1043
str_lwr
void str_lwr(char *str)
Definition: string.c:284
strpbrk
char * strpbrk(const char *str1, const char *str2)
Definition: string.c:305
abbr_groupname
char * abbr_groupname(const char *grpname, size_t len)
Definition: string.c:990
atol
long atol(const char *s)
Definition: string.c:385
t_fmt::d_before_u
t_bool d_before_u
Definition: tin.h:1833
my_strdup
char * my_strdup(const char *str)
Definition: string.c:133
_
#define _(Text)
Definition: tin.h:94
t_fmt::len_msgid
size_t len_msgid
Definition: tin.h:1822
my_realloc
#define my_realloc(ptr, size)
Definition: tin.h:2198
fmtstr
static void fmtstr(const char *value, int ljust, int len, int precision)
Definition: plp_snprintf.c:388
t_fmt::len_date_max
size_t len_date_max
Definition: tin.h:1813
t_fmt::d_before_f
t_bool d_before_f
Definition: tin.h:1831
my_strncpy
void my_strncpy(char *p, const char *q, size_t n)
Definition: string.c:190
t_fmt::date_str
char date_str[1024]
Definition: tin.h:1811
FOLD_TO_UPPER
#define FOLD_TO_UPPER(a)
Definition: string.c:461
strtol
long strtol(const char *str, char **ptr, int use_base)
Definition: string.c:410
t_fmt::len_grpdesc
size_t len_grpdesc
Definition: tin.h:1814
cThread
@ cThread
Definition: tin.h:107
show_description
t_bool show_description
Definition: init.c:152
t_fmt::flags_offset
size_t flags_offset
Definition: tin.h:1827
tinrc
struct t_config tinrc
Definition: init.c:191
value
Definition: plp_snprintf.c:180
SH_DOUBLE
#define SH_DOUBLE
Definition: string.c:666
sys_errlist
char * sys_errlist[]
curr_group
struct t_group * curr_group
Definition: group.c:55
t_fmt::g_before_u
t_bool g_before_u
Definition: tin.h:1834
signal_context
int signal_context
Definition: signal.c:105
t_fmt::len_grpname_max
size_t len_grpname_max
Definition: tin.h:1818
t_fmt::show_grpdesc
t_bool show_grpdesc
Definition: tin.h:1830
TRUNC_TAIL
#define TRUNC_TAIL
Definition: string.c:1061
DEFAULT_THREAD_FORMAT
#define DEFAULT_THREAD_FORMAT
Definition: tin.h:1375
t_fmt::g_before_f
t_bool g_before_f
Definition: tin.h:1832
tin.h
MBASE
#define MBASE
Definition: string.c:408
parse_format_string
void parse_format_string(const char *fmtstr, struct t_fmt *fmt)
Definition: string.c:1430
SH_META
#define SH_META
Definition: string.c:667
t_fmt::len_ucnt
size_t len_ucnt
Definition: tin.h:1826
t_fmt
Definition: tin.h:1809
cSelect
@ cSelect
Definition: tin.h:107
my_strerror
char * my_strerror(int n)
Definition: string.c:769
txt_error_format_string
constext txt_error_format_string[]
Definition: lang.c:196
t_fmt::mark_offset
size_t mark_offset
Definition: tin.h:1828
forever
#define forever
Definition: tin.h:810
vsnprintf
#define vsnprintf
Definition: tin.h:2420
t_fmt::len_from
size_t len_from
Definition: tin.h:1815
T_ARTNUM_PFMT
#define T_ARTNUM_PFMT
Definition: tin.h:227
t_fmt::len_score
size_t len_score
Definition: tin.h:1824
strsep
char * strsep(char **stringp, const char *delim)
Definition: string.c:503
t_fmt::len_initials
size_t len_initials
Definition: tin.h:1819
t_fmt::str
char str[1024]
Definition: tin.h:1810
buf
static char buf[16]
Definition: langinfo.c:50
FreeIfNeeded
#define FreeIfNeeded(p)
Definition: tin.h:2203
my_tolower
int my_tolower(int c)
Definition: string.c:254
assert
#define assert(p)
Definition: tin.h:1295
t_fmt::ucnt_offset
size_t ucnt_offset
Definition: tin.h:1829
SH_FORMAT
#define SH_FORMAT(c)
Definition: string.c:661
t_fmt::len_subj
size_t len_subj
Definition: tin.h:1825
cCOLS
int cCOLS
Definition: curses.c:53
LEN
#define LEN
Definition: tin.h:854
strunc
char * strunc(const char *message, int len)
Definition: string.c:1069
t_fmt::len_grpname_dsc
size_t len_grpname_dsc
Definition: tin.h:1817
cGroup
@ cGroup
Definition: tin.h:107
my_toupper
int my_toupper(int c)
Definition: string.c:269
DEFAULT_GROUP_FORMAT
#define DEFAULT_GROUP_FORMAT
Definition: tin.h:1374
sh_format
int sh_format(char *dst, size_t len, const char *fmt,...)
Definition: string.c:670
my_calloc
#define my_calloc(nmemb, size)
Definition: tin.h:2197
strncasecmp
int strncasecmp(const char *p, const char *q, size_t n)
Definition: string.c:484
buffer
static uschar * buffer
Definition: pcretest.c:154
strcasecmp
int strcasecmp(const char *p, const char *q)
Definition: string.c:468
strrstr
char * strrstr(const char *str, const char *pat)
Definition: string.c:787
atoi
int atoi(const char *s)
FALSE
#define FALSE
Definition: bool.h:70
STRCPY
#define STRCPY(dst, src)
Definition: tin.h:814
MSGID
#define MSGID(x)
Definition: filter.c:81
tin_strtok
char * tin_strtok(char *str, const char *delim)
Definition: string.c:154
t_fmt::len_date
size_t len_date
Definition: tin.h:1812
t_attribute::date_format
char * date_format
Definition: tin.h:1571
snprintf
#define snprintf
Definition: tin.h:2417
t_fmt::len_linenumber
size_t len_linenumber
Definition: tin.h:1820
t_artnum
long t_artnum
Definition: tin.h:226
error_message
void error_message(unsigned int sdelay, const char *fmt,...)
Definition: screen.c:184
tin_ltoa
char * tin_ltoa(t_artnum value, int digits)
Definition: string.c:80
show_subject
t_bool show_subject
Definition: thread.c:54
mystrcat
size_t mystrcat(char **t, const char *s)
Definition: string.c:235
expand_tab
char * expand_tab(char *str, size_t tab_width)
Definition: string.c:623
t_group::attribute
struct t_attribute * attribute
Definition: tin.h:1790
strcasestr
char * strcasestr(const char *haystack, const char *needle)
Definition: string.c:209
t_fmt::len_respcnt
size_t len_respcnt
Definition: tin.h:1823
t_bool
unsigned t_bool
Definition: bool.h:77
DIGIT
#define DIGIT(x)
Definition: string.c:407
TRUE
#define TRUE
Definition: bool.h:74
my_strftime
size_t my_strftime(char *s, size_t maxsize, const char *format, struct tm *timeptr)
Definition: strftime.c:64
str_trim
char * str_trim(char *string)
Definition: string.c:532
strstr
char * strstr(const char *text, const char *pattern)
Definition: string.c:327
eat_tab
char * eat_tab(char *s)
Definition: string.c:567
SH_SINGLE
#define SH_SINGLE
Definition: string.c:665
fmt_string
char * fmt_string(const char *fmt,...)
Definition: string.c:1379
t_fmt::len_linecnt
size_t len_linecnt
Definition: tin.h:1821
DEFAULT_SELECT_FORMAT
#define DEFAULT_SELECT_FORMAT
Definition: tin.h:1373
t_config::draw_arrow
t_bool draw_arrow
Definition: tinrc.h:212
my_malloc
#define my_malloc(size)
Definition: tin.h:2196