"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.5/src/string.c" (1 Dec 2020, 40546 Bytes) of package /linux/misc/tin-2.4.5.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.4.4_vs_2.4.5.

    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 *
   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 *
  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 *
  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
  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 *
  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
  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
  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++ = 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_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 *
  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) + 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 && !NOLOCALE */
 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, 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, 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 *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 *
 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 = 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 *
 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 */