"Fossies" - the Fresh Open Source Software Archive

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