"Fossies" - the Fresh Open Source Software Archive

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


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

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : rfc2046.c
    4  *  Author    : Jason Faultless <jason@altarstone.com>
    5  *  Created   : 2000-02-18
    6  *  Updated   : 2022-04-09
    7  *  Notes     : RFC 2046 MIME article parsing
    8  *
    9  * Copyright (c) 2000-2023 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  *
   16  * 1. Redistributions of source code must retain the above copyright notice,
   17  *    this list of conditions and the following disclaimer.
   18  *
   19  * 2. Redistributions in binary form must reproduce the above copyright
   20  *    notice, this list of conditions and the following disclaimer in the
   21  *    documentation and/or other materials provided with the distribution.
   22  *
   23  * 3. Neither the name of the copyright holder nor the names of its
   24  *    contributors may be used to endorse or promote products derived from
   25  *    this software without specific prior written permission.
   26  *
   27  * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   28  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   30  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
   31  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   37  * POSSIBILITY OF SUCH DAMAGE.
   38  */
   39 
   40 
   41 #ifndef TIN_H
   42 #   include "tin.h"
   43 #endif /* !TIN_H */
   44 
   45 
   46 /*
   47  * local prototypes
   48  */
   49 static char *get_charset(char *value);
   50 static char *get_quoted_string(char *source, char **dest);
   51 static char *get_token(const char *source);
   52 static char *strip_charset(char **value);
   53 static char *skip_equal_sign(char *source);
   54 static char *skip_space(char *source);
   55 static int boundary_cmp(const char *line, const char *boundary);
   56 static int count_lines(char *line);
   57 static int parse_multipart_article(FILE *infile, t_openartinfo *artinfo, t_part *part, int depth, t_bool show_progress_meter);
   58 static int parse_normal_article(FILE *in, t_openartinfo *artinfo, t_bool show_progress_meter);
   59 static int parse_rfc2045_article(FILE *infile, int line_count, t_openartinfo *artinfo, t_bool show_progress_meter);
   60 static unsigned int parse_content_encoding(char *encoding);
   61 static void decode_value(const char *charset, t_param *part);
   62 static void parse_content_type(char *type, t_part *content);
   63 static void parse_content_disposition(char *disp, t_part *part);
   64 static void parse_params(char *params, t_part *content);
   65 static void progress(int line_count);
   66 static void remove_cwsp(char *source);
   67 #ifdef DEBUG_ART
   68     static void dump_art(t_openartinfo *art);
   69 #endif /* DEBUG_ART */
   70 
   71 
   72 /*
   73  * Local variables
   74  */
   75 static int art_lines = 0;       /* lines in art on spool */
   76 static const char *progress_mesg = NULL;    /* message progress() should display */
   77 /* RFC 2231 decoding table */
   78 static const char xtbl[] = {
   79 /*        0  1  2  3    4  5  6  7    8  9  a  b    c  d  e  f */
   80 /* 0 */  -1,-1,-1,-1,  -1,-1,-1,-1,  -1,-1,-1,-1,  -1,-1,-1,-1,
   81 /* 1 */  -1,-1,-1,-1,  -1,-1,-1,-1,  -1,-1,-1,-1,  -1,-1,-1,-1,
   82 /* 2 */  -1,-1,-1,-1,  -1,-1,-1,-1,  -1,-1,-1,-1,  -1,-1,-1,-1,
   83 /* 3 */   0, 1, 2, 3,   4, 5, 6, 7,   8, 9,-1,-1,  -1,-1,-1,-1,
   84 /* 4 */  -1,10,11,12,  13,14,15,-1,  -1,-1,-1,-1,  -1,-1,-1,-1,
   85 /* 5 */  -1,-1,-1,-1,  -1,-1,-1,-1,  -1,-1,-1,-1,  -1,-1,-1,-1,
   86 /* 6 */  -1,10,11,12,  13,14,15,-1,  -1,-1,-1,-1,  -1,-1,-1,-1,
   87 /* 7 */  -1,-1,-1,-1,  -1,-1,-1,-1,  -1,-1,-1,-1,  -1,-1,-1,-1
   88 };
   89 
   90 #define XVAL(c) (xtbl[(unsigned int) (c)])
   91 /* C90: isxdigit(3) */
   92 #define IS_XDIGIT(c) (((c) >= '0' && (c) <= '9') || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F'))
   93 #define PARAM_SEP   "; \n"
   94 /* default parameters for Content-Type */
   95 #define CT_DEFPARMS "charset=US-ASCII"
   96 
   97 /*
   98  * Use the default message if one hasn't been supplied
   99  * Body search is currently the only function that has a different message
  100  */
  101 static void
  102 progress(
  103     int line_count)
  104 {
  105     if (progress_mesg != NULL && art_lines > 0 && line_count && line_count % MODULO_COUNT_NUM == 0)
  106         show_progress(progress_mesg, line_count, art_lines);
  107 }
  108 
  109 
  110 /*
  111  * Lookup content type in content_types[] array and return matching
  112  * index or -1
  113  */
  114 int
  115 content_type(
  116     char *type)
  117 {
  118     int i;
  119 
  120     if (type == NULL)
  121         return -1;
  122 
  123     for (i = 0; content_types[i] != NULL; ++i) {
  124         if (strcasecmp(type, content_types[i]) == 0)
  125             return i;
  126     }
  127 
  128     return -1;
  129 }
  130 
  131 
  132 /*
  133  * check if a line is a MIME boundary
  134  * returns BOUND_NONE if it is not, BOUND_START if normal boundary and
  135  * BOUND_END if closing boundary
  136  */
  137 static int
  138 boundary_cmp(
  139     const char *line,
  140     const char *boundary)
  141 {
  142     size_t blen = strlen(boundary);
  143     size_t len;
  144     char *e, *l;
  145     int nl;
  146 
  147     if ((len = strlen(line)) == 0)
  148         return BOUND_NONE;
  149 
  150     if (blen + 2 > len)
  151         return BOUND_NONE;
  152 
  153     /* remove trailing whites as per RFC 2046 5.1.1 */
  154     l = my_strdup(line);
  155     e = l + len - 1;
  156     while (e > l + blen + 1 && isspace((unsigned char) *e))
  157         *e-- = '\0';
  158 
  159     len = strlen(l);
  160 
  161     nl = l[len - 1] == '\n';
  162 
  163     if (len != blen + 2 + (size_t) nl && len != blen + 4 + (size_t) nl) {
  164         free(l);
  165         return BOUND_NONE;
  166     }
  167     if (l[0] != '-' || l[1] != '-') {
  168         free(l);
  169         return BOUND_NONE;
  170     }
  171 
  172     if (strncmp(l + 2, boundary, blen) != 0) {
  173         free(l);
  174         return BOUND_NONE;
  175     }
  176 
  177     if (l[blen + 2] != '-') {
  178         if (nl ? l[blen + 2] == '\n' : l[blen + 2] == '\0') {
  179             free(l);
  180             return BOUND_START;
  181         } else {
  182             free(l);
  183             return BOUND_NONE;
  184         }
  185     }
  186 
  187     if (l[blen + 3] != '-') {
  188         free(l);
  189         return BOUND_NONE;
  190     }
  191 
  192     if (nl ? l[blen + 4] == '\n' : l[blen + 4] == '\0') {
  193         free(l);
  194         return BOUND_END;
  195     }
  196     free(l);
  197     return BOUND_NONE;
  198 }
  199 
  200 
  201 /*
  202  * RFC2046 5.1.2 says that we are required to check for all possible
  203  * boundaries, not only the one that is expected. Iterate through all
  204  * the parts.
  205  */
  206 static int
  207 boundary_check(
  208     const char *line,
  209     t_part *part)
  210 {
  211     const char *boundary;
  212     int bnd = BOUND_NONE;
  213 
  214     for (; part != NULL; part = part->next) {
  215         /* We may not have even parsed a boundary for this part yet */
  216         if ((boundary = get_param(part->params, "boundary")) == NULL)
  217             continue;
  218         if ((bnd = boundary_cmp(line, boundary)) != BOUND_NONE)
  219             break;
  220     }
  221 
  222     return bnd;
  223 }
  224 
  225 
  226 #define ATTRIBUTE_DELIMS "()<>@,;:\\\"/[]?="
  227 
  228 static char *
  229 skip_space(
  230     char *source)
  231 {
  232     while ((*source) && ((*source == ' ') || (*source == '\t')))
  233         source++;
  234     return *source ? source : NULL;
  235 }
  236 
  237 
  238 /*
  239  * Removes comments and white space
  240  */
  241 static void
  242 remove_cwsp(
  243     char *source)
  244 {
  245     char *from, *to, src;
  246     int c_cnt = 0;
  247     t_bool inquotes = FALSE;
  248 
  249     from = to = source;
  250 
  251     while ((src = *from++) && c_cnt >= 0) {
  252         if (src == '"' && c_cnt == 0)
  253             inquotes = bool_not(inquotes);
  254 
  255         if (inquotes && src == '\\' && *from) {
  256             *to++ = src;
  257             *to++ = *from++;
  258             continue;
  259         }
  260 
  261         if (!inquotes) {
  262             /* skip over quoted pairs */
  263             if (c_cnt && src == '\\') {
  264                 ++from;
  265                 continue;
  266             }
  267             if (src == '(') {
  268                 ++c_cnt;
  269                 continue;
  270             }
  271             if (src == ')') {
  272                 --c_cnt;
  273                 continue;
  274             }
  275             if (c_cnt > 0 || src == ' ' || src == '\t')
  276                 continue;
  277         }
  278 
  279         *to++ = src;
  280     }
  281 
  282     /*
  283      * Setting *source = '\0' might be the right thing
  284      * because the header is damaged. Anyway, we let the
  285      * rest of the code pick up usable pieces.
  286      */
  287 #if 0
  288     if (c_cnt != 0)
  289         /* unbalanced parenthesis, header damaged */
  290         *source = '\0';
  291     else
  292 #endif /* 0 */
  293         *to = '\0';
  294 }
  295 
  296 
  297 static char *
  298 get_token(
  299     const char *source)
  300 {
  301     char *dest = my_strdup(source);
  302     char *ptr = dest;
  303 
  304     while (isascii((int) *ptr) && isprint((int) *ptr) && *ptr != ' ' && !strchr(ATTRIBUTE_DELIMS, *ptr))
  305         ptr++;
  306     *ptr = '\0';
  307 
  308     return my_realloc(dest, strlen(dest) + 1);
  309 }
  310 
  311 
  312 static char *
  313 get_quoted_string(
  314     char *source,
  315     char **dest)
  316 {
  317     char *ptr;
  318     t_bool quote = FALSE;
  319 
  320     *dest = my_malloc(strlen(source) + 1);
  321     ptr = *dest;
  322     source++; /* skip over double quote */
  323     while (*source) {
  324         if (*source == '\\') {
  325             quote = TRUE;   /* next char as-is */
  326             if (*++source == '\\') {
  327                 *ptr++ = *source++;
  328                 quote = FALSE;
  329             }
  330             continue;
  331         }
  332         if ((*source == '"') && !quote)
  333             break;  /* end of quoted-string */
  334         *ptr++ = *source++;
  335         quote = FALSE;
  336     }
  337     *ptr = '\0';
  338     *dest = my_realloc(*dest, strlen(*dest) + 1);
  339     return *source ? source + 1 : source;
  340 }
  341 
  342 
  343 /*
  344  * RFC 2231: Extract character set from parameter value
  345  */
  346 static char *
  347 get_charset(
  348     char *value)
  349 {
  350     char *charset, *ptr;
  351 
  352     /* no charset information present */
  353     if (!strchr(value, '\''))
  354         return NULL;
  355 
  356     /* no charset given -> fall back to us-ascii */
  357     if (*value == '\'')
  358         return my_strdup("US-ASCII");
  359 
  360     charset = my_strdup(value);
  361 
  362     if ((ptr = strchr(charset, '\'')))
  363         *ptr = '\0';
  364 
  365     return charset;
  366 }
  367 
  368 
  369 /*
  370  * RFC 2231: Decode parameter value according to the given
  371  *           character set
  372  */
  373 static void
  374 decode_value(
  375     const char *charset,
  376     t_param *part)
  377 {
  378     char *rptr, *wptr;
  379     const char *cset;
  380     size_t max_line_len = strlen(part->value);
  381 
  382     /*
  383      * we prefer part->charset if present, even if rfc 2231
  384      * forbids different charsets for each part
  385      */
  386     cset = part->charset ? part->charset : charset;
  387     rptr = wptr = part->value;
  388 
  389     while (*rptr) {
  390         if (*rptr == '%' && IS_XDIGIT(*(rptr + 1)) && IS_XDIGIT(*(rptr + 2))) {
  391             *wptr++ = (char) (XVAL(*(rptr + 1)) << 4 | XVAL(*(rptr + 2)));
  392             rptr += 3;
  393         } else
  394             *wptr++ = *rptr++;
  395     }
  396     *wptr = '\0';
  397 
  398     process_charsets(&(part->value), &max_line_len, cset, tinrc.mm_local_charset, FALSE);
  399     part->encoded = FALSE;
  400     FreeAndNull(part->charset);
  401 }
  402 
  403 
  404 /*
  405  * RFC 2231: Remove character set (and language information)
  406  *           from parameter value
  407  */
  408 static char *
  409 strip_charset(
  410     char **value)
  411 {
  412     char *newval, *ptr;
  413 
  414     if ((ptr = strrchr(*value, '\''))) {
  415         newval = my_strdup(ptr + 1);
  416         free(*value);
  417         *value = my_realloc(newval, strlen(newval) + 1);
  418     }
  419 
  420     return *value;
  421 }
  422 
  423 
  424 /*
  425  * Skip equal sign and (non compliant) white space around it
  426  */
  427 static char *
  428 skip_equal_sign(
  429     char *source)
  430 {
  431     if (!(source = skip_space(source)))
  432         return NULL;
  433 
  434     if (*source++ != '=')
  435         /* no equal sign, invalid header, stop parsing here */
  436         return NULL;
  437 
  438     return skip_space(source);
  439 }
  440 
  441 
  442 /*
  443  * Parse a Content-* parameter list into a linked list
  444  * Ensure the ->params element is correctly initialised before calling
  445  * TODO: may still not catch everything permitted in the RFC
  446  */
  447 static void
  448 parse_params(
  449     char *params,
  450     t_part *content)
  451 {
  452     char *name, *param, *value, *contp;
  453     int idx;
  454     t_bool encoded;
  455     t_param *ptr;
  456 
  457     param = params;
  458     while (*param) {
  459         idx = -1;
  460         encoded = FALSE;
  461         /* Skip over white space */
  462         if (!(param = skip_space(param)))
  463             break;
  464 
  465         /* catch parameter name */
  466         name = get_token(param);
  467         param += strlen(name);
  468 
  469         if (!*param) {
  470             /* Nothing follows, invalid, stop here */
  471             FreeIfNeeded(name);
  472             break;
  473         }
  474 
  475         /* RFC 2231 Character set and language information */
  476         if ((contp = strrchr(name, '*')) && !*(contp + 1)) {
  477             encoded = TRUE;
  478             *contp = '\0';
  479         }
  480 
  481         /* RFC 2231 Parameter Value Continuations */
  482         if ((contp = strchr(name, '*')) && *(contp + 1) >= '0' && *(contp + 1) <= '9') {
  483             idx = atoi(contp + 1);
  484             *contp = '\0';
  485         }
  486 
  487         if (!(param = skip_equal_sign(param))) {
  488             FreeIfNeeded(name);
  489             break;
  490         }
  491 
  492         /* catch parameter value; may be surrounded by double quotes */
  493         if (*param == '"')  /* parse quoted-string */
  494             param = get_quoted_string(param, &value);
  495         else {
  496             /* parse token */
  497             value = get_token(param);
  498             param += strlen(value);
  499         }
  500 
  501         ptr = new_params();
  502         ptr->name = name;
  503         if (encoded) {
  504             ptr->encoded = TRUE;
  505             ptr->charset = get_charset(value);
  506             ptr->value = strip_charset(&value);
  507         } else
  508             ptr->value = value;
  509 
  510         ptr->part = idx;
  511         ptr->next = content->params;        /* Push onto start of list */
  512         content->params = ptr;
  513 
  514         /* advance pointer to next parameter */
  515         while ((*param) && (*param != ';'))
  516             param++;
  517         if (*param == ';')
  518             param++;
  519     }
  520 }
  521 
  522 
  523 /*
  524  * Return a freshly allocated and initialised t_param structure
  525  */
  526 t_param *
  527 new_params(
  528     void)
  529 {
  530     t_param *ptr;
  531 
  532     ptr = my_malloc(sizeof(t_param));
  533     ptr->name = NULL;
  534     ptr->value = NULL;
  535     ptr->charset = NULL;
  536     ptr->part = -1;
  537     ptr->encoded = FALSE;
  538     ptr->enc_fallback = TRUE;
  539     ptr->next = NULL;
  540 
  541     return ptr;
  542 }
  543 
  544 
  545 /*
  546  * Free up a generic list object
  547  */
  548 void
  549 free_list(
  550     t_param *list)
  551 {
  552     while (list->next != NULL) {
  553         free_list(list->next);
  554         list->next = NULL;
  555     }
  556 
  557     free(list->name);
  558     free(list->value);
  559     FreeIfNeeded(list->charset);
  560     free(list);
  561 }
  562 
  563 
  564 /*
  565  * Return a parameter value from a param list or NULL
  566  */
  567 const char *
  568 get_param(
  569     t_param *list,
  570     const char *name)
  571 {
  572     char *tmpval, *charset = NULL;
  573     int i, j;
  574     size_t newlen;
  575     t_param *p_list, *c_list;
  576 
  577     for (p_list = list; p_list != NULL; p_list = p_list->next) {
  578         /*
  579          * RFC 2231 Parameter Value Continuations + Character Set
  580          *
  581          * part == 0,1,2...: parameter has several parts, must be concatenated
  582          * part == -1      : parameter has only one part
  583          * part == -2      : part has already been concatenated, main part has
  584          *                   part == -1
  585          *
  586          * charset         : character set if present
  587          */
  588         if (strcasecmp(name, p_list->name) == 0 && p_list->part > -2) {
  589             if (p_list->part == -1 && p_list->encoded && p_list->charset) {
  590                 decode_value(p_list->charset, p_list);
  591                 p_list->encoded = FALSE;
  592                 p_list->enc_fallback = FALSE;
  593             }
  594             if (p_list->part >= 0) {
  595                 newlen = 0;
  596                 if (p_list->charset) {
  597                     FreeIfNeeded(charset);
  598                     charset = my_strdup(p_list->charset);
  599                 }
  600                 for (j = 0, c_list = list; c_list != NULL; c_list = c_list->next) {
  601                     if (strcasecmp(name, c_list->name) == 0) {
  602                         if (c_list->part < 0)
  603                             continue;
  604                         if (c_list->part < p_list->part) {
  605                             if (c_list->charset) {
  606                                 FreeIfNeeded(charset);
  607                                 charset = my_strdup(c_list->charset);
  608                             }
  609                             p_list = c_list;
  610                         }
  611 
  612                         if (j < c_list->part)
  613                             j = c_list->part;
  614 
  615                         newlen += strlen(c_list->value);
  616                     }
  617                 }
  618                 p_list->value = my_realloc(p_list->value, newlen + 1);
  619                 if (charset)
  620                     decode_value(charset, p_list);
  621                 for (i = p_list->part + 1; i <= j; ++i) {
  622                     for (c_list = list; c_list != NULL; c_list = c_list->next) {
  623                         if (strcasecmp(name, c_list->name) == 0) {
  624                             if (c_list->part == i) {
  625                                 if (c_list->encoded && charset)
  626                                     decode_value(charset, c_list);
  627                                 strcat(p_list->value, c_list->value);
  628                                 c_list->part = -2;
  629                             }
  630                         }
  631                     }
  632                 }
  633                 p_list->part = -1;
  634                 p_list->encoded = FALSE;
  635                 p_list->enc_fallback = FALSE;
  636                 FreeAndNull(charset);
  637             }
  638             /*
  639              * RFC 2047 'encoded-word' is not allowed at this place but
  640              * some clients use this nevertheless -> we try to decode that
  641              */
  642             if (p_list->enc_fallback) {
  643                 tmpval = p_list->value;
  644                 if (*tmpval == '=' && *(++tmpval) == '?') {
  645                     if ((tmpval = rfc1522_decode(p_list->value))) {
  646                         free(p_list->value);
  647                         p_list->value = my_strdup(tmpval);
  648                     }
  649                 }
  650                 p_list->enc_fallback = FALSE;
  651             }
  652             return p_list->value;
  653         }
  654     }
  655 
  656     return NULL;
  657 }
  658 
  659 
  660 /*
  661  * Split a Content-Type header into a t_part structure
  662  */
  663 static void
  664 parse_content_type(
  665     char *type,
  666     t_part *content)
  667 {
  668     char *subtype, *params;
  669     int i;
  670 
  671     /* Remove comments and white space */
  672     remove_cwsp(type);
  673 
  674     /*
  675      * Split the type/subtype
  676      */
  677     if ((type = strtok(type, "/")) == NULL)
  678         return;
  679 
  680     /* Look up major type */
  681 
  682     /*
  683      * Unrecognised type, treat according to RFC
  684      */
  685     if ((i = content_type(type)) == -1) {
  686         content->type = TYPE_APPLICATION;
  687         free(content->subtype);
  688         content->subtype = my_strdup("octet-stream");
  689         return;
  690     } else
  691         content->type = (unsigned int) i;
  692 
  693     subtype = strtok(NULL, PARAM_SEP);
  694     /* save new subtype, or use pre-initialised value "plain" */
  695     if (subtype != NULL) {              /* check for broken Content-Type: is header without a subtype */
  696         free(content->subtype);             /* Pre-initialised to plain */
  697         content->subtype = my_strdup(subtype);
  698         str_lwr(content->subtype);
  699     }
  700 
  701     /*
  702      * Parse any parameters into a list
  703      */
  704     if ((params = strtok(NULL, "\n")) != NULL) {
  705         const char *format;
  706 #ifndef CHARSET_CONVERSION
  707         char defparms[] = CT_DEFPARMS;  /* must be writable */
  708 #endif /* !CHARSET_CONVERSION */
  709 
  710         parse_params(params, content);
  711         if (!get_param(content->params, "charset")) {   /* add default charset if needed */
  712 #ifndef CHARSET_CONVERSION
  713             parse_params(defparms, content);
  714 #else
  715             if (curr_group->attribute->undeclared_charset) {
  716                 char *charsetheader;
  717 
  718                 charsetheader = my_malloc(strlen(curr_group->attribute->undeclared_charset) + 9); /* 9=len('charset=\0') */
  719                 sprintf(charsetheader, "charset=%s", curr_group->attribute->undeclared_charset);
  720                 parse_params(charsetheader, content);
  721                 free(charsetheader);
  722             } else {
  723                 char defparms[] = CT_DEFPARMS;  /* must be writable */
  724 
  725                 parse_params(defparms, content);
  726             }
  727 #endif /* !CHARSET_CONVERSION */
  728         }
  729         if ((format = get_param(content->params, "format"))) {
  730             if (!strcasecmp(format, "flowed"))
  731                 content->format = FORMAT_FLOWED;
  732         }
  733     }
  734 }
  735 
  736 
  737 static unsigned int
  738 parse_content_encoding(
  739     char *encoding)
  740 {
  741     unsigned int i;
  742 
  743     /* Remove comments and white space */
  744     remove_cwsp(encoding);
  745 
  746     for (i = 0; content_encodings[i] != NULL; ++i) {
  747         if (strcasecmp(encoding, content_encodings[i]) == 0)
  748             return i;
  749     }
  750 
  751     /*
  752      * TODO: check rfc - may need to switch Content-Type to
  753      * application/octet-steam where this header exists but is unparsable.
  754      *
  755      * RFC 2045 6.2:
  756      * Labelling unencoded data containing 8bit characters as "7bit" is not
  757      * allowed, nor is labelling unencoded non-line-oriented data as anything
  758      * other than "binary" allowed.
  759      */
  760     return ENCODING_BINARY;
  761 }
  762 
  763 
  764 /*
  765  * We're only really interested in the filename parameter, which has
  766  * a higher precedence than the name parameter from Content-Type (RFC 1806)
  767  * Attach the parsed params to the part passed in 'part'
  768  */
  769 static void
  770 parse_content_disposition(
  771     char *disp,
  772     t_part *part)
  773 {
  774     char *ptr;
  775 
  776     /* Remove comments and white space */
  777     remove_cwsp(disp);
  778 
  779     strtok(disp, PARAM_SEP);
  780     if ((ptr = strtok(NULL, "\n")) == NULL)
  781         return;
  782 
  783     parse_params(ptr, part);
  784 }
  785 
  786 
  787 /*
  788  * Return a freshly allocated and initialised part structure attached to the
  789  * end of the list of article parts given
  790  */
  791 t_part *
  792 new_part(
  793     t_part *part)
  794 {
  795     t_part *p;
  796     t_part *ptr = my_malloc(sizeof(t_part));
  797 #ifndef CHARSET_CONVERSION
  798     char defparms[] = CT_DEFPARMS;  /* must be writable */
  799 #endif /* !CHARSET_CONVERSION */
  800 
  801     ptr->type = TYPE_TEXT;                  /* Defaults per RFC */
  802     ptr->subtype = my_strdup("plain");
  803     ptr->description = NULL;
  804     ptr->encoding = ENCODING_7BIT;
  805     ptr->format = FORMAT_FIXED;
  806     ptr->params = NULL;
  807 
  808 #ifndef CHARSET_CONVERSION
  809     parse_params(defparms, ptr);
  810 #else
  811     if (curr_group && curr_group->attribute->undeclared_charset) {
  812         char *charsetheader;
  813 
  814         charsetheader = my_malloc(strlen(curr_group->attribute->undeclared_charset) + 9); /* 9=len('charset=\0') */
  815         sprintf(charsetheader, "charset=%s", curr_group->attribute->undeclared_charset);
  816         parse_params(charsetheader, ptr);
  817         free(charsetheader);
  818     } else {
  819         char defparms[] = CT_DEFPARMS;  /* must be writable */
  820 
  821         parse_params(defparms, ptr);
  822     }
  823 #endif /* !CHARSET_CONVERSION */
  824 
  825     ptr->offset = 0;
  826     ptr->line_count = 0;
  827     ptr->depth = 0;                         /* Not an embedded object (yet) */
  828     ptr->uue = NULL;
  829     ptr->next = NULL;
  830 
  831     if (part == NULL)                       /* List head - we don't do this */
  832         return ptr;
  833 
  834     for (p = part; p->next != NULL; p = p->next)
  835         ;
  836     p->next = ptr;
  837 
  838     return ptr;
  839 }
  840 
  841 
  842 /*
  843  * Free a linked list of t_part
  844  */
  845 void
  846 free_parts(
  847     t_part *ptr)
  848 {
  849     while (ptr->next != NULL) {
  850         free_parts(ptr->next);
  851         ptr->next = NULL;
  852     }
  853 
  854     free(ptr->subtype);
  855     FreeAndNull(ptr->description);
  856     if (ptr->params)
  857         free_list(ptr->params);
  858     if (ptr->uue)
  859         free_parts(ptr->uue);
  860     free(ptr);
  861 }
  862 
  863 
  864 void
  865 free_and_init_header(
  866     struct t_header *hdr)
  867 {
  868     /*
  869      * Initialise the header struct
  870      */
  871     FreeAndNull(hdr->from);
  872     FreeAndNull(hdr->to);
  873     FreeAndNull(hdr->cc);
  874     FreeAndNull(hdr->bcc);
  875     FreeAndNull(hdr->date);
  876     FreeAndNull(hdr->subj);
  877     FreeAndNull(hdr->org);
  878     FreeAndNull(hdr->replyto);
  879     FreeAndNull(hdr->newsgroups);
  880     FreeAndNull(hdr->messageid);
  881     FreeAndNull(hdr->references);
  882     FreeAndNull(hdr->distrib);
  883     FreeAndNull(hdr->keywords);
  884     FreeAndNull(hdr->summary);
  885     FreeAndNull(hdr->followup);
  886     FreeAndNull(hdr->ftnto);
  887 #ifdef XFACE_ABLE
  888     FreeAndNull(hdr->xface);
  889 #endif /* XFACE_ABLE */
  890     hdr->mime = FALSE;
  891 
  892     if (hdr->ext)
  893         free_parts(hdr->ext);
  894     hdr->ext = NULL;
  895 }
  896 
  897 
  898 /*
  899  * buf:         Article header
  900  * pat:         Text to match in header
  901  * decode:      RFC2047-decode the header
  902  * structured:  extract address-part before decoding the header
  903  *
  904  * Returns:
  905  *  (decoded) body of header if matched or NULL
  906  */
  907 char *
  908 parse_header(
  909     char *buf,
  910     const char *pat,
  911     t_bool decode,
  912     t_bool structured,
  913     t_bool keep_tab)
  914 {
  915     size_t plen = strlen(pat);
  916     char *ptr = buf + plen;
  917 
  918     /*
  919      * Does ': ' follow the header text?
  920      */
  921     if (!(*ptr && *(ptr + 1) && *ptr == ':' && *(ptr + 1) == ' '))
  922         return NULL;
  923 
  924     /*
  925      * If the header matches, skip past the ': ' and any leading whitespace
  926      */
  927     if (strncasecmp(buf, pat, plen) != 0)
  928         return NULL;
  929 
  930     ptr += 2;
  931 
  932     str_trim(ptr);
  933     if (!*ptr)
  934         return NULL;
  935 
  936     if (decode) {
  937         if (structured) {
  938             char addr[HEADER_LEN];
  939             char name[HEADER_LEN];
  940             int type;
  941 
  942             if (gnksa_split_from(ptr, addr, name, &type) == GNKSA_OK) {
  943                 buffer_to_ascii(addr);
  944 
  945                 if (*name) {
  946                     if (type == GNKSA_ADDRTYPE_OLDSTYLE)
  947                         sprintf(ptr, "%s (%s)", addr, convert_to_printable(rfc1522_decode(name), keep_tab));
  948                     else
  949                         sprintf(ptr, "%s <%s>", convert_to_printable(rfc1522_decode(name), keep_tab), addr);
  950                 } else
  951                     strcpy(ptr, addr);
  952             } else
  953                 return convert_to_printable(ptr, keep_tab);
  954         } else
  955             return (convert_to_printable(rfc1522_decode(ptr), keep_tab));
  956     }
  957 
  958     return ptr;
  959 }
  960 
  961 
  962 /*
  963  * Read main article headers into a blank header structure.
  964  * Pass the data 'from' -> 'to' when reading via NNTP
  965  * Return tin_errno (basically will be !=0 if reading was 'q'uit)
  966  * We have to guard against 'to' here since this function is exported
  967  */
  968 int
  969 parse_rfc822_headers(
  970     struct t_header *hdr,
  971     FILE *from,
  972     FILE *to)
  973 {
  974     char *line;
  975     char *ptr;
  976 
  977     memset(hdr, 0, sizeof(struct t_header));
  978     hdr->mime = FALSE;
  979     hdr->ext = new_part(NULL);      /* Initialise MIME data */
  980 
  981     while ((line = tin_fgets(from, TRUE)) != NULL) {
  982         if (read_news_via_nntp && to) {
  983             fprintf(to, "%s\n", line);      /* Put raw data */
  984 #ifdef DEBUG
  985             if ((debug & DEBUG_NNTP) && verbose > 1)
  986                 debug_print_file("NNTP", "<<<%s%s", logtime(), line);
  987 #endif /* DEBUG */
  988         }
  989         /*
  990          * End of headers ?
  991          */
  992         if (line[0] == '\0') {
  993             if (to)
  994                 hdr->ext->offset = ftell(to);   /* Offset of main body */
  995 
  996             /* avoid null subject */
  997             if (!hdr->subj)
  998                 hdr->subj = my_strdup("");
  999 
 1000             return 0;
 1001         }
 1002 
 1003         /*
 1004          * FIXME: multiple headers of the same name could lead to information
 1005          *        loss (multiple Cc: lines are allowed, for example)
 1006          */
 1007         unfold_header(line);
 1008         if ((ptr = parse_header(line, "From", TRUE, TRUE, FALSE))) {
 1009             FreeIfNeeded(hdr->from);
 1010             hdr->from = my_strdup(ptr);
 1011             continue;
 1012         }
 1013         if ((ptr = parse_header(line, "To", TRUE, TRUE, FALSE))) {
 1014             FreeIfNeeded(hdr->to);
 1015             hdr->to = my_strdup(ptr);
 1016             continue;
 1017         }
 1018         if ((ptr = parse_header(line, "Cc", TRUE, TRUE, FALSE))) {
 1019             FreeIfNeeded(hdr->cc);
 1020             hdr->cc = my_strdup(ptr);
 1021             continue;
 1022         }
 1023         if ((ptr = parse_header(line, "Bcc", TRUE, TRUE, FALSE))) {
 1024             FreeIfNeeded(hdr->bcc);
 1025             hdr->bcc = my_strdup(ptr);
 1026             continue;
 1027         }
 1028         if ((ptr = parse_header(line, "Date", FALSE, FALSE, FALSE))) {
 1029             FreeIfNeeded(hdr->date);
 1030             hdr->date = my_strdup(ptr);
 1031             continue;
 1032         }
 1033         if ((ptr = parse_header(line, "Subject", TRUE, FALSE, TRUE))) {
 1034             FreeIfNeeded(hdr->subj);
 1035             hdr->subj = my_strdup(ptr);
 1036             continue;
 1037         }
 1038         if ((ptr = parse_header(line, "Organization", TRUE, FALSE, TRUE))) {
 1039             FreeIfNeeded(hdr->org);
 1040             hdr->org = my_strdup(ptr);
 1041             continue;
 1042         }
 1043         if ((ptr = parse_header(line, "Reply-To", TRUE, TRUE, FALSE))) {
 1044             FreeIfNeeded(hdr->replyto);
 1045             hdr->replyto = my_strdup(ptr);
 1046             continue;
 1047         }
 1048         if ((ptr = parse_header(line, "Newsgroups", FALSE, FALSE, FALSE))) {
 1049             FreeIfNeeded(hdr->newsgroups);
 1050             hdr->newsgroups = my_strdup(ptr);
 1051             continue;
 1052         }
 1053         if ((ptr = parse_header(line, "Message-ID", FALSE, FALSE, FALSE))) {
 1054             FreeIfNeeded(hdr->messageid);
 1055             hdr->messageid = my_strdup(ptr);
 1056             continue;
 1057         }
 1058         if ((ptr = parse_header(line, "References", FALSE, FALSE, FALSE))) {
 1059             FreeIfNeeded(hdr->references);
 1060             hdr->references = my_strdup(ptr);
 1061             continue;
 1062         }
 1063         if ((ptr = parse_header(line, "Distribution", FALSE, FALSE, FALSE))) {
 1064             FreeIfNeeded(hdr->distrib);
 1065             hdr->distrib = my_strdup(ptr);
 1066             continue;
 1067         }
 1068         if ((ptr = parse_header(line, "Keywords", TRUE, FALSE, FALSE))) {
 1069             FreeIfNeeded(hdr->keywords);
 1070             hdr->keywords = my_strdup(ptr);
 1071             continue;
 1072         }
 1073         if ((ptr = parse_header(line, "Summary", TRUE, FALSE, FALSE))) {
 1074             FreeIfNeeded(hdr->summary);
 1075             hdr->summary = my_strdup(ptr);
 1076             continue;
 1077         }
 1078         if ((ptr = parse_header(line, "Followup-To", FALSE, FALSE, FALSE))) {
 1079             FreeIfNeeded(hdr->followup);
 1080             hdr->followup = my_strdup(ptr);
 1081             continue;
 1082         }
 1083         if ((ptr = parse_header(line, "X-Comment-To", TRUE, TRUE, FALSE))) {
 1084             FreeIfNeeded(hdr->ftnto);
 1085             hdr->ftnto = my_strdup(ptr);
 1086             continue;
 1087         }
 1088 #ifdef XFACE_ABLE
 1089         if ((ptr = parse_header(line, "X-Face", FALSE, FALSE, FALSE))) {
 1090             FreeIfNeeded(hdr->xface);
 1091             hdr->xface = my_strdup(ptr);
 1092             continue;
 1093         }
 1094 #endif /* XFACE_ABLE */
 1095         /* TODO: check version */
 1096         if (parse_header(line, "MIME-Version", FALSE, FALSE, FALSE)) {
 1097             hdr->mime = TRUE;
 1098             continue;
 1099         }
 1100         if ((ptr = parse_header(line, "Content-Type", FALSE, FALSE, FALSE))) {
 1101             parse_content_type(ptr, hdr->ext);
 1102             continue;
 1103         }
 1104         if ((ptr = parse_header(line, "Content-Transfer-Encoding", FALSE, FALSE, FALSE))) {
 1105             hdr->ext->encoding = parse_content_encoding(ptr);
 1106             continue;
 1107         }
 1108         if ((ptr = parse_header(line, "Content-Description", TRUE, FALSE, FALSE))) {
 1109             FreeIfNeeded(hdr->ext->description);
 1110             hdr->ext->description = my_strdup(ptr);
 1111             continue;
 1112         }
 1113         if ((ptr = parse_header(line, "Content-Disposition", FALSE, FALSE, FALSE))) {
 1114             parse_content_disposition(ptr, hdr->ext);
 1115             continue;
 1116         }
 1117     }
 1118 
 1119     return tin_errno;
 1120 }
 1121 
 1122 
 1123 /*
 1124  * Count lines in a continuated header.
 1125  * line MUST NOT end in a newline.
 1126  */
 1127 static int
 1128 count_lines(
 1129     char *line)
 1130 {
 1131     char *src = line;
 1132     char c;
 1133     int lines = 1;
 1134 
 1135     while ((c = *src++))
 1136         if (c == '\n')
 1137             lines++;
 1138     return lines;
 1139 }
 1140 
 1141 
 1142 /*
 1143  * Unfold header, i.e. strip any newline off it. Don't strip other
 1144  * whitespace, it depends on the header if this is legal (structured
 1145  * headers) or not (unstructured headers, e.g. Subject)
 1146  */
 1147 void
 1148 unfold_header(
 1149     char *line)
 1150 {
 1151     char *src = line, *dst = line;
 1152     char c;
 1153 
 1154     while ((c = *src++)) {
 1155         if (c != '\n')
 1156             *dst++ = c;
 1157     }
 1158     *dst = c;
 1159 }
 1160 
 1161 
 1162 #define M_SEARCHING 1   /* Looking for boundary */
 1163 #define M_HDR       2   /* In MIME headers */
 1164 #define M_BODY      3   /* In MIME body */
 1165 
 1166 #define TIN_EOF     0xf00   /* Used internally for error recovery */
 1167 
 1168 /*
 1169  * Handles multipart/ article types, write data to a raw stream when reading via NNTP
 1170  * artinfo is used for generic article pointers
 1171  * part contains content info about the attachment we're parsing
 1172  * depth is the number of levels by which the current part is embedded
 1173  * Returns a tin_errno value which is '&'ed with TIN_EOF if the end of the
 1174  * article is reached (to prevent broken articles from hanging the NNTP socket)
 1175  */
 1176 static int
 1177 parse_multipart_article(
 1178     FILE *infile,
 1179     t_openartinfo *artinfo,
 1180     t_part *part,
 1181     int depth,
 1182     t_bool show_progress_meter)
 1183 {
 1184     char *line;
 1185     char *ptr;
 1186     const char *bd;
 1187     int bnd;
 1188     int state = M_SEARCHING;
 1189     t_bool is_rfc822 = FALSE;
 1190     t_part *curr_part = NULL, *rfc822_part = NULL;
 1191 
 1192     while ((line = tin_fgets(infile, (state == M_HDR))) != NULL) {
 1193 /* fprintf(stderr, "%d---:%s\n", depth, line); */
 1194 
 1195         /*
 1196          * Check current line for boundary markers
 1197          */
 1198         bnd = boundary_check(line, artinfo->hdr.ext);
 1199 
 1200         if (read_news_via_nntp) {
 1201             fprintf(artinfo->raw, "%s\n", line);
 1202 #ifdef DEBUG
 1203             if ((debug & DEBUG_NNTP) && verbose > 1)
 1204                 debug_print_file("NNTP", "<<<%s%s", logtime(), line);
 1205 #endif /* DEBUG */
 1206         }
 1207 
 1208         artinfo->hdr.ext->line_count += count_lines(line);
 1209         if (show_progress_meter)
 1210             progress(artinfo->hdr.ext->line_count);     /* Overall line count */
 1211 
 1212         if (part && part != artinfo->hdr.ext)
 1213             part->line_count += count_lines(line);
 1214 
 1215         if (is_rfc822 && rfc822_part)
 1216             rfc822_part->line_count += count_lines(line);
 1217 
 1218         if (bnd == BOUND_END) {                         /* End of this part detected */
 1219             if (is_rfc822 && rfc822_part)
 1220                 rfc822_part->line_count -= count_lines(line);
 1221             /*
 1222              * When we have reached the end boundary of the outermost envelope
 1223              * just log any trailing data for the raw article format.
 1224              */
 1225             if ((bd = get_param(artinfo->hdr.ext->params, "boundary")) != NULL) {
 1226                 if (boundary_cmp(line, bd) == BOUND_END)
 1227                     depth = 0;
 1228             }
 1229 #if 0 /* doesn't count tailing lines after envelop mime part - correct but confusing */
 1230             if (read_news_via_nntp && depth == 0)
 1231                 while ((line = tin_fgets(infile, FALSE)) != NULL)
 1232                     fprintf(artinfo->raw, "%s\n", line);
 1233 #else
 1234             if (depth == 0) {
 1235                 while ((line = tin_fgets(infile, FALSE)) != NULL) {
 1236                     if (read_news_via_nntp)
 1237                         fprintf(artinfo->raw, "%s\n", line);
 1238                     artinfo->hdr.ext->line_count++;
 1239                 }
 1240                 return tin_errno | TIN_EOF;     /* Flag EOF */
 1241             }
 1242 #endif /* 0 */
 1243             return tin_errno;
 1244         }
 1245 
 1246         switch (state) {
 1247             case M_SEARCHING:
 1248                 switch (bnd) {
 1249                     case BOUND_NONE:
 1250                         break;              /* Keep looking */
 1251 
 1252                     case BOUND_START:
 1253                         state = M_HDR;      /* Now parsing headers of a part */
 1254                         curr_part = new_part(part);
 1255                         curr_part->depth = depth;
 1256                         break;
 1257                 }
 1258                 break;
 1259 
 1260             case M_HDR:
 1261                 switch (bnd) {
 1262                     case BOUND_START:
 1263 #ifdef DEBUG
 1264                         if (debug & DEBUG_MISC)
 1265                             error_message(2, _(txt_error_mime_start));
 1266 #endif /* DEBUG */
 1267                         continue;
 1268 
 1269                     case BOUND_NONE:
 1270                         break;              /* Correct - No boundary */
 1271                 }
 1272 
 1273                 if (*line == '\0') {        /* End of MIME headers */
 1274                     state = M_BODY;
 1275                     curr_part->offset = ftell(artinfo->raw);
 1276 
 1277                     if (curr_part->type == TYPE_MULTIPART) {    /* Complex multipart article */
 1278                         int ret, old_line_count;
 1279 
 1280                         old_line_count = curr_part->line_count;
 1281                         if ((ret = parse_multipart_article(infile, artinfo, curr_part, depth + 1, show_progress_meter)) != 0)
 1282                             return ret;                         /* User abort or EOF reached */
 1283                         if (part && part != artinfo->hdr.ext)
 1284                             part->line_count += curr_part->line_count - old_line_count;
 1285                         if (is_rfc822 && rfc822_part)
 1286                             rfc822_part->line_count += curr_part->line_count - old_line_count;
 1287                     } else if (curr_part->type == TYPE_MESSAGE && !strcasecmp("RFC822", curr_part->subtype)) {
 1288                         is_rfc822 = TRUE;
 1289                         rfc822_part = curr_part;
 1290                         state = M_HDR;
 1291                         curr_part = new_part(part);
 1292                         curr_part->depth = ++depth;
 1293                     }
 1294                     break;
 1295                 }
 1296 
 1297                 /*
 1298                  * Keep headers that interest us
 1299                  */
 1300 /* fprintf(stderr, "HDR:%s\n", line); */
 1301                 unfold_header(line);
 1302                 if ((ptr = parse_header(line, "Content-Type", FALSE, FALSE, FALSE))) {
 1303                     parse_content_type(ptr, curr_part);
 1304                     break;
 1305                 }
 1306                 if ((ptr = parse_header(line, "Content-Transfer-Encoding", FALSE, FALSE, FALSE))) {
 1307                     curr_part->encoding = parse_content_encoding(ptr);
 1308                     break;
 1309                 }
 1310                 if ((ptr = parse_header(line, "Content-Disposition", FALSE, FALSE, FALSE))) {
 1311                     parse_content_disposition(ptr, curr_part);
 1312                     break;
 1313                 }
 1314                 if ((ptr = parse_header(line, "Content-Description", TRUE, FALSE, FALSE))) {
 1315                     FreeIfNeeded(curr_part->description);
 1316                     curr_part->description = my_strdup(ptr);
 1317                     break;
 1318                 }
 1319                 break;
 1320 
 1321             case M_BODY:
 1322                 switch (bnd) {
 1323                     case BOUND_NONE:
 1324 /* fprintf(stderr, "BOD:%s\n", line); */
 1325                         curr_part->line_count++;
 1326                         break;
 1327 
 1328                     case BOUND_START:       /* Start new attachment */
 1329                         if (is_rfc822) {
 1330                             --depth;
 1331                             rfc822_part->line_count--;
 1332                             rfc822_part = NULL;
 1333                             is_rfc822 = FALSE;
 1334                         }
 1335                         state = M_HDR;
 1336                         curr_part = new_part(part);
 1337                         curr_part->depth = depth;
 1338                         break;
 1339                 }
 1340                 break;
 1341         } /* switch (state) */
 1342     } /* while() */
 1343 
 1344     /*
 1345      * We only reach this point when we (unexpectedly) reach the end of the
 1346      * article
 1347      */
 1348     return tin_errno | TIN_EOF;     /* Flag EOF */
 1349 }
 1350 
 1351 
 1352 /*
 1353  * Parse a non-multipart article, merely a passthrough and bean counter
 1354  */
 1355 static int
 1356 parse_normal_article(
 1357     FILE *in,
 1358     t_openartinfo *artinfo,
 1359     t_bool show_progress_meter)
 1360 {
 1361     char *line;
 1362 
 1363     while ((line = tin_fgets(in, FALSE)) != NULL) {
 1364         if (read_news_via_nntp) {
 1365             fprintf(artinfo->raw, "%s\n", line);
 1366 #ifdef DEBUG
 1367             if ((debug & DEBUG_NNTP) && verbose > 1)
 1368                 debug_print_file("NNTP", "<<<%s%s", logtime(), line);
 1369 #endif /* DEBUG */
 1370         }
 1371 
 1372         ++artinfo->hdr.ext->line_count;
 1373 
 1374         if (show_progress_meter)
 1375             progress(artinfo->hdr.ext->line_count);
 1376     }
 1377     return tin_errno;
 1378 }
 1379 
 1380 
 1381 #ifdef DEBUG_ART
 1382 /* DEBUG dump of what we got */
 1383 static void
 1384 dump_uue(
 1385     t_part *ptr,
 1386     t_openartinfo *art)
 1387 {
 1388     if (ptr->uue != NULL) {
 1389         t_part *uu;
 1390         for (uu = ptr->uue; uu != NULL; uu = uu->next) {
 1391             fprintf(stderr, "UU: %s\n", get_param(uu->params, "name"));
 1392             fprintf(stderr, "    Content-Type: %s/%s\n    Content-Transfer-Encoding: %s\n",
 1393                 content_types[uu->type], uu->subtype,
 1394                 content_encodings[uu->encoding]);
 1395             fprintf(stderr, "    Offset: %ld  Lines: %d\n", uu->offset, uu->line_count);
 1396             fprintf(stderr, "    Depth: %d\n", uu->depth);
 1397             fseek(art->raw, uu->offset, SEEK_SET);
 1398             fprintf(stderr, "[%s]\n\n", tin_fgets(art->raw, FALSE));
 1399         }
 1400     }
 1401 }
 1402 
 1403 
 1404 static void
 1405 dump_art(
 1406     t_openartinfo *art)
 1407 {
 1408     t_part *ptr;
 1409     t_param *pptr;
 1410     struct t_header note_h = art->hdr;
 1411 
 1412     fprintf(stderr, "\nMain body\nMIME-Version: %u\n", note_h.mime);
 1413     fprintf(stderr, "Content-Type: %s/%s\nContent-Transfer-Encoding: %s\n",
 1414         content_types[note_h.ext->type], note_h.ext->subtype,
 1415         content_encodings[note_h.ext->encoding]);
 1416     if (note_h.ext->description)
 1417         fprintf(stderr, "Content-Description: %s\n", note_h.ext->description);
 1418     fprintf(stderr, "Offset: %ld\nLines: %d\n", note_h.ext->offset, note_h.ext->line_count);
 1419     for (pptr = note_h.ext->params; pptr != NULL; pptr = pptr->next)
 1420         fprintf(stderr, "P: %s = %s\n", pptr->name, pptr->value);
 1421     dump_uue(note_h.ext, art);
 1422     fseek(art->raw, note_h.ext->offset, SEEK_SET);
 1423     fprintf(stderr, "[%s]\n\n", tin_fgets(art->raw, FALSE));
 1424     fprintf(stderr, "\n");
 1425 
 1426     for (ptr = note_h.ext->next; ptr != NULL; ptr = ptr->next) {
 1427         fprintf(stderr, "Attachment:\n");
 1428         fprintf(stderr, "\tContent-Type: %s/%s\n\tContent-Transfer-Encoding: %s\n",
 1429             content_types[ptr->type], ptr->subtype,
 1430             content_encodings[ptr->encoding]);
 1431         if (ptr->description)
 1432             fprintf(stderr, "\tContent-Description: %s\n", ptr->description);
 1433         fprintf(stderr, "\tOffset: %ld\n\tLines: %d\n", ptr->offset, ptr->line_count);
 1434         fprintf(stderr, "\tDepth: %d\n", ptr->depth);
 1435         for (pptr = ptr->params; pptr != NULL; pptr = pptr->next)
 1436             fprintf(stderr, "\tP: %s = %s\n", pptr->name, pptr->value);
 1437         dump_uue(ptr, art);
 1438         fseek(art->raw, ptr->offset, SEEK_SET);
 1439         fprintf(stderr, "[%s]\n\n", tin_fgets(art->raw, FALSE));
 1440     }
 1441 }
 1442 #endif /* DEBUG_ART */
 1443 
 1444 
 1445 /*
 1446  * Core parser for all article types
 1447  * Return NULL if we couldn't open an output stream when reading via NNTP
 1448  * When reading from local spool we assign the filehandle of the on-spool
 1449  * article directly to artinfo->raw
 1450  */
 1451 static int
 1452 parse_rfc2045_article(
 1453     FILE *infile,
 1454     int line_count,
 1455     t_openartinfo *artinfo,
 1456     t_bool show_progress_meter)
 1457 {
 1458     int ret = ART_ABORT;
 1459 
 1460     if (read_news_via_nntp && !(artinfo->raw = tmpfile()))
 1461         goto error;
 1462 
 1463     if (!read_news_via_nntp)
 1464         artinfo->raw = infile;
 1465 
 1466     art_lines = line_count;
 1467 
 1468     if ((ret = parse_rfc822_headers(&artinfo->hdr, infile, artinfo->raw)) != 0)
 1469         goto error;
 1470 
 1471     /* no article data returned, just a '.' after 220er response */
 1472     if (artinfo->hdr.ext->offset == 0) {
 1473         ret = ART_UNAVAILABLE;
 1474         goto error;
 1475     }
 1476 
 1477     /*
 1478      * Is this a MIME article ?
 1479      * We don't bother to parse all plain text articles
 1480      */
 1481     if (artinfo->hdr.mime && artinfo->hdr.ext->type == TYPE_MULTIPART) {
 1482         if ((ret = parse_multipart_article(infile, artinfo, artinfo->hdr.ext, 1, show_progress_meter)) != 0) {
 1483             /* Strip off EOF condition if present */
 1484             if (ret & TIN_EOF) {
 1485                 ret ^= TIN_EOF;
 1486 #ifdef DEBUG
 1487                 if (debug & DEBUG_MISC)
 1488                     error_message(2, _(txt_error_mime_end), content_types[artinfo->hdr.ext->type], artinfo->hdr.ext->subtype);
 1489 #endif /* DEBUG */
 1490                 if (ret != 0)
 1491                     goto error;
 1492             } else
 1493                 goto error;
 1494         }
 1495     } else {
 1496         if ((ret = parse_normal_article(infile, artinfo, show_progress_meter)) != 0)
 1497             goto error;
 1498     }
 1499 
 1500     if (read_news_via_nntp)
 1501         TIN_FCLOSE(infile);
 1502 
 1503     return 0;
 1504 
 1505 error:
 1506     if (read_news_via_nntp)
 1507         TIN_FCLOSE(infile);
 1508     art_close(artinfo);
 1509     return ret;
 1510 }
 1511 
 1512 
 1513 /*
 1514  * Open a mail/news article using NNTP ARTICLE command
 1515  * or directly off local spool
 1516  * Return:
 1517  *      A pointer to the open postprocessed file
 1518  *      NULL pointer if article open fails in some way
 1519  */
 1520 FILE *
 1521 open_art_fp(
 1522     struct t_group *group,
 1523     t_artnum art)
 1524 {
 1525     FILE *art_fp;
 1526 
 1527 #ifdef NNTP_ABLE
 1528     if (read_news_via_nntp && group->type == GROUP_TYPE_NEWS) {
 1529         char buf[NNTP_STRLEN];
 1530         snprintf(buf, sizeof(buf), "ARTICLE %"T_ARTNUM_PFMT, art);
 1531         art_fp = nntp_command(buf, OK_ARTICLE, NULL, 0);
 1532     } else {
 1533 #endif /* NNTP_ABLE */
 1534         char buf[PATH_LEN];
 1535         char pbuf[PATH_LEN];
 1536         char fbuf[NAME_LEN + 1];
 1537         char *group_path = my_malloc(strlen(group->name) + 2); /* tailing "/\0" */;
 1538 
 1539         make_group_path(group->name, group_path);
 1540         joinpath(buf, sizeof(buf), group->spooldir, group_path);
 1541         free(group_path);
 1542         snprintf(fbuf, sizeof(fbuf), "%"T_ARTNUM_PFMT, art);
 1543         joinpath(pbuf, sizeof(pbuf), buf, fbuf);
 1544 
 1545         art_fp = fopen(pbuf, "r");
 1546 #ifdef NNTP_ABLE
 1547     }
 1548 #endif /* NNTP_ABLE */
 1549 
 1550     return art_fp;
 1551 }
 1552 
 1553 
 1554 /* ----------- art_open() and art_close() are the only interface --------- */
 1555 /* ------------------------for accessing articles ------------------- */
 1556 
 1557 /*
 1558  * Opens and postprocesses an article
 1559  * Populates the passed in artinfo structure if successful
 1560  *
 1561  * Returns:
 1562  *      0               Art opened successfully
 1563  *      ART_UNAVAILABLE Couldn't find article
 1564  *      ART_ABORT       User aborted during read of article
 1565  */
 1566 int
 1567 art_open(
 1568     t_bool wrap_lines,
 1569     struct t_article *art,
 1570     struct t_group *group,
 1571     t_openartinfo *artinfo,
 1572     t_bool show_progress_meter,
 1573     const char *pmesg)
 1574 {
 1575     FILE *fp;
 1576 
 1577     memset(artinfo, 0, sizeof(t_openartinfo));
 1578 
 1579     if ((fp = open_art_fp(group, art->artnum)) == NULL)
 1580         return ((tin_errno == 0) ? ART_UNAVAILABLE : ART_ABORT);
 1581 
 1582 #ifdef DEBUG_ART
 1583     fprintf(stderr, "art_open(%p)\n", (void *) artinfo);
 1584 #endif /* DEBUG_ART */
 1585 
 1586     progress_mesg = pmesg;
 1587     if (parse_rfc2045_article(fp, art->line_count, artinfo, show_progress_meter) != 0) {
 1588         progress_mesg = NULL;
 1589         return ((tin_errno == 0) ? ART_UNAVAILABLE : ART_ABORT);
 1590     }
 1591     progress_mesg = NULL;
 1592 
 1593     /*
 1594      * TODO: compare art->msgid and artinfo->hdr.messageid and issue a
 1595      *       warning (once) about broken overviews if they differ
 1596      */
 1597 
 1598     if ((artinfo->tex2iso = ((group->attribute->tex2iso_conv) ? is_art_tex_encoded(artinfo->raw) : FALSE)))
 1599         wait_message(0, _(txt_is_tex_encoded));
 1600 
 1601     /* Maybe fix it so if this fails, we default to raw? */
 1602     if (!cook_article(wrap_lines, artinfo, tinrc.hide_uue, FALSE))
 1603         return ART_ABORT;
 1604 
 1605 #ifdef DEBUG_ART
 1606     dump_art(artinfo);
 1607 #endif /* DEBUG_ART */
 1608 
 1609     /*
 1610      * If Newsgroups is empty it is a good bet the article is a mail article
 1611      * TODO: Why do this ?
 1612      */
 1613     if (!artinfo->hdr.newsgroups)
 1614         artinfo->hdr.newsgroups = my_strdup(group->name);
 1615 
 1616     return 0;
 1617 }
 1618 
 1619 
 1620 /*
 1621  * Close an open article identified by an 'artinfo' handle
 1622  */
 1623 void
 1624 art_close(
 1625     t_openartinfo *artinfo)
 1626 {
 1627 #ifdef DEBUG_ART
 1628     fprintf(stderr, "art_close(%p)\n", (void *) artinfo);
 1629 #endif /* DEBUG_ART */
 1630 
 1631     if (artinfo == NULL)
 1632         return;
 1633 
 1634     free_and_init_header(&artinfo->hdr);
 1635 
 1636     artinfo->tex2iso = FALSE;
 1637 
 1638     if (artinfo->raw) {
 1639         fclose(artinfo->raw);
 1640         artinfo->raw = NULL;
 1641     }
 1642 
 1643     if (artinfo->cooked) {
 1644         fclose(artinfo->cooked);
 1645         artinfo->cooked = NULL;
 1646     }
 1647 
 1648     FreeAndNull(artinfo->rawl);
 1649     FreeAndNull(artinfo->cookl);
 1650 }