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