"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.1/src/cook.c" (12 Oct 2016, 28292 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 "cook.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    : cook.c
    4  *  Author    : J. Faultless
    5  *  Created   : 2000-03-08
    6  *  Updated   : 2016-02-28
    7  *  Notes     : Split from page.c
    8  *
    9  * Copyright (c) 2000-2017 Jason Faultless <jason@altarstone.com>
   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 #ifndef TIN_H
   38 #   include "tin.h"
   39 #endif /* !TIN_H */
   40 #ifndef TCURSES_H
   41 #   include "tcurses.h"
   42 #endif /* !TCURSES_H */
   43 
   44 
   45 /*
   46  * We malloc() this many t_lineinfo's at a time
   47  */
   48 #define CHUNK       50
   49 
   50 #define STRIP_ALTERNATIVE(x) \
   51             (curr_group->attribute->alternative_handling && \
   52             (x)->hdr.ext->type == TYPE_MULTIPART && \
   53             strcasecmp("alternative", (x)->hdr.ext->subtype) == 0)
   54 
   55 #define MATCH_REGEX(x,y,z)  (pcre_exec(x.re, x.extra, y, z, 0, 0, NULL, 0) >= 0)
   56 
   57 
   58 static t_bool charset_unsupported(const char *charset);
   59 static t_bool header_wanted(const char *line);
   60 static t_part *new_uue(t_part **part, char *name);
   61 static void process_text_body_part(t_bool wrap_lines, FILE *in, t_part *part, int hide_uue);
   62 static void put_cooked(size_t buf_len, t_bool wrap_lines, int flags, const char *fmt, ...);
   63 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
   64     static t_bool wexpand_ctrl_chars(wchar_t **wline, size_t *length, size_t lcook_width);
   65 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
   66 #ifdef DEBUG_ART
   67     static void dump_cooked(void);
   68 #endif /* DEBUG_ART */
   69 
   70 
   71 /*
   72  * These are used globally within this module for access to the context
   73  * currently being built. They must not leak outside.
   74  */
   75 static t_openartinfo *art;
   76 
   77 
   78 /*
   79  * Handle backspace, expand tabs, expand control chars to a literal ^[A-Z]
   80  * Allows \n through
   81  * Return TRUE if line contains a ^L (form-feed)
   82  */
   83 t_bool
   84 expand_ctrl_chars(
   85     char **line,
   86     size_t *length,
   87     size_t lcook_width)
   88 {
   89     t_bool ctrl_L = FALSE;
   90 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
   91     wchar_t *wline = char2wchar_t(*line);
   92     size_t wlen;
   93 
   94     /*
   95      * remove the assert() before release
   96      * it should help us find problems with wide-char strings
   97      * in the development branch
   98      */
   99     assert(wline != NULL);
  100     wlen = wcslen(wline);
  101     ctrl_L = wexpand_ctrl_chars(&wline, &wlen, lcook_width);
  102     free(*line);
  103     *line = wchar_t2char(wline);
  104     free(wline);
  105     assert(line != NULL);
  106     *length = strlen(*line);
  107 #else
  108     int curr_len = LEN;
  109     unsigned int i = 0, j, ln = 0;
  110     char *buf = my_malloc(curr_len);
  111     unsigned char *c;
  112 
  113     c = (unsigned char *) *line;
  114     while (*c) {
  115         if (i > curr_len - (lcook_width + 1)) {
  116             curr_len <<= 1;
  117             buf = my_realloc(buf, curr_len);
  118         }
  119         if (*c == '\n')
  120             ln = i + 1;
  121         if (*c == '\t') { /* expand tabs */
  122             j = i + lcook_width - ((i - ln) % lcook_width);
  123             for (; i < j; i++)
  124                 buf[i] = ' ';
  125         } else if (((*c) & 0xFF) < ' ' && *c != '\n' && (!IS_LOCAL_CHARSET("Big5") || *c != 27)) {  /* literal ctrl chars */
  126             buf[i++] = '^';
  127             buf[i++] = ((*c) & 0xFF) + '@';
  128             if (*c == '\f')     /* ^L detected */
  129                 ctrl_L = TRUE;
  130         } else {
  131             if (!my_isprint(*c) && *c != '\n')
  132                 buf[i++] = '?';
  133             else
  134                 buf[i++] = *c;
  135         }
  136         c++;
  137     }
  138     buf[i] = '\0';
  139     *length = i + 1;
  140     *line = my_realloc(*line, *length);
  141     strcpy(*line, buf);
  142     free(buf);
  143 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  144     return ctrl_L;
  145 }
  146 
  147 
  148 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  149 static t_bool
  150 wexpand_ctrl_chars(
  151     wchar_t **wline,
  152     size_t *length,
  153     size_t lcook_width)
  154 {
  155     size_t cur_len = LEN, i = 0, j, ln = 0;
  156     wchar_t *wbuf = my_malloc(cur_len * sizeof(wchar_t));
  157     wchar_t *wc;
  158     t_bool ctrl_L = FALSE;
  159 
  160     wc = *wline;
  161     while (*wc) {
  162         if (i > cur_len - (lcook_width + 1)) {
  163             cur_len <<= 1;
  164             wbuf = my_realloc(wbuf, cur_len * sizeof(wchar_t));
  165         }
  166         if (*wc == '\n')
  167             ln = i + 1;
  168         if (*wc == '\t') {      /* expand_tabs */
  169             j = i + lcook_width - ((i - ln) % lcook_width);
  170             for (; i < j; i++)
  171                 wbuf[i] = ' ';
  172         } else if (*wc < ' ' && *wc != '\n' && (!IS_LOCAL_CHARSET("Big5") || *wc != 27)) {  /* literal ctrl chars */
  173             wbuf[i++] = '^';
  174             wbuf[i++] = *wc + '@';
  175             if (*wc == '\f')    /* ^L detected */
  176                 ctrl_L = TRUE;
  177         } else {
  178             if (!iswprint((wint_t) *wc) && *wc != '\n')
  179                 wbuf[i++] = '?';
  180             else
  181                 wbuf[i++] = *wc;
  182         }
  183         wc++;
  184     }
  185     wbuf[i] = '\0';
  186     *length = i + 1;
  187     *wline = my_realloc(*wline, *length * sizeof(wchar_t));
  188     wcscpy(*wline, wbuf);
  189     free(wbuf);
  190     return ctrl_L;
  191 }
  192 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  193 
  194 
  195 /*
  196  * Output text to the cooked stream. Wrap lines as necessary.
  197  * Update the line count and the array of line offsets
  198  * Extend the lineoffset array as needed in CHUNK amounts.
  199  * flags are 'hints' to the pager about line content.
  200  * buf_len is the size put_cooked should use for its buffer.
  201  */
  202 static void
  203 put_cooked(
  204     size_t buf_len,
  205     t_bool wrap_lines,
  206     int flags,
  207     const char *fmt,
  208     ...)
  209 {
  210     char *p, *bufp, *buf;
  211     int wrap_column;
  212     int space;
  213     static int saved_flags = 0;
  214     va_list ap;
  215 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  216     int bytes;
  217     wint_t *wp;
  218 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  219 
  220     buf = my_malloc(buf_len + 1);
  221 
  222     va_start(ap, fmt);
  223     vsnprintf(buf, buf_len + 1, fmt, ap);
  224 
  225     if (tinrc.wrap_column < 0)
  226         wrap_column = ((tinrc.wrap_column > -cCOLS) ? cCOLS + tinrc.wrap_column : cCOLS);
  227     else
  228 #if 1
  229         wrap_column = ((tinrc.wrap_column > 0) ? tinrc.wrap_column : cCOLS);
  230 #else   /* never cut off long lines */
  231         wrap_column = (((tinrc.wrap_column > 0) && (tinrc.wrap_column < cCOLS)) ? tinrc.wrap_column : cCOLS);
  232 #endif /* 1 */
  233 
  234     p = bufp = buf;
  235 
  236 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  237     wp = my_malloc((MB_CUR_MAX + 1) * sizeof(wint_t));
  238 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  239 
  240     while (*p) {
  241         if (wrap_lines) {
  242             space = wrap_column;
  243             while (space > 0 && *p && *p != '\n') {
  244 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  245                 if ((bytes = mbtowc((wchar_t *) wp, p, MB_CUR_MAX)) > 0) {
  246                     if ((space -= wcwidth(*wp)) < 0)
  247                         break;
  248                     p += bytes;
  249                 } else
  250                     p++;
  251 #else
  252                 p++;
  253                 space--;
  254 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  255             }
  256         } else {
  257             while (*p && *p != '\n')
  258                 p++;
  259         }
  260         fwrite(bufp, 1, p - bufp, art->cooked);
  261         fputs("\n", art->cooked);
  262         if (*p == '\n')
  263             p++;
  264         bufp = p;
  265 
  266         if (art->cooked_lines == 0) {
  267             art->cookl = my_malloc(sizeof(t_lineinfo) * CHUNK);
  268             art->cookl[0].offset = 0;
  269         }
  270 
  271         /*
  272          * Pick up flags from a previous partial write
  273          */
  274         art->cookl[art->cooked_lines].flags = flags | saved_flags;
  275         saved_flags = 0;
  276         art->cooked_lines++;
  277 
  278         /*
  279          * Grow the array of lines if needed - we resize it properly at the end
  280          */
  281         if (art->cooked_lines % CHUNK == 0)
  282             art->cookl = my_realloc(art->cookl, sizeof(t_lineinfo) * CHUNK * ((art->cooked_lines / CHUNK) + 1));
  283 
  284         art->cookl[art->cooked_lines].offset = ftell(art->cooked);
  285     }
  286 
  287 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  288     free(wp);
  289 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  290 
  291     /*
  292      * If there is anything left over, then it must be a non \n terminated
  293      * partial line from base64 decoding etc.. Dump it now and the rest of
  294      * the line (with the \n) will fill in the t_lineinfo
  295      * We must save the flags now as the rest of the line may not have the same properties
  296      * We need to keep the length for accounting purposes
  297      */
  298     if (*bufp != '\0') {
  299         fputs(bufp, art->cooked);
  300         saved_flags = flags;
  301     }
  302 
  303     va_end(ap);
  304     free(buf);
  305 }
  306 
  307 
  308 /*
  309  * Add a new uuencode attachment description to the current part
  310  */
  311 static t_part *
  312 new_uue(
  313     t_part **part,
  314     char *name)
  315 {
  316     t_part *ptr = new_part((*part)->uue);
  317 
  318     if (!(*part)->uue)          /* new_part() is simple and doesn't attach list heads */
  319         (*part)->uue = ptr;
  320 
  321     free_list(ptr->params);
  322     /*
  323      * Load the name into the parameter list
  324      */
  325     ptr->params = new_params();
  326     ptr->params->name = my_strdup("name");
  327     ptr->params->value = my_strdup(str_trim(name));
  328 
  329     ptr->encoding = ENCODING_UUE;   /* treat as x-uuencode */
  330 
  331     ptr->offset = ftell(art->cooked);
  332     ptr->depth = (*part)->depth;    /* uue is at the same depth as the envelope */
  333 
  334     /*
  335      * If an extension is present, try and add a Content-Type
  336      */
  337     if ((name = strrchr(name, '.')) != NULL)
  338         lookup_mimetype(name + 1, ptr);
  339 
  340     return ptr;
  341 }
  342 
  343 
  344 /*
  345  * Get the suggested filename for an attachment. RFC says Content-Disposition
  346  * 'filename' supersedes Content-Type 'name'. We must also remove path
  347  * information.
  348  */
  349 const char *
  350 get_filename(
  351     t_param *ptr)
  352 {
  353     const char *name;
  354     char *p;
  355 
  356     if (!(name = get_param(ptr, "filename"))) {
  357         if (!(name = get_param(ptr, "name")))
  358             return NULL;
  359     }
  360 
  361     if ((p = strrchr(name, DIRSEP)))
  362         return p + 1;
  363 
  364     return name;
  365 }
  366 
  367 
  368 #define PUT_UUE(part, qualifier_text) \
  369     put_cooked(LEN, wrap_lines, C_UUE, _(txt_uue), \
  370         part->depth ? (part->depth - 1) * 4 : 0, "", \
  371         content_types[part->type], part->subtype, \
  372         qualifier_text, part->line_count, get_filename(part->params))
  373 
  374 #define PUT_ATTACH(part, depth, name, charset) \
  375     put_cooked(LEN, wrap_lines, C_ATTACH, _(txt_attach), \
  376         depth, "", \
  377         content_types[part->type], part->subtype, \
  378         content_encodings[part->encoding], \
  379         charset ? _(txt_attach_charset) : "", BlankIfNull(charset), \
  380         part->line_count, \
  381         name ? _(txt_name) : "", BlankIfNull(name)); \
  382         \
  383     if (part->description) \
  384         put_cooked(LEN, wrap_lines, C_ATTACH, \
  385             _(txt_attach_description), \
  386             depth, "", \
  387             part->description); \
  388     if (part->next != NULL || IS_PLAINTEXT(part)) \
  389         put_cooked(1, wrap_lines, C_ATTACH, "\n")
  390 
  391 /*
  392  * Decodes text bodies, remove sig's, detects uuencoded sections
  393  */
  394 static void
  395 process_text_body_part(
  396     t_bool wrap_lines,
  397     FILE *in,
  398     t_part *part,
  399     int hide_uue)
  400 {
  401     char *rest = NULL;
  402     char *line = NULL, *buf, *tmpline;
  403     const char *ncharset;
  404     size_t max_line_len = 0;
  405     int flags, len, lines_left, len_blank;
  406     int offsets[6];
  407     int size_offsets = ARRAY_SIZE(offsets);
  408     unsigned int lines_skipped = 0;
  409     t_bool in_sig = FALSE;          /* Set when in sig portion */
  410     t_bool in_uue = FALSE;          /* Set when in uuencoded section */
  411     t_bool in_verbatim = FALSE;     /* Set when in verbatim section */
  412     t_bool verbatim_begin = FALSE;  /* Set when verbatim_begin_regex matches */
  413     t_bool is_uubody;               /* Set when current line looks like a uuencoded line */
  414     t_bool first_line_blank = TRUE; /* Unset when first non-blank line is reached */
  415     t_bool put_blank_lines = FALSE; /* Set when previously skipped lines needs to put */
  416     t_part *curruue = NULL;
  417 
  418     if (part->uue) {                /* These are redone each time we recook/resize etc.. */
  419         free_parts(part->uue);
  420         part->uue = NULL;
  421     }
  422 
  423     fseek(in, part->offset, SEEK_SET);
  424 
  425     if (part->encoding == ENCODING_BASE64)
  426         (void) mmdecode(NULL, 'b', 0, NULL);        /* flush */
  427 
  428     lines_left = part->line_count;
  429     while ((lines_left > 0) || rest) {
  430         switch (part->encoding) {
  431             case ENCODING_BASE64:
  432                 lines_left -= read_decoded_base64_line(in, &line, &max_line_len, lines_left, &rest);
  433                 break;
  434 
  435             case ENCODING_QP:
  436                 lines_left -= read_decoded_qp_line(in, &line, &max_line_len, lines_left);
  437                 break;
  438 
  439             default:
  440                 if ((buf = tin_fgets(in, FALSE)) == NULL) {
  441                     FreeAndNull(line);
  442                     break;
  443                 }
  444 
  445                 /*
  446                  * tin_fgets() uses the returned space also internally
  447                  * so it's not advisable to use it for our own purposes
  448                  * especially if we must resize it.
  449                  * So copy buf to line (and resize line if necessary).
  450                  */
  451                 if (max_line_len < strlen(buf) + 2) {
  452                     max_line_len = strlen(buf) + 2;
  453                     line = my_realloc(line, max_line_len);
  454                 }
  455                 strcpy(line, buf);
  456 
  457                 /*
  458                  * FIXME: Some code in cook.c expects a '\n' at the end
  459                  * of the line. As tin_fgets() strips trailing '\n', re-add it.
  460                  * This should problably be fixed in that other code.
  461                  */
  462                 strcat(line, "\n");
  463 
  464                 lines_left--;
  465                 break;
  466         }
  467         if (!(line && strlen(line))) {
  468             FreeIfNeeded(rest);
  469             break;  /* premature end of file, file error etc. */
  470         }
  471 
  472         /* convert network to local charset, tex2iso, iso2asc etc. */
  473         ncharset = get_param(part->params, "charset");
  474         process_charsets(&line, &max_line_len, ncharset ? ncharset : "US-ASCII", tinrc.mm_local_charset, curr_group->attribute->tex2iso_conv && art->tex2iso);
  475 
  476 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  477         if (IS_LOCAL_CHARSET("UTF-8"))
  478             utf8_valid(line);
  479 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  480 
  481         len = (int) strlen(line);
  482 
  483         /*
  484          * trim article body and sig (not verbatim blocks):
  485          * - skip leading blank lines
  486          * - replace multiple blank lines with one empty line
  487          * - skip tailing blank lines, keep one if an
  488          *   attachment follows
  489          */
  490         if (curr_group->attribute->trim_article_body && !in_uue && !in_verbatim && !verbatim_begin) {
  491             len_blank = 1;
  492             tmpline = line;
  493             /* check if line contains only whitespace */
  494             while ((' ' == *tmpline) || ('\t' == *tmpline)) {
  495                 len_blank++;
  496                 tmpline++;
  497             }
  498             if (len_blank == len) {     /* line is blank */
  499                 if (lines_left == 0 && (curr_group->attribute->trim_article_body & SKIP_TRAILING)) {
  500                     if (!(part->next == NULL || (STRIP_ALTERNATIVE(art) && !IS_PLAINTEXT(part->next))))
  501                         put_cooked(1, TRUE, in_sig ? C_SIG : C_BODY, "\n");
  502                     continue;
  503                 }
  504                 if (first_line_blank) {
  505                     if (curr_group->attribute->trim_article_body & SKIP_LEADING)
  506                         continue;
  507                 } else if ((curr_group->attribute->trim_article_body & (COMPACT_MULTIPLE | SKIP_TRAILING)) && (!in_sig || curr_group->attribute->show_signatures)) {
  508                     lines_skipped++;
  509                     if (lines_left == 0 && !(curr_group->attribute->trim_article_body & SKIP_TRAILING)) {
  510                         for (; lines_skipped > 0; lines_skipped--)
  511                             put_cooked(1, TRUE, in_sig ? C_SIG : C_BODY, "\n");
  512                     }
  513                     continue;
  514                 }
  515             } else {    /* line is not blank */
  516                 if (first_line_blank)
  517                     first_line_blank = FALSE;
  518                 if (lines_skipped && (!in_sig || curr_group->attribute->show_signatures)) {
  519                     if (strcmp(line, SIGDASHES) != 0 || curr_group->attribute->show_signatures) {
  520                         if (curr_group->attribute->trim_article_body & COMPACT_MULTIPLE)
  521                             put_cooked(1, TRUE, in_sig ? C_SIG : C_BODY, "\n");
  522                         else
  523                             put_blank_lines = TRUE;
  524                     } else if (!(curr_group->attribute->trim_article_body & SKIP_TRAILING))
  525                         put_blank_lines = TRUE;
  526                     if (put_blank_lines) {
  527                         for (; lines_skipped > 0; lines_skipped--)
  528                             put_cooked(1, TRUE, in_sig ? C_SIG : C_BODY, "\n");
  529                     }
  530                     put_blank_lines = FALSE;
  531                     lines_skipped = 0;
  532                 }
  533             }
  534         } /* if (tinrc.trim_article_body...) */
  535 
  536         /* look for verbatim marks, set in_verbatim only for lines in between */
  537         if (curr_group->attribute->verbatim_handling) {
  538             if (verbatim_begin) {
  539                 in_verbatim = TRUE;
  540                 verbatim_begin = FALSE;
  541             } else if (!in_sig && !in_uue && !in_verbatim && MATCH_REGEX(verbatim_begin_regex, line, len))
  542                 verbatim_begin = TRUE;
  543             if (in_verbatim && MATCH_REGEX(verbatim_end_regex, line, len))
  544                 in_verbatim = FALSE;
  545         }
  546 
  547         if (!in_verbatim) {
  548             /*
  549              * Detect and skip signatures if necessary
  550              */
  551             if (!in_sig) {
  552                 if (strcmp(line, SIGDASHES) == 0) {
  553                     in_sig = TRUE;
  554                     if (in_uue) {
  555                         in_uue = FALSE;
  556                         if (hide_uue)
  557                             PUT_UUE(curruue, _(txt_incomplete));
  558                     }
  559                 }
  560             }
  561 
  562             if (in_sig && !(curr_group->attribute->show_signatures))
  563                 continue;                   /* No further processing needed */
  564 
  565             /*
  566              * Detect and process uuencoded sections
  567              * Look for the start or the end of a uuencoded section
  568              *
  569              * TODO: look for a tailing size line after end (non standard
  570              *       extension)?
  571              */
  572             if (pcre_exec(uubegin_regex.re, uubegin_regex.extra, line, len, 0, 0, offsets, size_offsets) != PCRE_ERROR_NOMATCH) {
  573                 in_uue = TRUE;
  574                 curruue = new_uue(&part, line + offsets[1]);
  575                 if (hide_uue)
  576                     continue;               /* Don't cook the 'begin' line */
  577             } else if (strncmp(line, "end\n", 4) == 0) {
  578                 if (in_uue) {
  579                     in_uue = FALSE;
  580                     if (hide_uue) {
  581                         PUT_UUE(curruue, "");
  582                         continue;           /* Don't cook the 'end' line */
  583                     }
  584                 }
  585             }
  586 
  587             /*
  588              * See if this line looks like a uuencoded 'body' line
  589              */
  590             is_uubody = FALSE;
  591 
  592             if (MATCH_REGEX(uubody_regex, line, len)) {
  593                 int sum = (((*line) - ' ') & 077) * 4 / 3;      /* uuencode octet checksum */
  594 
  595                 /* sum = 0 in a uubody only on the last line, a single ` */
  596                 if (sum == 0 && len == 1 + 1)           /* +1 for the \n */
  597                     is_uubody = TRUE;
  598                 else if (len == sum + 1 + 1)
  599                     is_uubody = TRUE;
  600 #ifdef DEBUG_ART
  601                 if (debug & DEBUG_MISC)
  602                     fprintf(stderr, "%s sum=%d len=%d (%s)\n", bool_unparse(is_uubody), sum, len, line);
  603 #endif /* DEBUG_ART */
  604             }
  605 
  606             if (in_uue) {
  607                 if (is_uubody)
  608                     curruue->line_count++;
  609                 else {
  610                     if (line[0] == '\n') {      /* Blank line in a uubody - definitely a failure */
  611                         /* fprintf(stderr, "not a uue line while reading a uue body?\n"); */
  612                         in_uue = FALSE;
  613                         if (hide_uue)
  614                             /* don't continue here, so we see the line that 'broke' in_uue */
  615                             PUT_UUE(curruue, _(txt_incomplete));
  616                     }
  617                 }
  618             } else {
  619                 /*
  620                  * UUE_ALL = 'Try harder' - we never saw a begin line, but useful
  621                  * when uue sections are split across > 1 article
  622                  */
  623                 if (is_uubody && hide_uue == UUE_ALL) {
  624                     char name[] = N_("(unknown)");
  625 
  626                     curruue = new_uue(&part, name);
  627                     curruue->line_count++;
  628                     in_uue = TRUE;
  629                     continue;
  630                 }
  631             }
  632 
  633             /*
  634              * Skip output if we're hiding uue or the sig
  635              */
  636             if (in_uue && hide_uue)
  637                 continue;   /* No further processing needed */
  638         }
  639 
  640         flags = in_verbatim ? C_VERBATIM : in_sig ? C_SIG : C_BODY;
  641 
  642         /*
  643          * Don't do any further handling of uue || verbatim lines
  644          */
  645         if (in_uue) {
  646             put_cooked(max_line_len, wrap_lines, flags, "%s", line);
  647             continue;
  648         } else if (in_verbatim) {
  649             expand_ctrl_chars(&line, &max_line_len, 8);
  650             put_cooked(max_line_len, wrap_lines, flags, "%s", line);
  651             continue;
  652         }
  653 
  654 #ifdef HAVE_COLOR
  655         /* keep order in sync with color.c:draw_pager_line() */
  656         if (quote_regex3.re) {
  657             if (MATCH_REGEX(quote_regex3, line, len))
  658                 flags |= C_QUOTE3;
  659             else if (quote_regex2.re) {
  660                 if (MATCH_REGEX(quote_regex2, line, len))
  661                     flags |= C_QUOTE2;
  662                 else if (curr_group->attribute->extquote_handling && extquote_regex.re) {
  663                     if (MATCH_REGEX(extquote_regex, line, len))
  664                         flags |= C_EXTQUOTE;
  665                     else if (quote_regex.re) {
  666                         if (MATCH_REGEX(quote_regex, line, len))
  667                             flags |= C_QUOTE1;
  668                     }
  669                 } else if (quote_regex.re) {
  670                     if (MATCH_REGEX(quote_regex, line, len))
  671                         flags |= C_QUOTE1;
  672                 }
  673             }
  674         }
  675 #endif /* HAVE_COLOR */
  676 
  677         if (MATCH_REGEX(url_regex, line, len))
  678             flags |= C_URL;
  679         if (MATCH_REGEX(mail_regex, line, len))
  680             flags |= C_MAIL;
  681         if (MATCH_REGEX(news_regex, line, len))
  682             flags |= C_NEWS;
  683 
  684         if (expand_ctrl_chars(&line, &max_line_len, tabwidth))
  685             flags |= C_CTRLL;               /* Line contains form-feed */
  686 
  687         buf = line;
  688 
  689         /*
  690          * Skip over the first space in case of Format=Flowed (space-stuffing)
  691          */
  692         if (part->format == FORMAT_FLOWED) {
  693             if (line[0] == ' ')
  694                 ++buf;
  695         }
  696 
  697         put_cooked(max_line_len, wrap_lines && (!IS_LOCAL_CHARSET("Big5")), flags, "%s", buf);
  698     } /* while */
  699 
  700     /*
  701      * Were we reading uue and ran off the end ?
  702      */
  703     if (in_uue && hide_uue)
  704         PUT_UUE(curruue, _(txt_incomplete));
  705 
  706     free(line);
  707 }
  708 
  709 
  710 /*
  711  * Return TRUE if this header should be printed as per
  712  * news_headers_to_[not_]display
  713  */
  714 static t_bool
  715 header_wanted(
  716     const char *line)
  717 {
  718     int i;
  719     t_bool ret = FALSE;
  720 
  721     if (curr_group->attribute->headers_to_display->num && (curr_group->attribute->headers_to_display->header[0][0] == '*'))
  722         ret = TRUE; /* wild do */
  723     else {
  724         for (i = 0; i < curr_group->attribute->headers_to_display->num; i++) {
  725             if (!strncasecmp(line, curr_group->attribute->headers_to_display->header[i], strlen(curr_group->attribute->headers_to_display->header[i]))) {
  726                 ret = TRUE;
  727                 break;
  728             }
  729         }
  730     }
  731 
  732     if (curr_group->attribute->headers_to_not_display->num && (curr_group->attribute->headers_to_not_display->header[0][0] == '*'))
  733         ret = FALSE; /* wild don't: doesn't make sense! */
  734     else {
  735         for (i = 0; i < curr_group->attribute->headers_to_not_display->num; i++) {
  736             if (!strncasecmp(line, curr_group->attribute->headers_to_not_display->header[i], strlen(curr_group->attribute->headers_to_not_display->header[i]))) {
  737                 ret = FALSE;
  738                 break;
  739             }
  740         }
  741     }
  742 
  743     return ret;
  744 }
  745 
  746 
  747 /* #define DEBUG_ART    1 */
  748 #ifdef DEBUG_ART
  749 static void
  750 dump_cooked(
  751     void)
  752 {
  753     char *line;
  754     int i;
  755 
  756     for (i = 0; i < art->cooked_lines; i++) {
  757         fseek(art->cooked, art->cookl[i].offset, SEEK_SET);
  758         line = tin_fgets(art->cooked, FALSE);
  759         fprintf(stderr, "[%3d] %4ld %3x [%s]\n", i, art->cookl[i].offset, art->cookl[i].flags, line);
  760     }
  761     fprintf(stderr, "%d lines cooked\n", art->cooked_lines);
  762 }
  763 #endif /* DEBUG_ART */
  764 
  765 
  766 /*
  767  * Check for charsets which may contain NULL bytes and thus break string
  768  * functions. Possibly incomplete.
  769  *
  770  * TODO: fix the other code to handle those charsets properly.
  771  */
  772 static t_bool
  773 charset_unsupported(
  774     const char *charset)
  775 {
  776     static const char *charsets[] = {
  777         "csUnicode",    /* alias for ISO-10646-UCS-2 */
  778         "csUCS4",       /* alias for ISO-10646-UCS-4 */
  779         "ISO-10646-UCS-2",
  780         "ISO-10646-UCS-4",
  781         "UTF-16",       /* covers also BE/LE */
  782         "UTF-32",       /* covers also BE/LE */
  783         NULL };
  784     const char **charsetptr = charsets;
  785     t_bool ret = FALSE;
  786 
  787     if (!charset)
  788         return ret;
  789 
  790     do {
  791         if (!strncasecmp(charset, *charsetptr, strlen(*charsetptr)))
  792             ret = TRUE;
  793     } while (!ret && *(++charsetptr) != NULL);
  794 
  795     return ret;
  796 }
  797 
  798 
  799 /*
  800  * 'cooks' an article, ie, prepare what will actually appear on the screen
  801  * It is not easy to do this in the same pass as the initial read since
  802  * boundary conditions for multipart articles make it harder to do on the
  803  * fly decoding.
  804  * We could have cooked the headers whilst they were being read but we're
  805  * trying to keep this simple.
  806  *
  807  * Expects:
  808  *      Fresh article context to write into
  809  *      parse_uue is set only when the art is opened to create t_parts for
  810  *      uue sections found, when resizing this is not needed
  811  *      hide_uue determines the folding of uue sections
  812  * Handles:
  813  *      multipart articles
  814  *      stripping of non text sections if skip_alternative
  815  *      Q and B decoding of text sections
  816  *      handling of uuencoded sections
  817  *      stripping of sigs if !show_signatures
  818  * Returns:
  819  *      TRUE on success
  820  *
  821  * TODO:
  822  *      give an error-message on at least disk-full
  823  */
  824 t_bool
  825 cook_article(
  826     t_bool wrap_lines,
  827     t_openartinfo *artinfo,
  828     int hide_uue,
  829     t_bool show_all_headers)
  830 {
  831     const char *charset;
  832     const char *name;
  833     char *line;
  834     struct t_header *hdr = &artinfo->hdr;
  835     t_bool header_put = FALSE;
  836     static const char *struct_header[] = {
  837         "Approved: ", "From: ", "Originator: ",
  838         "Reply-To: ", "Sender: ", "X-Cancelled-By: ", "X-Comment-To: ",
  839         "X-Submissions-To: ", "To: ", "Cc: ", "Bcc: ", "X-Originator: ", 0 };
  840 
  841     art = artinfo;              /* Global saves lots of passing artinfo around */
  842 
  843     if (!(art->cooked = tmpfile()))
  844         return FALSE;
  845 
  846     art->cooked_lines = 0;
  847 
  848     rewind(artinfo->raw);
  849 
  850     /*
  851      * Put down just the headers we want
  852      */
  853     while ((line = tin_fgets(artinfo->raw, TRUE)) != NULL) {
  854         if (line[0] == '\0') {              /* End of headers? */
  855             if (STRIP_ALTERNATIVE(artinfo)) {
  856                 if (header_wanted(_(txt_info_x_conversion_note))) {
  857                     header_put = TRUE;
  858                     put_cooked(LEN, wrap_lines, C_HEADER, _(txt_info_x_conversion_note));
  859                 }
  860             }
  861             if (header_put)
  862                 put_cooked(1, TRUE, 0, "\n");       /* put a newline after headers */
  863             break;
  864         }
  865 
  866         if (show_all_headers || header_wanted(line)) {  /* Put cooked data */
  867             const char **strptr = struct_header;
  868             char *l = NULL, *ptr, *foo, *bar;
  869             size_t i = LEN;
  870             t_bool found = FALSE;
  871 
  872             /* structured headers */
  873             do {
  874                 if (!strncasecmp(line, *strptr, strlen(*strptr))) {
  875                     foo = my_strdup(*strptr);
  876                     if ((ptr = strchr(foo, ':'))) {
  877                         *ptr = '\0';
  878                         unfold_header(line);
  879                         if ((ptr = parse_header(line, foo, TRUE, TRUE, FALSE))) {
  880 #if 0
  881                             /*
  882                              * TODO:
  883                              * idna_decode() currently expects just a FQDN
  884                              * or a mailaddress (with all comments stripped).
  885                              *
  886                              * we need to look for something like
  887                              * (?i)((?:\S+\.)?xn--[a-z0-9\.\-]{3,}\S+)\b
  888                              * and just decode $1
  889                              * maybe also in process_text_body_part()
  890                              */
  891                             bar = idna_decode(ptr);
  892 #else
  893                             bar = my_strdup(ptr);
  894 #endif /* 0 */
  895                             l = my_calloc(1, strlen(bar) + strlen(*strptr) + 1);
  896                             strncpy(l, line, strlen(*strptr));
  897                             strcat(l, bar);
  898                             free(bar);
  899                         }
  900                     }
  901                     free(foo);
  902                     found = TRUE;
  903                 }
  904             } while (!found && *(++strptr) != 0);
  905 
  906             /* unstructured but must not be decoded */
  907             if (l == NULL && (!strncasecmp(line, "References: ", 12) || !strncasecmp(line, "Message-ID: ", 12) || !strncasecmp(line, "Date: ", 6) || !strncasecmp(line, "Newsgroups: ", 12) || !strncasecmp(line, "Distribution: ", 14) || !strncasecmp(line, "Followup-To: ", 13) || !strncasecmp(line, "X-Face: ", 8) || !strncasecmp(line, "Cancel-Lock: ", 13) || !strncasecmp(line, "Cancel-Key: ", 12) || !strncasecmp(line, "Supersedes: ", 12)))
  908                 l = my_strdup(line);
  909 
  910             if (l == NULL)
  911                 l = my_strdup(rfc1522_decode(line));
  912 
  913 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  914             if (IS_LOCAL_CHARSET("UTF-8"))
  915                 utf8_valid(l);
  916 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  917             header_put = TRUE;
  918             expand_ctrl_chars(&l, &i, tabwidth);
  919             put_cooked(i, wrap_lines, C_HEADER, "%s", l);
  920             free(l);
  921         }
  922     }
  923 
  924     if (tin_errno != 0)
  925         return FALSE;
  926 
  927     /*
  928      * Process the attachments in turn, print a neato header, and process/decode
  929      * the body if of text type
  930      */
  931     if (hdr->mime && hdr->ext->type == TYPE_MULTIPART) {
  932         t_part *ptr;
  933 
  934         for (ptr = hdr->ext->next; ptr != NULL; ptr = ptr->next) {
  935             /*
  936              * Ignore non text/plain sections with alternative handling
  937              */
  938             if (STRIP_ALTERNATIVE(artinfo) && !IS_PLAINTEXT(ptr))
  939                 continue;
  940 
  941             name = get_filename(ptr->params);
  942             if (!strcmp(content_types[ptr->type], "text"))
  943                 charset = get_param(ptr->params, "charset");
  944             else
  945                 charset = NULL;
  946             PUT_ATTACH(ptr, (ptr->depth - 1) * 4, name, charset);
  947 
  948             /* Try to view anything of type text, may need to review this */
  949             if (IS_PLAINTEXT(ptr)) {
  950                 if (charset_unsupported(charset)) {
  951                     put_cooked(LEN, wrap_lines, C_ATTACH, _(txt_attach_unsup_charset), (ptr->depth - 1) * 4, "", charset);
  952                     if (ptr->next)
  953                         put_cooked(1, wrap_lines, C_ATTACH, "\n");
  954                 } else
  955                     process_text_body_part(wrap_lines, artinfo->raw, ptr, hide_uue);
  956             }
  957         }
  958     } else {
  959         if (!strcmp(content_types[hdr->ext->type], "text"))
  960             charset = get_param(hdr->ext->params, "charset");
  961         else
  962             charset = NULL;
  963         /*
  964          * A regular single-body article
  965          */
  966         if (IS_PLAINTEXT(hdr->ext)) {
  967             if (charset_unsupported(charset))
  968                 put_cooked(LEN, wrap_lines, C_ATTACH, _(txt_attach_unsup_charset), 0, "", charset);
  969             else
  970                 process_text_body_part(wrap_lines, artinfo->raw, hdr->ext, hide_uue);
  971         } else {
  972             /*
  973              * Non-textual main body
  974              */
  975             name = get_filename(hdr->ext->params);
  976             PUT_ATTACH(hdr->ext, 0, name, charset);
  977         }
  978     }
  979 
  980 #ifdef DEBUG_ART
  981     dump_cooked();
  982 #endif /* DEBUG_ART */
  983 
  984     if (art->cooked_lines > 0)
  985         art->cookl = my_realloc(art->cookl, sizeof(t_lineinfo) * art->cooked_lines);
  986 
  987     rewind(art->cooked);
  988     return (tin_errno != 0) ? FALSE : TRUE;
  989 }