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