"Fossies" - the Fresh Open Source Software Archive

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


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

A hint: This file contains one or more very long lines, so maybe it is better readable using the pure text view mode that shows the contents as wrapped lines within the browser window.


    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : rfc2047.c
    4  *  Author    : Chris Blum <chris@resolution.de>
    5  *  Created   : 1995-09-01
    6  *  Updated   : 2021-03-04
    7  *  Notes     : MIME header encoding/decoding stuff
    8  *
    9  * Copyright (c) 1995-2023 Chris Blum <chris@resolution.de>
   10  * All rights reserved.
   11  *
   12  * Redistribution and use in source and binary forms, with or without
   13  * modification, are permitted provided that the following conditions
   14  * are met:
   15  *
   16  * 1. Redistributions of source code must retain the above copyright notice,
   17  *    this list of conditions and the following disclaimer.
   18  *
   19  * 2. Redistributions in binary form must reproduce the above copyright
   20  *    notice, this list of conditions and the following disclaimer in the
   21  *    documentation and/or other materials provided with the distribution.
   22  *
   23  * 3. Neither the name of the copyright holder nor the names of its
   24  *    contributors may be used to endorse or promote products derived from
   25  *    this software without specific prior written permission.
   26  *
   27  * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   28  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   30  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
   31  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   37  * POSSIBILITY OF SUCH DAMAGE.
   38  */
   39 
   40 
   41 #ifndef TIN_H
   42 #   include "tin.h"
   43 #endif /* !TIN_H */
   44 
   45 
   46 #define isreturn(c) ((c) == '\r' || ((c) == '\n'))
   47 
   48 /*
   49  * Modified to return TRUE for '(' and ')' only if
   50  * it's in structured header field. '(' and ')' are
   51  * NOT to be treated differently than other characters
   52  * in unstructured headers like Subject, Keyword and Summary
   53  * c.f. RFC 2047
   54  */
   55 /*
   56  * On some systems isspace(0xa0) returns TRUE (UTF-8 locale).
   57  * 0xa0 can be the second byte of a UTF-8 character and must not be
   58  * treated as whitespace, otherwise Q and B encoding fails.
   59  */
   60 #if 0
   61 #   define isbetween(c, s) (isspace((unsigned char) c) || ((s) && ((c) == '(' || (c) == ')' || (c) == '"')))
   62 #else
   63 #   define my_isspace(c) ((c) == '\t' || (c) == '\n' || (c) == '\v' || (c) == '\f' || (c) == '\r' || (c) == ' ')
   64 #   define isbetween(c, s) (my_isspace(c) || ((s) && ((c) == '(' || (c) == ')' || (c) == '"')))
   65 #endif /* 0 */
   66 #define NOT_RANKED 255
   67 
   68 #if 0
   69     /* inside a quoted word these 7bit chars need to be encoded too */
   70 #   define RFC2047_ESPECIALS "[]<>.;@,=?_\"\\"
   71 #endif /* 0 */
   72 
   73 const char base64_alphabet[64] =
   74 {
   75     'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
   76     'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
   77     'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
   78     'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
   79 
   80 static unsigned char base64_rank[256];
   81 static int base64_rank_table_built;
   82 
   83 /* fixed prefix and default part for tin-generated MIME boundaries */
   84 static const char MIME_BOUNDARY_PREFIX[] = "=_tin=_";
   85 static const char MIME_BOUNDARY_DEFAULT_PART[] = "====____====____====____";
   86 /* required size of a buffer containing a MIME boundary, including the final '\0' */
   87 enum {
   88     MIME_BOUNDARY_SIZE = sizeof(MIME_BOUNDARY_PREFIX) + sizeof(MIME_BOUNDARY_DEFAULT_PART) - 1
   89 };
   90 
   91 /*
   92  * local prototypes
   93  */
   94 static FILE *compose_message_rfc822(FILE *articlefp, t_bool *is_8bit);
   95 static FILE *compose_multipart_mixed(FILE *textfp, FILE *articlefp);
   96 static int do_b_encode(char *w, char *b, size_t max_ewsize, t_bool isstruct_head);
   97 static int sizeofnextword(char *w);
   98 static int which_encoding(char *w);
   99 static t_bool contains_8bit_characters(FILE *fp);
  100 static t_bool contains_nonprintables(char *w, t_bool isstruct_head);
  101 static t_bool contains_string(FILE *fp, const char *str);
  102 static t_bool rfc1522_do_encode(char *what, char **where, const char *charset, t_bool break_long_line);
  103 static t_bool split_mail(const char *filename, FILE **headerfp, FILE **textfp);
  104 static unsigned hex2bin(int x);
  105 static void build_base64_rank_table(void);
  106 static void do_rfc15211522_encode(FILE *f, constext * mime_encoding, struct t_group *group, t_bool allow_8bit_header, t_bool ismail, t_bool contains_headers);
  107 static void generate_mime_boundary(char *boundary, FILE *f, FILE *g);
  108 static void generate_random_mime_boundary(char *boundary, size_t len);
  109 static void str2b64(const char *from, char *to);
  110 
  111 
  112 static void
  113 build_base64_rank_table(
  114     void)
  115 {
  116     int i;
  117 
  118     if (!base64_rank_table_built) {
  119         for (i = 0; i < 256; i++)
  120             base64_rank[i] = NOT_RANKED;
  121         for (i = 0; i < 64; i++)
  122             base64_rank[(int) base64_alphabet[i]] = (unsigned char) i;
  123         base64_rank_table_built = TRUE;
  124     }
  125 }
  126 
  127 
  128 static unsigned
  129 hex2bin(
  130     int x)
  131 {
  132     if (x >= '0' && x <= '9')
  133         return (unsigned) (x - '0');
  134     if (x >= 'A' && x <= 'F')
  135         return (unsigned) ((x - 'A') + 10);
  136     if (x >= 'a' && x <= 'f')
  137         return (unsigned) ((x - 'a') + 10);
  138     return 255;
  139 }
  140 
  141 
  142 /*
  143  * Do B or Q decoding of a chunk of data in 'what' to 'where'
  144  * Return number of bytes decoded into 'where' or -1.
  145  */
  146 int
  147 mmdecode(
  148     const char *what,
  149     int encoding,
  150     int delimiter,
  151     char *where)
  152 {
  153     char *t;
  154 
  155     t = where;
  156     encoding = my_tolower((unsigned char) encoding);
  157     if (encoding == 'q') {      /* quoted-printable */
  158         int x;
  159         unsigned hi, lo;
  160 
  161         if (!what || !where) /* should not happen with 'q'-encoding */
  162             return -1;
  163 
  164         while (*what != delimiter) {
  165             if (*what != '=') {
  166                 if (!delimiter || *what != '_')
  167                     *t++ = *what++;
  168                 else {
  169                     *t++ = ' ';
  170                     what++;
  171                 }
  172                 continue;
  173             }
  174             what++;
  175             if (*what == delimiter)     /* failed */
  176                 return -1;
  177 
  178             x = *what++;
  179             if (x == '\n')
  180                 continue;
  181             if (*what == delimiter)
  182                 return -1;
  183 
  184             hi = hex2bin(x);
  185             lo = hex2bin(*what);
  186             what++;
  187             if (hi == 255 || lo == 255)
  188                 return -1;
  189             x = (int) ((hi << 4) + lo);
  190             *EIGHT_BIT(t)++ = (unsigned char) x;
  191         }
  192         return (int) (t - where);
  193     } else if (encoding == 'b') {       /* base64 */
  194         static unsigned short pattern = 0;
  195         static int bits = 0;
  196         unsigned char x;
  197 
  198         if (!what || !where) {      /* flush */
  199             pattern = 0;
  200             bits = 0;
  201             return 0;
  202         }
  203 
  204         build_base64_rank_table();
  205 
  206         while (*what != delimiter) {
  207             x = base64_rank[(unsigned char) (*what++)];
  208             /* ignore everything not in the alphabet, including '=' */
  209             if (x == NOT_RANKED)
  210                 continue;
  211             pattern <<= 6;
  212             pattern |= x;
  213             bits += 6;
  214             if (bits >= 8) {
  215                 x = (unsigned char) ((pattern >> (bits - 8)) & 0xff);
  216                 *t++ = (char) x;
  217                 bits -= 8;
  218             }
  219         }
  220         return (int) (t - where);
  221     }
  222     return -1;
  223 }
  224 
  225 
  226 /*
  227  * This routine decodes encoded headers in the
  228  * =?charset?encoding?coded text?=
  229  * format
  230  */
  231 char *
  232 rfc1522_decode(
  233     const char *s)
  234 {
  235     char *c, *sc;
  236     const char *d;
  237     char *t;
  238     static char *buffer = NULL;
  239     static int buffer_len = 0;
  240     size_t max_len;
  241     char charset[1024];
  242     char encoding;
  243     t_bool adjacentflag = FALSE;
  244 
  245     if (!s) {
  246         FreeAndNull(buffer);
  247         return NULL;
  248     }
  249 
  250     charset[0] = '\0';
  251     c = my_strdup(s);
  252     max_len = strlen(c) + 1;
  253 
  254     if (!buffer) {
  255         buffer_len = (int) max_len;
  256         buffer = my_malloc((size_t) buffer_len);
  257     } else if (max_len > (size_t) buffer_len) {
  258             buffer_len = (int) max_len;
  259             buffer = my_realloc(buffer, (size_t) buffer_len);
  260     }
  261 
  262     t = buffer;
  263 
  264     /*
  265      * remove non-ASCII chars if MIME_STRICT_CHARSET is set
  266      * must be changed if UTF-8 becomes default charset for headers:
  267      *
  268      * process_charsets(c, len, "UTF-8", tinrc.mm_local_charset, FALSE);
  269      */
  270 #ifndef CHARSET_CONVERSION
  271     process_charsets(&c, &max_len, "US-ASCII", tinrc.mm_local_charset, FALSE);
  272 #else
  273     process_charsets(&c, &max_len, (CURR_GROUP.attribute->undeclared_charset) ? (CURR_GROUP.attribute->undeclared_charset) : "US-ASCII", tinrc.mm_local_charset, FALSE);
  274 #endif /* !CHARSET_CONVERSION */
  275     sc = c;
  276 
  277     while (*c && t - buffer < buffer_len - 1) {
  278         if (*c != '=') {
  279             if (adjacentflag && isspace((unsigned char) *c)) {
  280                 const char *dd;
  281 
  282                 dd = c + 1;
  283                 while (isspace((unsigned char) *dd))
  284                     dd++;
  285                 if (*dd == '=') {       /* brute hack, makes mistakes under certain circumstances comp. 6.2 */
  286                     c++;
  287                     continue;
  288                 }
  289             }
  290             adjacentflag = FALSE;
  291             *t++ = *c++;
  292             continue;
  293         }
  294         d = c++;
  295         if (*c == '?') {
  296             char *e;
  297 
  298             e = charset;
  299             c++;
  300             while (*c && *c != '?') {
  301                 /* skip over optional language tags (RFC2231, RFC5646) */
  302                 if (*c == '*') {
  303                     while (*++c && *c != '?')
  304                         ;
  305                     continue;
  306                 }
  307                 *e++ = *c++;
  308             }
  309             *e = '\0';
  310             if (*c == '?') {
  311                 c++;
  312                 encoding = (char) my_tolower((unsigned char) *c);
  313                 if (encoding == 'b')
  314                     (void) mmdecode(NULL, 'b', 0, NULL);    /* flush */
  315                 if (*c)
  316                     c++;
  317                 if (*c == '?') {
  318                     c++;
  319                     if ((e = strchr(c, '?'))) {
  320                         int i;
  321 
  322                         i = mmdecode(c, encoding, '?', t);
  323                         if (i > 0) {
  324                             char *tmpbuf;
  325                             int chars_to_copy;
  326 
  327                             max_len = (size_t) (i + 1);
  328                             tmpbuf = my_malloc(max_len);
  329                             strncpy(tmpbuf, t, (size_t) i);
  330                             *(tmpbuf + i) = '\0';
  331                             process_charsets(&tmpbuf, &max_len, charset, tinrc.mm_local_charset, FALSE);
  332                             chars_to_copy = (int) strlen(tmpbuf);
  333                             if (chars_to_copy > buffer_len - (t - buffer) - 1)
  334                                 chars_to_copy = (int) (buffer_len - (t - buffer) - 1);
  335                             strncpy(t, tmpbuf, (size_t) chars_to_copy);
  336                             free(tmpbuf);
  337                             t += chars_to_copy;
  338                             e++;
  339                             if (*e == '=')
  340                                 e++;
  341                             d = c = e;
  342                             adjacentflag = TRUE;
  343                         }
  344                     }
  345                 }
  346             }
  347         }
  348         while (d != c && t - buffer < buffer_len - 1)
  349             *t++ = *d++;
  350     }
  351     *t = '\0';
  352     free(sc);
  353 
  354     return buffer;
  355 }
  356 
  357 
  358 /*
  359  * adopted by J. Shin(jshin@pantheon.yale.edu) from
  360  * Woohyung Choi's(whchoi@cosmos.kaist.ac.kr) sdn2ks and ks2sdn
  361  */
  362 static void
  363 str2b64(
  364     const char *from,
  365     char *to)
  366 {
  367     short int i, count;
  368     unsigned long tmp;
  369 
  370     while (*from) {
  371         for (i = count = 0, tmp = 0; i < 3; i++)
  372             if (*from) {
  373                 tmp = (tmp << 8) | (unsigned long) (*from++ & 0x0ff);
  374                 count++;
  375             } else
  376                 tmp = (tmp << 8) | (unsigned long) 0;
  377 
  378         *to++ = base64_alphabet[(0x0fc0000 & tmp) >> 18];
  379         *to++ = base64_alphabet[(0x003f000 & tmp) >> 12];
  380         *to++ = count >= 2 ? base64_alphabet[(0x0000fc0 & tmp) >> 6] : '=';
  381         *to++ = count >= 3 ? base64_alphabet[0x000003f & tmp] : '=';
  382     }
  383 
  384     *to = '\0';
  385 }
  386 
  387 
  388 static int
  389 do_b_encode(
  390     char *w,
  391     char *b,
  392     size_t max_ewsize,
  393     t_bool isstruct_head)
  394 {
  395     char tmp[60];               /* strings to be B encoded */
  396     char *t = tmp;
  397     int count = (int) (max_ewsize / 4 * 3);
  398     t_bool isleading_between = TRUE;        /* are we still processing leading space */
  399 
  400 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  401     while (count-- > 0 && (!isbetween(*w, isstruct_head) || isleading_between) && *w) {
  402         if (!isbetween(*w, isstruct_head))
  403             isleading_between = FALSE;
  404         *(t++) = *(w++);
  405         /*
  406          * ensure that the next multi-octet character
  407          * fits into the remaining space
  408          */
  409         if (mbtowc(NULL, w, MB_CUR_MAX) > count)
  410             break;
  411     }
  412 #else
  413     int len8 = 0;               /* the number of trailing 8bit chars, which
  414                                    should be even (i.e. the first and second byte
  415                                    of wide_char should NOT be split into two
  416                                    encoded words) in order to be compatible with
  417                                    some CJK mail client */
  418 
  419     while (count-- > 0 && (!isbetween(*w, isstruct_head) || isleading_between) && *w) {
  420         len8 += (is_EIGHT_BIT(w) ? 1 : -len8);
  421         if (!isbetween(*w, isstruct_head))
  422             isleading_between = FALSE;
  423         *(t++) = *(w++);
  424     }
  425 
  426 /* if (len8 & (unsigned long) 1 && !isbetween(*w,isstruct_head)) */
  427     if (len8 != len8 / 2 * 2 && !isbetween(*w, isstruct_head) && (*w))
  428         t--;
  429 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  430 
  431     *t = '\0';
  432 
  433     str2b64(tmp, b);
  434 
  435     return (int) (t - tmp);
  436 }
  437 
  438 
  439 /*
  440  * find out whether encoding is necessary and which encoding
  441  * to use if necessary by scanning the whole header field
  442  * instead of each fragment of it.
  443  * This will ensure that either Q or B encoding will be used in a single
  444  * header (i.e. two encoding won't be mixed in a single header line).
  445  * Mixing two encodings is not a violation of RFC 2047 but may break
  446  * some news/mail clients.
  447  *
  448  * mmnwcharset is ignored unless CHARSET_CONVERSION
  449  */
  450 static int
  451 which_encoding(
  452     char *w)
  453 {
  454     int chars = 0;
  455     int schars = 0;
  456     int nonprint = 0;
  457 
  458     while (*w && isspace((unsigned char) *w))
  459         w++;
  460     while (*w) {
  461         if (is_EIGHT_BIT(w))
  462             nonprint++;
  463         if (!nonprint && *w == '=' && *(w + 1) == '?')
  464             nonprint = 1;
  465         if (*w == '=' || *w == '?' || *w == '_')
  466             schars++;
  467         chars++;
  468         w++;
  469     }
  470     if (nonprint) {
  471         if (chars + 2 * (nonprint + schars) /* QP size */ >
  472              (chars * 4 + 3) / 3        /* B64 size */)
  473             return 'B';
  474         return 'Q';
  475     }
  476     return 0;
  477 }
  478 
  479 
  480 /* now only checks if there's any 8bit chars in a given "fragment" */
  481 static t_bool
  482 contains_nonprintables(
  483     char *w,
  484     t_bool isstruct_head)
  485 {
  486     t_bool nonprint = FALSE;
  487 
  488     /* first skip all leading whitespaces */
  489     while (*w && isspace((unsigned char) *w))
  490         w++;
  491 
  492     /* then check the next word */
  493     while (!nonprint && *w && !isbetween(*w, isstruct_head)) {
  494         if (is_EIGHT_BIT(w))
  495             nonprint = TRUE;
  496         else if (*w == '=' && *(w + 1) == '?') {
  497             /*
  498              * to be exact we must look for ?= in the same word
  499              * not in the whole string and check ?B? or ?Q? in the word...
  500              * best would be using a regexp like
  501              * ^=\?\S+\?[qQbB]\?\S+\?=
  502              */
  503             if (strstr(w, "?=") != NULL)
  504                 nonprint = TRUE;
  505         }
  506         w++;
  507     }
  508     return nonprint;
  509 }
  510 
  511 
  512 /*
  513  * implement mandatory break-up of long lines in mail messages in accordance
  514  * with rfc 2047 (rfc 1522)
  515  */
  516 static int
  517 sizeofnextword(
  518     char *w)
  519 {
  520     char *x;
  521 
  522     x = w;
  523     while (*x && isspace((unsigned char) *x))
  524         x++;
  525     while (*x && !isspace((unsigned char) *x))
  526         x++;
  527     return (int) (x - w);
  528 }
  529 
  530 
  531 static t_bool
  532 rfc1522_do_encode(
  533     char *what,
  534     char **where,
  535     const char *charset,
  536     t_bool break_long_line)
  537 {
  538     /*
  539      * We need to meet several partly contradictional requirements here.
  540      * First of all, a line containing MIME encodings must not be longer
  541      * than 76 chars (including delimiters, charset, encoding). Second,
  542      * we should not encode more than necessary. Third, we should not
  543      * produce more overhead than absolutely necessary; this means we
  544      * should extend chunks over several words if there are more
  545      * characters-to-quote to come. This means we have to rely on some
  546      * heuristics. We process whole words, checking if it contains
  547      * characters to be quoted. If not, the word is output 'as is',
  548      * previous quoting being terminated before. If two adjoining words
  549      * contain non-printable characters, they are encoded together (up
  550      * to 60 characters). If a resulting encoded word would break the
  551      * 76 characters boundary, we 'break' the line, output a SPACE, then
  552      * output the encoded word. Note that many wide-spread news applications,
  553      * notably INN's xover support, does not understand multiple-lines,
  554      * so it's a compile-time feature with default off.
  555      *
  556      * To make things a bit easier, we do all processing in two stages;
  557      * first we build all encoded words without any bells and whistles
  558      * (just checking that they don get longer than 76 characters),
  559      * then, in a second pass, we replace all SPACEs inside encoded
  560      * words by '_', break long lines, etc.
  561      */
  562     char *buffer;               /* buffer for encoded stuff */
  563     char *c;
  564     char *t;
  565     char buf2[80];              /* buffer for this and that */
  566     int encoding;               /* which encoding to use ('B' or 'Q') */
  567     size_t ew_taken_len;
  568     int word_cnt = 0;
  569     int offset;
  570     size_t bufferlen = 2048;        /* size of buffer */
  571     size_t ewsize = 0;          /* size of current encoded-word */
  572     t_bool quoting = FALSE;     /* currently inside quote block? */
  573     t_bool any_quoting_done = FALSE;
  574     t_bool isbroken_within = FALSE; /* is word broken due to length restriction on encoded of word? */
  575     t_bool isstruct_head = FALSE;       /* are we dealing with structured header? */
  576     t_bool rightafter_ew = FALSE;
  577 /*
  578  * the list of structured header fields where '(' and ')' are
  579  * treated specially in rfc 1522 encoding
  580  */
  581     static const char *struct_header[] = {
  582         "Approved: ", "From: ", "Originator: ",
  583         "Reply-To: ", "Sender: ", "X-Cancelled-By: ", "X-Comment-To: ",
  584         "X-Submissions-To: ", "To: ", "Cc: ", "Bcc: ", "X-Originator: ", NULL };
  585     const char **strptr = struct_header;
  586 
  587     do {
  588         if (!strncasecmp(what, *strptr, strlen(*strptr))) {
  589             isstruct_head = TRUE;
  590             break;
  591         }
  592     } while (*(++strptr) != NULL);
  593 
  594     t = buffer = my_malloc(bufferlen);
  595     encoding = which_encoding(what);
  596     ew_taken_len = strlen(charset) + 7 /* =?c?E?d?= */;
  597     while (*what) {
  598         if (break_long_line)
  599             word_cnt++;
  600         /*
  601          * if a word with 8bit chars is broken in the middle, whatever
  602          * follows after the point where it's split should be encoded (i.e.
  603          * even if they are made of only 7bit chars)
  604          */
  605         if (contains_nonprintables(what, isstruct_head) || isbroken_within) {
  606             if (encoding == 'Q') {
  607                 if (!quoting) {
  608                     snprintf(buf2, sizeof(buf2), "=?%s?%c?", charset, encoding);
  609                     while ((size_t) (t - buffer) + strlen(buf2) >= bufferlen) {
  610                         /* buffer too small, double its size */
  611                         offset = (int) (t - buffer);
  612                         bufferlen <<= 1;
  613                         buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  614                         t = buffer + offset;
  615                     }
  616                     ewsize = mystrcat(&t, buf2);
  617                     if (break_long_line) {
  618                         if (word_cnt == 2) {
  619                             /*
  620                              * Make sure we fit the first encoded
  621                              * word in with the header keyword,
  622                              * since we cannot break the line
  623                              * directly after the keyword.
  624                              */
  625                             ewsize = (size_t) (t - buffer);
  626                         }
  627                     }
  628                     quoting = TRUE;
  629                     any_quoting_done = TRUE;
  630                 }
  631                 isbroken_within = FALSE;
  632                 while (*what && !isbetween(*what, isstruct_head)) {
  633 #if 0
  634                     if (is_EIGHT_BIT(what) || (strchr(RFC2047_ESPECIALS, *what)))
  635 #else
  636                     if (is_EIGHT_BIT(what) || !isalnum((int)(unsigned char) *what))
  637 #endif /* 0 */
  638                     {
  639                         snprintf(buf2, sizeof(buf2), "=%2.2X", *EIGHT_BIT(what));
  640                         if ((size_t) (t - buffer + 3) >= bufferlen) {
  641                             /* buffer too small, double its size */
  642                             offset = (int) (t - buffer);
  643                             bufferlen <<= 1;
  644                             buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  645                             t = buffer + offset;
  646                         }
  647                         *t++ = buf2[0];
  648                         *t++ = buf2[1];
  649                         *t++ = buf2[2];
  650                         ewsize += 3;
  651                     } else {
  652                         if ((size_t) (t - buffer + 1) >= bufferlen) {
  653                             /* buffer too small, double its size */
  654                             offset = (int) (t - buffer);
  655                             bufferlen <<= 1;
  656                             buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  657                             t = buffer + offset;
  658                         }
  659                         *t++ = *what;
  660                         ewsize++;
  661                     }
  662                     what++;
  663                     /*
  664                      * Be sure to encode at least one char, even if
  665                      * that overflows the line limit, otherwise, we
  666                      * will be stuck in a loop (if this were in the
  667                      * while condition above). (Can only happen in
  668                      * the first line, if we have a very long
  669                      * header keyword, I think).
  670                      */
  671                     if (ewsize >= 71) {
  672                         isbroken_within = TRUE;
  673                         break;
  674                     }
  675                 }
  676                 if (!contains_nonprintables(what, isstruct_head) || ewsize >= 70 - strlen(charset)) {
  677                     /* next word is 'clean', close encoding */
  678                     if ((size_t) (t - buffer + 2) >= bufferlen) {
  679                         /* buffer too small, double its size */
  680                         offset = (int) (t - buffer);
  681                         bufferlen <<= 1;
  682                         buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  683                         t = buffer + offset;
  684                     }
  685                     *t++ = '?';
  686                     *t++ = '=';
  687                     ewsize += 2;
  688                     /*
  689                      */
  690                     if (ewsize >= 70 - strlen(charset) && (contains_nonprintables(what, isstruct_head) || isbroken_within)) {
  691                         if ((size_t) (t - buffer + 1) >= bufferlen) {
  692                             /* buffer too small, double its size */
  693                             offset = (int) (t - buffer);
  694                             bufferlen <<= 1;
  695                             buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  696                             t = buffer + offset;
  697                         }
  698                         *t++ = ' ';
  699                         ewsize++;
  700                     }
  701                     quoting = FALSE;
  702                 } else {
  703                     /* process whitespace in-between by quoting it properly */
  704                     while (*what && isspace((unsigned char) *what)) {
  705                         if ((size_t) (t - buffer + 3) >= bufferlen) {
  706                             /* buffer probably too small, double its size */
  707                             offset = (int) (t - buffer);
  708                             bufferlen <<= 1;
  709                             buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  710                             t = buffer + offset;
  711                         }
  712                         if (*what == 32 /* not ' ', compare chapter 4! */ ) {
  713                             *t++ = '_';
  714                             ewsize++;
  715                         } else {
  716                             snprintf(buf2, sizeof(buf2), "=%2.2X", *EIGHT_BIT(what));
  717                             *t++ = buf2[0];
  718                             *t++ = buf2[1];
  719                             *t++ = buf2[2];
  720                             ewsize += 3;
  721                         }
  722                         what++;
  723                     }                   /* end of while */
  724                 }                       /* end of else */
  725             } else {                    /* end of Q encoding and beg. of B encoding */
  726                 /*
  727                  * if what immediately precedes the current fragment with 8bit
  728                  * char is encoded word, the leading spaces should be encoded
  729                  * together with 8bit chars following them. No need to worry
  730                  * about '(',')' and '"' as they're already excluded with
  731                  * contain_nonprintables used in outer if-clause
  732                  */
  733                 while (*what && (!isbetween(*what, isstruct_head) || rightafter_ew)) {
  734                     snprintf(buf2, sizeof(buf2), "=?%s?%c?", charset, encoding);
  735                     while ((size_t) (t - buffer) + strlen(buf2) >= bufferlen) {
  736                         /* buffer too small, double its size */
  737                         offset = (int) (t - buffer);
  738                         bufferlen <<= 1;
  739                         buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  740                         t = buffer + offset;
  741                     }
  742                     ewsize = mystrcat(&t, buf2);
  743 
  744                     if (word_cnt == 2)
  745                         ewsize = (size_t) (t - buffer);
  746                     what += do_b_encode(what, buf2, 75 - ew_taken_len, isstruct_head);
  747                     while ((size_t) (t - buffer) + strlen(buf2) + 3 >= bufferlen) {
  748                         /* buffer too small, double its size */
  749                         offset = (int) (t - buffer);
  750                         bufferlen <<= 1;
  751                         buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  752                         t = buffer + offset;
  753                     }
  754                     ewsize += mystrcat(&t, buf2);
  755                     *t++ = '?';
  756                     *t++ = '=';
  757                     *t++ = ' ';
  758                     ewsize += 3;
  759                     if (break_long_line)
  760                         word_cnt++;
  761                     rightafter_ew = FALSE;
  762                     any_quoting_done = TRUE;
  763                 }
  764                 rightafter_ew = TRUE;
  765                 word_cnt--;     /* compensate double counting */
  766                 /*
  767                  * if encoded word is followed by 7bit-only fragment, we need to
  768                  * eliminate ' ' inserted in while-block above
  769                  */
  770                 if (!contains_nonprintables(what, isstruct_head)) {
  771                     t--;
  772                     ewsize--;
  773                 }
  774             }       /* end of B encoding */
  775         } else {
  776             while (*what && !isbetween(*what, isstruct_head)) {
  777                 if ((size_t) (t - buffer + 1) >= bufferlen) {
  778                     /* buffer too small, double its size */
  779                     offset = (int) (t - buffer);
  780                     bufferlen <<= 1;
  781                     buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  782                     t = buffer + offset;
  783                 }
  784                 *t++ = *what++;     /* output word unencoded */
  785             }
  786             while (*what && isbetween(*what, isstruct_head)) {
  787                 if ((size_t) (t - buffer + 1) >= bufferlen) {
  788                     /* buffer too small, double its size */
  789                     offset = (int) (t - buffer);
  790                     bufferlen <<= 1;
  791                     buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  792                     t = buffer + offset;
  793                 }
  794                 *t++ = *what++;     /* output trailing whitespace unencoded */
  795             }
  796             rightafter_ew = FALSE;
  797         }
  798     }       /* end of pass 1 while loop */
  799     *t = '\0';
  800 
  801     /* Pass 2: break long lines if there are MIME-sequences in the result */
  802     c = buffer;
  803     if (break_long_line && any_quoting_done) {
  804         char *new_buffer;
  805         size_t new_bufferlen = strlen(buffer) * 2 + 1; /* maximum length if
  806 every "word" were a space ... */
  807         int column = 0;             /* current column */
  808 
  809         new_buffer = my_malloc(new_bufferlen);
  810         t = new_buffer;
  811         word_cnt = 1;           /*
  812                          * note, if the user has typed a continuation
  813                          * line, we will consider the initial
  814                          * whitespace to be delimiting word one (well,
  815                          * just assume an empty word).
  816                          */
  817         while (*c) {
  818             if (isspace((unsigned char) *c)) {
  819                 /*
  820                  * According to rfc1522, header lines containing encoded
  821                  * words are limited to 76 chars, but if the first line is
  822                  * too long (due to a long header keyword), we cannot stick
  823                  * to that, since we would break the line directly after the
  824                  * keyword's colon, which is not allowed. The same is
  825                  * necessary for a continuation line with an unencoded word
  826                  * that is too long.
  827                  */
  828                 if (sizeofnextword(c) + column > 76 && word_cnt != 1) {
  829                     *t++ = '\n';
  830                     column = 0;
  831                 }
  832                 if (c > buffer && !isspace((unsigned char) *(c - 1)))
  833                     word_cnt++;
  834                 *t++ = *c++;
  835                 column++;
  836             } else
  837                 while (*c && !isspace((unsigned char) *c)) {
  838                     *t++ = *c++;
  839                     column++;
  840                 }
  841         }
  842         FreeIfNeeded(buffer);
  843         buffer = new_buffer;
  844     }
  845     *t = '\0';
  846     *where = buffer;
  847     return any_quoting_done;
  848 }
  849 
  850 
  851 /*
  852  * calling code must free() the result if it's no longer needed
  853  */
  854 char *
  855 rfc1522_encode(
  856     char *s,
  857     const char *charset,
  858     t_bool ismail)
  859 {
  860     char *buf;
  861 
  862     /*
  863      * break_long_line is FALSE for news posting unless
  864      * MIME_BREAK_LONG_LINES is defined, but it's TRUE for mail messages
  865      * regardless of whether or not MIME_BREAK_LONG_LINES is defined
  866      */
  867 #ifdef MIME_BREAK_LONG_LINES
  868     t_bool break_long_line = TRUE;
  869 #else
  870     /*
  871      * Even if MIME_BREAK_LONG_LINES is NOT defined, long headers in mail
  872      * messages should be broken up in accordance with RFC 2047 (1522)
  873      */
  874     t_bool break_long_line = ismail;
  875 #endif /* MIME_BREAK_LONG_LINES */
  876 
  877     rfc1522_do_encode(s, &buf, charset, break_long_line);
  878 
  879     return buf;
  880 }
  881 
  882 
  883 /*
  884  * Helper function doing the hard work for rfc15211522_encode().
  885  * Code moved from rfc15211522_encode(), with some adjustments to work on a
  886  * file specified by a FILE* instead of a filename.
  887  */
  888 static void
  889 do_rfc15211522_encode(
  890     FILE *f,
  891     constext * mime_encoding,
  892     struct t_group *group,
  893     t_bool allow_8bit_header,
  894     t_bool ismail,
  895     t_bool contains_headers)
  896 {
  897     FILE *g;
  898     char *c;
  899     char *header;
  900     char encoding;
  901     char buffer[2048];
  902     t_bool mime_headers_needed = FALSE;
  903     BodyPtr body_encode;
  904     int i;
  905 #ifdef CHARSET_CONVERSION
  906     int mmnwcharset;
  907 
  908     if (group) /* Posting */
  909         mmnwcharset = group->attribute->mm_network_charset;
  910     else /* E-Mail */
  911         mmnwcharset = tinrc.mm_network_charset;
  912 #endif /* CHARSET_CONVERSION */
  913 
  914     if ((g = tmpfile()) == NULL)
  915         return;
  916 
  917     while (contains_headers && (header = tin_fgets(f, TRUE))) {
  918 #ifdef CHARSET_CONVERSION
  919         buffer_to_network(header, mmnwcharset);
  920 #endif /* CHARSET_CONVERSION */
  921         if (*header == '\0')
  922             break;
  923 
  924         /*
  925          * TODO: - what about 8bit chars in the mentioned headers
  926          *         when !allow_8bit_header?
  927          *       - what about lines longer 998 octets?
  928          */
  929         if (allow_8bit_header || (!strncasecmp(header, "References: ", 12) || !strncasecmp(header, "Message-ID: ", 12) || !strncasecmp(header, "Date: ", 6) || !strncasecmp(header, "Newsgroups: ", 12) || !strncasecmp(header, "Distribution: ", 14) || !strncasecmp(header, "Followup-To: ", 13) || !strncasecmp(header, "X-Face: ", 8) || !strncasecmp(header, "Cancel-Lock: ", 13) || !strncasecmp(header, "Cancel-Key: ", 12)))
  930             fputs(header, g);
  931         else {
  932             char *p;
  933 
  934 #ifdef CHARSET_CONVERSION
  935             p = rfc1522_encode(header, txt_mime_charsets[mmnwcharset], ismail);
  936 #else
  937             p = rfc1522_encode(header, tinrc.mm_charset, ismail);
  938 #endif /* CHARSET_CONVERSION */
  939 
  940             fputs(p, g);
  941             free(p);
  942         }
  943         fputc('\n', g);
  944     }
  945 
  946     fputc('\n', g);
  947 
  948     while (fgets(buffer, 2048, f)) {
  949 #ifdef CHARSET_CONVERSION
  950         buffer_to_network(buffer, mmnwcharset);
  951 #endif /* CHARSET_CONVERSION */
  952         fputs(buffer, g);
  953         if (!allow_8bit_header) {
  954             /* see if there are any 8bit chars in the body... */
  955             for (c = buffer; *c && !isreturn(*c); c++) {
  956                 if (is_EIGHT_BIT(c)) {
  957                     mime_headers_needed = TRUE;
  958                     break;
  959                 }
  960             }
  961         }
  962     }
  963 
  964     rewind(g);
  965     rewind(f);
  966 #ifdef HAVE_FTRUNCATE
  967     if (ftruncate(fileno(f), 0) == -1) {
  968 #   ifdef DEBUG
  969         int e = errno;
  970         if (debug & DEBUG_MISC)
  971             error_message(2, "ftruncate(): Error: %s", strerror(e));
  972 #   endif /* DEBUG */
  973     }
  974 #endif /* HAVE_FTRUNCATE */
  975 
  976     /* copy header */
  977     while (fgets(buffer, 2048, g) && !isreturn(buffer[0]))
  978         fputs(buffer, f);
  979 
  980     if (!allow_8bit_header) {
  981         /*
  982          * 7bit charsets except US-ASCII also need mime headers
  983          */
  984         for (i = 1; txt_mime_7bit_charsets[i] != NULL; i++) {
  985 #ifdef CHARSET_CONVERSION
  986             if (!strcasecmp(txt_mime_charsets[mmnwcharset], txt_mime_7bit_charsets[i])) {
  987                 mime_headers_needed = TRUE;
  988                 break;
  989             }
  990 #else
  991             if (!strcasecmp(tinrc.mm_charset, txt_mime_7bit_charsets[i])) {
  992                 mime_headers_needed = TRUE;
  993                 break;
  994             }
  995 #endif /* CHARSET_CONVERSION */
  996         }
  997 
  998         /*
  999          * now add MIME headers as necessary
 1000          */
 1001         if (mime_headers_needed) {
 1002             if (contains_headers)
 1003                 fprintf(f, "MIME-Version: %s\n", MIME_SUPPORTED_VERSION);
 1004 #ifdef CHARSET_CONVERSION
 1005             fprintf(f, "Content-Type: text/plain; charset=%s\n", txt_mime_charsets[mmnwcharset]);
 1006 #else
 1007             fprintf(f, "Content-Type: text/plain; charset=%s\n", tinrc.mm_charset);
 1008 #endif /* CHARSET_CONVERSION */
 1009             fprintf(f, "Content-Transfer-Encoding: %s\n", mime_encoding);
 1010         }
 1011     }
 1012     fputc('\n', f);
 1013 
 1014     if (!allow_8bit_header) {
 1015         if (!strcasecmp(mime_encoding, txt_base64))
 1016             encoding = 'b';
 1017         else if (!strcasecmp(mime_encoding, txt_quoted_printable))
 1018             encoding = 'q';
 1019         else if (!strcasecmp(mime_encoding, txt_7bit))
 1020             encoding = '7';
 1021         else
 1022             encoding = '8';
 1023 
 1024         /* avoid break of long lines for US-ASCII/quoted-printable */
 1025         if (!mime_headers_needed)
 1026             encoding = '8';
 1027 
 1028         body_encode = rfc1521_encode;
 1029 
 1030         while (fgets(buffer, 2048, g))
 1031             body_encode(buffer, f, encoding);
 1032 
 1033         if (encoding == 'b' || encoding == 'q' || encoding == '7')
 1034             body_encode(NULL, f, encoding); /* flush */
 1035     } else {
 1036         while (fgets(buffer, 2048, g))
 1037             fputs(buffer, f);
 1038     }
 1039 
 1040     fclose(g);
 1041 }
 1042 
 1043 
 1044 void
 1045 rfc15211522_encode(
 1046     const char *filename,
 1047     constext * mime_encoding,
 1048     struct t_group *group,
 1049     t_bool allow_8bit_header,
 1050     t_bool ismail)
 1051 {
 1052     FILE *fp;
 1053 
 1054     if ((fp = fopen(filename, "r+")) == NULL)
 1055         return;
 1056 
 1057     do_rfc15211522_encode(fp, mime_encoding, group, allow_8bit_header, ismail, TRUE);
 1058 
 1059     fclose(fp);
 1060 }
 1061 
 1062 
 1063 /*
 1064  * Generate a MIME boundary being unique with high probability, consisting
 1065  * of len - 1 random characters.
 1066  * This function is used as a last resort if anything else failed to
 1067  * generate a truly unique boundary.
 1068  */
 1069 static void
 1070 generate_random_mime_boundary(
 1071     char *boundary,
 1072     size_t len)
 1073 {
 1074     size_t i;
 1075 
 1076     srand((unsigned int) time(NULL));
 1077     for (i = 0; i < len - 1; i++)
 1078         boundary[i] = base64_alphabet[(size_t) rand() % sizeof(base64_alphabet)];
 1079     boundary[len - 1] = '\0';
 1080 }
 1081 
 1082 
 1083 /*
 1084  * Generate a unique MIME boundary.
 1085  * boundary must have enough space for at least MIME_BOUNDARY_SIZE characters.
 1086  */
 1087 static void
 1088 generate_mime_boundary(
 1089     char *boundary,
 1090     FILE *f,
 1091     FILE *g)
 1092 {
 1093     const char nice_chars[] = { '-', '_', '=' };
 1094     const size_t prefix_len = sizeof(MIME_BOUNDARY_PREFIX) - 1;
 1095     char *s;
 1096     size_t i = 0;
 1097     t_bool unique = FALSE;
 1098 
 1099     /*
 1100      * Choose MIME boundary as follows:
 1101      *   - Always start with MIME_BOUNDARY_PREFIX.
 1102      *   - Append MIME_BOUNDARY_DEFAULT_PART.
 1103      *   - If necessary, change it from right to left, choosing from a set of
 1104      *     `nice_chars' characters.
 1105      *   - After that, if it is still not unique, replace MIME_BOUNDARY_DEFAULT_PART
 1106      *     with random characters and hope the best.
 1107      */
 1108 
 1109     strcpy(boundary, MIME_BOUNDARY_PREFIX);
 1110     strcat(boundary, MIME_BOUNDARY_DEFAULT_PART);
 1111 
 1112     s = boundary + MIME_BOUNDARY_SIZE - 2; /* set s to last character before '\0' */
 1113     do {
 1114         /*
 1115          * Scan for entire boundary in both f and g.
 1116          * When found: modify and redo.
 1117          */
 1118         if (contains_string(f, boundary) || contains_string(g, boundary)) {
 1119             *s = nice_chars[i];
 1120             if ((i = (i + 1) % sizeof(nice_chars)) == 0)
 1121                 --s;
 1122         } else
 1123             unique = TRUE;
 1124     } while (!unique && s >= boundary + prefix_len);
 1125 
 1126     if (!unique)
 1127         generate_random_mime_boundary(boundary + prefix_len, sizeof(MIME_BOUNDARY_DEFAULT_PART));
 1128 }
 1129 
 1130 
 1131 /*
 1132  * Split mail into header and (optionally) body.
 1133  *
 1134  * If textfp is not NULL, everything behind the header is stored in it.
 1135  * Whenever an error is encountered, all files are closed and FALSE is returned.
 1136  */
 1137 static t_bool
 1138 split_mail(
 1139     const char *filename,
 1140     FILE **headerfp,
 1141     FILE **textfp)
 1142 {
 1143     FILE *fp;
 1144     char *line;
 1145 
 1146     if ((fp = fopen(filename, "r")) == NULL)
 1147         return FALSE;
 1148 
 1149     /* Header */
 1150     if ((*headerfp = tmpfile()) == NULL) {
 1151         fclose(fp);
 1152         return FALSE;
 1153     }
 1154 
 1155     while ((line = tin_fgets(fp, TRUE))) {
 1156         if (*line == '\0')
 1157             break;
 1158         else
 1159             fprintf(*headerfp, "%s\n", line);
 1160     }
 1161 
 1162     /* Body */
 1163     if (textfp != NULL) {
 1164         if ((*textfp = tmpfile()) == NULL) {
 1165             fclose(fp);
 1166             fclose(*headerfp);
 1167             return FALSE;
 1168         }
 1169 
 1170         while ((line = tin_fgets(fp, FALSE)))
 1171             fprintf(*textfp, "%s\n", line);
 1172     }
 1173 
 1174     fclose(fp);
 1175     return TRUE;
 1176 }
 1177 
 1178 
 1179 /*
 1180  * Compose a mail consisting of a sole text/plain MIME part.
 1181  */
 1182 void
 1183 compose_mail_text_plain(
 1184     const char *filename,
 1185     struct t_group *group)
 1186 {
 1187     rfc15211522_encode(filename, txt_mime_encodings[(group ? group->attribute->mail_mime_encoding : tinrc.mail_mime_encoding)], group, (group ? group->attribute->mail_8bit_header : tinrc.mail_8bit_header), TRUE);
 1188 }
 1189 
 1190 
 1191 /*
 1192  * Compose a mail consisting of an optional text/plain and a message/rfc822
 1193  * part.
 1194  *
 1195  * At this point, the file denoted by `filename' contains some common headers
 1196  * and any text the user entered. The file `articlefp' contains the forwarded
 1197  * article in raw form.
 1198  */
 1199 void
 1200 compose_mail_mime_forwarded(
 1201     const char *filename,
 1202     FILE *articlefp,
 1203     t_bool include_text,
 1204     struct t_group *group)
 1205 {
 1206     FILE *fp;
 1207     FILE *headerfp;
 1208     FILE *textfp = NULL;
 1209     FILE *entityfp;
 1210     char *line;
 1211     constext* encoding = txt_mime_encodings[(group ? group->attribute->mail_mime_encoding : tinrc.mail_mime_encoding)];
 1212     t_bool allow_8bit_header = (group ? group->attribute->mail_8bit_header : tinrc.mail_8bit_header);
 1213     t_bool _8bit;
 1214 
 1215     /* Split mail into headers and text */
 1216     if (!split_mail(filename, &headerfp, include_text ? &textfp : NULL))
 1217         return;
 1218 
 1219     /* Encode header and text */
 1220     rewind(headerfp);
 1221     do_rfc15211522_encode(headerfp, encoding, group, allow_8bit_header, TRUE, TRUE);
 1222 
 1223     if (textfp) {
 1224         rewind(textfp);
 1225         do_rfc15211522_encode(textfp, encoding, group, allow_8bit_header, TRUE, FALSE);
 1226         entityfp = compose_multipart_mixed(textfp, articlefp);  /* Compose top-level MIME entity */
 1227     } else
 1228         entityfp = compose_message_rfc822(articlefp, &_8bit);
 1229 
 1230     if (entityfp == NULL) {
 1231         fclose(headerfp);
 1232         if (textfp)
 1233             fclose(textfp);
 1234         return;
 1235     }
 1236 
 1237     if ((fp = fopen(filename, "w")) == NULL) {
 1238         fclose(headerfp);
 1239         fclose(entityfp);
 1240         if (textfp)
 1241             fclose(textfp);
 1242         return;
 1243     }
 1244 
 1245     /* Put it all together */
 1246     rewind(headerfp);
 1247     while ((line = tin_fgets(headerfp, TRUE))) {
 1248         if (*line != '\0')
 1249             fprintf(fp, "%s\n", line);
 1250     }
 1251     fprintf(fp, "MIME-Version: %s\n", MIME_SUPPORTED_VERSION);
 1252     rewind(entityfp);
 1253     copy_fp(entityfp, fp);
 1254 
 1255     /* Clean up */
 1256     fclose(fp);
 1257     fclose(headerfp);
 1258     fclose(entityfp);
 1259     if (textfp)
 1260         fclose(textfp);
 1261 }
 1262 
 1263 
 1264 /*
 1265  * Compose a message/rfc822 MIME entity containing articlefp.
 1266  */
 1267 static FILE *
 1268 compose_message_rfc822(
 1269     FILE *articlefp,
 1270     t_bool *is_8bit)
 1271 {
 1272     FILE *fp;
 1273 
 1274     if ((fp = tmpfile()) == NULL)
 1275         return NULL;
 1276 
 1277     *is_8bit = contains_8bit_characters(articlefp);
 1278 
 1279     /* Header: CT, CD, CTE */
 1280     fprintf(fp, "Content-Type: message/rfc822\n");
 1281     fprintf(fp, "Content-Disposition: inline\n");
 1282     fprintf(fp, "Content-Transfer-Encoding: %s\n", *is_8bit ? txt_8bit : txt_7bit);
 1283     fputc('\n', fp);
 1284 
 1285     /* Body: articlefp */
 1286     rewind(articlefp);
 1287     copy_fp(articlefp, fp);
 1288 
 1289     return fp;
 1290 }
 1291 
 1292 
 1293 /*
 1294  * Compose a multipart/mixed MIME entity consisting of a text/plain and a
 1295  * message/rfc822 part.
 1296  */
 1297 static FILE *
 1298 compose_multipart_mixed(
 1299     FILE *textfp,
 1300     FILE *articlefp)
 1301 {
 1302     FILE *fp;
 1303     FILE *messagefp;
 1304     char boundary[MIME_BOUNDARY_SIZE];
 1305     t_bool requires_8bit;
 1306 
 1307     if ((fp = tmpfile()) == NULL)
 1308         return NULL;
 1309 
 1310     /* First compose message/rfc822 part (needed for choosing the appropriate CTE) */
 1311     if ((messagefp = compose_message_rfc822(articlefp, &requires_8bit)) == NULL) {
 1312         fclose(fp);
 1313         return NULL;
 1314     }
 1315 
 1316     requires_8bit = (requires_8bit || contains_8bit_characters(textfp));
 1317 
 1318     /*
 1319      * Header: CT with multipart boundary, CTE
 1320      * TODO: -> lang.c
 1321      */
 1322     generate_mime_boundary(boundary, textfp, articlefp);
 1323     fprintf(fp, "Content-Type: multipart/mixed; boundary=\"%s\"\n", boundary);
 1324     fprintf(fp, "Content-Transfer-Encoding: %s\n\n", requires_8bit ? txt_8bit : txt_7bit);
 1325 
 1326     /*
 1327      * preamble
 1328      * TODO: -> lang.c
 1329      */
 1330     fprintf(fp, _("This message has been composed in the 'multipart/mixed' MIME-format. If you\n\
 1331 are reading this prefix, your mail reader probably has not yet been modified\n\
 1332 to understand the new format, and some of what follows may look strange.\n\n"));
 1333 
 1334     /*
 1335      * Body: boundary+text, message/rfc822 part, closing boundary
 1336      */
 1337     /* text */
 1338     fprintf(fp, "--%s\n", boundary);
 1339     rewind(textfp);
 1340     copy_fp(textfp, fp);
 1341     fputc('\n', fp);
 1342 
 1343     /* message/rfc822 part */
 1344     fprintf(fp, "--%s\n", boundary);
 1345     rewind(messagefp);
 1346     copy_fp(messagefp, fp);
 1347     fclose(messagefp);
 1348     fputc('\n', fp);
 1349 
 1350     /* closing boundary */
 1351     fprintf(fp, "--%s--\n", boundary);
 1352     /* TODO: insert an epilogue here? */
 1353     return fp;
 1354 }
 1355 
 1356 
 1357 /*
 1358  * Determines whether the file denoted by fp contains 8bit characters.
 1359  */
 1360 static t_bool
 1361 contains_8bit_characters(
 1362     FILE *fp)
 1363 {
 1364     char *line;
 1365 
 1366     rewind(fp);
 1367     while ((line = tin_fgets(fp, FALSE))) {
 1368         for (; *line != '\0'; line++) {
 1369             if (is_EIGHT_BIT(line))
 1370                 return TRUE;
 1371         }
 1372     }
 1373 
 1374     return FALSE;
 1375 }
 1376 
 1377 
 1378 /*
 1379  * Determines whether any line of the file denoted by fp contains str.
 1380  */
 1381 static t_bool
 1382 contains_string(
 1383     FILE *fp,
 1384     const char *str)
 1385 {
 1386     char *line;
 1387 
 1388     rewind(fp);
 1389     while ((line = tin_fgets(fp, FALSE))) {
 1390         if (strstr(line, str))
 1391             return TRUE;
 1392     }
 1393 
 1394     return FALSE;
 1395 }