"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.1/src/string.c" (4 Dec 2016, 38653 Bytes) of package /linux/misc/tin-2.4.1.tar.gz:


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