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