"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.1/src/string.c" (22 Dec 2021, 40819 Bytes) of package /linux/misc/tin-2.6.1.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.0_vs_2.6.1.

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