"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.3/src/rfc2046.c" (23 Nov 2018, 39240 Bytes) of package /linux/misc/tin-2.4.3.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.4.2_vs_2.4.3.

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