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 : 2020-05-31
7 * Notes :
8 *
9 * Copyright (c) 1997-2021 Urs Janssen <urs@tin.org>
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 *
16 * 1. Redistributions of source code must retain the above copyright notice,
17 * this list of conditions and the following disclaimer.
18 *
19 * 2. Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
22 *
23 * 3. Neither the name of the copyright holder nor the names of its
24 * contributors may be used to endorse or promote products derived from
25 * this software without specific prior written permission.
26 *
27 * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
31 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 * POSSIBILITY OF SUCH DAMAGE.
38 */
39
40
41 #ifndef TIN_H
42 # include "tin.h"
43 #endif /* !TIN_H */
44
45 #ifdef HAVE_UNICODE_NORMALIZATION
46 # ifdef HAVE_LIBUNISTRING
47 # ifdef HAVE_UNITYPES_H
48 # include <unitypes.h>
49 # endif /* HAVE_UNITYPES_H */
50 # ifdef HAVE_UNINORM_H
51 # include <uninorm.h>
52 # endif /* HAVE_UNINORM_H */
53 # else
54 # if defined(HAVE_LIBIDN) && defined(HAVE_STRINGPREP_H) && !defined(_STRINGPREP_H)
55 # include <stringprep.h>
56 # endif /* HAVE_LIBIDN && HAVE_STRINGPREP_H && !_STRINGPREP_H */
57 # endif /* HAVE_LIBUNISTRING */
58 #endif /* HAVE_UNICODE_NORMALIZATION */
59
60 /*
61 * this file needs some work
62 */
63
64 /*
65 * local prototypes
66 */
67 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
68 static wchar_t *my_wcsdup(const wchar_t *wstr);
69 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
70
71 /*
72 * special ltoa()
73 * converts value into a string with a maxlen of digits (usually should be
74 * >=4), last char may be one of the following:
75 * 'k'ilo, 'M'ega, 'G'iga, 'T'era, 'P'eta, 'E'xa, 'Z'etta, 'Y'otta,
76 * 'X'ona, 'W'eka, 'V'unda, 'U'da (these last 4 are no official SI-prefixes)
77 * or 'e' if an error occurs
78 */
79 char *
tin_ltoa(t_artnum value,int digits)80 tin_ltoa(
81 t_artnum value,
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 */
138 char *
my_strdup(const char * str)139 my_strdup(
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 */
159 char *
tin_strtok(char * str,const char * delim)160 tin_strtok(
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 */
195 void
my_strncpy(char * p,const char * q,size_t n)196 my_strncpy(
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 */
214 char *
strcasestr(const char * haystack,const char * needle)215 strcasestr(
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
240 size_t
mystrcat(char ** t,const char * s)241 mystrcat(
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 * 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
my_tolower(int c)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
my_toupper(int c)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
str_lwr(char * str)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 *
strpbrk(const char * str1,const char * str2)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 *
strstr(const char * text,const char * pattern)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
atol(const char * s)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
strtol(const char * str,char ** ptr,int use_base)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
strcasecmp(const char * p,const char * q)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
strncasecmp(const char * p,const char * q,size_t n)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 *
strsep(char ** stringp,const char * delim)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 *
str_trim(char * string)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 *
eat_tab(char * s)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 *
wexpand_tab(wchar_t * wstr,size_t tab_width)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 *
expand_tab(char * str,size_t tab_width)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++ = c
670
671 #define SH_SINGLE "\\\'"
672 #define SH_DOUBLE "\\\'\"`$"
673 #define SH_META "\\\'\"`$*%?()[]{}|<>^&;#~"
674
675 int
sh_format(char * dst,size_t len,const char * fmt,...)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 *
my_strerror(int n)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 *
strrstr(const char * str,const char * pat)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 *
char2wchar_t(const char * str)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_malloc(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 *
wchar_t2char(const wchar_t * wstr)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 *
spart(const char * str,int columns,t_bool pad)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 *
wcspart(const wchar_t * wstr,int columns,t_bool pad)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) + 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 *
abbr_wcsgroupname(const wchar_t * grpname,int len)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 *
abbr_groupname(const char * grpname,size_t len)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 && !NOLOCALE */
1043
1044
1045 /*
1046 * Returns the number of screen positions a string occupies
1047 */
1048 int
strwidth(const char * str)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 *
strunc(const char * message,int len)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, 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 *
wstrunc(const wchar_t * wmessage,int len)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, 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 *
my_wcsdup(const wchar_t * wstr)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 *
char2UChar(const char * str)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 *
UChar2char(const UChar * ustr)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 *
normalize(const char * str)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 *uname[] = {"nfc", "nfkc", "nfkc_cf"}; /* */
1249 const char *unamep;
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 unamep = uname[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 unamep = uname[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 unamep = uname[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 unamep = uname[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 unamep = uname[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, unamep, 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, unamep, 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 *
fmt_string(const char * fmt,...)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
parse_format_string(const char * fmtstr,struct t_fmt * fmt)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 = atoi(in);
1516 for (; *in >= '0' && *in <= '9'; in++)
1517 ;
1518 }
1519 if (*in == ',') {
1520 if (*++in > '0' && *in <= '9') {
1521 len2 = 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 = 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 }
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
1583 STRCPY(fmt->date_str, curr_group->attribute->date_format);
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 = 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 }
1709 } else
1710 out -= 2;
1711 break;
1712
1713 case 'S':
1714 /* Score */
1715 if (cCOLS > min_cols && !(flags & SCORE) && (signal_context == cGroup || signal_context == cThread)) {
1716 flags |= SCORE;
1717 fmt->len_score = (len ? len : 6);
1718 cnt += fmt->len_score;
1719 } else
1720 out -= 2;
1721 break;
1722
1723 case 'T':
1724 /* Thread tree */
1725 if (cCOLS > min_cols && !(flags & THREAD_TREE) && signal_context == cThread) {
1726 flags |= THREAD_TREE;
1727 show_subject = TRUE;
1728 if (len) {
1729 fmt->len_subj = len;
1730 }
1731 } else
1732 out -= 2;
1733 break;
1734
1735 case 'U':
1736 /* Unread count */
1737 if (cCOLS > min_cols && !(flags & U_CNT) && signal_context == cSelect) {
1738 flags |= U_CNT;
1739 fmt->len_ucnt = (len ? len : 5);
1740 fmt->ucnt_offset = cnt;
1741 if (flags & GRP_NAME)
1742 fmt->g_before_u = TRUE;
1743 if (flags & GRP_DESC)
1744 fmt->d_before_u = TRUE;
1745 cnt += fmt->len_ucnt;
1746 } else
1747 out -= 2;
1748 break;
1749
1750 default:
1751 out -= 2;
1752 *out++ = *in;
1753 cnt++;
1754 break;
1755 }
1756 }
1757
1758 *out = '\0';
1759
1760 /*
1761 * check the given values against the screen width, fallback
1762 * to a default format if necessary
1763 *
1764 * build defaults when no length were given
1765 *
1766 * if we draw no thread tree %F can use the entire space - otherwise
1767 * %F will use one third of the space
1768 */
1769 if (cnt > (size_t) cCOLS - 1) {
1770 flags = NO_FLAGS;
1771 fmt->len_linenumber = 4;
1772 switch (signal_context) {
1773 case cSelect:
1774 error_message(2, _(txt_error_format_string), DEFAULT_SELECT_FORMAT);
1775 STRCPY(fmt->str, DEFAULT_SELECT_FORMAT);
1776 flags = (GRP_FLAGS | LINE_NUMBER | U_CNT | GRP_NAME | GRP_DESC);
1777 cnt = tinrc.draw_arrow ? 18 : 16;
1778 fmt->show_grpdesc = TRUE;
1779 fmt->flags_offset = tinrc.draw_arrow ? 2 : 0;
1780 fmt->ucnt_offset = tinrc.draw_arrow ? 10 : 8;
1781 fmt->len_grpname_dsc = 32;
1782 fmt->len_grpname_max = cCOLS - cnt - 1;
1783 fmt->len_ucnt = 5;
1784 break;
1785
1786 case cGroup:
1787 error_message(2, _(txt_error_format_string), DEFAULT_GROUP_FORMAT);
1788 STRCPY(fmt->str, DEFAULT_GROUP_FORMAT);
1789 flags = (LINE_NUMBER | ART_MARKS | RESP_COUNT | LINE_CNT | SUBJECT | FROM);
1790 cnt = tinrc.draw_arrow ? 23 : 21;
1791 fmt->len_linecnt = 4;
1792 fmt->len_respcnt = 3;
1793 break;
1794
1795 case cThread:
1796 error_message(2, _(txt_error_format_string), DEFAULT_THREAD_FORMAT);
1797 STRCPY(fmt->str, DEFAULT_THREAD_FORMAT);
1798 flags = (LINE_NUMBER | ART_MARKS | LINE_CNT | THREAD_TREE | FROM);
1799 cnt = tinrc.draw_arrow ? 22 : 20;
1800 fmt->len_linecnt = 4;
1801 break;
1802
1803 default:
1804 break;
1805 }
1806
1807 if (flags & SUBJECT)
1808 fmt->len_from = (cCOLS - cnt - 1) / 3;
1809 else
1810 fmt->len_from = (cCOLS - cnt - 1);
1811
1812 fmt->len_subj = cCOLS - fmt->len_from - cnt - 1;
1813 } else {
1814 if (flags & (GRP_NAME | GRP_DESC))
1815 fmt->len_grpname_max = cCOLS - cnt - 1;
1816
1817 if (!show_description && !(flags & GRP_NAME))
1818 fmt->len_grpname_max = 0;
1819
1820 if (flags & DATE && fmt->len_date > (cCOLS - cnt - 1))
1821 fmt->len_date = (cCOLS - cnt - 1);
1822
1823 if (flags & DATE && (!fmt->len_date_max || fmt->len_date_max > (cCOLS - cnt - 1)))
1824 fmt->len_date_max = fmt->len_date;
1825
1826 if (flags & FROM && (!fmt->len_from || fmt->len_from > (cCOLS - fmt->len_date_max - cnt - 1))) {
1827 if (flags & (SUBJECT | THREAD_TREE)) {
1828 if (fmt->len_subj)
1829 fmt->len_from = cCOLS - fmt->len_date_max - fmt->len_subj - cnt - 1;
1830 else
1831 fmt->len_from = (cCOLS - fmt->len_date_max - cnt - 1) / 3;
1832 } else
1833 fmt->len_from = (cCOLS - fmt->len_date_max - cnt - 1);
1834 }
1835
1836 if (flags & (SUBJECT | THREAD_TREE) && (!fmt->len_subj || fmt->len_subj > (cCOLS - fmt->len_from - fmt->len_date_max - cnt - 1)))
1837 fmt->len_subj = (cCOLS - fmt->len_from - fmt->len_date_max - cnt - 1);
1838 }
1839 }
1840
1841
1842 #if defined(HAVE_LIBICUUC) && defined(MULTIBYTE_ABLE) && defined(HAVE_UNICODE_UBIDI_H) && !defined(NO_LOCALE)
1843 /*
1844 * prepare a string with bi-directional text for display
1845 * (converts from logical order to visual order)
1846 *
1847 * str: original string (in UTF-8)
1848 * is_rtl: pointer to a t_bool where the direction of the resulting string
1849 * will be stored (left-to-right = FALSE, right-to-left = TRUE)
1850 * returns a pointer to the reordered string.
1851 * In case of error NULL is returned and the value of is_rtl indefinite
1852 */
1853 char *
render_bidi(const char * str,t_bool * is_rtl)1854 render_bidi(
1855 const char *str,
1856 t_bool *is_rtl)
1857 {
1858 char *tmp;
1859 int32_t ustr_len;
1860 UBiDi *bidi_data;
1861 UChar *ustr, *ustr_reordered;
1862 UErrorCode status = U_ZERO_ERROR;
1863
1864 *is_rtl = FALSE;
1865
1866 /* make sure str is valid UTF-8 */
1867 tmp = my_strdup(str);
1868 utf8_valid(tmp);
1869
1870 if ((ustr = char2UChar(tmp)) == NULL) {
1871 free(tmp);
1872 return NULL;
1873 }
1874 free(tmp); /* tmp is not needed anymore */
1875
1876 bidi_data = ubidi_open();
1877 ubidi_setPara(bidi_data, ustr, -1, UBIDI_DEFAULT_LTR, NULL, &status);
1878 if (U_FAILURE(status)) {
1879 ubidi_close(bidi_data);
1880 free(ustr);
1881 return NULL;
1882 }
1883
1884 ustr_len = u_strlen(ustr) + 1;
1885 ustr_reordered = my_malloc(sizeof(UChar) * ustr_len);
1886 ubidi_writeReordered(bidi_data, ustr_reordered, ustr_len, UBIDI_REMOVE_BIDI_CONTROLS|UBIDI_DO_MIRRORING, &status);
1887 if (U_FAILURE(status)) {
1888 ubidi_close(bidi_data);
1889 free(ustr);
1890 free(ustr_reordered);
1891 return NULL;
1892 }
1893
1894 /*
1895 * determine the direction of the text
1896 * is the bidi level even => left-to-right
1897 * is the bidi level odd => right-to-left
1898 */
1899 *is_rtl = (t_bool) (ubidi_getParaLevel(bidi_data) & 1);
1900 ubidi_close(bidi_data);
1901
1902 /*
1903 * No need to check the return value. In both cases we must clean up
1904 * and return the returned value, will it be a pointer to the
1905 * resulting string or NULL in case of failure.
1906 */
1907 tmp = UChar2char(ustr_reordered);
1908 free(ustr);
1909 free(ustr_reordered);
1910
1911 return tmp;
1912 }
1913 #endif /* HAVE_LIBICUUC && MULTIBYTE_ABLE && HAVE_UNICODE_UBIDI_H && !NO_LOCALE */
1914