"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.2/src/string.c" (9 Dec 2022, 40746 Bytes) of package /linux/misc/tin-2.6.2.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "string.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.6.1_vs_2.6.2.

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