"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.1/src/rfc2046.c" (12 Oct 2016, 38783 Bytes) of archive /linux/misc/tin-2.4.1.tar.gz:


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

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